2011-03-04 41 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