devtk
devtk

Reputation: 2179

Python: How can I get a list of function names from within __getattr__ function?

How can I get the list of class functions from within __getattr__ function?

Python v2.7 if it matters.

Trying to use dir within __getattr__ leads to infinite recursion.

class Hal(object):
    def __getattr__(self, name):
        print 'I don\'t have a %s function' % name
        names = dir(self) # <-- infinite recursion happens here
        print 'My functions are: %s' % ', '.join(names)
        exit()
    def close_door(self):
        pass
x = Hal()       
x.open_door()

Here's the output I want:

I don't have a open_door function
My functions are: close_door, __getattr__, __init__, __doc__, ...

Any other solution which gets me the output I want will work fine. I want to do fuzzy string matching in the case when a function doesn't exist to try to suggest what the user might have meant.

Upvotes: 6

Views: 1819

Answers (4)

Mike Corcoran
Mike Corcoran

Reputation: 14565

is there any reason why you can't do this?

names = dir(self.__class__)

are you expecting consumers to extend instances of Hal to have custom methods?

if you only want methods you've implemented, with no built-ins listed, you could try this too:

names = [prop for prop in dir(self.__class__) if prop[1] != "_"]

Upvotes: 2

abarnert
abarnert

Reputation: 365915

One solution is to make a copy of the dir before adding the __getattr__ method:

class Hal(object):
    def __init__(self):
        self._names = dir(self)
        def __getattr__(self, name):
            print self.names
        self.__getattr__ = __getattr__

However, for simple cases, you can just call dir (and likewise getattr, or inspect.getmembers, or whatever) on your class object to solve the problem. This doesn't work if instance can have methods added after construction, but if that's not an issue, it's easy:

names = dir(self.__class__)

However you get the names, to filter for methods, there are a few things to do.

First, you can use isinstance on getattr(self, name) and make sure it's a method-wrapper (or get the type of the bound version and make sure it's an instancemethod). If you get the values directly out of self.__class__.__dict__, you don't get exactly the same thing as if you get the names in your favorite way and call either getattr(self, name) or getattr(self.__class__, name). In particular, an instance method will show up as a function, which is easier to test for than a method-wrapper. Although some of the other cases now get harder to detect.

At any rate, nothing based on type will find things that act like methods but aren't (e.g., because you've assigned a built-in function directly to the object, wrapped something in certain kinds of decorators, written custom descriptors, used a class with a __callable__ method as a function, etc.). If you're doing anything fancy (or worry that someone might later add something fancy), you really need to test whether you can explicitly bind the member (or fake-bind it to None), and then check if the result is callable, and then possibly do further tests to make sure it's callable properly (because otherwise you'll get fooled by @staticmethods and similar things). Really, if this comes up (and you've really thought through your design and convinced yourself and at least one other person that it isn't insane…), you should test everything you can think of against every case you have…

If you want to know if the methods are defined in Hal or the instance as opposed to object or another base class, there are a few ways to do this, but the simplest is to just subtract out the members of the base classes. (Of course if you don't care about methods defined in the instance, Hal.__dict__ already has what you want.)

Upvotes: 0

mgilson
mgilson

Reputation: 310049

This works I think:

import types

class Hal(object):
    def __getattr__(self, name):
        print ('I don\'t have a %s function' % name)
        funcs = (name for name,func in self.__class__.__dict__.items() if isinstance(func,types.FunctionType))

        #names = dir(self) # <-- infinite recursion happens here
        print ('My functions are: %s' % ', '.join(str(f) for f in funcs))
        exit()

    @staticmethod
    def foo():
        pass

    @classmethod
    def bar(cls):
        pass

    def qux(self):
        pass

    def close_door(self):
        pass

x = Hal()
x.foo = 'bar'
x.open_door()

Upvotes: 3

Joran Beasley
Joran Beasley

Reputation: 114038

names = self.__class__.__dict__

possibly?

>>> class A:
...   def hello(self,x):
...       print "hello ",x
...   def my_dir(self):
...       print self.__class__.__dict__
...
>>> A().my_dir()
{'__module__': '__main__', 'my_dir': <function my_dir at 0x029A5AB0>, 'hello': <
 function hello at 0x029A5CB0>, '__doc__': None}

Upvotes: 2

Related Questions