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 ;-)
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
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With