函数在 Python 中是第一类对象,这意味着:
函数是对象--它们可以被引用,传递到一个变量,也可以从其他函数返回。 函数可以被定义在另一个函数中--一个内部函数--也可以作为参数传递给另一个函数。
def say_name():
print("Guido van Rossum")
def say_nationality():
print("Netherlands")
def say(func):
return func
say(say_name)()
say(say_nationality)()
Output:
Guido van Rossum
Netherlands
这里我们将两个不同的函数引用(不是括号内的)作为参数发送给say函数,该函数再次返回引用。
这就是内部函数的工作方式。
def say():
def say_name():
print("Guido van Rossum")
def say_nationality():
print("Netherlands")
say_name()
say_nationality()
say()
Output:
Guido van Rossum
Netherlands
你已经看到函数就像Python中的其他对象一样,现在让我们举个例子来看看Python装饰器的魔力。
def say(func):
def employer():
print("说说你的事。")
def say_name():
print("我的名字是Guido van Rossum。")
def say_nationality():
print("我来自荷兰。")
def wrapper():
employer() #雇主()
say_name()
say_nationality()
func()
return wrapper
@say
def start_interview():
print("真正的面试开始了...")
start_interview()
输出:
说些关于你的事情。
我的名字是Guido van Rossum。
我来自荷兰。
真正的面试开始了...
这里当我们调用start_interview
方法时。它进入装饰函数say
,定义了雇主、say_name、say_nationality和包装函数,最后它返回包装函数的引用,并在调用者函数start_interview的地方调用它。
用参数装饰函数
我们还可以装饰一个带参数的函数。我们可以在封装函数中使用*args
和**kwargs
接受这些参数。
def say(func):
def wrapper(*args, **kwargs):
func(*args, **kwargs)
return wrapper
@say
def greet(name):
print("Hello {}".format(name))
greet("Goutom")
输出:
Hello Goutom
从装饰过的函数返回 当你的装饰函数返回一些东西时会发生什么?就是从包装器中返回调用者函数。让我们看一个例子来理解这一点。
def my_decorator(func):
def wrapper(*args, **kwargs):
return func(*args, **kwargs)
return wrapper
@my_decorator
def doubled(number):
return number*2
print(doubled(10))
Output:
20
自检
在Python中,自省是指一个对象在运行时了解其自身属性的能力。例如,一个函数知道它自己的名字和文档。让我们打印 doubled 函数的名字。
print(doubled.__name__)
Output:
wrapper
但它应该是被加倍的。在被装饰后,我们的双倍函数对其身份产生了混淆,因为它是从 wrapper 函数中被调用的。为了解决这个问题,装饰器应该在wrapper函数中使用@functools.wraps
装饰器,这将保留原函数的信息。在添加这个装饰器后,它将加倍返回其原始名称。
一个现实世界的例子
让我们写一个装饰器,它将计算函数的执行时间(以秒为单位)并在控制台中打印出来。
import functools
import time
def timer(func):
@functools.wraps(func)
def wrapper(*args, **kwargs):
start_time = time.perf_counter()
value = func(*args, **kwargs)
end_time = time.perf_counter()
run_time = end_time - start_time
print("Finished {} in {} secs".format(repr(func.__name__), round(run_time, 3)))
return value
return wrapper
Now let's use this decorator:
@timer
def doubled_and_add(num):
res = sum([i*2 for i in range(num)])
print("Result : {}".format(res))
doubled_and_add(100000)
doubled_and_add(1000000)
Output:
Result : 9999900000
Finished ‘doubled_and_add’ in 0.0119 secs
Result : 999999000000
Finished ‘doubled_and_add’ in 0.0897 secs
现在我们来使用这个装饰器。
注册插件
装饰器不一定要包装它们所装饰的函数。他们也可以简单地注册一个函数的存在,并返回它的非包装。例如,这可以用来创建一个轻量级的插件架构。
PLUGINS = dict()
def register(func):
PLUGINS[func.__name__] = func
return func
@register
def add(a, b):
return a+b
@register
def multiply(a, b):
return a*b
def operation(func_name, a, b):
func = PLUGINS[func_name]
return func(a, b)
print(PLUGINS)
print(operation('add', 2, 3))
print(operation('multiply', 2, 3))
Output :
{‘add’: <function add at 0x7fb27f7a8620>, ‘multiply’: <function multiply at 0x7fb27f7a88c8>}
5
6
@register装饰器只是在全局PLUGINS dict
中存储了一个对被装饰函数的引用。注意,在这个例子中,你不需要写一个内部函数或使用@functools.wraps,因为你返回的是未经修改的原始函数。
这种简单的插件架构的主要好处是,你不需要维护一个存在哪些插件的列表。这个列表是在插件自己注册时创建的。这使得添加一个新的插件变得非常简单:只需定义函数并使用@register
来装饰它。
装饰类
有两种不同的方法可以在类上使用装饰器。第一种是通过装饰类的方法或装饰整个类。
几个内置的类装饰器
一些内置在 Python 中的常用装饰器是 @classmethod, @staticmethod, 和 @property。@classmethod 和 @staticmethod 装饰器用于定义类命名空间内的方法,这些方法与该类的特定实例没有关系。@property装饰器用于定制类属性的getters和setters。展开下面的方框,可以看到一个使用这些装饰器的例子。你可以在这里了解更多关于内置装饰器的信息。
让我们看一个例子。
class Circle:
def __init__(self, radius):
self._radius = radius
@property
def radius(self):
""获取半径的值""
return self._radius
@radius.setter
def radius(self, value):
""设置半径,如果是负值,则引发错误"""
if value >=0:
self._radius = value
else:
raise ValueError("半径必须为正")
@property
def area(self):
""计算圆内的面积""
return self.pi() * self.radius**2
def cylinder_volume(self, height):
""计算以圆为底的圆柱体的体积""
return self.面积 * 高度
@classmethod
def unit_circle(cls):
""创建半径为1的圆的工厂方法""
return cls(1)
@classmethod
def pi():
""π的值,不过可以用math.pi代替""
return 3.1415926535
在这个类中:
.cylinder_volume()
是一个常规方法。
.radius
是一个可变的属性:它可以被设置成不同的值。
然而,通过定义一个setter
方法,我们可以做一些错误测试以确保它不会被设置为一个无意义的负数。属性作为属性被访问,没有括号。
.area
是一个不可变的属性:没有.setter()方法的属性不能被改变。即使它被定义为一个方法,它也可以作为一个属性被检索,不需要括号。
.unit_circle()
是一个类方法。它不与Circle的一个特定实例绑定。类方法经常被用作工厂方法,可以创建该类的特定实例。
.pi()
是一个静态方法。它并不真正依赖于Circle类,除了它是其命名空间的一部分。静态方法可以在一个实例或类上调用。
在控制台中测试。
>> c = Circle(5)
>>> c.radius
5
>> c.面积
78.5398163375
>> c.半径=2
>> c.面积
12.566370614
>>> c.面积=100
AttributeError: 不能设置属性
>> c.圆柱体_体积(高度=4)
50.265482456
>> c.radius = -1
ValueError。半径必须是正数
>> c = Circle.unit_circle()
>> c.radius
1
>> c.pi()
3.1415926535
>>> Circle.pi()
3.1415926535
装饰一个类方法
这里我们使用之前创建的定时器装饰器。
class Calculator:
def __init__(self, num):
self.num = num
@timer
def doubled_and_add(self):
res = sum([i * 2 for i in range(self.num)])
print("Result : {}".format(res))
c = Calculator(10000)
c.doubled_and_add()
Output:
Result : 99990000
Finished 'doubled_and_add' in 0.001 secs
Decorate a class
@timer
class Calculator:
def __init__(self, num):
self.num = num
import time
time.sleep(2)
def doubled_and_add(self):
res = sum([i * 2 for i in range(self.num)])
print("Result : {}".format(res))
c = Calculator(100)
Output:
Finished 'Calculator' in 2.001 secs
装饰一个类并不能装饰它的方法。在这里,@timer
只测量实例化类的时间。
嵌套装饰器
def hello(func):
def wrapper():
print("Hello")
func()
return wrapper
def welcome(func):
def wrapper():
print("Welcome")
func()
return wrapper
@hello
@welcome
def say():
print("Greeting Dome")
say()
Output:
Hello
Welcome
Greeting Dome
把这看成是装饰器按照它们列出的顺序被执行。换句话说,@hello
调用@welcome
,后者调用say()
带有参数的装饰器
这里repeat
处理装饰器的参数。
def repeat(*args_, **kwargs_):
def inner_function(func):
@functools.wraps(func)
def wrapper(*args, **kwargs):
for _ in range(args_[0]):
func(*args, **kwargs)
return wrapper
return inner_function
@repeat(4)
def say(name):
print(f "Hello {name}")
say("World")
输出:
Hello World
Hello World
Hello World
Hello World
你好,世界 你好,世界 你好,世界 你好,世界
有状态的装饰器
我们可以使用一个装饰器来跟踪状态。作为一个简单的例子,我们将创建一个装饰器来计算一个函数被调用的次数。
def count_calls(func):
@functools.wraps(func)
def wrapper(*args, **kwargs):
wrapper.num_calls += 1
print(f"Call {wrapper.num_calls} of {func.__name__!r}")
return func(*args, **kwargs)
wrapper.num_calls = 0
return wrapper
@count_calls
def say():
print("Hello!")
say()
say()
say()
say()
print(say.num_calls)
输出:
Call 1 of 'say'
Hello!
Call 2 of 'say'
Hello!
Call 3 of 'say'
Hello!
Call 4 of 'say'
Hello!
4
对'say'的调用1
你好!
调用'say'的第2次
你好!
调用'say'的3
你好!
呼叫4的 "说"。
你好!
4
对该函数的调用次数存储在包装函数的函数属性num_calls中。
作为装饰器的类
维护状态的最好方法是使用类。如果我们想使用类作为装饰器,它需要在其 .init() 方法中把 func 作为一个参数。此外,这个类需要是可调用的,这样它就可以代替被装饰的函数。为了使一个类可以被调用,我们实现了特殊的 .call() 方法。
class CountCalls。
def __init__(self, func):
functools.update_wrapper(self, func)
self.func = func
self.num_calls = 0
def __call__(self, *args, **kwargs):
self.num_calls += 1
print(f "调用{self.num_calls}的{self.func.__name__!r}")
return self.func(*args, **kwargs)
@CountCalls
def say():
print("Hello!")
say()
say()
say()
say()
print(say.num_calls)
输出过程:
对'say'的调用1 你好! 调用'say'的第2次 你好! 调用'say'的3 你好! 呼叫4的 "说"。 你好! 4
带参数的基于类的装饰器
class CountCalls:
def __init__(self, func):
functools.update_wrapper(self, func)
self.func = func
self.num_calls = 0
def __call__(self, *args, **kwargs):
self.num_calls += 1
print(f"Call {self.num_calls} of {self.func.__name__!r}")
return self.func(*args, **kwargs)
@CountCalls
def say():
print("Hello!")
say()
say()
say()
say()
print(say.num_calls)
Output:
Call 1 of 'say'
Hello!
Call 2 of 'say'
Hello!
Call 3 of 'say'
Hello!
Call 4 of 'say'
Hello!
4
Class-Based Decorators with Arguments
class ClassDecorator(object):
def __init__(self, arg1, arg2):
print("Arguements of decorator %s, %s" % (arg1, arg2))
self.arg1 = arg1
self.arg2 = arg2
def __call__(self, func):
functools.update_wrapper(self, func)
def wrapper(*args, **kwargs):
return func(*args, **kwargs)
return wrapper
@ClassDecorator("arg1", "arg2")
def print_args(*args):
for arg in args:
print(arg)
print_args(1, 2, 3)
Output:
Arguements of decorator arg1, arg2
1
2
3
几个实例扩展和基础用法总结。
本文由 mdnice 多平台发布