Repository navigation
AttributeError on with super() #129466
Copy link
Copy link
Open
Labels
interpreter-core(Objects, Python, Grammar, and Parser dirs)(Objects, Python, Grammar, and Parser dirs)type-featureA feature request or enhancementA feature request or enhancement
Description
Bug report
Bug description:
Writing with super() raises an AttributeError. I ran into this implementing context managers inspired by contextlib.contextmnager but which had extra functionality. I can see no reason why with super() should be disallowed, and in fact, super().__enter__() and super().__exit__() work just fine.
Attempting to use an ExitStack as a work around was unsuccessful.
import sys
from contextlib import ExitStack
class Base:
def __enter__(self):
print("Entering base context manager")
def __exit__(self, type, value, traceback):
print("Exiting base context manager")
def __derived_enter__(self):
self.context = self.contextlib_style_context()
next(self.context)
return self.context
def __derived_exit__(self, type, value, traceback):
try:
next(self.context)
except StopIteration:
pass
class DerivedUsingEnterExit(Base):
def contextlib_style_context(self):
print("Entering contextlib style context manager")
super().__enter__()
try:
yield
finally:
super().__exit__(*sys.exc_info())
print("Exiting contextlib style context manager")
__enter__ = __derived_enter__
__exit__ = __derived_exit__
class DerivedUsingWith(Base):
def contextlib_style_context(self):
print("Entering contextlib style context manager")
with super():
yield
print("Exiting contextlib style context manager")
__enter__ = __derived_enter__
__exit__ = __derived_exit__
class DerivedUsingExitStack(Base):
def contextlib_style_context(self):
print("Entering contextlib style context manager")
with ExitStack() as stack:
stack.enter_context(super())
yield
print("Exiting contextlib style context manager")
__enter__ = __derived_enter__
__exit__ = __derived_exit__
# Runs successfully
with DerivedUsingEnterExit() as d:
pass
# Raises AttributeError
with DerivedUsingWith() as d:
pass
# Raises AttributeError
with DerivedUsingExitStack() as d:
passCPython versions tested on:
3.13, 3.12
Operating systems tested on:
Windows
Reactions are currently unavailable
Metadata
Metadata
Assignees
Labels
interpreter-core(Objects, Python, Grammar, and Parser dirs)(Objects, Python, Grammar, and Parser dirs)type-featureA feature request or enhancementA feature request or enhancement
Activity
JeffersGlass commentedon Jan 30, 2025
The reason that it's currently an error (a TypeError, it seems?) is do to how the
enter_contextfunction works: it looks up the type of the object it's passed, then attempts to retrieve the__enter__and__exit__methods of that type. Butsuper()returns asuperobject, which does not itself have the__enter__and__exit__methods.cpython/Lib/contextlib.py
Lines 515 to 529 in 4e47e05
Perhaps the
__enter__and__exit__methods could be special-cased within the super object to lookup on the wrapped class? Orsuperobjects could be special-cased in contextlib lookup these methods on the correct class?I'm not convinced that's the best way forward... but I'd be happy to take a swing at a solution.
Edit: this is the case for 3.12 and 3.13; for 3.14 after alpha one, see the next post.
JeffersGlass commentedon Jan 30, 2025
Hmmm the error actually comes from a different place in 3.14. Specifically, from the bytecode implementation of
LOAD_SPECIAL(currently on main).cpython/Python/bytecodes.c
Lines 3284 to 3296 in 4e47e05
This is based on the specific verbiage of the error messages I'm seeing about missing methods:
cpython/Python/ceval.c
Lines 377 to 381 in 4e47e05
So again, some special-casing could occur there.
johnslavik commentedon Jan 30, 2025
+1, the special-cased behavior would be very intuitive and succinct
edwardwkrohne commentedon Jan 30, 2025
I'm trying to understand
LOAD_SPECIALhere, I've never gotten into the opcodes. The picture I'm getting is thatLOAD_SPECIALis used for dunders like__exit__when used in whatever their special capacities are, andsuperdoesn't have explicit support forLOAD_SPECIAL, sosupercan't be used in things likewith,for,next, etc. Is that correct? And I supposeLOAD_SPECIALis not used when dunders are accessed explicitly, which is whysuper().__enter__()works?I'd be in favor of adding that support to
super, or otherwise special casing.Those changes to
superby themselves wouldn't allowstack.enter_context(super())becausesuperis not a class that has the right methods; soExitStackwould also require explicit special casing in the standard library. I think the special case would be justified there too, sincesuperreally is a unique type of object that may need special handling, and if you canwithsomething, you ought to be able tostack.enter_contextit.I could submit an
ExitStackpatch easily enough, but since I'm not familiar with the opcodes I doubt I could changesuperitself myself without it becoming a massive project.picnixz commentedon Jan 31, 2025
The docs say:
The use of
with super()falls under a statement usage (the__enter__method is being looked up) hence it's not possible to use it. AFAICT, supportingwith super()should mean supportingsuper()[...]for completeness.So, strictly speaking, the behaviour is as expected although burried in the docs. Changing this may require a PEP but we may start by trying to provide a recipe for this (writing enter and exit calls explicitly may not be the first thing users may think of so it's better if we can write that recipe down).
OTOH, we may special-case the enter/exit special methods as well as their asynchronous counterparts which suffer from the same issue I think (I don't think
async with super()would work and I also wonder whether other asynchronous special methods have that shortcoming or not).JeffersGlass commentedon Feb 1, 2025
@picnixz Thank you for pointing that out in the docs - I'd agree with you that this is behaving as described, though I wonder if we can be even clearer there - perhaps adding the explicit example of context managers to the docs as in:
You're right that the error is raised with async context managers:
__enter__,__exit__,__aenter__, and__aexit__are the methods special-cased in ceval.c (in_Py_SpecialMethods[]).picnixz commentedon Feb 1, 2025
We had an opened issue that aimed to improve the error messages for those cases. Since there was no progression since then, I will have a look at this in two weeks or so. In the meantime I'm wondering whether we can decide first whether we want to support this kind of feature or not.
super()is meant to be a proxy but it's not a perfect proxy (it's not just a "we pick the correct method from the MRO and dispatch") as the docs say. Since I'm not an expert in the super() specs, I'll ask some typing experts @JelleZijlstra and @carljm, as well as some other core devs @serhiy-storchaka (for pickle possible implications) and @rhettinger (who wrote a guide for using super()).carljm commentedon Feb 1, 2025
I don't think this is really a typing question, just a language question. If we add the behavior to
superthat it supports the context manager protocol by looking up__enter__and__exit__from the MRO, type checkers will adapt to that change.I don't have strong feelings either way about the change. My main hesitance would be that it's not clear where we should stop; should
superobjects also support__getitem__?__eq__and comparison methods? Many other special methods? It may be better to keep the clear line thatsuperjust supports attribute access, which is the only thing it needs to support in order to fulfill its core function.The implementation might also get a little weird, in that I think the
__enter__and__exit__methods of thesupertype would have to copy/emulate the normal runtime error messages that occur when an object doesn't support the context manager protocol, if it can't find__enter__and__exit__in the MRO.There could also be strange/surprising behaviors if
superfinds__enter__and__exit__in different places in the MRO? Though this implies some type is defining one but not the other, which would be unusual.I think it would be pretty easy to write a small library class that could wrap a
superobject (or, really, any object with__enter__and__exit__attributes) and implement the context manager protocol. It would do the same thing that this issue proposessuperdo: implement__enter__and__exit__methods that proxy to those methods on the wrapped object. Then the code above could just be changed towith ProxyContext(super()):. It seems to me that's not too onerous for an uncommon need, and may be a better solution than building this directly intosuper.picnixz commentedon Feb 1, 2025
Ah yes Carl, sorry if I used "typing" here instead of "language". I forgot the word for a moment and went for the closest one I could remember :')
This is what happens if we have mixin classes where a mixin implements
__enter__and the other implements__exit__combined with a diamond inheritance. But this is perhaps not the best real-world example..How about adding this to contextlib recipes (not necessarily implement it on our side). Orthogonally, we can also list a few more cases where
<something>super()<something-else>doesn't work (currently we only list thesuper()[index]but we can also listwith super()).carljm commentedon Feb 1, 2025
Sure, those both seem like potentially reasonable things to do. The contextlib recipe would just need to be evaluated for how common this need is, and whether there's concern about bloating the contextlib recipes with too many very niche recipes.
picnixz commentedon Feb 1, 2025
Just to be clear, but by recipes, I only meant docs recipes. I don't think we have a page for them yet though (but we should perhaps have a cookbook for more modules in general).
I'll start a dpo thread next week on this matter (namely whether we should have more cookbooks for modules and how niche we should go)
JeffersGlass commentedon Feb 2, 2025
@picnixz Just for my own edification - what is a “dpo thread?”
picnixz commentedon Feb 2, 2025
A dpo thread is a thread on https://discuss.python.org. Generally features are proposed in the idea category (https://discuss.python.org/c/ideas/6) but sometimes we move them to the core development category or the PEP category depending on their status.
In this case, I guess a thread in the idea category could give us an idea of how many people would be interested in having the helper in the standard library itself. Having it in the docs recipe doesn't really need a thread I think (but it's always good to have one for new features that seem niche)