ipykernel icon indicating copy to clipboard operation
ipykernel copied to clipboard

Tab completion ignores __all__

Open rhattersley opened this issue 8 years ago • 22 comments

The Python docs describe "public names" as being defined by __all__ where it exists. It would be helpful if this rule was respected by the Python kernel to avoid module authors that want to provide helpful tab-completion having to obfuscate imports, etc. with leading underscores. NB. This use case is explicitly recognised in the Python docs:

... to avoid accidentally exporting items that are not part of the API (such as library modules which were imported and used within the module).

WIth the current kernel behaviour, code such as:

from __future__ import (division, print_function)
from six.moves import (filter, map)

import contextlib

__all__ = ['my_api']

def my_api():
    ...

needs to become:

from __future__ import (division as _, print_function as _)
from six.moves import (filter as _filter, map as _map)

import contextlib as _contextlib

__all__ = ['my_api']

def my_api():
    ...

rhattersley avatar May 04 '16 12:05 rhattersley

It's configurable, with the option IPCompleter.limit_to__all__ (config options docs).

The reason we do this is that we often want to use IPython to poke about and debug modules, so we like to see what's really there, not just what's whitelisted as public API. We should, however, do a better job of prioritising completions, so that things in __all__ come before things that aren't in it.

takluyver avatar May 04 '16 13:05 takluyver

Thanks for the documentation link. :+1:

we like to see what's really there, not just what's whitelisted as public API

I guess the question here is, who is "we"? I suspect most users are just getting on with using existing modules and not poking about debugging modules. That's what I've seen in workshops, user drop-ins, etc. Also, the ability to change the configuration is probably something a "developer" will be more aware of & happier doing. So perhaps a change of the default is in order?

rhattersley avatar May 04 '16 15:05 rhattersley

(Perhaps a different key combination could be used to allow an "enhanced" tab completion that shows more attributes, e.g. Ctrl+TAB)

rhattersley avatar May 04 '16 15:05 rhattersley

I'm not sure we can detect Ctrl+tab as a separate key combo in the terminal, and it would require changes to the message protocol for the Jupyter frontends - which is possible, but makes it quite a significant change.

I think resolving the priority is a sensible compromise, so that we show the names in __all__ before those that aren't.

takluyver avatar May 04 '16 15:05 takluyver

So changing the default isn't an option?

rhattersley avatar May 04 '16 21:05 rhattersley

It's definitely an option, just not my preferred one. ;-) Let's see if anyone else wants to weigh in.

takluyver avatar May 05 '16 09:05 takluyver

I tend to agree with @rhattersley.

For me tab completion is not just for poking about and debugging; it is my main point of entry for any module on the command line.

I've tried to restrict my own packages to return only their main API during tab completion but it leads to ugly and brittle code like this:

# Hide submodules in module namespace, to avoid confusion with corresponding class names
# If the module is reloaded, this will fail - ignore the resulting NameError
try:
    _target, _antenna, _timestamp, _flux, _catalogue, _ephem_extra, \
      _conversion, _projection, _pointing, _refraction, _delay = \
      target, antenna, timestamp, flux, catalogue, ephem_extra, \
      conversion, projection, pointing, refraction, delay
    del target, antenna, timestamp, flux, catalogue, ephem_extra, \
        conversion, projection, pointing, refraction, delay
except NameError:
    pass

(I have a class Target in the submodule target etc. which might confuse users of the package if both appear in the tab completion list)

ludwigschwardt avatar Jul 27 '16 14:07 ludwigschwardt

I noticed that IPCompleter.limit_to__all__ is listed as deprecated in the docs now: http://ipython.readthedocs.io/en/stable/config/options/terminal.html

Is there another supported way to do this?

(For what it's worth, I have also found this to be a minor pain point. Often users explore APIs using IPython, and this adds significant noise.)

shoyer avatar May 02 '17 16:05 shoyer

I'm running into this issue as well. I'm creating some modules for our team to use, and we rely on IPython/Jupyter pretty heavily. I'm writing my modules in such a way that they're easily explored interactively, and having to 'hide' imported functions and variables with _ is a bit ugly (and annoying).

aiguofer avatar Jun 01 '17 14:06 aiguofer

One of our developers/scientists just ran into this issue as well, and has been hiding, among other things, import numpy as _np so as not to expose this to our scientific user base who are not interested in the debugging so much as exploring, interacting and using code.

With the current defaults it seems that designing for interactivity and usability at some level is promoting non standard coding practices, as others have noticed. Echoing @aiguofer's sentiment here.

More discussion here: https://github.com/simpeg/simpeg/pull/611#discussion_r120962083

rowanc1 avatar Jun 09 '17 03:06 rowanc1

TensorFlow goes to some heroics to delete undocumented variables from modules to work around this same issue.

shoyer avatar Jun 09 '17 06:06 shoyer

sigh . I'll see what I can do to re-enable limit-to-all. The issue is that regardless of the default about 50% of our users will be unhappy

https://xkcd.com/1172/

The completer is currently getting (some) refactor – showing types, function signature... etc. Would a "This function/attribute is private" ui indicator, and sorting these further down in the completion list be an acceptable middle ground ?

Carreau avatar Jun 09 '17 17:06 Carreau

Here is a thought, could the auto completer recognize a triple tab?

\t is complete if single completion \t\t is show all list \t\t\t is show everything (either with or without _ is a question to consider)

hardkrash avatar Feb 27 '18 19:02 hardkrash

If ipython finds packages through dir(), Python 3.7 will allow overriding dir for a module (https://www.python.org/dev/peps/pep-0562/), thus giving back power to developers to control what is visible through tab-completion.

DiegoAlbertoTorres avatar Oct 25 '18 20:10 DiegoAlbertoTorres

If ipython finds packages through dir(), Python 3.7 will allow overriding dir for a module (https://www.python.org/dev/peps/pep-0562/), thus giving back power to developers to control what is visible through tab-completion.

This looks like a great solution -- does IPython use dir()?

shoyer avatar Oct 25 '18 21:10 shoyer

We have some custom logic and also fallback on Jedi.... we really need to get function to push tab-completiosn.

Carreau avatar Oct 25 '18 21:10 Carreau

So there is the option IPCompleter.limit_to__all__, why isn't it the other way around ? There should be an option to allow autocomplete for privates but it shouldn't be the default. It the mean time, some people are doing horrible things.

pilattebe avatar Feb 27 '19 11:02 pilattebe

:wave: Just checking in here, are there any updates on what the preferred way to expose an API that facilitates code-exploration through tab-completion?

P.S. I am happy to help with a pr if someone is willing to give me a few pointers :)

lheagy avatar May 07 '19 23:05 lheagy

So there is the option IPCompleter.limit_to__all__, why isn't it the other way around ? There should be an option to allow autocomplete for privates but it shouldn't be the default. It the mean time, some people are doing horrible things.

Totally agree, limit_to__all__ = True should in my opinion be the default option. Developers who want to expose non-public variables can either change that option or just call dir manually.

Stannislav avatar Sep 06 '19 09:09 Stannislav

I agree with the other comments that limiting to __all__ should be the default behavior. It's great for IPython to be able to list hidden names to allow under-the-hood poking and tweaking, but that doesn't make sense as a default behavior. Most users are just using libraries and the default should support using libraries as they were intended to be used, which means hiding things not in __all__. Showing such hidden names should be an advanced option.

BrenBarn avatar Aug 15 '20 22:08 BrenBarn

Over five years later and this is still an issue (aka - "bump"). EDIT: This seems to be rooted with jedi more than Ipython. Using %config IPCompleter.use_jedi = False actually clears this up.

idantene avatar Sep 30 '21 11:09 idantene

If ipython finds packages through dir(), Python 3.7 will allow overriding dir for a module (https://www.python.org/dev/peps/pep-0562/), thus giving back power to developers to control what is visible through tab-completion.

We have some custom logic and also fallback on Jedi.... we really need to get function to push tab-completiosn.

@Carreau It seems to me that overriding __dir__ works for changing the tab completion while still allowing access to the names that are removed from tab completion if they are typed out manually. Could this behavior be relied upon or is there something in the custom logic you mentioned that might make this unstable (if that logic is still in use since you made the comment two years ago)?

# Inside __init__.py
import sys


__all__ = [x for x in dir(sys.modules[__name__]) if x not in ['deprecated_module1', 'deprecated_module2']]

def __dir__():
    return sorted(__all__)

joelostblom avatar Mar 07 '22 20:03 joelostblom