78

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?

martineau
  • 119,623
  • 25
  • 170
  • 301
Tuxdude
  • 47,485
  • 15
  • 109
  • 110

5 Answers5

98

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

Matt Anderson
  • 19,311
  • 11
  • 41
  • 57
  • 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. :) – Tuxdude Jul 01 '10 at 06:49
  • 1
    If you always get `AttributeError` check out the other answer, which has the syntax for python3 (this example seems to be python2) – Chris Jun 21 '17 at 09:03
  • @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). – martineau Jun 06 '22 at 01:51
  • 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. – Martin Bonner supports Monica Feb 19 '23 at 09:12
  • @MartinBonnersupportsMonica Done. I have many posts I probably should update. It was a very different Python 13 years ago. – Matt Anderson Feb 20 '23 at 19:36
22

(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
shx2
  • 61,779
  • 13
  • 130
  • 153
  • 4
    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. – Chris Jun 21 '17 at 09:02
  • 1
    It could still work in theory -- read them from a file, and in a for-loop call `setattr(MyClass, prop_name, classproperty(..., ...))` – ntjess Oct 03 '22 at 20:05
12

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.

Ignacio Vazquez-Abrams
  • 776,304
  • 153
  • 1,341
  • 1,358
  • 1
    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? – CyberFonic Nov 17 '11 at 07:44
  • 2
    @CyberED: http://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()`." – Ignacio Vazquez-Abrams Nov 17 '11 at 07:51
  • 4
    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): pass – CyberFonic Nov 17 '11 at 08:00
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.

xaxxon
  • 19,189
  • 5
  • 50
  • 80
Jonas Schäfer
  • 20,140
  • 5
  • 55
  • 69
  • 2
    While I usually try to refrain from posting "Thanks", I seriously need to post "Thanks" right now: Thanks! – Chris Jun 21 '17 at 09:11
  • As mentioned in a [comment](https://stackoverflow.com/questions/3155436/getattr-for-static-class-variables#comment76327249_22729414) under a different properties-like answer, this is only feasible if you know the name of the properties in advance. – martineau Jun 06 '22 at 15:24
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.

geckos
  • 5,687
  • 1
  • 41
  • 53