我在试图找出另一个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__
答案 0 :(得分:5)
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
答案 1 :(得分: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)
答案 2 :(得分:0)
之前的答案已经解释了 __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
如您所见,我们获取和释放的资源正是我们定义的类的实例。
如果我们将资源作为属性持有并用 __enter__
代理它的 __exit__
和 __getattr__
会怎样?我们写了一些这样的代码:
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
上述行为很奇怪,可能与用户预期的不一样,原因如下:
with
上下文的资源不是我们发送的对象。with
上下文时,会调用代理对象的 __exit__
方法,而不是我们发送的外部对象。您可能认为如果我们添加一个 __exit__
定义可能会有所帮助在外部类上,但答案不是,因为外部类从未进入 with
上下文。总而言之,如果我们让 __getattr__
与 __enter__
和 __exit__
一起工作,它将导致不良行为。这不是一个好的设计。