5
class A:
    def foo(self):
        print "foo()"

getattr(A, foo) # True
A.foo() # error

getattr(A(), foo) # True
A().foo() # prints "foo()"

That being said, here is my problem:

I wish to store test case meta information as attributes of the Test Case class objects themselves, not on instances of them.
I have a list of attribute names to extract, but if there is an instance method of the same name, then getattr(class_obj, attr) will return True, but getattr(class_obj, attr)() raises an Error.

Is there a way to tell getattr not to include attributes of the instantiated class and only of the class object itself?

EDIT: I tried accessing class_obj.__dict__ directly (which I understand is bad practice), but it does not include some attributes like __name__

EDIT: Rephrase of the question. Is there a way to differentiate between methods of the class obj and the methods of an instance of the class?

waldol1
  • 1,841
  • 2
  • 18
  • 22
  • "Is there a way to tell getattr not to include attributes of the instantiated class and only of the class object itself?" I'm not sure what this means. `foo()` is a (non-static) method, thus an attribute named `foo` will be both on the class and on its instances. The reason why `A.foo()` raises an error is because you can't call an instance method on a class object, but it's still there as an unbound method. I think you might be confusing attributes and methods a little. "Does not have an attribute" and "the attribute isn't callable" are different things. – millimoose Aug 28 '13 at 21:19
  • Then I suppose getattr is not what I'm looking for then. Do you know of an alternative way to only get attributes if they are decorated with @classmethod? – waldol1 Aug 28 '13 at 21:21
  • 1
    (And, well, I'm sorry if this comes across as condescending, but this seems like a walk before you run situation. Are you sure you should be doing metaprogramming before the quirks of how Python's object model is internally structured seep in?) – millimoose Aug 28 '13 at 21:21
  • @waldol1 What are you trying to accomplish? Differentiate between class-methods and instance-methods? – Viktor Kerkez Aug 28 '13 at 21:22
  • @waldo1 How about using the approach "better ask for forgiveness than permission"? Just do `A.foo()`, catch the error with `try..except`, if an error occurs just pretend the attribute wasn't there. – millimoose Aug 28 '13 at 21:23
  • @ViktorKerkez yes, I would like to differentiate between methods I can call on the class object and the methods I can call on an instance of the class – waldol1 Aug 28 '13 at 21:27
  • @waldol1 Right, I provided an answer based on the assumption that you want to check if X (where X is some arbitrary object that happens to be an attribute of a class object) is callable without having to pass in a `self` parameter? (Practically, it would still require any other parameters though.) – millimoose Aug 28 '13 at 21:36
  • @waldol1 I updated the example, adding a complete attribute kind detection. (not just if it's a staticmethod or instancemethod) – Viktor Kerkez Aug 28 '13 at 22:25

3 Answers3

5

Is this good enough?

import types
class Test(object):
    @staticmethod
    def foo():
        print 'foo'

    def bar(self):
        print 'bar'

In combination with:

>>>(isinstance(getattr(Test, 'foo'), types.FunctionType),
    isinstance(getattr(Test, 'bar'), types.FunctionType))
True, False

You can also use the inspect module:

>>> inspect.isfunction(Test.foo)
True
>>> inspect.isfunction(Test.bar)
False

With a little additional work you can even distinguish class methods from instance methods and static methods:

import inspect

def get_type(cls, attr):
    try:
        return [a.kind for a in inspect.classify_class_attrs(cls) if a.name == attr][0]
    except IndexError:
        return None

class Test(object):
    @classmethod
    def foo(cls):
        print 'foo'

    def bar(self):
        print 'bar'

    @staticmethod
    def baz():
        print 'baz'

You can use it as:

>>> get_type(Test, 'foo')
'class method'
>>> get_type(Test, 'bar')
'method'
>>> get_type(Test, 'baz')
'static method'
>>> get_type(Test, 'nonexistant')
None
Viktor Kerkez
  • 45,070
  • 12
  • 104
  • 85
  • 1
    fyi, today, and under Python 3.9 isinstance(getattr(Test, 'foo'), types.FunctionType) isinstance(getattr(Test, 'bar'), types.FunctionType) both return True and not only for foo. Cheers – uhoenig Jan 23 '22 at 13:06
3

Your results from an incorrect definition of foo, not any underlying semantics of class attributes. By default, a function declared inside a class is an instance method, which must take at least one argument, an instance of the class. Conventionally, it is referred to as self:

class A:
    def foo(self):
        print "foo()"

Normally, you would call such a method like this:

a = A()
a.foo()    # passes the object 'a' implicitly as the value of the parameter 'self'

but this is legal as well

a = A()
A.foo(a)   # pass the object 'a' explicitly as the value of the parameter 'self'

In order to define a function inside a class that doesn't take any such implicit arguments, you need to decorate it with the @staticmethod decorator:

class A:
    @staticmethod
    def foo():
        print "foo()"

Now, you can call foo the way you tried to previously:

>>> A.foo()
foo()
chepner
  • 497,756
  • 71
  • 530
  • 681
  • I had meant to include `self` as a parameter to `foo`. I have edited my question. Still good to know `A.foo(a)` would work – waldol1 Aug 28 '13 at 21:40
0

You want something like this:

from inspect import ismethod
from collections import Callable
def can_i_call(func):
    if not isinstance(func, Callable):
        # not a callable at all
        return False

    if not ismethod(func):
        # regular function or class or whatever
        return True

    # func is a method
    return func.im_self is not None

Note: this will only test whether or not an attempt to call will error out because you're calling an unbound method without a self. It doesn't guarantee that func() will succeed, i.e. not fail for any other reason.

millimoose
  • 39,073
  • 9
  • 82
  • 134