一、函数装饰器
import time
from functools import wraps
def timethis(func):
'''
Decorator that reports the execution time.
'''
@wraps(func)
def wrapper(*args, **kwargs):
start = time.time()
result = func(*args, **kwargs)
elapsed = time.time() - start
print(func.__name__, elapsed)
return result
return wrapper
@timethis
def countdown(n):
while n > 0:
n -= 1
countdown(1000000)
# => countdown 0.29901695251464844
装饰器负责接收某个函数作为参数,然后返回一个新的函数作为输出。下面的代码:
@timethis
def countdown(n):
...
实际上等同于
def countdown(n):
...
countdown = timethis(countdown)
装饰器内部通常要定义一个接收任意参数(*args, **kwargs
)的函数,即 wrapper()
。在 wrapper 函数里,调用原始的作为参数传入的函数(func
)并获取其结果,再根据需求添加上执行其他操作的代码(比如计时、日志等)。最后新创建的 wrapper 函数被返回并替换掉被装饰的函数(countdown
),从而在不改变被装饰函数自身代码的情况下,为其添加额外的行为。
二、带参数的装饰器
from functools import wraps
import logging
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.
'''
logging.basicConfig(
level=logging.DEBUG, format='%(asctime)s - %(name)s - %(levelname)s - %(message)s')
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)
return wrapper
return decorate
# Example use
@logged(logging.WARNING)
def spam():
pass
@logged(logging.INFO, name='Example', message='This is log message')
def foo():
pass
spam()
foo()
# => 2019-10-24 09:22:25,780 - __main__ - WARNING - spam
# => 2019-10-24 09:22:25,783 - Example - INFO - This is log message
最外层的函数 logged()
用于接收传入装饰器的参数,并使这些参数能够被装饰器中的内部函数(decorate()
)访问。内部函数 decorate 则用于实现装饰器的“核心逻辑”,即接收某个函数作为参数,通过定义一个新的内部函数(wrapper
)添加某些行为,再将这个新的函数返回作为被装饰函数的替代品。
在类中定义的装饰器
from functools import wraps
class A:
# Decorator as an instance method
def decorator1(self, func):
@wraps(func)
def wrapper(*args, **kwargs):
print('Decorator 1')
return func(*args, **kwargs)
return wrapper
#Decorator as a class method
@classmethod
def decorator2(cls, func):
@wraps(func)
def wrapper(*args, **kwargs):
print('Decorator 2')
return func(*args, **kwargs)
return wrapper
# As an instance method
a = A()
@a.decorator1
def spam():
pass
spam()
# => Decorator 1
# As a class method
@A.decorator2
def grok():
pass
grok()
# => Decorator 2
利用装饰器向原函数中添加参数
from functools import wraps
import inspect
def optional_debug(func):
if 'debug' in inspect.getfullargspec(func).args:
raise TypeError('debug argument already defined')
@wraps(func)
def wrapper(*args, debug=False, **kwargs):
if debug:
print('Calling', func.__name__)
return func(*args, **kwargs)
return wrapper
@optional_debug
def add(x, y):
print(x + y)
add(2, 3)
# => 5
add(2, 3, debug=True)
# => Calling add
# => 5
装饰器修改类的定义
def log_getattribute(cls):
orig_getattribute = cls.__getattribute__
def new_getattribute(self, name):
print('getting: ', name)
return orig_getattribute(self, name)
cls.__getattribute__ = new_getattribute
return cls
@log_getattribute
class A:
def __init__(self, x):
self.x = x
def spam(self):
pass
a = A(42)
print(a.x)
a.spam()
# => getting: x
# => 42
# => getting: spam
类装饰器可以用来重写类的部分定义以修改其行为,作为一种直观的类继承或元类的替代方式。
比如上述功能也可以通过类继承来实现:
class LoggedGetattribute:
def __getattribute__(self, name):
print('getting: ', name)
return super().__getattribute__(name)
class A(LoggedGetattribute):
def __init__(self, x):
self.x = x
def spam(self):
pass
a = A(42)
print(a.x)
a.spam()
在某些情况下,类装饰器的方案要更为直观一些,并不会向继承层级中引入新的依赖。同时由于不使用 super()
函数,速度也稍快一点。
使用元类控制实例的创建
Python 中的类可以像函数那样调用,同时创建实例对象:
class Spam:
def __init__(self, name):
self.name = name
a = Spam('Guido')
b = Spam('Diana')
如果开发人员想要自定义创建实例的行为,可以通过元类重新实现一遍 __call__()
方法。假设在调用类时不创建任何实例:
class NoInstance(type):
def __call__(self, *args, **kwargs):
raise TypeError("Can't instantiate directly")
class Spam(metaclass=NoInstance):
@staticmethod
def grok(x):
print('Spam.grok')
Spam.grok(42) # Spam.grok
s = Spam()
# TypeError: Can't instantiate directly
元类实现单例模式
单例模式即类在创建对象时,单一的类确保只生成唯一的实例对象。
# singleton.py
class Singleton(type):
def __init__(self, *args, **kwargs):
self.__instance = None
super().__init__(*args, **kwargs)
def __call__(self, *args, **kwargs):
if self.__instance is None:
self.__instance = super().__call__(*args, **kwargs)
return self.__instance
else:
return self.__instance
class Spam(metaclass=Singleton):
def __init__(self):
print('Creating Spam')
>>> from singleton import *
>>> a = Spam()
Creating Spam
>>> b = Spam()
>>> a is b
True
>>> c = Spam()
>>> a is c
True
强制检查类定义中的代码规范
可以借助元类监控普通类的定义代码。通常的方式是定义一个继承自 type 的元类并重写其 __new__()
或 __init__()
方法。
class MyMeta(type):
def __new__(cls, clsname, bases, clsdict):
# clsname is name of class being defined
# bases is tuple of base classes
# clsdict is class dictionary
return super().__new__(cls, clsname, bases, clsdict)
class MyMeta(type):
def __init__(self, clsname, bases, clsdict):
# clsname is name of class being defined
# bases is tuple of base classes
# clsdict is class dictionary
return super().__init__(clsname, bases, clsdict)
为了使用元类,通常会先定义一个供其他对象继承的基类:
class Root(metaclass=MyMeta):
pass
class A(Root):
pass
class B(Root):
pass
元类的重要特性在于,它允许用户在类定义时检查类的内容。在重写的 __init__()
方法内部,可以方便地检查 class dictionary、base class 或者其他与类定义相关的内容。此外,当元类指定给某个普通类以后,该普通类的所有子类也都会继承元类的定义。
下面是一个用于检查代码规范的元类,确保方法的命名里只包含小写字母:
class NoMixedCaseMeta(type):
def __new__(cls, clsname, bases, clsdict):
for name in clsdict:
if name.lower() != name:
raise TypeError('Bad attribute name: ' + name)
return super().__new__(cls, clsname, bases, clsdict)
class Root(metaclass=NoMixedCaseMeta):
pass
class A(Root):
def foo_bar(self):
pass
class B(Root):
def fooBar(self):
pass
# TypeError: Bad attribute name: fooBar
元类的定义中重写 __new__()
还是 __init__()
方法取决于你想以何种方式产出类。__new__()
方法生效于类创建之前,通常用于对类的定义进行改动(通过修改 class dictionary 的内容);__init__()
方法生效于类创建之后,通常是与已经生成的类对象进行交互。比如 super()
函数只在类实例被创建后才能起作用。
以编程的方式定义类
可以通过编程的方式创建类,比如从字符串中产出类的源代码。
types.new_class()
函数可以用来初始化新的类对象,只需要向其提供类名、父类(以元组的形式)、关键字参数和一个用来更新 class dictionary 的回调函数。
# Methods
def __init__(self, name, shares, price):
self.name = name
self.shares = shares
self.price = price
def cost(self):
return self.shares * self.price
cls_dict = {
'__init__': __init__,
'cost': cost,
}
# Make a class
import types
Stock = types.new_class('Stock', (), {}, lambda ns: ns.update(cls_dict))
Stock.__module__ = __name__
s = Stock('ACME', 50, 91.1)
print(s)
# => <__main__.Stock object at 0x7f0e3b62edc0>
print(s.cost())
# => 4555.0
通常形式的类定义代码:
class Spam(Base, debug=True, typecheck=False):
...
转换成对应的 type.new_class()
形式的代码:
Spam = types.new_class('Spam', (Base,),
{'debug': True, 'typecheck': False},
lambda ns: ns.update(cls_dict))
从代码中产出类对象在某些场景下是很有用的,比如 collections.nametupe()
函数:
>>> import collections
>>> Stock = collections.namedtuple('Stock', ['name', 'shares', 'price'])
>>> Stock
<class '__main__.Stock'>
下面是一个类似 namedtuple 功能的实现代码:
import operator
import types
import sys
def named_tuple(classname, fieldnames):
# Populate a dictionary of field property accessors
cls_dict = { name: property(operator.itemgetter(n))
for n, name in enumerate(fieldnames) }
# Make a __new__ function and add to the class dict
def __new__(cls, *args):
if len(args) != len(fieldnames):
raise TypeError('Expected {} arguments'.format(len(fieldnames)))
return tuple.__new__(cls, args)
cls_dict['__new__'] = __new__
# Make the class
cls = types.new_class(classname, (tuple,), {},
lambda ns: ns.update(cls_dict))
cls.__module__ = sys._getframe(1).f_globals['__name__']
return cls
Point = named_tuple('Point', ['x', 'y'])
print(Point)
# => <class '__main__.Point'>
p = Point(4, 5)
print(p.x)
# => 4
print(p.y)
# => 5
p.x = 2
# => AttributeError: can't set attribute
在定义时初始化类成员
在类定义时完成初始化或其他设置动作,是元类的经典用法(元类在类定义时触发)。
import operator
class StructTupleMeta(type):
def __init__(cls, *args, **kwargs):
super().__init__(*args, **kwargs)
for n, name in enumerate(cls._fields):
setattr(cls, name, property(operator.itemgetter(n)))
class StructTuple(tuple, metaclass=StructTupleMeta):
_fields = []
def __new__(cls, *args):
if len(args) != len(cls._fields):
raise ValueError('{} arguments required'.format(len(cls._fields)))
return super().__new__(cls, args)
class Stock(StructTuple):
_fields = ['name', 'shares', 'price']
class Point(StructTuple):
_fields = ['x', 'y']
s = Stock('ACME', 50, 91.1)
print(s)
# => ('ACME', 50, 91.1)
print(s[0])
# => ACME
print(s.name)
# => ACME
s.shares = 23
# => AttributeError: can't set attribute
在上面的代码中,StructTupleMeta
元类从 _fields
类属性中读取属性名列表并将其转换成属性方法。operator.itemgetter()
函数负责创建访问方法(accessor function),property()
函数负责将它们转换成属性(property)。
StructTuple
类用作供其他类继承的基类。其中的 __new__()
方法负责创建新的实例对象。不同于 __init__()
,__new__()
方法会在实例创建之前触发,由于 tuple 是不可变对象,创建之后即无法被修改,因此这里使用 __new__()
。
网友评论