The previous answers has explained the fact that __getattr__
does not work with __enter__
and __exit__
. I'm here to give my thinking of why it SHOULD NOT work.
The only reason we define __enter__
and __exit__
methods on an object is that we need to use it in with
statement. The two methods help us get and release a resource implicitly, so we usually define them like this:
class Resource(object):
...
def __enter__(self):
return self
def __exit__(self, *exc):
self.close()
then you can write some code like this:
with Resource() as resource: # __enter__ is called and returns a value as `resource`
do_something_with_resource()
# `resource.__exit__` is called
As you have noticed, the resource we get and release is exactly an instance of the class we defined.
What if we hold a resource as an attribute and proxy its __enter__
and __exit__
with __getattr__
? We write some code like this:
class ResourceProxy(object):
def __init__(self):
self._resource = Resource()
def __getattr__(self, key):
return getattr(self._resource, key)
Assuming __getattr__
works fine with __enter__
and __exit__
, here is what will happen in with
statement:
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
The behavior above is strange and probably not as the user expected, for the following two reasons:
- the resource entered into
with
context is not the object we sent in.
- when exiting
with
context, __exit__
method of the proxied object is called, instead of the outer object we sent in. You may think it might help if we add an __exit__
definition on the outer class, but the answer is not, because the outer class has never enter with
context.
To conclude, if we make __getattr__
works with __enter__
and __exit__
, it will result in bad behaviors. It's not a good design.
FileHolder
a subclass ofobject
. But in your code below it, it says thata
is afile
object. Thats not consistent. – Tell__exit__
is from thefile
object assigned toself.f
, if you asktype(a)
, you'll getFileHolder
. – Superficiesclass FileHolder(object): pass
) -- serves me right. – Tell