getattr on class objects
Asked Answered
O

3

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?

Octopod answered 28/8, 2013 at 21:14 Comment(8)
"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.Positivism
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?Octopod
(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?)Positivism
@Octopod What are you trying to accomplish? Differentiate between class-methods and instance-methods?Washtub
@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.Positivism
@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 classOctopod
@Octopod 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.)Positivism
@Octopod I updated the example, adding a complete attribute kind detection. (not just if it's a staticmethod or instancemethod)Washtub
W
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
Washtub answered 28/8, 2013 at 21:29 Comment(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. CheersQuestor
T
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()
Triclinium answered 28/8, 2013 at 21:32 Comment(1)
I had meant to include self as a parameter to foo. I have edited my question. Still good to know A.foo(a) would workOctopod
P
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.

Positivism answered 28/8, 2013 at 21:35 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.