2011-03-04 42 views
62

在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 

我上面使用过的语法可能还是会需要更多?

我想要类属性的原因是我可以延迟加载类属性,这似乎足够合理。

+1

我不知道python ...这是你正在寻找的东西吗? http://www.python.org/download/releases/2。2/descrintro /#属性 – 2011-03-04 04:40:48

+0

如果我正在阅读这个权利,你可以创建方法来充当setter和getter(和其他语言一样),这样你就可以在第一次获取属性值时进行懒加载...我认为你想要什么? – 2011-03-04 04:45:56

+0

@白龙。您正在查看的属性功能将类属性添加到类实例中,而不是类本身。我在问'Example.I'不是'e.i'。 – 2011-03-04 04:57:04

回答

44

这是我会怎么做:

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工作的时候,因为我们呼吁 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) 

现在都将被罚款。

+0

hmm ..我的setter函数似乎被忽略,并且我无法在__set__中执行任何操作(即使我只是在这里有一些垃圾,没有任何变化) – 2011-04-22 21:37:23

+0

第二个断言在这里失败:http:// drktd。 com/6D2b – 2011-04-22 21:38:21

+0

似乎不适用于setter。 – I159 2013-02-10 19:02:25

22

我想你或许可以用元类来做到这一点。因为元类可以像类一样(如果有意义的话)。我知道你可以为元类指派一个__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__,其余都一样。

+0

所以它可以完成,但可以用方法装饰器完成吗? – 2011-03-04 04:53:49

+0

我知道你可以直接在你感兴趣的类上使用装饰器,但是元类中的'property'装饰器应该可以工作....我编辑了我的答案以包含装饰器元类方法。 – dappawit 2011-03-04 04:57:45

+1

另请参阅[此类似的问题](http://stackoverflow.com/questions/128573/using-property-on-classmethods)及其答案。似乎是相似的,所以它可能会有一些更有用的信息:) – Abbafei 2011-03-04 04:59:24

1

如果你只需要延迟加载,那么你可以只有一个类初始化方法。

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 

但是,元类方法看起来更清洁,具有更可预测的行为。

也许你正在寻找的是Singleton设计模式。有关于在Python中实现共享状态的a nice SO QA

20

如果您按如下定义classproperty,那么您的示例完全按照您的要求工作。

class classproperty(object): 
    def __init__(self, f): 
     self.f = f 
    def __get__(self, obj, owner): 
     return self.f(owner) 

需要注意的是,您不能将此用于可写属性。而e.I = 20将提高AttributeErrorExample.I = 20将覆盖属性对象本身。

2

据我所知,没有办法为类属性编写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... 

现在让你想正常的实际类,除了有它使用上面创建的元类。

# 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风格,你实际上可以在类体内定义元类。这样它就没有在模块范围中定义。

10

[基于python 3.4编写的答案;元类的语法在2中有所不同,但我认为该技术仍然有效]

您可以使用元类...主要做到这一点。 Dappawit几乎工程,但我认为它也有缺陷:

class MetaFoo(type): 
    @property 
    def thingy(cls): 
     return cls._thingy 

class Foo(object, metaclass=MetaFoo): 
    _thingy = 23 

这让你一个classproperty上富,但有一个问题...

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....? 

这到底是怎么回事?为什么我无法通过实例访问类属性?

在找到我相信的答案之前,我在这上面打了很久。 Python的@properties是描述符的一个子集,并从descriptor documentation(重点煤矿):属性的访问

默认行为是获取,设置或删除对象的字典中 属性。例如,a.x具有查找链 开始a.__dict__['x'],然后type(a).__dict__['x'],并继续通过 type(a)不含元类的基类。

所以方法解析顺序不包括我们的类属性(或其他在元类中定义的)。它可能使内置属性装饰器的行为有所不同,但(引文需要)我得到了印象谷歌搜索开发人员有一个很好的理由(我不明白)做到这一点办法。

这并不意味着我们运气不好;我们可以在类本身就好访问属性...我们可以从type(self)实例,我们可以用它来使@property调度中获取类:

class Foo(object, metaclass=MetaFoo): 
    _thingy = 23 

    @property 
    def thingy(self): 
     return type(self).thingy 

现在Foo().thingy作品用于两个班级和实例!如果一个派生类替代了它的基础_thingy(这是最初让我参与这个狩猎的用例),它也将继续做正确的事情。

这对我来说并不是100%满意 - 必须在元类和对象类中进行设置才会感觉违反了DRY原则。但后者只是一个单线调度员;我现在大部分都没问题,如果你真的想要的话,你可以把它压缩到一个lambda或什么东西。

+0

这应该是最好的答案它普遍适用于子类。 – geckon 2017-03-26 19:18:59