如何使一个类的财产?
在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 ei == 20 e.inc_i() assert ei == 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
在我们调用“Bar.bar”的时候,setter没有工作,因为我们调用了“ Type.Bar.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)
现在会没事的
我想你可以用元类来做到这一点。 由于元类可以像类(如果这是有道理的)类。 我知道你可以给元类分配一个__call__()
方法来覆盖调用类MyClass()
。 我不知道在元类上使用property
装饰器的操作是否类似。 (我没有尝试过,但现在我好奇…)
[更新:]
哇,它确实工作:
class MetaClass(type): def getfoo(self): return self._foo foo = property(getfoo) @property def bar(self): return self._bar class MyClass(object): __metaclass__ = MetaClass _foo = 'abc' _bar = 'def' print MyClass.foo print MyClass.bar
注意:这是在Python 2.7中。 Python 3+使用不同的技术来声明一个元类。 使用: class MyClass(metaclass=MetaClass):
__metaclass__
删除__metaclass__
,其余部分是一样的。
如果您按照以下方式定义classproperty
,那么您的示例完全按照您的要求工作。
class classproperty(object): def __init__(self, f): self.f = f def __get__(self, obj, owner): return self.f(owner)
需要注意的是,您不能将其用于可写属性。 当eI = 20
会引发AttributeError
, Example.I = 20
会覆盖属性对象本身。
[基于Python 3.4编写的答案; 元类的语法不同于2,但是我认为这个技术仍然可行]
你可以用元类来做这件事。 Dappawit几乎可以工作,但我认为它有一个缺陷:
class MetaFoo(type): @property def thingy(cls): return cls._thingy class Foo(object, metaclass=MetaFoo): _thingy = 23
这会给你一个关于Foo的类属性,但是有一个问题…
print("Foo.thingy is {}".format(Foo.thingy)) # Foo.thingy is 23 # Yay, the classmethod-property is working as intended! foo = Foo() if hasattr(foo, "thingy"): print("Foo().thingy is {}".format(foo.thingy)) else: print("Foo instance has no attribute 'thingy'") # Foo instance has no attribute 'thingy' # Wha....?
这到底是怎么回事? 为什么我不能从一个实例中获得类属性?
在find我相信的答案之前,我在这方面已经打了很长时间了。 Python @properties是描述符的一个子集,从描述符文档 (我的重点)中:
属性访问的默认行为是从对象的字典中获取,设置或删除属性。 例如,
ax
有一个a.__dict__['x']
开始的查找链,然后type(a).__dict__['x']
,然后继续排除元类的基类type(a)
。
所以方法parsing顺序不包括我们的类属性(或者在元类中定义的其他东西)。 有可能使内置的属性装饰器的子类performance出不同的行为,但是(引用需要)我得到的印象是,开发者有一个很好的理由(我不明白)是这样做的。
这并不意味着我们运气不好, 我们可以访问类本身的属性就好了…我们可以从实例中的type(self)
获取类,我们可以使用它来创build@property调度器:
class Foo(object, metaclass=MetaFoo): _thingy = 23 @property def thingy(self): return type(self).thingy
现在, Foo().thingy
对于类和实例都是既定的。 如果一个派生类取代了它的底层_thingy
(这是最初让我进行这个search的用例),它也将继续做正确的事情。
这对我来说并不是100%满意 – 必须在元类和对象类中进行设置,这样做违反了DRY原则。 但是后者只是一个单线调度员, 我现在大部分都可以,如果你真的想要的话,你可以把它压缩成lambda或者什么东西。
如果你只需要延迟加载,那么你可以只有一个类初始化方法。
EXAMPLE_SET = False class Example(object): @classmethod def initclass(cls): global EXAMPLE_SET if EXAMPLE_SET: return cls.the_I = 'ok' EXAMPLE_SET = True def __init__( self ): Example.initclass() self.an_i = 20 try: print Example.the_I except AttributeError: print 'ok class not "loaded"' foo = Example() print foo.the_I print Example.the_I
但是元类方法看起来更清晰,并且具有更可预测的行为。
也许你正在寻找的是Singletondevise模式。 关于在Python中实现共享状态有一个很好的SO QA 。
据我所知,没有创build一个新的元类没有办法为类属性编写setter。
我发现以下方法可行。 用你想要的所有类属性和设置器定义一个元类。 IE浏览器,我想要一个带有setter的title
属性的类。 这是我写的:
class TitleMeta(type): @property def title(self): return getattr(self, '_title', 'Default Title') @title.setter def title(self, title): self._title = title # Do whatever else you want when the title is set...
现在让你想要的实际类像正常一样,除非它使用你上面创build的元类。
# Python 2 style: class ClassWithTitle(object): __metaclass__ = TitleMeta # The rest of your class definition... # Python 3 style: class ClassWithTitle(object, metaclass = TitleMeta): # Your class definition...
如果我们只在单个类上使用它,像上面那样定义这个元类是有点奇怪的。 在这种情况下,如果你使用的是Python 2风格,你实际上可以在类体内定义元类。 这样它没有在模块范围内定义。