一、函数装饰器
1 | import time |
装饰器负责接收某个函数作为参数,然后返回一个新的函数作为输出。下面的代码:1
2
3
def countdown(n):
...
实际上等同于1
2
3def countdown(n):
...
countdown = timethis(countdown)
装饰器内部通常要定义一个接收任意参数(*args, **kwargs
)的函数,即 wrapper()
。在 wrapper 函数里,调用原始的作为参数传入的函数(func
)并获取其结果,再根据需求添加上执行其他操作的代码(比如计时、日志等)。最后新创建的 wrapper 函数被返回并替换掉被装饰的函数(countdown
),从而在不改变被装饰函数自身代码的情况下,为其添加额外的行为。
二、带参数的装饰器
1 | from functools import wraps |
最外层的函数 logged()
用于接收传入装饰器的参数,并使这些参数能够被装饰器中的内部函数(decorate()
)访问。内部函数 decorate 则用于实现装饰器的“核心逻辑”,即接收某个函数作为参数,通过定义一个新的内部函数(wrapper
)添加某些行为,再将这个新的函数返回作为被装饰函数的替代品。
在类中定义的装饰器
1 | from functools import wraps |
利用装饰器向原函数中添加参数
1 | from functools import wraps |
装饰器修改类的定义
1 | def log_getattribute(cls): |
类装饰器可以用来重写类的部分定义以修改其行为,作为一种直观的类继承或元类的替代方式。
比如上述功能也可以通过类继承来实现:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17class 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 中的类可以像函数那样调用,同时创建实例对象:1
2
3
4
5
6
7class Spam:
def __init__(self, name):
self.name = name
a = Spam('Guido')
b = Spam('Diana')
如果开发人员想要自定义创建实例的行为,可以通过元类重新实现一遍 __call__()
方法。假设在调用类时不创建任何实例:1
2
3
4
5
6
7
8
9
10
11
12
13
14 class NoInstance(type):
def __call__(self, *args, **kwargs):
raise TypeError("Can't instantiate directly")
class Spam(metaclass=NoInstance):
def grok(x):
print('Spam.grok')
Spam.grok(42) # Spam.grok
s = Spam()
# TypeError: Can't instantiate directly
元类实现单例模式
单例模式即类在创建对象时,单一的类确保只生成唯一的实例对象。1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17# 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')
1 | from singleton import * |
强制检查类定义中的代码规范
可以借助元类监控普通类的定义代码。通常的方式是定义一个继承自 type 的元类并重写其 __new__()
或 __init__()
方法。1
2
3
4
5
6class 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)
1 | class MyMeta(type): |
为了使用元类,通常会先定义一个供其他对象继承的基类:1
2
3
4
5
6
7
8class Root(metaclass=MyMeta):
pass
class A(Root):
pass
class B(Root):
pass
元类的重要特性在于,它允许用户在类定义时检查类的内容。在重写的 __init__()
方法内部,可以方便地检查 class dictionary、base class 或者其他与类定义相关的内容。此外,当元类指定给某个普通类以后,该普通类的所有子类也都会继承元类的定义。
下面是一个用于检查代码规范的元类,确保方法的命名里只包含小写字母:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21class 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 的回调函数。1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26# 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
通常形式的类定义代码:1
2class Spam(Base, debug=True, typecheck=False):
...
转换成对应的 type.new_class()
形式的代码:1
2
3Spam = types.new_class('Spam', (Base,),
{'debug': True, 'typecheck': False},
lambda ns: ns.update(cls_dict))
从代码中产出类对象在某些场景下是很有用的,比如 collections.nametupe()
函数:1
2
3
4import collections
'Stock', ['name', 'shares', 'price']) Stock = collections.namedtuple(
Stock
<class '__main__.Stock'>
下面是一个类似 namedtuple 功能的实现代码:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35import 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
在定义时初始化类成员
在类定义时完成初始化或其他设置动作,是元类的经典用法(元类在类定义时触发)。1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34import 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__()
。