Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Setters for class variables

I have a simple class within a module named foo.py:

class Foo
    foo = 1

that I import into other modules (bar.py and baz.py) and I alter the class variable foo within these other modules, e.g.:

# bar.py

from foo import Foo
print(Foo.foo) # should print 1.
Foo.foo = 2

and

# baz.py

from foo import Foo

print(Foo.foo) # should print 2.
Foo.foo = 3

The changes to Foo.foo however should be checked before they are set. Therefore, I'm currently using a setter method in Foo:

# foo.py

@classmethod
def set_foo(cls, new_foo):
    # do some checks on the supplied new_foo, then set foo.
    cls.foo = new_foo

Is this the pythonic way to set a class variable? Or are there better methods (similar to the @property a and @a.setter declarations for instance variables)? I want the class variable foo to persist when importing Foo in these other modules and don't really want to make an instance of Foo as it's more of a class thing I guess.

Thanks SO ;-)

like image 451
alexjrlewis Avatar asked Jul 22 '26 12:07

alexjrlewis


1 Answers

If you don't mind a little magic, this can be done relatively sanely by using the descriptor protocol.

class FooDescriptor:

    def __get__(self, obj, objtype=None):
        if obj is None:
            return self
        return obj._foo

    def __set__(self, obj, value):
        if not isinstance(obj, type):
            # disable instance name shadowing for sanity's sake
            raise AttributeError("this attribute should be set on the class object")
        obj._foo = value + "!!"


class FooMeta(type):

    foo = FooDescriptor()

    def __new__(cls, clsname, bases, namespace):
        # pluck the "foo" attr out of the class namespace,
        # and swap in our descriptor in its place
        namespace["_foo"] = namespace.pop("foo", "(default foo val)")
        namespace["foo"] = FooMeta.foo
        return type.__new__(cls, clsname, bases, namespace)

When constructing a class Foo, this will replace the foo class attribute defined in the normal declarative fashion with a data descriptor (to provide custom getters and setters). We will be storing the raw "unmanaged" value in Foo._foo instead.

Demo:

>>> class Foo(metaclass=FooMeta): 
...     foo = "foo0" 
... 
>>> obj = Foo() 
>>> obj.foo  # accessible from instance, like a class attr
'foo0'
>>> Foo.foo  # accessible from class
'foo0'
>>> Foo.foo = "foo1"  # setattr has magic, this will add exclams
>>> obj.foo
'foo1!!'
>>> Foo.foo
'foo1!!'
>>> vars(obj)  # still no instance attributes
{}
>>> type(Foo).foo  # who does the trick?
<__main__.FooDescriptor at 0xcafef00d>
>>> obj.foo = "boom"  # prevent name shadowing (optional!)
AttributeError: this attribute should be set on the class object
like image 193
wim Avatar answered Jul 25 '26 00:07

wim



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!