Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

__getattr__ doesn't work with __enter__ -> AttributeError

Tags:

python

I would like to use with on an object that uses __getattr__ to redirect calls.
Howerver, this does not seem to work with the method __enter__

Please consider the following, simplified code to reproduce the error:

class EnterTest(object):
    def myenter(self):
        pass
    def __exit__(self, type, value, traceback):
        pass

    def __getattr__(self, name):
        if name == '__enter__':
            return self.myenter

enter_obj = EnterTest()
print getattr(enter_obj, '__enter__')

with enter_obj:
    pass

Output:

<bound method EnterTest.myenter of <__main__.EnterTest object at 0x00000000021432E8>>
Traceback (most recent call last):
  File "test.py", line 14, in <module>
    with enter_obj:
AttributeError: __enter__

Why doesn't it fall back to __getattr__ since __enter__ does not exist on the object?

Of course, I could make it work if I just create an __enter__ method and redirect from there instead, but I'm wondering why it doesn't work otherwise.

My python version is the following:

C:\Python27\python27.exe 2.7 (r27:82525, Jul  4 2010, 07:43:08) [MSC v.1500 64 bit (AMD64)]
like image 446
phant0m Avatar asked Nov 10 '10 15:11

phant0m


1 Answers

According to upstream, this working was a bug in 2.6 which was "fixed" in 2.7. The short answer is that methods like __enter__ are looked up on the class, not on the object.

The documentation for this obscure behavior is at http://docs.python.org/reference/datamodel#specialnames: x[i] is roughly equivalent to ... type(x).__getitem__(x, i) for new-style classes.

You can see this behavior with other special methods:

class foo(object):
    def __iadd__(self, i):
        print i
a = foo()
a += 1

class foo2(object):
    def __getattr__(self, key):
        print key
        raise AttributeError
b = foo2()
b += 1

class foo3(object):
    pass
def func(self, i):
    print i
c = foo3()
c.__iadd__ = func
c += 1

The first works; the second two don't. Python 2.6 didn't conform to this behavior for __enter__ and __exit__, but 2.7 does. http://bugs.python.org/issue9259

That said, it's painfully inconsistent that these methods can't be handled dynamically like any other attributes can. Similarly, you can't instrument accesses to these methods with __getattribute__ like you can any other method. I can't find any intrinsic design logic to this. Python is normally very consistent, and this is a fairly unpleasant wart.

like image 91
Glenn Maynard Avatar answered Nov 20 '22 20:11

Glenn Maynard