Python从门到精通(八):元类-02-装饰器
  TEZNKK3IfmPf 2024年03月29日 22 0

1.1、​函数添加装饰器

不会修改原始函数的参数签名和返回值。使用用*args和 **kargs的目的是确保任何参数都能适用。装饰器类似一个AOP程序

import time
from functools import wraps

def time_use(func):
@wraps(func)
def wrapper(*args, **kwargs):
start = time.time()
result = func(*args, **kwargs) #func就是原始的函数count_down
end = time.time()
print(f'func name: {func.__name__}, time use: {end - start} s') #注意这里的__name__写法
return result
return wrapper

@time_use
def count_down(n):
while n > 0:
n -= 1

count_down(100000)# func name: count_down, time use: 0.005406856536865234 s
count_down(10000000)# func name: count_down, time use: 0.5120158195495605 s

​1.2、函数解除装饰器

import time
from functools import wraps

def time_use(func):
@wraps(func)
def wrapper1(*args, **kwargs):
start = time.time()
result = func(*args, **kwargs) #func就是原始的函数count_down
end = time.time()
print(f'func name: {func.__name__}, time use: {end - start} s') #注意这里的__name__写法
return result
return wrapper1

@time_use
def count_down(n):
while n > 0:
n -= 1

count_down(100000)# func name: count_down, time use: 0.005406856536865234 s
count_down(10000000)# func name: count_down, time use: 0.5120158195495605 s

@time_use
def add(x, y):
return x + y

#用了这行以后就解除了装饰器,但必须要调用一次否则是不生效的,比如在下面代码中插入
orig_add = add.__wrapped__
# add(3,5) #这行还是会执行wraps代码
print(f'add result: {orig_add(3, 5)}') #add result: 8

#这种写法也可以
print(f'add result: {add.__wrapped__(3, 5)}')

1.3、得到函数元信息

print(f'func name: {count_down.__name__}')
print(f'doc is: {count_down.__doc__}')
print(f'annotations is: {count_down.__annotations__}')

from inspect import signature
print(f'signature: {signature(count_down)}')#signature: (*args, **kwargs)

1.4、在类中定义装饰器

这样装饰器可以同时在类的内部和外部一起使用了。

class A:
# Decorator as an instance method
def decorator_1(self, func):
@wraps(func)
def wrapper(*args, **kwargs):
print('Decorator 1')
return func(*args, **kwargs)
return wrapper

# Decorator as a class method
@classmethod
def decorator_2(cls, func):
@wraps(func)
def wrapper(*args, **kwargs):
print('Decorator 2')
return func(*args, **kwargs)
return wrapper


# As an instance method
a = A()
@a.decorator_1
def spam():
pass
# As a class method
@A.decorator_2
def grok():
pass

class B(A):
@A.decorator_2
def bar(self):
pass
class Profiled:
def __init__(self, func):
wraps(func)(self)
self.ncalls = 0

def __call__(self, *args, **kwargs):
self.ncalls += 1
return self.__wrapped__(*args, **kwargs)

#确保绑定方法对象能正确的创建,types.MethodType创建一个绑定方法,只有当实例被使用时才会被创建。
def __get__(self, instance, cls):
if instance is None:
return self
else:
return types.MethodType(self, instance)

@Profiled
def add(x, y):
return x + y

class Spam:
@Profiled
def bar(self, x):
print(f'object: {self}, param is: {x}')


print(f'number add result: {add(3, 5)}')
print(f'number add result: {add(5, 8)}')
print(f'ncalls: {add.ncalls}')
# number add result: 8
# number add result: 13
# ncalls: 2

s = Spam()
s.bar(1)
s.bar(2)
s.bar(3)
print(f'bar ncalls: {Spam.bar.ncalls}')
# object: <__main__.Spam object at 0x10d334f70>, param is: 1
# object: <__main__.Spam object at 0x10d334f70>, param is: 2
# object: <__main__.Spam object at 0x10d334f70>, param is: 3
# bar ncalls: 3


s = Spam()
def grok(self, x):
pass

print(f'grok get: {grok.__get__(s, Spam)}')
# grok get: <bound method grok of <__main__.Spam object at 0x10d334f70>>

1.5、运行时设置装饰器参数

from functools import wraps, partial
import logging
# Utility decorator to attach a function as an attribute of obj
def attach_wrapper(obj, func=None):
if func is None:
return partial(attach_wrapper, obj)
setattr(obj, func.__name__, func)
return func

def logged(level, name=None, message=None):
'''
Add logging to a function. level is the logging
level, name is the logger name, and message is the
log message. If name and message aren't specified,
they default to the function's module and name.
'''
def decorate(func):
logname = name if name else func.__module__
log = logging.getLogger(logname)
logmsg = message if message else func.__name__

@wraps(func)
def wrapper(*args, **kwargs):
log.log(level, logmsg)
return func(*args, **kwargs)

# Attach setter functions
@attach_wrapper(wrapper)
def set_level(newlevel):
nonlocal level #修改函数内部的变量
level = newlevel

@attach_wrapper(wrapper)
def set_message(newmsg):
nonlocal logmsg
logmsg = newmsg

return wrapper

return decorate

# Example use
@logged(logging.DEBUG)
def add(x, y):
return x + y

@logged(logging.CRITICAL, 'example')
def spam():
print('Spam!')


import logging
logging.basicConfig(level=logging.DEBUG)
print(f'add result: {add(3, 5)}')
add.set_message('Add called') #修改函数内部变量
print(f'add result: {add(3, 5)}')
add.set_level(logging.WARNING)
print(f'add result: {add(3, 5)}')

二、参数相关

2.1、带参数的装饰器

from functools import wraps
import logging

def logged(level, name=None, message=None):
def decorate(func):
logname = name if name else func.__module__
log = logging.getLogger(logname)
logmsg = message if message else func.__name__

@wraps(func)
def wrapper(*args, **kwargs):
log.log(level, message)
return func(*args, **kwargs)
return wrapper
return decorate

# Example use
@logged(logging.CRITICAL)
def add(x, y):
return x + y

add(1, 2) #add

@logged(logging.CRITICAL, 'example')
def spam():
print('Spam!')

spam() #spam

2.2、带可选参数的装饰器

from functools import wraps, partial
import logging

def logged(func=None, *, level=logging.DEBUG, name=None, message=None):
if func is None:
return partial(logged, level=level, name=name, message=message)

logname = name if name else func.__module__
log = logging.getLogger(logname)
logmsg = message if message else func.__name__

@wraps(func)
def wrapper(*args, **kwargs):
log.log(level, logmsg)
return func(*args, **kwargs)

return wrapper

# Example use
@logged
def add(x, y):
return x + y

@logged(level=logging.CRITICAL, name='example')
def spam():
print('Spam!')

三、装饰器使用

3.1、函数类型检查

from inspect import signature
from functools import wraps

def type_assert(*ty_args, **ty_kwargs):
def decorate(func):
# If in optimized mode, disable type checking
if not __debug__:
return func

# Map function argument names to supplied types
sig = signature(func)
#从指定类型到名称的部分绑定,它与bind的区别是不允许忽略任何参数
bound_types = sig.bind_partial(*ty_args, **ty_kwargs).arguments

@wraps(func)
def wrapper(*args, **kwargs):
bound_values = sig.bind(*args, **kwargs)
# Enforce type assertions across supplied arguments
for name, value in bound_values.arguments.items():
if name in bound_types:
if not isinstance(value, bound_types[name]):
raise TypeError(f'Argument {name} must be {bound_types[name]}')
return func(*args, **kwargs)
return wrapper
return decorate


@type_assert(int, int)
def add(x, y):
return x + y

print(f'add result: {add(2, 3)}')
# add(2, 'hello')


@type_assert(int, z=int)
def spam(x, y, z=42):
print(f'x = {x}, y = {y}, z = {z}')

spam(1, 2, 3)
spam(1, 'hello', 3)
# spam(1, 'hello', 'world')


def decorate(func):
# If in optimized mode, disable type checking
if not __debug__:
return func


from inspect import signature
def spam(x, y, z=42):
pass

sig = signature(spam)
print(f'sig = {sig}')
print(f'parameters: {sig.parameters}')
print(f'parameters z name = {sig.parameters["z"].name}')
print(f'parameters z default = {sig.parameters["z"].default}')
print(f'parameters z kind = {sig.parameters["z"].kind}')


bound_types = sig.bind_partial(int, z=int)
print(f'bound_types = {bound_types}')
print(f'bound_types arguments = {bound_types.arguments}')


bound_values = sig.bind(1, 2, 3)
print(f'arguments = {bound_values.arguments}')


for name, value in bound_values.arguments.items():
if name in bound_types.arguments:
if not isinstance(value, bound_types.arguments[name]):
raise TypeError


@type_assert(int, list)
def bar(x, items=None):
if items is None:
items = []
items.append(x)
return items
print(f'bar single param: {bar(3)}')
# print(f'bar double param: {bar(3, 5)}')
print(f'bar mix param: {bar(4, [1, 2, 3])}')


@type_assert
def spam(x:int, y, z:int = 42):
print(x,y,z)
add result: 5
x = 1, y = 2, z = 3
x = 1, y = hello, z = 3
sig = (x, y, z=42)
parameters: OrderedDict([('x', <Parameter "x">), ('y', <Parameter "y">), ('z', <Parameter "z=42">)])
parameters z name = z
parameters z default = 42
parameters z kind = POSITIONAL_OR_KEYWORD
bound_types = <BoundArguments (x=<class 'int'>, z=<class 'int'>)>
bound_types arguments = {'x': <class 'int'>, 'z': <class 'int'>}
arguments = {'x': 1, 'y': 2, 'z': 3}
bar single param: [3]
bar mix param: [1, 2, 3, 4]

3.2、给函数添加参数

from functools import wraps

def optional_debug(func):
@wraps(func)
def wrapper(*args, debug=False, **kwargs):
if debug:
print('Calling', func.__name__)
return func(*args, **kwargs)

return wrapper


@optional_debug
def spam(a, b, c):
print(f'a = {a}, b = {b}, c = {c}')

spam(1, 2, 3)
spam(1, 2, 3, debug=True)
# a = 1, b = 2, c = 3
# Calling spam
# a = 1, b = 2, c = 3

from functools import wraps
import inspect

def optional_debug(func):
if 'debug' in inspect.getfullargspec(func):
raise TypeError('debug argument already defined')

@wraps(func)
def wrapper(*args, debug=False, **kwargs):
if debug:
print(f'Calling {func.__name__}')
return func(*args, **kwargs)

sig = inspect.signature(func)
parms = list(sig.parameters.values())
parms.append(inspect.Parameter('debug',
inspect.Parameter.KEYWORD_ONLY,
default=False))
wrapper.__signature__ = sig.replace(parameters=parms)
return wrapper


@optional_debug
def add(x,y):
return x+y

print(f'signature: {inspect.signature(add)}')
print(f'add result: {add(5,3)}')
# signature: (x, y, *, debug=False)
# add result: 8

3.3、扩充类的功能

def log_getattribute(cls):
# Get the original implementation
orig_getattribute = cls.__getattribute__

# Make a new definition
def new_getattribute(self, name):
print(f'getting name: {name}')
return orig_getattribute(self, name)

# Attach to the class and return
cls.__getattribute__ = new_getattribute
return cls

# Example use
@log_getattribute
class A:
def __init__(self,x):
self.x = x
def spam(self):
pass


a = A(30)
print(f'a.x = {a.x}')
print(f'a.spam(): {a.spam()}')
# getting name: x
# a.x = 30
# getting name: spam
# a.spam(): None

class LoggedGetattribute:
def __getattribute__(self, name):
print(f'getting name: {name}')
return super().__getattribute__(name)

# Example:
class A(LoggedGetattribute):
def __init__(self,x):
self.x = x
def spam(self):
pass
【版权声明】本文内容来自摩杜云社区用户原创、第三方投稿、转载,内容版权归原作者所有。本网站的目的在于传递更多信息,不拥有版权,亦不承担相应法律责任。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@moduyun.com

  1. 分享:
最后一次编辑于 2024年03月29日 0

暂无评论

推荐阅读
  TEZNKK3IfmPf   2024年05月31日   32   0   0 python开发语言
  TEZNKK3IfmPf   2024年05月31日   25   0   0 python
  TEZNKK3IfmPf   2024年05月31日   25   0   0 python
TEZNKK3IfmPf