Reputation: 2179
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
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
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 @staticmethod
s 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
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
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