Python装饰器实例讲解(二
你最好去看下第一篇,虽然也不是紧密的链接在一起
一键三连哦
本文的知识点主要是 装饰器的本质(up主说的万能公式
案例
def count_time(func:
def wrapper(*args,**kwargs:
from time import time
start_time = time(
result = func(*args,**kwargs
end_time = time(
print(f'统计花了{end_time-start_time}时间'
return result
return wrapper
改造为类装饰器(注意对比
- 你得知道基础的python的面向对象的知识
- 一些类的魔术方法如__init__和__call__
class CountTime:
def __init__(self,function_name: # 类没传参一说,但实例化是可以传参的,类比 def count_time(func:
self.function_name = function_name
def __call__(self, *args, **kwargs: # 类实例的(,像函数的call,==>def wrapper(*args,**kwargs:
from time import time
start_time = time(
result = self.function_name(*args,**kwargs # 也就改了这里,其他都一样
end_time = time(
print(f'统计花了{end_time-start_time}时间'
return result
完整的代码
def is_prime(x:
if x == 2 :
return True
elif x % 2 == 0 or x == 1 :
return False
for i in range(3, int(x ** 0.5 + 1, 2:
if x % i == 0:
return False
return True
class CountTime:
... # 就不重复上面了
@CountTime # 类是一个装饰器
def get_prime_nums(start,end:
prime_nums = 0
for num in range(start,end:
if is_prime(num:
prime_nums = prime_nums + 1
return prime_nums
print(get_prime_nums(2,50000 # 效果是一样的
码农高天说
我把up主的一些话摘录一些写到这里,辅助大家理解
- 装饰器decorator:是一个输入是函数,输出也是函数的函数(看讲解一中的装饰器
- 类装饰器 class decorator,up主说有一定的歧义
- 可以当做装饰器的类(装饰器本身
- 可以装饰类的装饰器(装饰器要装饰的对象
- 装饰器本身既可以是函数也可以是类,装饰的对象同样可以是函数或者类
- 背这些理论没有意义,关键要弄懂背后的原理
- __call__可以让类的实例当做函数用(就是callable
万能公式
class CountTime:
...# 同上
@CountTime
def add(a,b: # 就用码农的demo函数
return a+b
print(add(1,2
@CountTime等价于,所谓的万能公式咯
add = CountTime(add
print(add(1,2已经不再是使用的原始的add了,用的是新的add
print(add(1,2 等价于
print(CountTime(add(1,2
也就是说
# @CountTime # 去掉装饰器,你就是定义了一个简单的函数
# # add = CountTime(add # 函数名被重定义了 相当于这样
def add(a,b:
return a+b
print(CountTime(add(1,2
你还可以这样
def add(a,b:
return a+b
new_add = CountTime(add
print(new_add(1,2
是的,被装饰过的函数已经不再是原来的函数了,它总是会先去执行装饰器(CountTime(add
- 在一个函数上做装饰器,等价于装饰器调用这个函数
- 在类装饰器的这个例子中,add从一个函数变成了一个类的实例(type看下即可
改造,有参数的装饰器
-
计时: 0.46秒 或者 用时: 0.46秒
-
你希望是这样装饰和调用的
@CountTime(prefix='用时:' def add(a,b: return a+b print(add(1,2
-
那咋实现呢?
-
@CountTime(prefix='用时:' def add(a,b: ... # 等价于 add = CountTime(add # 那么 @CountTime(prefix='用时:' def add(a,b: ... # 等价于 add = CountTime(prefix='用时:'(add
-
CountTime这个类能CountTime(prefix='用时:',就是实例化做到的,所以...类的init方法要改一下,不再是传参function_name了,而是传你的prefix,像这样
class CountTimeV2: def __init__(self,prefix='用时:': self.prefix = prefix
-
但现在还不能继续,add = CountTime(prefix='用时:'(add中你(add还要处理,前面是init做的,(就是callable做的,里面的参数是add,也就是函数的名字,所以你的call也要改造,像这样吗?
def __call__(self, function_name: from time import time start_time = time( result = function_name(*args,**kwargs end_time = time( print(f'统计花了{end_time-start_time}时间' return result
-
不对的,光这样改造不够的,因为你这个function_name(*args,**kwargs在IDE中就会报错,哪里来的呢?没有定义。
-
class CountTimeV2: def __init__(self, prefix='用时:': self.prefix = prefix def __call__(self, function_name: def wrapper(*args, **kwargs: # 加了个函数,包裹一层 from time import time start_time = time( result = function_name(*args, **kwargs # 这样就可以用参数了 end_time = time( print(f'{self.prefix}{end_time - start_time}' # 用之前的定义 return result return wrapper @CountTimeV2(prefix='耗时:' # 可以改为用时、计时等等 def add(a, b: return a + b print(add(1, 2
我们看到过很多的装饰是有参数的,这是怎么实现的呢?
前面谈的是类是一个装饰器,装了一个函数
类的装饰器
现在有这么一个类
class Person:
pass
wuxianfeng = Person(
print(wuxianfeng # <__main__.Person object at 0x000002361C15A460>
你学过python可以这样修改
class Person:
def __str__(self:
return f"{self.__class__.__name__}"
wuxianfeng = Person(
print(wuxianfeng # Person
但如果有很多的类都要如此呢?
怎么写?回想刚才你学到的知识,万能公式!
def show_classname(: # 先不写参数
pass # 先不写内容
@show_classname
class Person:
pass
Person = show_classname(Person
wuxianfeng = Person(
print(wuxianfeng
-
你现在要写一个函数,名字随意,如show_classname
-
@show_classname class Person: pass
-
根据万能公式,你的Person应该变了
Person = show_classname(Person # 从上面这段代码,你要能分析出以下内容 # 1. show_classname应该有个参数,传参是个类名 # 2. 因为可以Person =,所以show_classname有个返回值
-
对于使用者而言,应该没有任何操作上的差异
wuxianfeng = Person( # 从上面这段代码,你要能分析出以下内容 # 1. Person已经被你改变了 # 2. Person(==>show_classname(Person(,所以show_classname这个函数的返回值还是一个类 print(wuxianfeng
-
分析完了,函数体部分是有点不好理解的
def show_classname(class_name: def __str__(self: return self.__class__.__name__ class_name.__str__ = __str__ return class_name @show_classname class Person: pass Person = show_classname(Person wuxianfeng = Person( print(wuxianfeng
-
看着这个结果,我们来解释下(也许你会更好理解
1. show_classname(Person 返回仍然是Person 2. 但这个时候的Person被改变了一点(你要做的不就是如此吗? 3. 原来你是这样写的 class Person: def __str__(self: return f"{self.__class__.__name__}" 看看现在的写法 def __str__(self: return self.__class__.__name__ class_name.__str__ = __str__ # 前面的class_name.__str__ 是类自己的函数(本段解释的line 5 # 后面的= __str__,是line8的函数 # 是的,函数可以被重新赋值,函数是一等对象,
-
如果还不明白...尽力了
带参数的类的装饰器
当然如果你真懂了前面的"改造,有参数的装饰器",也很简单
def show_classname(info='类名:':
def wrapper(class_name:
def __str__(self:
return info+ self.__class__.__name__
class_name.__str__ = __str__
return class_name
return wrapper
@show_classname('类的名字是:' #
class Person:
pass
wuxianfeng = Person(
print(wuxianfeng
默认值就是='类名:',怎么用呢
@show_classname(
class Human:
pass
qianyuli = Human(
print(qianyuli
注意不能这样
@show_classname
class Human:
pass
qianyuli = Human(
print(qianyuli
提示错误
Traceback (most recent call last:
File "demo.py", line 21, in <module>
qianyuli = Human(
TypeError: wrapper( missing 1 required positional argument: 'class_name'
提个问题,为何会报错?
答案其实还是万能公式。
@show_classname
class Human:
pass
# 1.
等价于(万能公式来了
Human = show_classname(Human
# 2.
show_classname(Human 执行这个的时候其实你在做
def show_classname(info='类名:':
...
Human这个东西传给了info
# 你要不信,你改为下面这样就知道了;信的话就过
def show_classname(info='类名:':
print('info是啥?',info.name
class Human:
name = '女娲'
# 3.
show_classname(Human这个的返回是wrapper
但wrapper这个函数是有个参数的,看你的定义def wrapper(class_name:
# 4.
定义的时候是感知不到问题的,下面的报错行
qianyuli = Human(
其实你是在
Human(=>show_classname(Human(=>wrapper(,错了,(看3,你需要一个class_name参数
如果还不明白...尽力了