Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Attempting to use class with both __enter__ & __exit__ undefined yields __exit__ attribute error #71287

Closed
ellingtonjp mannequin opened this issue May 24, 2016 · 24 comments
Assignees
Labels
3.7 (EOL) end of life interpreter-core (Objects, Python, Grammar, and Parser dirs) type-bug An unexpected behavior, bug, or error

Comments

@ellingtonjp
Copy link
Mannequin

ellingtonjp mannequin commented May 24, 2016

BPO 27100
Nosy @rhettinger, @ncoghlan, @benjaminp, @ned-deily, @serhiy-storchaka, @1st1, @zhangyangyu, @TeamSpen210, @maggyero
PRs
  • bpo-39048: Look up __aenter__ before __aexit__ in the async with statement #17609
  • Files
  • with.patch
  • fix_with_refleak.diff: Fix reference leak
  • regexp-deprecated.patch
  • Note: these values reflect the state of the issue at the time it was migrated and might not reflect the current state.

    Show more details

    GitHub fields:

    assignee = 'https://github.com/1st1'
    closed_at = <Date 2017-04-04.15:13:02.650>
    created_at = <Date 2016-05-24.03:28:37.689>
    labels = ['interpreter-core', 'type-bug', '3.7']
    title = 'Attempting to use class with both __enter__ & __exit__ undefined yields __exit__ attribute error'
    updated_at = <Date 2019-12-14.20:02:46.174>
    user = 'https://bugs.python.org/ellingtonjp'

    bugs.python.org fields:

    activity = <Date 2019-12-14.20:02:46.174>
    actor = 'maggyero'
    assignee = 'yselivanov'
    closed = True
    closed_date = <Date 2017-04-04.15:13:02.650>
    closer = 'ncoghlan'
    components = ['Interpreter Core']
    creation = <Date 2016-05-24.03:28:37.689>
    creator = 'ellingtonjp'
    dependencies = []
    files = ['42962', '45604', '45618']
    hgrepos = []
    issue_num = 27100
    keywords = ['patch']
    message_count = 24.0
    messages = ['266219', '266222', '266226', '266227', '266232', '277570', '277592', '277666', '277735', '281426', '281427', '281454', '281455', '281497', '281505', '281509', '281510', '281602', '281606', '281610', '281643', '291092', '291124', '358404']
    nosy_count = 11.0
    nosy_names = ['rhettinger', 'ncoghlan', 'benjamin.peterson', 'ned.deily', 'python-dev', 'serhiy.storchaka', 'yselivanov', 'xiang.zhang', 'Spencer Brown', 'ellingtonjp', 'maggyero']
    pr_nums = ['17609']
    priority = 'normal'
    resolution = 'fixed'
    stage = 'resolved'
    status = 'closed'
    superseder = None
    type = 'behavior'
    url = 'https://bugs.python.org/issue27100'
    versions = ['Python 3.6', 'Python 3.7']

    @ellingtonjp
    Copy link
    Mannequin Author

    ellingtonjp mannequin commented May 24, 2016

    Attempting to use class that has both __exit__() and __enter__() undefined as a context manager yields an AttributeError referring to __exit__:

    >>> class A():
    ...     pass
    ...
    >>> with A():
    ...     pass
    ...
    Traceback (most recent call last):
      File "<stdin>", line 1, in <module>
    AttributeError: A instance has no attribute '__exit__'

    Knowing that the 'with' statement should first execute __enter__, this is unexpected.

    The patch ensures the attribute error refers to __enter__() when both methods are undefined.

    @ellingtonjp ellingtonjp mannequin added interpreter-core (Objects, Python, Grammar, and Parser dirs) type-bug An unexpected behavior, bug, or error labels May 24, 2016
    @zhangyangyu
    Copy link
    Member

    This seems to be designed. From PEP-343 it tells clearly:

    If either of the relevant methods are not found
    as expected, the interpreter will raise AttributeError, in the
    order that they are tried (__exit__, __enter__).
    

    But currently I don't find out why.

    @rhettinger
    Copy link
    Contributor

    Nick, I've seen the come-up several times with people learning about context managers. The current error message seems cause confusion because it checks for __exit__ before __enter__, implying that the __enter__ is optional which it isn't. Do you have any objections testing for __enter__ first?

    @rhettinger rhettinger self-assigned this May 24, 2016
    @benjaminp
    Copy link
    Contributor

    I don't completely understand. If you get an AttributeError for __enter__, will you think only an __enter__ is required?

    @ncoghlan
    Copy link
    Contributor

    As Jonathan notes, the cause for confusion here is that we currently retrieve the attributes in the opposite order from the way we execute them, prompting the "Why is it looking for __exit__ first?", "Oh, __enter__ must be optional, so it didn't care that it was missing" train of thought.

    If I recall correctly, rather than stemming from any particular design principle, the current behaviour is an artifact of the initial implementation that didn't use any dedicated opcodes: since CPython uses a stack-based VM, we needed __enter__ to be above __exit__ on the stack, and the most efficient way to achieve that was to do the attribute lookups in the reverse order of invocation.

    When SETUP_WITH was introduced, that original method lookup ordering (and associated error message precedence) was preserved, even though the technical rationale for that ordering no longer applied.

    Given that, I don't see any problem with switching the lookup order now, and I agree that should be less confusing, given that the most obvious explanation for getting an error message about __enter__ when both are missing is that the interpreter tries to call __enter__ before it tries to do anything with __exit__. (That intuitive explanation still isn't 100% accurate, but it's closer than thinking __enter__ is optional)

    @serhiy-storchaka serhiy-storchaka added the 3.7 (EOL) end of life label Sep 27, 2016
    @rhettinger
    Copy link
    Contributor

    I don't see any problem with switching the lookup order now,
    and I agree that should be less confusing,

    Thanks Nick. I concur as well and will apply this.

    Would it be reasonable to apply this to 3.6? IMO, it isn't a new feature, it is more of a usability bug due to an oversight.

    @ncoghlan
    Copy link
    Contributor

    +1 for 3.6 from me - as you say, this is fixing a usability bug in the error handling rather than being a new feature.

    @TeamSpen210
    Copy link
    Mannequin

    TeamSpen210 mannequin commented Sep 28, 2016

    Alternatively, SETUP_WITH could additionally check for the other method, and raise an AttributeError with a custom message mentioning both methods.

    @ncoghlan
    Copy link
    Contributor

    Due to the expected semantics of AttributeError being relatively precise, changing to a protocol check rather than just changing the order we look up the protocol attributes would also involving changing from AttributeError to TypeError.

    Since that's a potentially more disruptive change and just changing the order of the checks is likely sufficient to reduce confusion, the latter approach is preferable, at least for now (if folks are still getting confused by the time 3.7 rolls around, then that option would be worth reconsidering).

    @python-dev
    Copy link
    Mannequin

    python-dev mannequin commented Nov 22, 2016

    New changeset 3aafb232f2db by Raymond Hettinger in branch '3.6':
    Issue bpo-27100: With statement reports missing __enter__ before __exit__. (Contributed by Jonathan Ellington.)
    https://hg.python.org/cpython/rev/3aafb232f2db

    @rhettinger
    Copy link
    Contributor

    Thanks for the patch Jonathan. Nick, thanks for chiming in.

    @vadmium
    Copy link
    Member

    vadmium commented Nov 22, 2016

    https://mail.python.org/pipermail/python-checkins/2016-November/147171.html

    test_collections leaked [0, 0, 7] memory blocks, sum=7
    test_contextlib leaked [38, 38, 38] references, sum=114
    test_contextlib leaked [13, 12, 12] memory blocks, sum=37
    test_descr leaked [164, 164, 164] references, sum=492
    test_descr leaked [59, 59, 59] memory blocks, sum=177
    test_functools leaked [0, 3, 1] memory blocks, sum=4
    test_with leaked [37, 37, 37] references, sum=111
    test_with leaked [13, 13, 13] memory blocks, sum=39

    Command line was: ['./python', '-m', 'test.regrtest', '-uall', '-R', '3:3:/home/psf-users/antoine/refleaks/reflogWMjqsJ', '--timeout', '7200']

    Looking at the code change, it looks like you aren’t releasing __enter__ if the __exit__ lookup fails.

    A separate question: why not swap the “async with” __aenter__ and __aexit__ code at the same time?

    @vadmium vadmium reopened this Nov 22, 2016
    @vadmium
    Copy link
    Member

    vadmium commented Nov 22, 2016

    Hmm, it seems I already discovered this leak six months ago: https://bugs.python.org/review/27100

    @rhettinger
    Copy link
    Contributor

    Serhiy, do you care to double check this patch?

    Martin, I'll leave the async_with for Yuri to fix-up. I believe that is his code, so he should do the honors.

    @serhiy-storchaka
    Copy link
    Member

    The patch LGTM.

    @python-dev
    Copy link
    Mannequin

    python-dev mannequin commented Nov 22, 2016

    New changeset 749c5d6c4ba5 by Raymond Hettinger in branch '3.6':
    Issue bpo-27100: Fix ref leak
    https://hg.python.org/cpython/rev/749c5d6c4ba5

    @rhettinger
    Copy link
    Contributor

    Yuri, do you want to do the same for async-with?

    @rhettinger rhettinger removed their assignment Nov 22, 2016
    @vadmium
    Copy link
    Member

    vadmium commented Nov 24, 2016

    The tests changes also produce a DeprecationWarning:

    ======================================================================
    ERROR: testEnterAttributeError1 (test.test_with.FailureTestCase)
    ----------------------------------------------------------------------

    Traceback (most recent call last):
      File "/home/proj/python/cpython/Lib/test/test_with.py", line 120, in testEnterAttributeError1
        self.assertRaisesRegexp(AttributeError, '__enter__', fooLacksEnter)
      File "/home/proj/python/cpython/Lib/unittest/case.py", line 1311, in deprecated_func
        DeprecationWarning, 2)
    DeprecationWarning: Please use assertRaisesRegex instead.

    The fix is simple and just affects the tests, but since most people don’t seem to worry about warnings, I guess this can wait for 3.6 to be released before committing the fix.

    @serhiy-storchaka
    Copy link
    Member

    Oh, how I missed this? I usually run tests with -Wa.

    Since the patch is simple and fixes a regression introduced just before releasing beta 4, I'm fine with fixing it. We are trying to support tests warnings-free. Some buildbots can run tests with -We.

    @rhettinger
    Copy link
    Contributor

    Since the patch is simple and fixes a regression introduced
    just before releasing beta 4, I'm fine with fixing it. We are
    trying to support tests warnings-free. Some buildbots can run
    tests with -We.

    +1

    @python-dev
    Copy link
    Mannequin

    python-dev mannequin commented Nov 24, 2016

    New changeset e11df6aa5bf1 by Raymond Hettinger in branch '3.6':
    Issue bpo-27100: Silence deprecation warning in Lib/test/test_with.py
    https://hg.python.org/cpython/rev/e11df6aa5bf1

    @rhettinger
    Copy link
    Contributor

    Can this be closed now?

    @ncoghlan
    Copy link
    Contributor

    ncoghlan commented Apr 4, 2017

    Reviewing the discussion, I assume this was left open to cover reordering the __aenter__ and __aexit__ checks for async with, but that can just as easily be handled as a separate issue (which would also be clearer at the NEWS level).

    @ncoghlan ncoghlan closed this as completed Apr 4, 2017
    @maggyero
    Copy link
    Mannequin

    maggyero mannequin commented Dec 14, 2019

    @ncoghlan

    Reviewing the discussion, I assume this was left open to cover reordering the __aenter__ and __aexit__ checks for async with, but that can just as easily be handled as a separate issue (which would also be clearer at the NEWS level).

    Here it is: https://bugs.python.org/issue39048

    @ezio-melotti ezio-melotti transferred this issue from another repository Apr 10, 2022
    Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
    Labels
    3.7 (EOL) end of life interpreter-core (Objects, Python, Grammar, and Parser dirs) type-bug An unexpected behavior, bug, or error
    Projects
    None yet
    Development

    No branches or pull requests

    7 participants