【问题标题】:python __get__ methodpython __get__ 方法
【发布时间】:2012-09-30 11:25:02
【问题描述】:

我正在阅读以下链接中关于描述符如何工作的说明:http://users.rcn.com/python/download/Descriptor.htm#properties

但是,在这里,在Property 类的__get__ 方法下,我对方法签名有疑问。方法签名是:

def __get__(self, obj, objtype=None):

在这里,我知道obj 何时以及如何可以是 None 或实际对象。

但是,我不明白:在什么情况下objtype 可以是None?以及它在实际示例中的用途。

【问题讨论】:

  • 也许这将受益于指南的相关部分在问题中......明确提出与“我如何使用__get__”不同的问题......跨度>

标签: python descriptor getattribute


【解决方案1】:

签名

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.bazunbound方法Bar.bazfoo.baz() raises

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 参数形式都有效。

【讨论】:

  • 这不是在回答问题。当然,您可以手动调用它,但这不是重点。
  • @unutbu:你能举个例子,当使用 Property 调用 get 时 objtype 可以是 None 吗?
  • @phant0m:我认为您现在可以删除您的评论,因为 unutbu 已根据我想知道的所需解释和示例编辑了他的答案。
  • @GodMan 我不同意,方法不是属性。
  • @phant0m:属性是方法。方法是描述符。我认为这个问题更多地与__get__ 如何与一般描述符一起使用而不是与特定属性有关。
【解决方案2】:

也许我正在改写上述答案,但对我来说,上述想法的这种呈现似乎更容易理解。

考虑@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

我对“为什么要检查 objNone?”有同样的问题。和“为什么要返回自己?”

以下是这两种情况如何出现的示例

典型用法:

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)

等等,是的,这就是我所期望的,当objNone 时呢?

不那么典型的用法(获取对属性的未绑定版本的访问)

(取同上Foo

>> Foo.foo
<__main__.cached_property at 0x178bed0>

在这种情况下,调用看起来像Foo.foo.__get__(None, Foo)

【讨论】:

    【解决方案3】:

    python 文档在Implementing Descriptors 下对此进行了很好的讨论。签名实际上是下面提供的。正如你提到的实例可能没有但所有者永远不应该是无。也许您阅读的方法有误。

    object.__get__(self, instance, owner)
    

    【讨论】:

      【解决方案4】:

      objtype 表示 obj 的“所有者”类,这意味着您将实例传递给 obj 并将其基类传递给 objtype。但是,这意味着 obj 可以是 None,因为可能没有给定类的实例,但 objtype 不能,因为这会引发 AttributeError 异常。 正如Marwan所说,你确定你没有把什么混在一起吗? ;)

      【讨论】:

      【解决方案5】:

      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()
      

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2018-05-07
        • 1970-01-01
        • 1970-01-01
        • 2011-04-17
        • 1970-01-01
        相关资源
        最近更新 更多