I have a class like:
class MyClass:
Foo = 1
Bar = 2
Whenever MyClass.Foo
or MyClass.Bar
is invoked, I n
__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
# in python 3:
# class MyClass(metaclass=FooType):
# pass
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.
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.
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
.
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.
(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