Search code examples
pythonattrwith-statement

Why doesn't __getattr__ work with __exit__?


I came across this as a bit of a surprise while trying to work out another question.

This seemed extremely odd to me, I thought it was worth asking the question. Why doesn't __getattr__ appear to work with with?

if I make this object:

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

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

and using it with 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>

Why does this happen?

EDIT

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

It definitely isn't inheriting __exit__


Solution

  • I can't say for sure, but after reading over the PEP describing the with statement:

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

    This jumped out at me:

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

    Right there. The with statement does not call __getattr__(__exit__) but calls type(a).__exit__ which does not exist giving the error.

    So you just need to define those:

    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)