Strange tab completion oddity with enums?

Chris Angelico rosuav at gmail.com
Mon Oct 7 03:21:20 EDT 2019


On Mon, Oct 7, 2019 at 6:00 PM Peter Otten <__peter__ at web.de> wrote:
>
> Chris Angelico wrote:
>
> > I'm not sure what's going on here, and it's probably not actually
> > enum-specific, but that's where I saw it.
> >
> > If you create a plain class and have an attribute with an annotation,
> > you can see that:
> >
> >>>> class Foo:
> > ...     spam: "ham" = 1
> > ...
> >>>> Foo.__a
> > Foo.__abstractmethods__  Foo.__annotations__
> >>>> Foo.__annotations__
> > {'spam': 'ham'}
> >
> > Note that __annotations__ shows up when tab-completing "__a".
> >
> > Now consider an enumeration:
> >
> >>>> from enum import Flag, auto
> >>>> class Bar(Flag):
> > ...     quux: "asdf" = auto()
> > ...
> >>>> Bar.__
> > Bar.__abstractmethods__  Bar.__getattr__(         Bar.__ne__(
> > Bar.__base__(            Bar.__getattribute__(    Bar.__new__(
> > Bar.__bases__            Bar.__getitem__(         Bar.__prepare__(
> > Bar.__basicsize__        Bar.__gt__(              Bar.__qualname__
> > Bar.__bool__(            Bar.__hash__(            Bar.__reduce__(
> > Bar.__call__(            Bar.__init__(            Bar.__reduce_ex__(
> > Bar.__class__(           Bar.__init_subclass__(   Bar.__repr__(
> > Bar.__contains__(        Bar.__instancecheck__(   Bar.__reversed__(
> > Bar.__delattr__(         Bar.__itemsize__         Bar.__setattr__(
> > Bar.__dict__             Bar.__iter__(            Bar.__sizeof__(
> > Bar.__dictoffset__       Bar.__le__(              Bar.__str__(
> > Bar.__dir__(             Bar.__len__(             Bar.__subclasscheck__(
> > Bar.__doc__              Bar.__lt__(              Bar.__subclasses__(
> > Bar.__eq__(              Bar.__members__          Bar.__subclasshook__(
> > Bar.__flags__            Bar.__module__           Bar.__text_signature__
> > Bar.__format__(          Bar.__mro__              Bar.__weakrefoffset__
> > Bar.__ge__(              Bar.__name__
> >>>> Bar.__annotations__
> > {'quux': 'asdf'}
> >
> > Double-tabbing "__" shows everything but, and double-tabbing "__ann"
> > has nothing... but the attribute is most definitely there.
> >
> > Perhaps notable is dir():
> >
> >>>> dir(Foo)
> > ['__annotations__', '__class__', '__delattr__', '__dict__', '__dir__',
> > '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__',
> > '__gt__', '__hash__', '__init__', '__init_subclass__', '__le__',
> > '__lt__', '__module__', '__ne__', '__new__', '__reduce__',
> > '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__str__',
> > '__subclasshook__', '__weakref__', 'spam']
> >>>> dir(Bar)
> > ['__class__', '__doc__', '__members__', '__module__', 'quux']
> >
> > But that's not the whole story, since tab completing "Bar.__" will
> > still show "__class__" and "__init__" that aren't in dir().
> >
> > Tested with the default REPL CPython 3.6, 3.7, 3.8, and 3.9. Tested
> > also in IDLE on 3.9 but tab completion of dunders behaves differently
> > there (it ONLY seems to want to tab complete __class__, for some
> > reason) so it's not comparable.
> >
> > What's actually going on here?
>
> With Python 3.7:
>
> >>> from enum import Flag
> >>> import inspect
> >>> print(inspect.getsource(Flag.__dir__))
>     def __dir__(self):
>         added_behavior = [
>                 m
>                 for cls in self.__class__.mro()
>                 for m in cls.__dict__
>                 if m[0] != '_' and m not in self._member_map_
>                 ]
>         return (['__class__', '__doc__', '__module__'] + added_behavior)
>
> Looks like everything starting with an underscore (except class, doc, and
> module) is suppressed, probably to suppress some noise...
>

That's why dir() shows what it does, but tab completion seems to have
some other source, as it's able to find a lot of other attributes but
not __annotations__. Very strange.

ChrisA



More information about the Python-list mailing list