国产操作系统生态圈推动信息安全与技术自主发展的新机遇
906
2022-11-09
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小时内删除侵权内容。
发表评论
暂时没有评论,来抢沙发吧~