2

Does Python have a mechanism for class constructors, i.e. a function that is called whenever the class is first referenced (as opposed to when an instance of that object is created)? I know this exists in some other languages, but I haven't come across it in Python.

Basically, I would like to initialise some static attributes in that function. I put an example below of what I would expect. Of course, the example returns None, but I would like it return 'foo'.

class T:
    arg = None
    def __class_constructor__():
        T.arg = 'foo'

print(T.arg)  # returns None

To avoid confusion: I am well aware of the object constructor, but that's not what I want, because it is only called once the first object is created, not before:

class T:
    arg = None
    def __init__(self):
        type(self).arg = 'foo'

print(T.arg)  # returns None
obj = T()
print(T.arg)  # returns 'foo'
Elias Mi
  • 611
  • 6
  • 14
  • 4
    Why not `class T: arg = 'foo'`…? The `class` keyword *is* the "class constructor". – deceze Aug 04 '17 at 11:26
  • The actual value I want to assign is a bit more complex than just 'foo', so unfortunately it can't really be assigned like this – Elias Mi Aug 04 '17 at 11:28
  • 2
    Unless the value is dependent on information not available at the time you define the class, there's no reason you couldn't do this. Some details perhaps, please? – deceze Aug 04 '17 at 11:29
  • 1
    @EliasMi Any code you can write in `__class_constructor__` you can also write in the class itself. It isn't any less powerful. – Aran-Fey Aug 04 '17 at 11:30
  • I also tried to combine the annotations \@property and \@staticmethod, to define T.arg as a static property with a getter, but Python doesn't seem to like the two keywords together... and it seems a bit dirty anyways – Elias Mi Aug 04 '17 at 11:31
  • You *could* use a metaclass for this, but like other commenters I would want to know why you can't do this directly in the definition. – Daniel Roseman Aug 04 '17 at 11:32
  • 2
    OK, so: no, there is no "class constructor that kicks in when the class is first **referenced**." The first reference to the class is at `class T`. The only next opportunity is instance creation time. So you'll have to be a bit more detailed about your actual issue so we can recommend a Pythonic way to solve it. – deceze Aug 04 '17 at 11:33
  • 1
    actually, you're right -- this works: `class T: arg = 0 for i in range(4): arg += i print(T.arg) # returns 6`. I just never thought about adding actual code into the class header – Elias Mi Aug 04 '17 at 11:35
  • Everything in Python is *code*. Even class declarations are procedural code. – deceze Aug 04 '17 at 11:35
  • I sometimes defer initialization of class static data to the first instance construction if that operation is expensive -- e.g. loading data from a database. Other than that I just initialize it like @deceze suggests. When I need to pull in data only if the class actually gets instantiated, I do something like: class T: arg = None ... def __init__(self): if arg is None: # go fetch the data for arg from the database... (sorry, no line breaks to make it pretty, but you get the picture.) That way, you lazy-load it for the first use only. – Steve L May 27 '20 at 18:02
  • Does this answer your question? [Is there a static constructor or static initializer in Python?](https://stackoverflow.com/questions/7396092/is-there-a-static-constructor-or-static-initializer-in-python) – Iwan Aucamp Sep 15 '20 at 13:56

3 Answers3

3

You can use a class decorator:

def add_arg(cls):
    if not hasattr(cls, "arg"):
        cls.arg = 'foo'
    return cls

@add_arg
class T(object):
    pass

Or a custom metaclass:

class WithArg(type):
    def __new__(meta, name, bases, attrs):
        cls = type.__new__(meta, name, bases, attrs)
        if not hasattr(cls, "arg"):
            cls.arg = "foo"
        return cls

# python 2
class T(object):
    __metaclass__ = WithArg

# python 3
class T(metaclass=WithArg):
    pass

But as others already mention this won't give you much more than plainly setting the class attribute in the class statement.

NB : if you want a computed attribute on the class itself, you'll have to either set it as a property on a custom metaclass

 class WithProp(type):
     @property
     def arg(cls):
         return "foo"

 class T(object):
     __metaclass__ = WithProp

 T.arg
 => 'foo'

But arg will only be available on the class object itself, not on it's instances:

T().arg
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
AttributeError: 'T' object has no attribute 'arg'

or write your own custom descriptor:

class ArgDescriptor(object):
    def __get__(self, obj, cls=None):
        return 42

class T(object):
    arg = ArgDescriptor()

T.arg
=> 42
T().arg
=> 42
bruno desthuilliers
  • 75,974
  • 6
  • 88
  • 118
  • Neither of those activate "when the class is first referenced"… – deceze Aug 04 '17 at 11:34
  • Yes I just edited my answer about this. Problem is "when the class is first referenced" doesn't mean much. – bruno desthuilliers Aug 04 '17 at 11:35
  • I forgot to mention one important detail... the attribute should be an instance of the class itself. But now I have that sorted out.... I just define the class first, and set the attribute after (because otherwise, the class doesn't know itself) – Elias Mi Aug 04 '17 at 11:48
  • So basically, what I want (and now have) is: `class Config: //// def __init__(self, foo): //// self._foo = foo`, and then afterwards: `Config.DEFAULT = Config('default params')` – Elias Mi Aug 04 '17 at 11:49
  • 1
    "the attribute should be an instance of the class itself" => well all of the solutions I posted would allow that - but are indeed overkill for something so simple. XY problem as often... – bruno desthuilliers Aug 04 '17 at 11:52
2

You simply have to initialise the class variable when declaring it within the class

class T:
    arg = 'foo' #this initialises the class instance

    def __init__(self):
        self.arg = 'bar' #this initialises the object instance

print(T.arg)  # class instance returns 'foo'
obj = T()
print(T.arg)  # class instance still returns 'foo'
print(obj.arg)  # object instance returns 'bar'
Simon Black
  • 913
  • 8
  • 20
0

I create a static_init decorator which calls a static_init class method if it exists. This static_init class method will run when the class decorator is evaluated which is when the class is defined - so not quite when the class is first referenced - but it is analogous to static initialization in other languages like Java.

Here is the decorator and example of how to use it to initialize a class variable on an enum class:

# pylint: disable=missing-docstring,no-member

import enum

def static_init(cls):
    if getattr(cls, "static_init", None):
        cls.static_init()
    return cls

@static_init
class SomeEnum(enum.Enum):
    VAL_A = enum.auto()
    VAL_B = enum.auto()
    VAL_C = enum.auto()
    VAL_D = enum.auto()

    @classmethod
    def static_init(cls):
        text_dict = {}
        setattr(cls, 'text_dict', text_dict)
        for value in cls:
            text_dict[value.name.lower().replace("_", " ").title()] = value

def test_static_init():
    assert SomeEnum.text_dict["Val A"] == SomeEnum.VAL_A
    assert SomeEnum.text_dict["Val B"] == SomeEnum.VAL_B
    assert SomeEnum.text_dict["Val C"] == SomeEnum.VAL_C
    assert SomeEnum.text_dict["Val D"] == SomeEnum.VAL_D
Iwan Aucamp
  • 1,469
  • 20
  • 21