JustinLovinger
JustinLovinger

Reputation: 922

What is the best way for a class to reference itself in a class attribute?

Unfortunately, in Python, a class attribute cannot reference its class name. The following raises a NameError:

class Foo(object):
    bar = Foo

In a situation where a class attribute must reference its class name (in my case, it's part of a validation scheme for a library). What is the clearest way to do so?

The following is my current attempt:

class Foo(object):
    bar = None
Foo.bar = Foo

This works, but even with comments is likely to cause confusion, since you expect a class attribute to be initialized when declared, not after the class is declared.

Does anyone have a better workaround?

Upvotes: 13

Views: 5876

Answers (6)

martineau
martineau

Reputation: 123433

You could define a class decorator that replaced placeholder strings with the class being defined:

def fixup(cls):
    placeholder = '@' + cls.__name__
    for k,v in vars(cls).items():
        if v == placeholder:
            setattr(cls, k, cls)
    return cls

@fixup
class Foo(object):
    bar = '@Foo'

print('Foo.bar: {!r}'.format(Foo.bar))  # -> Foo.bar: <class '__main__.Foo'>

Another alternative would be to use the __init_subclass__() special method which was introduced in Python 3.6 to create a base class and then derive your class from it instead of the generic object:

class Base(object):
    def __init_subclass__(cls, /, **kwargs):
        super().__init_subclass__(**kwargs)
        cls.bar = cls

class Foo(Base):
    pass

print('Foo.bar: {!r}'.format(Foo.bar))  # -> Foo.bar: <class '__main__.Foo'>

Upvotes: 2

Yuriy Petrovskiy
Yuriy Petrovskiy

Reputation: 8178

You can use lambda expression:

class Foo(object):
    bar = lambda: Foo

This violates PEP 8, but works

Upvotes: 0

Brendan Abel
Brendan Abel

Reputation: 37509

Use a meta class to automatically set it.

def my_meta(name, bases, attrs):
    cls = type(name, bases, attrs)
    cls.bar = cls
    return cls


class Foo(object):
    __metaclass__ = my_meta


>>> print Foo.bar
<class '__main__.Foo'>

Upvotes: 5

thebjorn
thebjorn

Reputation: 27311

You can use a class decorator

def moi(fieldname):
    def _selfref(cls):
        setattr(cls, fieldname, cls.__name__)
        return cls

    return _selfref

usage:

@moi('bar')
class Foo(object):
    pass

then:

>>> print Foo.bar
Foo

Upvotes: 2

bperson
bperson

Reputation: 1335

Why not use a property if you just want to ''alias'' __class__ ?

@property
def bar(self):
    return self.__class__

That said it's bound to create issues if you play with some inheritance.

Upvotes: 0

Mikko Ohtamaa
Mikko Ohtamaa

Reputation: 83788

You can use metaclasses. Define a __new__ which iterates over set attributes and then sets the back pointer to the class itself.

Upvotes: 1

Related Questions