13

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?

martineau
  • 119,623
  • 25
  • 170
  • 301
JustinLovinger
  • 782
  • 7
  • 15

6 Answers6

5

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'>
Brendan Abel
  • 35,343
  • 14
  • 88
  • 118
2

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
thebjorn
  • 26,297
  • 11
  • 96
  • 138
2

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'>
martineau
  • 119,623
  • 25
  • 170
  • 301
  • 1
    To avoid hardcoding the class name into strings in the class, you could hardcode the placeholder to be some constant like `''`. – martineau Dec 08 '15 at 21:34
1

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

Community
  • 1
  • 1
Mikko Ohtamaa
  • 82,057
  • 50
  • 264
  • 435
  • Could you elaborate on how this could be done? My understanding is Python does not create the class object until after all class attributes are parsed. Hence why this problem exists. – JustinLovinger Dec 06 '15 at 21:56
  • `__new__` is executed after class definition closes. Then you iterate over all defined attributes and "fix" them with a code which sets a pointer to class itself. – Mikko Ohtamaa Dec 07 '15 at 04:34
  • Class decorator solution below is a better one. I recommend it. – Mikko Ohtamaa Dec 07 '15 at 04:34
0

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.

bperson
  • 1,285
  • 10
  • 19
  • This doesn't seem to allow you to use the class in a class attribute, since you would need an instance of the class to call bar. – JustinLovinger Dec 06 '15 at 19:46
  • But in what scenario would you ever require a class attribute that refers to the class itself? Anything that would refer to the property could just refer to the class object itself? – Brendan Abel Dec 06 '15 at 19:51
  • not sure but could you use both decorators: `@property` and `@classmethod` ? (it would be quite confusing if it worked but why not ;) ) – bperson Dec 06 '15 at 20:50
  • Tried using class method, but since you can't call Foo.method, it doesn't work. – JustinLovinger Dec 06 '15 at 21:54
0

You can use lambda expression:

class Foo(object):
    bar = lambda: Foo

This violates PEP 8, but works

Yuriy Petrovskiy
  • 7,888
  • 10
  • 30
  • 34