装饰器与注册器
当你的项目中需要成批量的函数和类,且这些函数和类功能上相似或并行时,为了方便管理,你可以把这些指定的函数和类整合到一个字典,你可以用函数名或类名作为字典的key,也可用使用自定义的名字作为key,对应的函数或类作为value。构建这样一个字典的过程就是注册,Python引入注册器机制保证了这个字典可以自动维护,增加或删除新的函数或类时,不需要手动去修改字典。
Python注册器机制本质上是用装饰器来实现的。下面将从基本的Python函数出发,逐步介绍装饰器,最后学习注册器。
python函数
首先定义一个函数,然后用不同的方式调用它:
def math():
return "I love math!"
print(math())
# output: 'I love math!'
# 这里math后面没有小括号,不是调用math函数
# 而是将math函数赋值给变量mathematics
mathematics = math
print(mathematics())
# output: 'I love math!'
在函数体中还可以定义函数,只是这个函数体内的函数不能在函数体外被直接调用。函数体内的函数虽然不能在函数体外被直接调用,但是可以将它们返回出来。注意到返回的add
和multiply
后面没有小括号,那它就可以被传递,并且可以赋值给别的变量而被执行,如果有小括号,那它就会被执行。
def math(operation = 'add'):
def add():
return 'I am in the add() function!'
def multiply():
return 'I am in the multiply() function!'
if operation == 'add':
return add
else:
return multiply
op = math()
print(op)
# output: <function math.<locals>.add at 0x7f0e64c9f560>
print(op())
# output: I am in the add() function!
我们还可以将函数作为参数传递给另一个函数:
def math(operation = 'add'):
return 'I love math!'
def precompute(func):
print('I am doing precomputations!')
print(func())
precompute(math)
python装饰器
为什么需要装饰器
程序员太“懒”了,基本上什么事情都想少做,追求是DRY,那么什么是DRY,如下: DRY(Don't repeat yourself ),字面意思来看:"不要重复自己"。强调的意思就是在进行编程时相同的代码不要重复写,最好只写一次,然后可以在其他地方直接引用。如此一来,可以提高代码重用率,缩减代码量,同时也有助于提高代码的可读性和可维护性。当需要做出更改时,只需要更改一个地方即可。
有了这个指导思想,就很明白了,就是少写代码,装饰器的目的也就是少写代码,复用代码。复用代码有很多种方式,比如面向对象里的继承,虚函数等,但是想在函数层面来复用代码,有什么方法呢?一般情况之下,就是函数中调用另一个函数来达到继承和复用。函数里调用别的函数,如下面的例子:
def add():
return 1 + 1
def sub():
return 2 -1
print(add())
print(sub())
# output: 2 1
如果这时,我们想把每个函数添加一行星号打印输出,以便分隔开来,更好看一些,我们往往会这样做修改,每个函数里添加一行星号就行了,修改如下:
def add():
print('*************************************')
return 1 + 1
def sub():
print('*************************************')
return 2 -1
print(add())
print(sub())
当我们只有两个函数时,这样修改很快的,并且很快就完成工作了,但是如果有1000个这样函数呢?那么是否需要添加1000遍?
如果你每个函数去添加一个也是可以完成任务的,但是能否不修改原来的函数,即是原来的函数代码一点都不改变,又能增加这样的功能呢?答案是可以的,如下修改:
def add():
return 1 + 1
def sub():
return 2 -1
#定义一个新的函数
def printStar(func):
print('*************************************')
return func()
print(printStar(add))
print(printStar(sub))
在这里增加了一个函数,这个函数接收一个函数对象作为参数,这样就不需要修改原来的函数,达到原来稳定并且测试通过的代码不作任何修改,减少出错的风险,特别已经上线运行的系统,更是如此;或者像8代单传的代码,没有人敢去修改它,否则领导会怪你,怎么样把产品越改越差,本来是请你来做好产品的,结果不行。做到这一步,就结束了吗?还不行啊,因为每个调用这个函数的地方都需要修改,因此再继续修改,结果改成这样:
#定义一个新的函数
def printStar(func):
print('*************************************')
return func()
@printStar
def add():
return 1 + 1
def sub():
return 2 -1
print(add)
print(printStar(sub))
这里发现调用add方法,还是不一样,继续修改,代码如下:
#定义一个新的函数
def printStar(func):
def f():
print('*************************************')
return func()
return f
@printStar
def add():
return 1 + 1
def sub():
return 2 -1
print(add())
sub = printStar(sub)
print(sub())
到这里,可以发现使用嵌套函数来实现,就可以返回一个可调用的对象,这样更加完美了。
#定义一个新的函数
def printStar(func):
def f():
print('*************************************')
return func()
return f
@printStar
def add():
return 1 + 1
@printStar
def sub():
return 2 -1
print(add())
print(sub())
在这里可以发现这两段代码是相等的:
def sub():
return 2 -1
sub = printStar(sub)
和
@printStar
def sub():
return 2 -1
由此可见@printStar
是一个语法改变,它的目标就是实现不修改原来函数的代码,又可以复用原来的函数并且作出修改,也称作为元编程,并且装饰器函数可以复用,实现共享的目标。
本部分内容参考自:CSDN:python里为什么需要使用装饰器(decorator)
计算执行时间的例子
装饰器是什么?顾名思义,就是增强函数或类的功能的一个函数。
举个例子:如何计算函数的执行时间?如下,你需要计算 add 函数的执行时间。
def add(a, b):
res = a + b
return res
你可能会这样写:
import time
def add(a, b)
start_time = time.time()
res = a + b
exec_time = time.time() - start_time
print("add函数,花费的时间是:{}".format(exec_time))
return res
这个时候,老板又让你计算减法函数(sub)的时间。不用装饰器的话,你又得重复写一段减法的代码。
def sub(a, b)
start_time = time.time()
res = a - b
exec_time = time.time() - start_time
print("sub函数,花费的时间是:{}".format(exec_time))
return res
这样显得很麻烦,也不灵活,万一计算时间的代码有改动,你得每个函数都要改动。所以,我们需要引入装饰器。使用装饰器之后的代码是这样的:
import time
# 定义装饰器
def time_calc(func):
def wrapper(*args, **kargs):
start_time = time.time()
f = func(*args,**kargs)
exec_time = time.time() - start_time
return f
return wrapper
# 使用装饰器
@time_calc
def add(a, b):
return a + b
@time_calc
def sub(a, b):
return a - b
是不是看起来清爽多了?
装饰器的作用:增强函数的功能,确切的说,可以装饰函数,也可以装饰类。
装饰器的原理:函数是python的一等公民,函数也是对象。
定义装饰器
def decorator(func):
def wrapper(*args,**kargs):
# 可以自定义传入的参数
print(func.__name__)
# 返回传入的方法名参数的调用
return func(*args,**kargs)
# 返回内层函数函数名
return wrapper
使用装饰器
假设decorator是定义好的装饰器。
方法一:不用语法糖@符号
# 装饰器不传入参数时
f = decorator(函数名)
# 装饰器传入参数时
f = (decorator(参数))(函数名)
方法二:采用语法糖@符号
# 已定义的装饰器
@decorator
def f():
pass
# 执行被装饰过的函数
f()
参数
装饰器可以传参,也可以不用传参。
自身不传入参数的装饰器(采用两层函数定义装饰器):
def login(func):
def wrapper(*args,**kargs):
print('函数名:%s'% func.__name__)
return func(*args,**kargs)
return wrapper
@login
def f():
print('inside decorator!')
f()
# 输出:
# >> 函数名:f
# >> 函数本身:inside decorator!
自身传入参数的装饰器(采用三层函数定义装饰器):
def login(text):
def decorator(func):
def wrapper(*args,**kargs):
print('%s----%s'%(text, func.__name__))
return func(*args,**kargs)
return wrapper
return decorator
# 等价于 ==> (login(text))(f) ==> 返回 wrapper
@login('this is a parameter of decorator')
def f():
print('2019-06-13')
# 等价于 ==> (login(text))(f)() ==> 调用 wrapper() 并返回 f()
f()
# 输出:
# => this is a parameter of decorator----f
# => 2019-06-13
内置装饰器
常见的内置装饰器有三种,@property、@staticmethod、@classmethod
@property
把类内方法当成属性来使用,必须要有返回值,相当于getter;
假如没有定义 @func.setter
修饰方法的话,就是只读属性
class Car:
def __init__(self, name, price):
self._name = name
self._price = price
@property
def car_name(self):
return self._name
# car_name可以读写的属性
@car_name.setter
def car_name(self, value):
self._name = value
# car_price是只读属性
@property
def car_price(self):
return str(self._price) + '万'
benz = Car('benz', 30)
print(benz.car_name) # benz
benz.car_name = "baojun"
print(benz.car_name) # baojun
print(benz.car_price) # 30万
@staticmethod 与 @classmethod
@staticmethod:静态方法,不需要表示自身对象的self和自身类的cls参数,就跟使用函数一样。
@classmethod:类方法,不需要self参数,但第一个参数需要是表示自身类的cls参数。
class Demo(object):
text = "三种方法的比较"
def instance_method(self):
print("调用实例方法")
@classmethod
def class_method(cls):
print("调用类方法")
print("在类方法中 访问类属性 text: {}".format(cls.text))
print("在类方法中 调用实例方法 instance_method: {}".format(cls().instance_method()))
@staticmethod
def static_method():
print("调用静态方法")
print("在静态方法中 访问类属性 text: {}".format(Demo.text))
print("在静态方法中 调用实例方法 instance_method: {}".format(Demo().instance_method()))
if __name__ == "__main__":
# 实例化对象
d = Demo()
# 对象可以访问 实例方法、类方法、静态方法
# 通过对象访问text属性
print(d.text)
# 通过对象调用实例方法
d.instance_method()
# 通过对象调用类方法
d.class_method()
# 通过对象调用静态方法
d.static_method()
# 类可以访问类方法、静态方法
# 通过类访问text属性
print(Demo.text)
# 通过类调用类方法
Demo.class_method()
# 通过类调用静态方法
Demo.static_method()
@staticmethod和@classmethod的区别和使用场景:
区别:
- 在定义静态类方法和类方法时,@staticmethod 装饰的静态方法里面,想要访问类属性或调用实例方法,必须需要把类名写上;
- 而@classmethod装饰的类方法里面,会传一个cls参数,代表本类,这样就能够避免手写类名的硬编码。
- 在调用静态方法和类方法时,实际上写法都差不多,一般都是通过 类名.静态方法() 或 类名.类方法()。
- 也可以用实例化对象去调用静态方法和类方法,但为了和实例方法区分,最好还是用类去调用静态方法和类方法。
使用场景:在定义类的时候,
- 假如不需要用到与类相关的属性或方法时,就用静态方法@staticmethod;
- 假如需要用到与类相关的属性或方法,然后又想表明这个方法是整个类通用的,而不是对象特异的,就可以使用类方法@classmethod。
本部分内容参考自:知乎:一文搞懂什么是Python装饰器?Python装饰器怎么用?
python注册器
为什么需要注册器
有了装饰器的基础之后,我们现在要走入注册器的世界了。但是,为什么需要注册器呢?
当你的项目中需要成批量的函数和类,且这些函数和类功能上相似或并行时,为了方便管理,你可以把这些指定的函数和类整合到一个字典,你可以用函数名或类名作为字典的key,也可用使用自定义的名字作为 key,对应的函数或类作为 value。构建这样一个字典的过程就是注册,Python 引入注册器机制保证了这个字典可以自动维护,增加或删除新的函数或类时,不需要手动去修改字典。
构建及使用注册器
Python的注册器本质上就是用装饰器的原理实现的。Registry提供了字符串到函数或类的映射,这个映射会被整合到一个字典中,开发者只要输入相应的字符串(为函数或类起的名字)和参数,就能获得一个函数或初始化好的类。 为了说明Registry的好处,我们首先看一下用一个字典存放字符串到函数的映射:
register = {}
def func():
pass
f = lambda x : x
class cls(object):
pass
register[func.__name__] = func
register[f.__name__] = f
register[cls.__name__] = cls
print(register)
# output:
# {'func': <function func at 0x7fec95a1add0>, '<lambda>': <function <lambda> at 0x7fec95a27a70>, 'cls': <class '__main__.cls'>}
这样做的缺点是我们需要手动维护register这个字典,当增加新的函数或类,或者删除某些函数或类时,我们也要手动修改register这个字典,因此我们需要一个可以自动维护的字典,在我们定义一个函数或类的时候就自动把它整合到字典中。为了达到这一目的,这里就使用到了装饰器,在装饰器中将我们新定义的函数或类存放的字典中,这个过程我们称之为注册。
这里我们需要定义一个装饰器类Register,其中核心部分就是成员函数register,它作为一个装饰器函数:
class Register(dict):
def __init__(self, *args, **kwargs):
super(Register, self).__init__(*args, **kwargs)
self._dict = {}
def register(self, target):
def add_item(key, value):
if not callable(value):
raise Exception(f"Error:{value} must be callable!")
if key in self._dict:
print(f"\033[31mWarning:\033[0m {value.__name__} already exists and will be overwritten!")
self[key] = value
return value
if callable(target): # 传入的target可调用 --> 没有给注册名 --> 传入的函数名或类名作为注册名
return add_item(target.__name__, target)
else: # 不可调用 --> 传入了注册名 --> 作为可调用对象的注册名
return lambda x : add_item(target, x)
def __setitem__(self, key, value):
self._dict[key] = value
def __getitem__(self, key):
return self._dict[key]
def __contains__(self, key):
return key in self._dict
def __str__(self):
return str(self._dict)
def keys(self):
return self._dict.keys()
def values(self):
return self._dict.values()
def items(self):
return self._dict.items()
将Register实例化,然后打印验证一下:
register_func = Register()
@register_func.register
def add(a, b):
return a + b
@register_func.register
def multiply(a, b):
return a * b
@register_func.register('matrix multiply')
def multiply(a, b):
pass
@register_func.register
def minus(a, b):
return a - b
@register_func.register
def minus(a, b):
return a - b
for k, v in register_func.items():
print(f"key: {k}, value: {v}")
# output:
# Warning: minus already exists and will be overwritten!
# key: add, value: <function add at 0x7fd18ee7cb90>
# key: multiply, value: <function multiply at 0x7fd18ee95170>
# key: matrix multiply, value: <function multiply at 0x7fd18ee95320>
# key: minus, value: <function minus at 0x7fd18ee95200>
如果不想手动调用register()函数,可以在Register类中添加一个__call__()函数:
class Register(dict):
def __init__(self, *args, **kwargs):
super(Register, self).__init__(*args, **kwargs)
self._dict = {}
def __call__(self, target):
return self.register(target)
def register(self, target):
def add_item(key, value):
if not callable(value):
raise Exception(f"Error:{value} must be callable!")
if key in self._dict:
print(f"\033[31mWarning:\033[0m {value.__name__} already exists and will be overwritten!")
self[key] = value
return value
if callable(target): # 传入的target可调用 --> 没有给注册名 --> 传入的函数名或类名作为注册名
return add_item(target.__name__, target)
else: # 不可调用 --> 传入了注册名 --> 作为可调用对象的注册名
return lambda x : add_item(target, x)
def __setitem__(self, key, value):
self._dict[key] = value
def __getitem__(self, key):
return self._dict[key]
def __contains__(self, key):
return key in self._dict
def __str__(self):
return str(self._dict)
def keys(self):
return self._dict.keys()
def values(self):
return self._dict.values()
def items(self):
return self._dict.items()
register_func = Register()
@register_func
def add(a, b):
return a + b
@register_func
def multiply(a, b):
return a * b
@register_func('matrix multiply')
def multiply(a, b):
pass
@register_func
def minus(a, b):
return a - b
for k, v in register_func.items():
print(f"key: {k}, value: {v}")
# output:
# key: add, value: <function add at 0x7fdedd53cb90>
# key: multiply, value: <function multiply at 0x7fdedd540200>
# key: matrix multiply, value: <function multiply at 0x7fdedd5403b0>
# key: minus, value: <function minus at 0x7fdedd540320>