How to fully emulate the behavior of static class variables of other languages in Python -
it established python not have "static class variables" in same sense in other languages. instead, has class attributes, similar, different.
for example, class attribute (a variable defined inside of class definition) can "overridden" instance attribute of same name:
class a(): = 1 assert a.i == 1 = a() assert a.i == 1 a.i = 2 # has a.i been updated 2 well? assert a.i == 2 # error: no, hasn't assert a.i == 2 # a.i , a.i 2 different variables del a.i assert a.i == 1 # if a.i doesn't exist, falls in a.i
in actuality, there no "overriding" going on - class dictionary comes later in attribute lookup order instance dictionary.
this behavior can partially overcome using property
decorator:
class a(): _i = 1 @property def i(self): return type(self)._i @i.setter def i(self,value): type(self)._i = value
now, property
remain "in sync" between multiple instances:
a1 = a() a2 = a() a1.i = 2 assert a1.i == a2.i # attribute remained in sync
however, "gotcha" here cannot access or set property
via class itself:
assert a.i == a.i # error type(a.i) # class 'property' a.i = 10 # if try set via class... assert a.i == 10 # appears have worked! a.i = 5 # but... assert a.i == a.i # error! didn't work. type(a.i) # int - setting property via class overwrote
this difficulty can overcome using full-fledged descriptor (of property
1 type):
class myproperty(): def __init__(self,name): self.name = name def __get__(self,inst,cls): return getattr(cls,self.name) def __set__(self,inst,value): setattr(type(inst),self.name,value) def __delete__(self,inst): delattr(type(inst),self.name) class a(): = myproperty('i')
now, getting , setting attribute via class , via instance work, , deleting via instance works:
a = a() a.i = 5 # can set via class assert a.i == a.i a.i = 10 assert a.i == a.i # still in sync! del a.i assert a.i # error, expected a.i = 2 assert a.i == 2
however, there still problem when try delete via class:
del a.i # myproperty descriptor has been deleted a.i = 2 a.i = 4 assert a.i == a.i # error!
how can full emulation of "static variables" achieved in python?
i present (python 3) solution below informational purposes only. not endorsing "good solution". have doubts whether emulating static variable behavior of other languages in python ever necessary. however, regardless whether useful, creating code below helped me further understand how python works, might others well.
the metaclass have created below attempts emulate "static variable" behavior of other languages. it's pretty complicated, works replacing normal getter, setter, , deleter versions check see if attribute being requested "static variable". catalog of "static variables" stored in staticvarmeta.statics
attribute. if requested attribute not "static variable", class fall on default attribute get/set/delete behavior. if "static variable", attempted resolve attribute request using substitute resolution order (which have dubbed __sro__
, or "static resolution order").
i sure there simpler ways accomplish this, , forward seeing other answers.
from functools import wraps class staticvarsmeta(type): '''a metaclass creating classes emulate "static variable" behavior of other languages. not advise using anything!!! behavior intended similar classes use __slots__. however, "normal" attributes , __statics___ can coexist (unlike __slots__). example usage: class mybaseclass(metaclass = staticvarsmeta): __statics__ = {'a','b','c'} = 1 # regular attribute class myparentclass(mybaseclass): __statics__ = {'d','e','f'} j = 2 # regular attribute d, e, f = 3, 4, 5 # static vars a, b, c = 6, 7, 8 # static vars (inherited mybaseclass, defined here) class mychildclass(myparentclass): __statics__ = {'a','b','c'} j = 2 # regular attribute (redefines j myparentclass) d, e, f = 9, 10, 11 # static vars (inherited myparentclass, redefined here) a, b, c = 12, 14, 14 # static vars (overriding previous definition in myparentclass here)''' statics = {} def __new__(mcls, name, bases, namespace): # class object cls = super().__new__(mcls, name, bases, namespace) # establish "statics resolution order" cls.__sro__ = tuple(c c in cls.__mro__ if isinstance(c,mcls)) # replace class getter, setter, , deleter instance attributes cls.__getattribute__ = staticvarsmeta.__inst_getattribute__(cls, cls.__getattribute__) cls.__setattr__ = staticvarsmeta.__inst_setattr__(cls, cls.__setattr__) cls.__delattr__ = staticvarsmeta.__inst_delattr__(cls, cls.__delattr__) # store list of static variables class object # list permanent , cannot changed, similar __slots__ try: mcls.statics[cls] = getattr(cls,'__statics__') except attributeerror: mcls.statics[cls] = namespace['__statics__'] = set() # no static vars provided # check , make sure statics var names strings if any(not isinstance(static,str) static in mcls.statics[cls]): typ = dict(zip((not isinstance(static,str) static in mcls.statics[cls]), map(type,mcls.statics[cls])))[true].__name__ raise typeerror('__statics__ items must strings, not {0}'.format(typ)) # move existing, not overridden statics static var parent class(es) if len(cls.__sro__) > 1: attr,value in namespace.items(): if attr not in staticvarsmeta.statics[cls] , attr != ['__statics__']: c in cls.__sro__[1:]: if attr in staticvarsmeta.statics[c]: setattr(c,attr,value) delattr(cls,attr) return cls def __inst_getattribute__(self, orig_getattribute): '''replaces class __getattribute__''' @wraps(orig_getattribute) def wrapper(self, attr): if staticvarsmeta.is_static(type(self),attr): return staticvarsmeta.__getstatic__(type(self),attr) else: return orig_getattribute(self, attr) return wrapper def __inst_setattr__(self, orig_setattribute): '''replaces class __setattr__''' @wraps(orig_setattribute) def wrapper(self, attr, value): if staticvarsmeta.is_static(type(self),attr): staticvarsmeta.__setstatic__(type(self),attr, value) else: orig_setattribute(self, attr, value) return wrapper def __inst_delattr__(self, orig_delattribute): '''replaces class __delattr__''' @wraps(orig_delattribute) def wrapper(self, attr): if staticvarsmeta.is_static(type(self),attr): staticvarsmeta.__delstatic__(type(self),attr) else: orig_delattribute(self, attr) return wrapper def __getstatic__(cls,attr): '''static variable getter''' c in cls.__sro__: if attr in staticvarsmeta.statics[c]: try: return getattr(c,attr) except attributeerror: pass raise attributeerror(cls.__name__ + " object has no attribute '{0}'".format(attr)) def __setstatic__(cls,attr,value): '''static variable setter''' c in cls.__sro__: if attr in staticvarsmeta.statics[c]: setattr(c,attr,value) break def __delstatic__(cls,attr): '''static variable deleter''' c in cls.__sro__: if attr in staticvarsmeta.statics[c]: try: delattr(c,attr) break except attributeerror: pass raise attributeerror(cls.__name__ + " object has no attribute '{0}'".format(attr)) def __delattr__(cls,attr): '''prevent __sro__ attribute deletion''' if attr == '__sro__': raise attributeerror('readonly attribute') super().__delattr__(attr) def is_static(cls,attr): '''returns true if attribute static variable of class in __sro__''' if any(attr in staticvarsmeta.statics[c] c in cls.__sro__): return true return false
Comments
Post a Comment