我正在阅读链接中描述符如何工作的解释:http://users.rcn.com/python/download/Descriptor.htm#properties。
但是,在这里,在类Property
的{{1}}方法下,我对方法签名有疑问。方法签名是:
__get__
在这里,我知道def __get__(self, obj, objtype=None):
何时以及如何可以是无或实际对象。
但是,我无法理解:obj
objtype
在什么情况下可以None
?并且,它在实际例子中是如何有用的。
答案 0 :(得分:6)
签名
def __get__(self, obj, objtype=None):
表示objtype
是一个可选参数。如果仅使用一个参数调用__get__
,则objtype
将设置为None
。
例如,Foo可以通过这种方式定义foo.baz
来从Bar中窃取方法:
class Foo(object):
pass
class Bar(object):
def baz(self):
print('Hi')
foo = Foo()
foo.baz = Bar.baz.__get__(foo)
print(foo.__dict__)
# {'baz': <bound method ?.baz of <__main__.Foo object at 0xb787006c>>}
foo.baz()
# Hi
相反,如果使用__get__
的2参数形式,
foo.baz = Bar.baz.__get__(foo, foo.__class__)
然后foo.baz
是未绑定方法Bar.baz
和foo.baz()
加注
TypeError: unbound method baz() must be called with Bar instance as first argument (got nothing instead)
请注意,在Python3中,unbound method
的概念已被删除。没有更多检查看到调用obj的类是正确的类型。所以在Python3中,
用于定义foo.baz
的1参数和2参数形式都可以。
答案 1 :(得分:1)
python docs在Implementing Descriptors下讨论了这个问题。签名实际上是下面提供的。如你所提到的那样,实例可能是None,但所有者永远不应该是None也许在你如何读书时有一个错误。
object.__get__(self, instance, owner)
答案 2 :(得分:1)
也许我正在重写上面的答案,但上面提到的这个想法对我来说似乎更容易理解。
考虑@cached_property
的这种实现class cached_property(object):
"""Like @property, but caches the value."""
def __init__(self, func):
self._func = func
def __get__(self, obj, cls):
if obj is None:
return self
value = self._func(obj)
obj.__dict__[self.__name__] = value
return value
我有同样的问题“为什么obj
检查了None
?”和“为什么要回归自我?”
以下是两种情况如何产生的例子
典型用法:
class Foo(object):
@cached_property
def foo(self):
# Would usually have significant computation here
return 9001
foo_instance = Foo()
foo_foo = foo_instance.foo # Behind the scenes invokes Foo.foo.__get__(foo_instance, Foo)
等等,是的,这就是我的期望,obj
None
时的情况怎么样?
不那么典型的用法(抓取对未绑定版本的属性的访问权限)
(采取与上述相同的Foo
)
>> Foo.foo
<__main__.cached_property at 0x178bed0>
在这种情况下,通话看起来像Foo.foo.__get__(None, Foo)
答案 3 :(得分:0)
objtype表示obj的“owner”类,这意味着您将实例传递给obj,将其基类传递给objtype。但是,这意味着obj可以是None,因为可能没有给定类的实例,但是objtype不能,因为这会引发AttributeError异常。 正如Marwan所说,你确定你没有在那里混淆什么? ;)
答案 4 :(得分:0)
从https://github.com/youngsterxyf/mpdp-code/blob/master/chapter9/lazy.py
重新编辑 # lazy.py
class LazyProperty:
def __init__(self, method):
self.method = method
self.method_name = method.__name__
#print('function overriden: {}'.format(self.method))
#print("function's name: {}".format(self.method_name))
def __get__(self, obj, cls):
if not obj:
return None
value = self.method(obj)
#print('value {}'.format(value))
setattr(obj, self.method_name, value)
return value
class Test:
def __init__(self):
self.x = 'foo'
self.y = 'bar'
self._resource = None
@LazyProperty
def resource(self):
print('initializing self._resource which is: {}'.format(self._resource))
self._resource = tuple(range(5))
return self._resource
def main_Py27():
# python 2.7.12
t = Test()
print(t.x)
#>>> foo
print(t.y)
#>>> bar
print(t.resource)
#>>> <__main__.LazyProperty instance at 0x02C2E058>
print(t.resource.__get__(t,Test))
#>>> initializing self._resource which is: None
#>>> (0, 1, 2, 3, 4)
print(t.resource)
#>>> (0, 1, 2, 3, 4)
def main_Py3():
# python 3.x
t = Test()
print(t.x)
#>>> foo
print(t.y)
#>>> bar
print(t.resource)
#>>> initializing self._resource which is: None
#>>> (0, 1, 2, 3, 4)
print(t.resource)
#>>> (0, 1, 2, 3, 4)
def main():
import sys
if sys.version_info < (3,0):
main_Py27()
else:
main_Py3()
if __name__ == '__main__':
main()