Search code examples
pythonipythonmagic-methods

What is __methods__ and why is it calling __getattr__?


I have recently tried to implement a "pass through" on my a few classes to allow calling methods on specific properties of a classes using the __getattr__ method. This has broken (sort-of) autocompleting in ipython and I am curious to understand why. First:

class MyClass(object):
    def __init__():
        #create an instance of class ClassProperty
        self.class_property = ClassProperty()

    def __getattr__(self, item):
        #pass through calls to non-existent methods, print first
        print item
        return getattr(self.class_property,item)

When creating an instance of this class and then attempting to tab-complete in Ipython (0.12.1), it seems that several functions are called including __methods__, trait_names and _getAttributeNames.

In [1]: my_class = MyClass()
In [2]: my_class.not_in_my_class<tab>
__methods__
trait_names
_getAttributeNames

I am curious to understand what is happening.

Thanks,

P.S. I know that doing this is very opaque and is temporary. Still, I'm curious.

UPDATE

Based on what I learned from the accepted answer below I have been able to successfully "pass through" autocompletion so that methods that exist in ClassProperty now autocomplete on instances of MyClass. What was required to successfully pass through was an update to the __dir__ method

class MyClass(object):
    def __init__():
        #create an instance of class ClassProperty
        self.class_property = ClassProperty()

    def __getattr__(self, item):
        #pass through calls to non-existent methods, print first
        print item
        return getattr(self.class_property,item)

    def __dir__(self):
        dir_list = self.__dict__.keys()
        try:
            dir_list.extend(dir(self.class_property))
        except:
            pass
        return dir_list

The reason why I have implemented this is because we are using python as an instrument control package with an ipython command line interface. We have devices (i.e. a laser) that selects an actuator based on json config file. We want to have top level access to the methods on the actuator for user convenience.

I would be curious if there are thoughts on my approach and if I am inviting any problems down the road.


Solution

  • IPython is asking your object for a list of its attributes, trying several protocols of varying levels of obscurity. None of these protocols are part of the core Python language (at least, not any more), and the current IPython source code seems to have removed most of these checks.


    __methods__ is a really old part of Python that was phased out when they unified the type and class systems way back in 2.2, about 14 years ago.

    It used to be the case that instances of built-in types would provide a __methods__ attribute that would be a list of all the methods they supported. This required an instance of the type, and the system it was part of had a number of other flaws.

    In contrast, user-defined classes provided the introspection system you're familiar with from modern Python, with object and class __dict__s. This was more powerful and more consistent than the other system, so when they unified types and classes, the class-based introspection system was kept and the other was removed.

    __methods__ is pretty much completely gone now. There might be some third-party legacy code still providing a __methods__ attribute, but the only thing that comes with Python that still supports __methods__ seems to be this weird thing in idlelib. In Python 2, dir still tries to look for it, though, and IPython's tab completion uses dir, so that's where the __methods__ access is coming from. There should also be an attempt to access __members__, which you might not have noticed due to the message appearing on the same line you were typing on.


    _getAttributeNames is a protocol apparently introduced by PyCrust, an alternative Python shell. If defined, a _getAttributeNames method would provide a list of all dynamic attributes an object supports through __getattr__. (This was before __getattribute__ existed.)

    _getAttributeNames appears to have been introduced around 2001, making it newer than __methods__, which disappeared around the same time frame. Some time since IPython 0.12.1, the _getAttributeNames check has been removed from IPython.


    trait_names comes from Enthought Traits. For a class that uses traits, the trait_names method is an instance method that returns a list of the names of its traits. It also accepts search criteria to list specific traits. Like _getAttributeNames, this has been removed from IPython, apparently because the check isn't needed to handle traits (any more?).