__getattr__ for static/class variables
Asked Answered
F

5

79

I have a class like:

class MyClass:
     Foo = 1
     Bar = 2

Whenever MyClass.Foo or MyClass.Bar is invoked, I need a custom method to be invoked before the value is returned. Is it possible in Python? I know it is possible if I create an instance of the class and I can define my own __getattr__ method. But my scnenario involves using this class as such without creating any instance of it.

Also I need a custom __str__ method to be invoked when str(MyClass.Foo) is invoked. Does Python provide such an option?

Flite answered 1/7, 2010 at 6:9 Comment(0)
C
101

__getattr__() and __str__() for an object are found on its class, so if you want to customize those things for a class, you need the class-of-a-class. A metaclass.

class FooType(type):
    def _foo_func(cls):
        return 'foo!'

    def _bar_func(cls):
        return 'bar!'

    def __getattr__(cls, key):
        if key == 'Foo':
            return cls._foo_func()
        elif key == 'Bar':
            return cls._bar_func()
        raise AttributeError(key)

    def __str__(cls):
        return 'custom str for %s' % (cls.__name__,)

class MyClass(metaclass=FooType):
    pass

# # in python 2:
# class MyClass:
#    __metaclass__ = FooType


print(MyClass.Foo)
print(MyClass.Bar)
print(str(MyClass))

printing:

foo!
bar!
custom str for MyClass

And no, an object can't intercept a request for a stringifying one of its attributes. The object returned for the attribute must define its own __str__() behavior.

Updated 2023-02-20 for Python 3.x default implementation (python 2 as a comment).

Continuant answered 1/7, 2010 at 6:24 Comment(5)
Thanks Ignacio and Matt. I think now I understand how class (not instance) creation works and also I thought the required str behaviour was difficult to achieve. I think metaclasses should be good enough for my requirement. :)Flite
If you always get AttributeError check out the other answer, which has the syntax for python3 (this example seems to be python2)Makassar
@Chris: This basic idea in the code works in Python 3. Aside from the print statements, the only thing that needs to be changed is how the metaclass is specified (as mentioned is a comment in it now).Selfabsorbed
What does the panel think about editing this answer to lead with the python 3 syntax, and reducing the python 2 to a comment? I'm happy to make the edit.Alford
@MartinBonnersupportsMonica Done. I have many posts I probably should update. It was a very different Python 13 years ago.Continuant
S
23

(I know this is an old question, but since all the other answers use a metaclass...)

You can use the following simple classproperty descriptor:

class classproperty(object):
    """ @classmethod+@property """
    def __init__(self, f):
        self.f = classmethod(f)
    def __get__(self, *a):
        return self.f.__get__(*a)()

Use it like:

class MyClass(object):

     @classproperty
     def Foo(cls):
        do_something()
        return 1

     @classproperty
     def Bar(cls):
        do_something_else()
        return 2
Sustentation answered 29/3, 2014 at 9:43 Comment(2)
This will only help if you know the names of your properties beforehand. If you read them from file and need to react dynamically, this will not work.Makassar
It could still work in theory -- read them from a file, and in a for-loop call setattr(MyClass, prop_name, classproperty(..., ...))Reciprocal
U
13

For the first, you'll need to create a metaclass, and define __getattr__() on that.

class MyMetaclass(type):
  def __getattr__(self, name):
    return '%s result' % name

class MyClass(object):
  __metaclass__ = MyMetaclass

print MyClass.Foo

For the second, no. Calling str(MyClass.Foo) invokes MyClass.Foo.__str__(), so you'll need to return an appropriate type for MyClass.Foo.

Uziel answered 1/7, 2010 at 6:26 Comment(3)
Tested this example and it only works for me in Python 2.x. After changing the print to a function, Python 3.2 gives me a "AttributeError: type object 'MyClass' has no attribute 'Foo'" error. Any suggestions?Midbrain
@CyberED: docs.python.org/py3k/reference/datamodel.html#metaclasses : "When the class definition is read, if a callable metaclass keyword argument is passed after the bases in the class definition, the callable given will be called instead of type()."Uziel
Couldn't wait .... after a bit of Googling and testing : The example is for Python 2.x. In Python 3.x you need : class MyClass(metaclass=MyMetaclass): passMidbrain
D
9

Surprised no one pointed this one out:

class FooType(type):
    @property
    def Foo(cls):
        return "foo!"

    @property
    def Bar(cls):
        return "bar!"

class MyClass(metaclass=FooType):
    pass

Works:

>>> MyClass.Foo
'foo!'
>>> MyClass.Bar
'bar!'

(for Python 2.x, change definition of MyClass to:

class MyClass(object):
    __metaclass__ = FooType

)

What the other answers say about str holds true for this solution: It must be implemented on the type actually returned.

Dysphemism answered 28/1, 2014 at 20:26 Comment(2)
While I usually try to refrain from posting "Thanks", I seriously need to post "Thanks" right now: Thanks!Makassar
As mentioned in a comment under a different properties-like answer, this is only feasible if you know the name of the properties in advance.Selfabsorbed
C
1

Depending on the case I use this pattern

class _TheRealClass:
    def __getattr__(self, attr):
       pass

LooksLikeAClass = _TheRealClass()

Then you import and use it.

from foo import LooksLikeAClass
LooksLikeAClass.some_attribute

This avoid use of metaclass, and handle some use cases.

Course answered 21/9, 2018 at 14:37 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.