5

在尝试解决另一个问题时,我感到有些意外。

这对我来说似乎非常奇怪,我认为值得提出这个问题。为什么似乎无法__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__

4

3 回答 3

6

with语句操作码SETUP_WITH查找__exit__为“特殊方法查找”,它忽略和__getattr__新式__getattribute__类(但不是旧式类)。有关更多信息,请参阅此邮件列表线程,他们在其中讨论添加特殊方法查找语义with(他们最终会这样做)。另请参阅新式类的特殊方法查找,以详细讨论为什么以这种方式查找这些特殊方法。

特别是,特殊方法查找也会绕过__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
于 2012-09-28T02:51:34.883 回答
5

我不能肯定地说,但是在阅读了描述 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)
于 2012-09-28T02:51:51.010 回答
0

__getattr__先前的答案已经解释了不适用于__enter__and的事实__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

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

如果我们将资源作为属性并代理它的and__enter__怎么办?我们写一些这样的代码:__exit____getattr__

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

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

假设使用and__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语境。

To conclude, if we make __getattr__ works with __enter__ and __exit__, it will result in bad behaviors. It's not a good design.

于 2021-05-10T03:56:52.307 回答