@classmethod在 python 中,我可以使用装饰器将方法添加到类中。是否有类似的装饰器可以将属性添加到类中?我可以更好地展示我在说什么。
@classmethod
class Example(object): the_I = 10 def __init__( self ): self.an_i = 20 @property def i( self ): return self.an_i def inc_i( self ): self.an_i += 1 # is this even possible? @classproperty def I( cls ): return cls.the_I @classmethod def inc_I( cls ): cls.the_I += 1 e = Example() assert e.i == 20 e.inc_i() assert e.i == 21 assert Example.I == 10 Example.inc_I() assert Example.I == 11
我上面使用的语法是可能的还是需要更多的东西?
我想要类属性的原因是我可以延迟加载类属性,这似乎很合理。
这是我将如何做到这一点:
class ClassPropertyDescriptor(object): def __init__(self, fget, fset=None): self.fget = fget self.fset = fset def __get__(self, obj, klass=None): if klass is None: klass = type(obj) return self.fget.__get__(obj, klass)() def __set__(self, obj, value): if not self.fset: raise AttributeError("can't set attribute") type_ = type(obj) return self.fset.__get__(obj, type_)(value) def setter(self, func): if not isinstance(func, (classmethod, staticmethod)): func = classmethod(func) self.fset = func return self def classproperty(func): if not isinstance(func, (classmethod, staticmethod)): func = classmethod(func) return ClassPropertyDescriptor(func) class Bar(object): _bar = 1 @classproperty def bar(cls): return cls._bar @bar.setter def bar(cls, value): cls._bar = value # test instance instantiation foo = Bar() assert foo.bar == 1 baz = Bar() assert baz.bar == 1 # test static variable baz.bar = 5 assert foo.bar == 5 # test setting variable on the class Bar.bar = 50 assert baz.bar == 50 assert foo.bar == 50
在我们调用时,setter 没有工作 Bar.bar,因为我们正在调用 TypeOfBar.bar.__set__,但事实并非如此Bar.bar.__set__。
Bar.bar
TypeOfBar.bar.__set__
Bar.bar.__set__
添加元类定义解决了这个问题:
class ClassPropertyMetaClass(type): def __setattr__(self, key, value): if key in self.__dict__: obj = self.__dict__.get(key) if obj and type(obj) is ClassPropertyDescriptor: return obj.__set__(self, value) return super(ClassPropertyMetaClass, self).__setattr__(key, value) # and update class define: # class Bar(object): # __metaclass__ = ClassPropertyMetaClass # _bar = 1 # and update ClassPropertyDescriptor.__set__ # def __set__(self, obj, value): # if not self.fset: # raise AttributeError("can't set attribute") # if inspect.isclass(obj): # type_ = obj # obj = None # else: # type_ = type(obj) # return self.fset.__get__(obj, type_)(value)
现在一切都会好起来的。