类 StructTupleMeta 获取到类属性 _fields 中的属性名字列表, 然后将它们转换成相应的可访问特定元组槽的方法。函数 operator.itemgetter() 创建一个访问器函数, 然后 property() 函数将其转换成一个属性。
本节最难懂的部分是知道不同的初始化步骤是什么时候发生的。 StructTupleMeta 中的 init() 方法只在每个类被定义时被调用一次。 cls 参数就是那个被定义的类。实际上,上述代码使用了 _fields 类变量来保存新的被定义的类, 然后给它再添加一点新的东西。
StructTuple 类作为一个普通的基类,供其他使用者来继承。 这个类中的 new() 方法用来构造新的实例。 这里使用 new() 并不是很常见,主要是因为我们要修改元组的调用签名, 使得我们可以像普通的实例调用那样创建实例。就像下面这样:
s = Stock('ACME', 50, 91.1) # OK s = Stock(('ACME', 50, 91.1)) # Error
跟 init() 不同的是,new() 方法在实例被创建之前被触发。 由于元组是不可修改的,所以一旦它们被创建了就不可能对它做任何改变。而 init() 会在实例创建的最后被触发, 这样的话我们就可以做我们想做的了。这也是为什么 new() 方法已经被定义了。
尽管本节很短,还是需要你能仔细研读,深入思考Python类是如何被定义的,实例是如何被创建的, 还有就是元类和类的各个不同的方法究竟在什么时候被调用。
PEP 422 提供了一个解决本节问题的另外一种方法。 但是,截止到我写这本书的时候,它还没被采纳和接受。 尽管如此,如果你使用的是Python 3.3或更高的版本,那么还是值得去看一下的。
在类定义时就执行初始化或设置操作是元类的一个典型应用场景。本质上讲,一个元类会在定义时被触发, 这时候你可以执行一些额外的操作。
下面是一个例子,利用这个思路来创建类似于 collections 模块中的命名元组的类:
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)
>>> s
('ACME', 50, 91.1)
>>> s[0]
'ACME'
>>> s.name
'ACME'
>>> s.shares * s.price
4555.0
>>> s.shares = 23
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
AttributeError: can't set attribute
>>>
版权声明:本文内容由阿里云实名注册用户自发贡献,版权归原作者所有,阿里云开发者社区不拥有其著作权,亦不承担相应法律责任。具体规则请查看《阿里云开发者社区用户服务协议》和《阿里云开发者社区知识产权保护指引》。如果您发现本社区中有涉嫌抄袭的内容,填写侵权投诉表单进行举报,一经查实,本社区将立刻删除涉嫌侵权内容。