6

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.

4 Answers 4

3

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()
Sign up to request clarification or add additional context in comments.

4 Comments

@JoranBeasley -- Yeah. I was writing it up when yours still said self.__dict__. It seems that you've corrected that in the mean-time. +1 to yours for being here first.
I don't think FunctionType is what you want here. This will return True for static methods, False for instance methods.
@abarnert -- from my tests, this removes staticmethod and classmethod decorated functions, but passes through un-decorated functions (see updated code). The decorated functions are actually descriptors, so they're probably of type: types.InstanceType or something similar. I'm not exactly sure how to get those ones reliably (without getting other stuff).
@mgilson: Sorry, I didn't realize you were getting the values out of self.__class__.__dict__. In that case, unbound instance methods are functions, you're right. It's when you get the dir and do getattr(self.__class__, name) or equivalent that you get the method-wrapper objects.
2

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] != "_"]

1 Comment

That works, thanks! I had assumed dir(self) and dir(Hal) would perform the same, since I don't have any special __setattr__ stuff happening. Knowledge acquired.
2
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}

7 Comments

probably filtering the values with types.MethodType or something of that sort.
A.__dict__ does, self.__dict__ does not.
@devtk -- good point, but I suppose you could filter self.__class__.__dict__
you would need self.__class__.__dict__ I guess
In which case, you also need to filter on types.FunctionType as they're not bound methods when they're attached to the class's dictionary.
|
0

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.)

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.