This issue tracker has been migrated to GitHub, and is currently read-only.
For more information, see the GitHub FAQs in the Python's Developer Guide.

classification
Title: 'with instance' references class's __enter__ attribute rather than instance's
Type: behavior Stage: resolved
Components: Interpreter Core Versions: Python 3.3, Python 2.7
process
Status: closed Resolution: not a bug
Dependencies: Superseder:
Assigned To: Nosy List: dewin, r.david.murray
Priority: normal Keywords:

Created on 2014-01-06 17:42 by dewin, last changed 2022-04-11 14:57 by admin. This issue is now closed.

Messages (2)
msg207457 - (view) Author: Daniel Grace (dewin) Date: 2014-01-06 17:42
I was writing code for a class that uses one threading.RLock per object.  For convenience, I wanted to be able to use that lock's context manager and acquire/release methods without referencing the lock directly, so I opted to take a shortcut by assigning self.__enter__ = self.lock.__enter__ and so forth.

This fails in Python 3.3.1 (self-compiled on Debian) and both "3.3.2+" and "2.7.5+" (currently available Ubuntu packages).  Judging by the error messages, it looks 'with' is examining the __enter__ and __exit__ attributes defined on the instance's class, rather than those defined on the instance itself.

The workaround here is simple enough, but I'm under the impression that it shouldn't be needed anyways.

Test case follows:

import threading

class Foo(object):
#       Uncommenting these yields "NoneType is not callable" rather than an AttributeError
#       __enter__ = None
#       __exit__ = None
#       acquire = None
#       release = None
#       lock = None

        def __init__(self):
                self.lock = threading.RLock()
                print(repr(self.lock))
                self.__enter__ = self.lock.__enter__
                self.__exit__ = self.lock.__exit__
                self.acquire = self.lock.acquire
                self.release = self.lock.release

foo = Foo()
# These all function as expected.  The fourth line fails (correctly) on 2.7.5.
print(repr(foo.__enter__))
print(repr(foo.__exit__))
print(foo.__enter__())
print(foo.__exit__())

# This does not
with foo:
        pass
msg207472 - (view) Author: R. David Murray (r.david.murray) * (Python committer) Date: 2014-01-06 19:44
"magic methods" like __enter__ and __exit__ are only looked up on the class, not on the instance.  This is by design.

In some older versions of Python some specific methods (and I think __enter__ and __exit__ were among them for a time) were looked up on the instances, but this was a bug.

(Personally I think it would be nice if they were looked up on the instances, but there are good reasons why this is not done.)
History
Date User Action Args
2022-04-11 14:57:56adminsetgithub: 64348
2014-01-06 19:44:25r.david.murraysetstatus: open -> closed

nosy: + r.david.murray
messages: + msg207472

resolution: not a bug
stage: resolved
2014-01-06 17:42:24dewincreate