> Apart from that, what my snippet does is essentially your second
> preference, except that removing an alias from `__dir__` is not
> sufficient to hide it from tab-completion.
It's not just removing it from `__dir__`? What else is needed?
As long as it's done automatically without programmer intervention, I
don't feel too strongly about the implementation details. I just don't
want something like adding @remove_alias_from_autocomplete to every
aliased method to become yet another quirk of Sage development that we
need to teach to every new contributor (and retroactively apply to the
codebase).
Is there any downside to this idea? Of course, I would turn it into something easier to use, like a decorator.
For the most part I think these questions end up being interface questions, not questions particular to sagemath (although the needs of sagemath might be a bit different than for other python projects), so perhaps the right level to engage with them is to contribute tools for it to IPython/jupyter. The feedback and positive or negative reception there might also be informative. There are other python-based projects with way more experience/focus on user interface than sagemath. Let's try to be compatible with that.
I agree with what Nils said (what he said is what I was trying to
get at when I said what my first preferred option would be). But
after looking some things up it seems like modifying __dir__ is
actually the recommended way of implementing this! I was surprised
to learn this.
Below you can find a minimal example that shows the difference. Both classes, `A` and `B` have a method `bad`, but in `A` this method is visible to ipython's tab-completion, in `A` it is not.
Because
dir()
is supplied primarily as a convenience for use at an interactive prompt, it tries to supply an interesting set of names more than it tries to supply a rigorously or consistently defined set of names, and its detailed behavior may change across releases. For example, metaclass attributes are not in the result list when the argument is a class.
So it seems that changing __dir__ is actually the correct way to implement this. I am surprised, I would have expected it to be via an IPython-specific interface. And given how the Python docs describe dir, it also seems like other introspection tools shouldn't be relying on it. Of course we'd need to try it to see for sure. Sphinx is probably the most likely thing to be relying on __dir__, so looking at how the generated docs turn out (especially with aliases) would be a good sanity check that nothing broke. Worst-case, since this wouldn't change any Sage functionality (so no deprecation policies apply), we could always revert the change if we find out later that it broke some obscure but important thing.
If we want this to apply to all Sage objects (I see no reason
not to), then implementing this behaviour in __dir__ for
`SageObject` seems appropriate.
Even if we want to hide all aliases: I don't know whether there is any way to distinguish an alias from a method defined as usual in python - how would you?
I did this in https://github.com/sagemath/sage/pull/40753 using the Sphinx API, but it can be done in pure Python. There are probably some edge cases the following code doesn't account for (lambdas, public aliases of private functions), but it illustrates the idea.
class A:
def foo(self):
return 5
bar = foo
a = A()
a.__getattribute__('foo').__name__ == 'foo' # True
a.__getattribute__('bar').__name__ ==
'bar' # False, because a.__getattribute__('bar').__name__
is 'foo'
So you can distinguish an alias like this. (Both 'foo' and 'bar'
are in `a.__dir__()` by default, so iterate over each `s` in the
super `__dir__` and check if `self.__getattribute__(s).__name__ ==
s`.)
And if you only want to exclude some aliases, you're working with
strings so you can just write code to do whatever string
processing you need to only apply it for some aliases. (So for
your example, check if the alias is from num_xxx to n_xxx). Is
there any reason not to filter out all public aliases of public
methods though? The only thing I can think of is if the original
non-alias name is something unexpected, but in that case we could
just swap what's the original and what's the alias. Maybe there
could be a few special cases we want to include both for (I don't
think we are consistent about which of sqrt or square_root is the
alias, maybe we include both in __dir__ until we refactor things
to make one of them consistently the original).
Also, both Python and IPython exclude private (_foo) methods from
the tab complete unless you try to tab complete on something
starting with an underscore, I think this behaviour is fine and
should be left alone. I'd only filter out public aliases of public
methods.
When handling inheritance, there is a disadvantage to defining an alias
with `bar = foo`.
In some places in Sage an alias `baz` will be defined as
def baz(self):
return self.foo()
and a short doc that says it is an alias, instead of the better solution
of having the full docstring of foo() with a prefix that tells this is
an alias.
dir()
is
supplied primarily as a convenience for use at an interactive prompt, it tries to supply an interesting set of names more than it tries to supply a rigorously or consistently defined set of names, and its detailed
behavior may change across releases. For example, metaclass attributes are not in the result list when the argument is a class.[△EXTERNAL] |
When I try your example in IPython `bad` is not visible to tab-completion for instances of `A` or instances of `B`. It is visible for `A` itself, but methods are normally called on instances, not classes so that's fine. When I try it with a regular Python prompt then it is as you said. So modifying __dir__ is sufficient for the IPython interface, and I'm happy to sacrifice alias handling for tab complete behaviour in a regular (not IPython) terminal if it means much simpler code. I'm not sure how many people use Sage in an interactive non-IPython terminal anyway.