离线下载
PDF版 ePub版

qyuhen · 更新于 2018-11-28 11:00:43

描述符

很少有人会去刻意关注描述符 (Descriptor),尽管它时时刻刻以属性、方法的身份出现。

描述符协议:

__get__(self, instance, owner) --> return value
__set__(self, instance, value)
__delete__(self, instance)

描述符对象以类型 (owner class) 成员的方式出现,且最少要实现一个协议方法。最常见的描述符有 property、staticmethod、classsmethod。访问描述符类型成员时,解释器会自动调用与行为相对应的协议方法。

  • 实现 getset 方法,称为 data descriptor。
  • 仅有 get 方法的,称为 non-data descriptor。
  • get 对 owner_class、owner_instance 访问有效。
  • setdelete 仅对 owner_instance 访问有效。
>>> class MyDescriptor(object):
...     def __get__(self, instance, owner):  # 本例中 owner 是 class Data。
...         print "get:", instance, owner
...         return hex(id(instance))
...
...     def __set__(self, instance, value):
...         print "set:", instance, value
...
...     def __delete__(self, instance):
...         print "del:", instance

>>> class Data(object):
...     x = MyDescriptor()

>>> d = Data()

>>> d.x       # __get__ 的返回值。
get: <__main__.Data object at 0x107a23790> <class '__main__.Data'>
'0x107a23790'

>>> d.x = 100      # d 被当做 instance 实参。
set: <__main__.Data object at 0x107a23790> 100

>>> del d.x       # d 被当做 instance 实参。
del: <__main__.Data object at 0x107a23790>

>>> Data.x       # 以 owner 类型访问时,__get__ 有效。
get: None <class '__main__.Data'>   # instance = None
'0x106a96148'

>>> Data.x = 1      # __set__ 对 class 调用无效。
        # 因此 Data.x 被重新赋值。

>>> type(Data.x)
<type 'int'>

如果没有定义 get 方法,那么直接返回描述符对象,不会有默认 get 实现。

property

属性总是 data descriptor,这和是否提供 setter 无关。其优先级总是高过同名实例字段,如果没有提供 setter,set 方法会阻止赋值操作。

>>> class Data(object):
...     oid = property(lambda s: hex(id(s)))

>>> hasattr(Data.oid, "__set__")
True

>>> d = Data()

>>> d.oid
'0x107a23a90'

>>> d.oid = 123
AttributeError: can't set attribute

non-data

non-data descriptor 会被同名实例字段抢先。

>>> class Descriptor(object):
...     def __get__(self, instance, owner):
...         print "__get__"

>>> class Data(object):
...     x = Descriptor()

>>> d = Data()

>>> d.x    # 描述符有效。
__get__

>>> d.__dict__   # instance.__dict__ 没有同名字段。
{}

>>> d.x = 123   # 没有 __set__,创建同名实例字段。

>>> d.__dict__  
{'x': 123}

>>> d.x    # 依据成员查找规则,实例字段被优先命中。
123

>>> Data.x    # 描述符在 owner_class.__dict___。
__get__

bound method

通过描述符,我们可以了解实例方法 self 参数是如何隐式传递的。

>>> class Data(object):
...     def test(self): print "test"

>>> d = Data()

>>> d.test      # 只有 bound method 才会隐式传递 self。
<bound method Data.test of <__main__.Data object at 0x10740b050>>

>>> Data.test.__get__(d, Data)   # 向 __get__ 传递 instance 参数。
<bound method Data.test of <__main__.Data object at 0x10740b050>>

>>> Data.test     # unbound method 需显式传递 self。
<unbound method Data.test>

>>> Data.test.__get__(None, Data)  # instance 为 None。
<unbound method Data.test>

现在可以看出,bound/unbound 是 get 造成的,关键就是 instance 参数。那么 self 参数存在哪?由谁替我们自动传递 self 参数呢?

>>> bm = Data.test.__get__(d, Data)

>>> bm.__func__     # 实际的目标函数 test。
<function test at 0x107404488>

>>> bm.__self__     # __get__ instance 参数,也就是 self。
<__main__.Data object at 0x10740b050>

>>> bm.__call__()     # __call__ 内部替我们传递 self !
test

>>> unbm = Data.test.__get__(None, Data) # unbound method

>>> unbm.__func__
<function test at 0x107404488>

>>> unbm.__self__ is None   # instance == None, self == None。
True

>>> unbm.__call__()    # __call__ 会检查 __self__。
TypeError: unbound method test() must be called with Data instance as first argument
(got nothing instead)

>>> unbm.__call__(d)    # 只好给 __call__ 有效的 instance。
test

classmethod

不同于 staticmethod,classmethod 会 bound 类型对象。

>>> class Data(object):
...     @classmethod
...     def test(cls): print cls

>>> Data.test.__get__(None, Data)
<bound method type.test of <class '__main__.Data'>>

>>> m = Data.test.__get__(None, Data)

>>> m.__self__     # 类型对象,也就是隐式 cls 参数。
<class '__main__.Data'>

>>> m.__call__()
<class '__main__.Data'>
上一篇: 装饰器 下一篇: 元类