【问题标题】:Why doesn't __getattr__ work with __exit__?为什么 __getattr__ 不能与 __exit__ 一起使用?
【发布时间】:2012-09-19 22:02:47
【问题描述】:

在尝试计算另一个 question 时,我感到有点意外。

这对我来说似乎非常奇怪,我认为值得提出这个问题。为什么__getattr__ 似乎不能与with 一起使用?

如果我制作这个对象:

class FileHolder(object):
    def __init__(self,*args,**kwargs):
        self.f= file(*args,**kwargs)

    def __getattr__(self,item):
        return getattr(self.f,item)

并与with一起使用,

>>> a= FileHolder("a","w")
>>> a.write
<built-in method write of file object at 0x018D75F8>
>>> with a as f:
...   print f
...
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
AttributeError: __exit__
>>> a.__exit__
<built-in method __exit__ of file object at 0x018D75F8>

为什么会这样?

编辑

>>> object.__exit__
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
AttributeError: type object 'object' has no attribute '__exit__'

绝对不是继承__exit__

【问题讨论】:

  • 这里发生了一些事情,在您的类定义中,您将FileHolder 设为object 的子类。但是在你下面的代码中,它说a 是一个file 对象。那是不一致的。
  • @jedwards 老实说不是。自己测试一下:)
  • @jedwards,__exit__ 来自分配给self.ffile 对象,如果你问type(a),你会得到FileHolder
  • @Adam,你说得对——实际上我并没有创建这个类(我做了类似class FileHolder(object): pass 之类的事情)——对我有用。

标签: python attr with-statement


【解决方案1】:

with 语句操作码 SETUP_WITH__exit__ 查找为“特殊方法查找”,它忽略了新式类(但不是旧式类)上的 __getattr____getattribute__。有关更多信息,请参阅this mailing list thread,他们在其中讨论将特殊方法查找语义添加到with(他们最终会这样做)。有关为什么以这种方式查找这些特殊方法的详细讨论,另请参阅 special method lookup for new-style classes

特别是,特殊方法查找绕过类型对象上的__getattr__。因此,即使文档说该方法被查找为type(mgr).__exit__,此代码也不起作用:

class M(type):
    def __getattr__(*args): return lambda: 0

class X(object):
    __metaclass__ = M

x = X()
type(x).__exit__ # works, returns a lambda

with x: pass # fails, AttributeError

【讨论】:

  • 这确实引出了一个问题,为什么__enter__ 没有得到AttributeError?除非它首先寻找退出,这似乎不太可能
  • 正如@korylprince 的回答所表明的那样,首先查找__exit__,这样他们就不会在没有功能性__exit__ 的情况下调用__enter__ 时遇到问题。你可以看到确切的 CPython 实现here
【解决方案2】:

我不能肯定地说,但是在阅读了描述 with 语句的 PEP 之后:

http://www.python.org/dev/peps/pep-0343/

这让我大吃一惊:

A new statement is proposed with the syntax:

    with EXPR as VAR:
        BLOCK

....

The translation of the above statement is:

    mgr = (EXPR)
    exit = type(mgr).__exit__  # Not calling it yet
    value = type(mgr).__enter__(mgr)

....

就在那儿。 with 语句不调用__getattr__(__exit__),而是调用不存在的type(a).__exit__,给出错误。

所以你只需要定义那些:

class FileHolder(object):                                                                                                                 
    def __init__(self,*args,**kwargs):
        self.f= file(*args,**kwargs)

    def __enter__(self,*args,**kwargs):
        return self.f.__enter__(*args,**kwargs)

    def __exit__(self,*args,**kwargs):
        self.f.__exit__(*args,**kwargs)

    def __getattr__(self,item):
        return getattr(self.f,item)

【讨论】:

  • 令人惊讶的是,这个文档并不完全正确——特殊方法查找也忽略了type(x).__getattr__。看我的回答...
  • 我不能肯定。我唯一的猜测是设计就是这样。可能需要在邮件列表中询问。
  • @GP89:我做了更多的挖掘;这是设计使然。请参阅here 了解全部原因。
  • 我还对@nneonneo 提到的邮件列表进行了一些挖掘。这是一个简短的摘要:mail.python.org/pipermail/python-dev/2009-May/089576.html
【解决方案3】:

前面的答案已经解释了__getattr__ 不适用于__enter____exit__ 的事实。我在这里给出我的想法,为什么它不应该起作用。

我们在对象上定义__enter____exit__ 方法的唯一原因是我们需要在with 语句中使用它。这两个方法帮助我们隐式地获取和释放资源,所以我们通常这样定义它们:

class Resource(object):
    ...
    def __enter__(self):
        return self
            
    def __exit__(self, *exc):
        self.close()

然后你可以写一些这样的代码:

with Resource() as resource:  # __enter__ is called and returns a value as `resource`
    do_something_with_resource()
    # `resource.__exit__` is called

如您所见,我们获取和释放的资源正是我们定义的类的一个实例。

如果我们将资源作为属性并用__getattr__ 代理它的__enter____exit__ 会怎样?我们这样写一些代码:

class ResourceProxy(object):
    def __init__(self):
        self._resource = Resource()

    def __getattr__(self, key):
        return getattr(self._resource, key)

假设__getattr____enter____exit__ 可以正常工作,下面是with 语句中会发生的情况:

with ResourceProxy() as resource:  # proxied __enter__ is called
    # now `resource` is NOT a ResourceProxy instance, because what we called is `_resource.__enter__`
    do_something_with_resource()
    # `_resource.__exit__` is called and closed itself properly. 
    # Here is nothing to do with ResourceProxy, because it has never enter `with` context

上述行为很奇怪,可能与用户预期的不一样,原因如下:

  1. 进入with上下文的资源不是我们发送的对象。
  2. 退出with上下文时,调用代理对象的__exit__方法,而不是我们发送的外部对象。您可能认为如果我们在外部类上添加__exit__定义可能会有所帮助,但是答案是否定的,因为外部类从未进入 with 上下文。

总而言之,如果我们让__getattr____enter____exit__ 一起使用,将会导致不良行为。这不是一个好的设计。

【讨论】:

    猜你喜欢
    • 2011-06-05
    • 2018-03-09
    • 2021-06-14
    • 2012-10-09
    • 2020-03-18
    • 2017-11-21
    • 2019-04-11
    • 2013-12-30
    • 2011-04-20
    相关资源
    最近更新 更多