python中的闭包、装饰器和@property @staticmethod

网友投稿 906 2022-11-09

python中的闭包、装饰器和@property @staticmethod

python中的闭包、装饰器和@property @staticmethod

目录

​​问题引出​​

​​使用方法​​

​​闭包是什么​​

​​装饰器的实现​​

看一段 mido 这个库的源代码 mido/midifiles/tracks.py

class MidiTrack(list): @property def name(self): """ doc... """ for message in self: if message.type == 'track_name': return message.name else: return '' @name.setter def name(self, name): # Find the first track_name message and modify it. for message in self: if message.type == 'track_name': message.name = name return else: # No track name found, add one. self.insert(0, MetaMessage('track_name', name=name, time=0))

这些 @ 符号是什么意思?

问题引出

定义类函数时会有大量相同的需求,比如给某个函数记录运行日志,设置某个变量只读,限制函数输入参数范围并在范围外抛出异常;

为了使得代码更简洁,把这种与函数本身的功能无关的功能集合起来,叫做装饰器,用@表示,把@叫做装饰器的语法糖;

装饰器把大量相同的工作省去了,使得程序更整洁(关注核心功能)

使用方法

在想要装饰的函数前写上 @xxx 即可

如上文中用 property 装饰了 name 这个函数

常用的装饰器有

@property

@xxx.name

@staticmethod

@classmethod

@abstractmethod

@wraps

下面分别讲上述接种常见装饰器的常见用法,​​这篇博客​​讲的也不错,可以参考看看

@property 与 @xxx.name 一起

将一个方法伪装成属性,被修饰的特性方法,内部可以实现处理逻辑,但对外提供统一的调用方式,实现一个实例属性的get,set,delete三种方法的内部逻辑。看一个例子

class Screen(object): @property def width(self): return self._width @width.setter def width(self, value): if value < 0: raise ValueError('width must be a positive number.') self._width = value @property def height(self): return self._height @height.setter def height(self, value): if value < 0: raise ValueError('height must be a positive number.') self._height = value @property def resolution(self): return self.width * self.heights = Screen()s.height = 20s.width = 40print(s.resolution)

正确输出 height*width=800

只有 @property

设置某个只读属性,通常用在类内成员变量赋值防止修改

在上面的例子运行后,加一句 s.resolution = 1

报错 AttributeError: can't set attribute

@staticmethod

class c: @staticmethod def f(*args, **kwargs): pass

在类函数的前面加上@staticmethod就是定义了一个静态方法,不用实例化类 c 也可以调用函数 f,即 c.f()

classmethod

类似于@staticmethod,也可以在不实例化类的情况下调用类内函数,区别是

abstracmethod

翻译过来就是抽象方法,用在抽象类(虚类)的虚函数上;含abstractmethod方法的类不能实例化,继承了含abstractmethod方法的子类必须复写所有abstractmethod装饰的方法,未被装饰的可以不重写;下面这个示例代码来自上面提到的CSDN博客链接

# -*- coding:utf-8 -*-from abc import ABC, abstractmethodclass **A**(**ABC**): @abstractmethod def **test**(self): passclass **B**(**A**): def **test_1**(self): print("未覆盖父类abstractmethod") class **C**(**A**): def **test**(self): print("覆盖父类abstractmethod") if __name__ == '__main__': a = A() b = B() c = C()"""前两个分别报错如下:a = A()TypeError: Can't instantiate abstract class A with abstract methods testb = B()TypeError: Can't instantiate abstract class **B** **with** **abstract** **methods** **test**第三个实例化是正确的"""

@wraps

见下方,说明装饰器原理后叙述

以下的内容需要一些时间消化,需要耐心理解

笔者水平有限,推荐一个讲的比较通俗易懂的视频 ​​Python的闭包与装饰器_哔哩哔哩_bilibili​​

闭包是什么

闭包是一个普遍的概念,许多语言里都有

闭包:在作用域外(以特定方式)使用局部变量

复习一些概念

变量有生命周期(作用域),C++中的作用域是{ },pyhton中的作用域是tab之间(比如 def 和 return 之间)离开作用域后无法访问到变量

def fun(*args, **kwargs): a = 1print(a)# output: NameError: name 'a' is not defined

加入一些新概念

函数也是一种变量所以函数里也能定义函数,叫嵌套函数函数的返回值可以是函数

def fun(): print("fun") def fun_in(): print("fun_in") return fun_in return fun_invar = fun() # 调用fun,初始化fun_invar() # 调用fun_in# output:# fun# fun_in

看到,在函数 fun 的外部实现了内部函数 fun_in 的调用,也就是在变量作用域外“访问”了 fun_in;

这是因为在调用 fun 的时候初始化了 fun_in ,并将该内部变量保存在 var 里;

此时 var = fun_in,所以调用 var 效果与 fun_in( ) 相同

再看一个例子

def fun(): a = 1 def fun_in(num): return num + a return fun_invar = fun()var(3)# output: 4

正确输出了 a+3 = 4

在作用域外依然能使用局部变量 a ,也是因为这个 a 保存在 var 里了

但是直接 print(a) 会报错,因为 a 这个变量只存在于 var 中,只有 var 能(通过调用 fun_in 的方式)使用

装饰器的实现

那闭包和装饰器有什么关系?

再看下文章最开始的代码

class MidiTrack(list): @property def name(self): ...

class property(object): """ Property attribute. ... """ def __init__(self, fget=None, fset=None, fdel=None, doc=None): # known special case of property.__init__ ...

也就是说 property 也是一个类

@property 实际上就是初始化 property 这个类,也就是调用 __init__ 函数

装饰器 @xxx 的实质就是调用函数

在函数 def fun 前使用 @decorator 等同于 decorator(fun)

看下面一段例子,想给函数 real_fun 记录一段日志

import loggingdef decorator(fun): logging.basicConfig(level=logging.DEBUG, format='%(asctime)s %(filename)s[line:%(lineno)d] %(levelname)s %(message)s', datefmt='%a, %d %b %Y %H:%M:%S', filename='./test.log', filemode='w') def fun_in(*args, **kwargs): logging.debug("{} will run.".format(fun.__name__)) fun(*args, **kwargs) logging.debug("{} finished.".format(fun.__name__)) return fun_indef real_fun(): print("real_fun")real_fun = decorator(real_fun)real_fun()# output: real fun

当前文件夹下生成的日志文件 test.log

Thu, 19 Mar 2020 13:15:51 property_test.py[line:88] DEBUG real_fun will run.Thu, 19 Mar 2020 13:15:51 property_test.py[line:90] DEBUG real_fun finished.

为了简洁美观,关注有更有价值的 real_fun 的功能

在函数 real_fun 前使用@decorator  与  real_fun = decorator(real_fun)   是一样的

把上例写成

import loggingdef decorator(fun): logging.basicConfig(level=logging.DEBUG, format='%(asctime)s %(filename)s[line:%(lineno)d] %(levelname)s %(message)s', datefmt='%a, %d %b %Y %H:%M:%S', filename='./test.log', filemode='w') def fun_in(*args, **kwargs): logging.debug("{} will run.".format(fun.__name__)) fun(*args, **kwargs) logging.debug("{} finished.".format(fun.__name__)) return fun_in@decoratordef real_fun(): print("real_fun")real_fun()

输出与日志记录内容相同

现在我们可以把 @decorator 之前所有内容放到另一个 .py 文件中,在 real_fun 所在文件调用装饰器,优雅地完成日志记录

最后来说前文提到的 @wraps

在完成上面的工作后,还有个问题

print(real_fun.__name__)# output:fun_in

因为实际上调用是在 fun_in 内部发生的

为了 real_fun 的 __name__ 仍是 real_fun ,在内部函数 fun_in 之前使用 @wraps

现在代码是这样

import loggingfrom functools import wrapsdef decorator(fun): logging.basicConfig(level=logging.DEBUG, format='%(asctime)s %(filename)s[line:%(lineno)d] %(levelname)s %(message)s', datefmt='%a, %d %b %Y %H:%M:%S', filename='./test.log', filemode='w') @wraps(fun) def fun_in(): logging.debug("{} will run.".format(fun.__name__)) fun() logging.debug("{} finished.".format(fun.__name__)) return fun_in@decoratordef real_fun(): print("real_fun")real_fun()print(real_fun.__name__)# output: real_fun# output: real_fun

版权声明:本文内容由网络用户投稿,版权归原作者所有,本站不拥有其著作权,亦不承担相应法律责任。如果您发现本站中有涉嫌抄袭或描述失实的内容,请联系我们jiasou666@gmail.com 处理,核实后本网站将在24小时内删除侵权内容。

上一篇:Mybatis批量插入Oracle数据的方法实例
下一篇:python寻找近义词:预训练模型 nltk+20newsbydate / gensim glove 转 word2vec
相关文章

 发表评论

暂时没有评论,来抢沙发吧~