2010-07-08 15 views
50

अनिवार्य रूप से मैं ऐसा कुछ करना चाहता हूं:पायथन में केवल पढ़ने योग्य वर्ग संपत्ति कैसे बनाएं?

class foo: 
    x = 4 
    @property 
    @classmethod 
    def number(cls): 
     return x 

फिर मैं निम्नलिखित कार्य करना चाहता हूं:

>>> foo.number 
4 

दुर्भाग्यवश, उपर्युक्त काम नहीं करता है। मुझे 4 देने के बजाय यह मुझे <property object at 0x101786c58> देता है। क्या ऊपर प्राप्त करने का कोई तरीका है?

उत्तर

36

property डिस्क्रिप्टर हमेशा कक्षा से पहुंचने पर खुद को लौटाता है (यानी। जब instanceNone__get__ विधि में है)।

अगर ऐसा नहीं तुम क्या चाहते हो, आप एक नया वर्णनकर्ता कि हमेशा वर्ग वस्तु (owner) का उपयोग करता है लिख सकते हैं उदाहरण की बजाय:

>>> class classproperty(object): 
...  def __init__(self, getter): 
...   self.getter= getter 
...  def __get__(self, instance, owner): 
...   return self.getter(owner) 
... 
>>> class Foo(object): 
...  x= 4 
...  @classproperty 
...  def number(cls): 
...   return cls.x 
... 
>>> Foo().number 
4 
>>> Foo.number 
4 
+19

यह केवल पढ़ने योग्य विशेषता नहीं है। पायथन वर्णनकर्ता जानबूझकर वर्ग-स्तरीय कॉल को 'setattr' और' delattr' को उनके '__set__' और '__delete__' विधियों के साथ अवरुद्ध नहीं करते हैं। तो 'Foo.number = 6' अभी भी काम कर सकता है। – HardlyKnowEm

47

यह कर देगा Foo.number एक केवल पढ़ने के लिए संपत्ति:

class MetaFoo(type): 
    @property 
    def number(cls): 
     return cls.x 

class Foo(object, metaclass=MetaFoo): 
    x = 4 

print(Foo.number) 
# 4 

Foo.number = 6 
# AttributeError: can't set attribute 

स्पष्टीकरण: सामान्य परिदृश्य जब @property का उपयोग कर इस तरह दिखता है:

class Foo(object): 
    @property 
    def number(self): 
     ... 
foo = Foo() 

एक संपत्ति Foo में परिभाषित केवल इसके उदाहरणों के संबंध में पढ़ा जाता है। यही है, foo.number = 6AttributeError बढ़ाएगा।

आकस्मिक रूप से, यदि आप Foo.numberAttributeError बढ़ाने के लिए चाहते हैं तो आपको type(Foo) में परिभाषित एक संपत्ति सेट अप करने की आवश्यकता होगी। इसलिए मेटाक्लास की आवश्यकता है।


ध्यान दें कि यह पठनीयता केवल हैकर्स से प्रतिरक्षा नहीं है। संपत्ति फू के वर्ग को बदलकर लिखने योग्य बनाया जा सकता है:,

class Base(type): pass 
Foo.__class__ = Base 

# makes Foo.number a normal class attribute 
Foo.number = 6 
print(Foo.number) 

प्रिंट

6 

या, यदि आप Foo.number एक settable संपत्ति करना चाहते हैं

class WritableMetaFoo(type): 
    @property 
    def number(cls): 
     return cls.x 
    @number.setter 
    def number(cls, value): 
     cls.x = value 
Foo.__class__ = WritableMetaFoo 

# Now the assignment modifies `Foo.x` 
Foo.number = 6 
print(Foo.number) 

भी 6 प्रिंट

+0

@unutbu: क्या यह रिवर्स निष्पादित करना संभव है: यह Foo.number को Foo क्लास के पुनर्निर्माण के बिना लिखने योग्य बनाता है। – user2284570

+0

@ user2284570: आप 'मेटाफू 'से कुछ और करने के लिए' Foo' के मेटाक्लास को बदलकर 'Foo.number' लिखने योग्य बना सकते हैं। आप या तो 'संख्या' को एक सेट करने योग्य संपत्ति बना सकते हैं या बस इसका उल्लेख नहीं कर सकते हैं, जिस स्थिति में 'Foo.number' सेटिंग सामान्य वर्ग विशेषता 'Foo.number' बनाती है। मैंने जो कुछ मतलब है उसे दिखाने के लिए मैंने ऊपर कुछ कोड जोड़ा है। – unutbu

+0

@unutbu: और क्या मैं इसे मेटाक्लास वापस बदल सकता हूं? क्या यह कोड ऑब्जेक्ट्स के लिए काम करेगा? – user2284570

12

मैं unubtu's answer से सहमत; ऐसा लगता है कि यह काम करता है, हालांकि, यह पायथन 3 पर विशेष सटीक वाक्यविन्यास के साथ काम नहीं करता है (विशेष रूप से, पायथन 3.4 वह है जिसे मैंने संघर्ष किया था)। यहां बताया गया है कि किसी को पायथन 3 के तहत पैटर्न कैसे बनाना चाहिए।4 चीजें काम करने के लिए, ऐसा लगता है:

class MetaFoo(type): 
    @property 
    def number(cls): 
     return cls.x 

class Foo(metaclass=MetaFoo): 
    x = 4 

print(Foo.number) 
# 4 

Foo.number = 6 
# AttributeError: can't set attribute 
2

ऊपर समाधान के साथ समस्या यह है कि यह उदाहरण चर से वर्ग चर तक पहुँचने के लिए काम नहीं होता है:

print(Foo.number) 
# 4 

f = Foo() 
print(f.number) 
# 'Foo' object has no attribute 'number' 

इसके अलावा, metaclass स्पष्ट का उपयोग नहीं किया जाता है तो अच्छा, नियमित property सजावट का उपयोग करने के रूप में।

मैंने इन समस्याओं को हल करने का प्रयास किया। यहाँ कैसे यह अब काम करता है:

@classproperty_support 
class Bar(object): 
    _bar = 1 

    @classproperty 
    def bar(cls): 
     return cls._bar 

    @bar.setter 
    def bar(cls, value): 
     cls._bar = value 


# @classproperty should act like regular class variable. 
# Asserts can be tested with it. 
# class Bar: 
#  bar = 1 


assert Bar.bar == 1 

Bar.bar = 2 
assert Bar.bar == 2 

foo = Bar() 
baz = Bar() 
assert foo.bar == 2 
assert baz.bar == 2 

Bar.bar = 50 
assert baz.bar == 50 
assert foo.bar == 50 

जैसा कि आप देख, हम @classproperty उस वर्ग चर के लिए @property के रूप में उसी तरह काम करता है। केवल एक चीज जिसकी हमें आवश्यकता होगी अतिरिक्त @classproperty_support कक्षा सजावट है।

समाधान केवल पढ़ने-योग्य वर्ग गुणों के लिए भी काम करता है।

class classproperty: 
    """ 
    Same as property(), but passes obj.__class__ instead of obj to fget/fset/fdel. 
    Original code for property emulation: 
    https://docs.python.org/3.5/howto/descriptor.html#properties 
    """ 
    def __init__(self, fget=None, fset=None, fdel=None, doc=None): 
     self.fget = fget 
     self.fset = fset 
     self.fdel = fdel 
     if doc is None and fget is not None: 
      doc = fget.__doc__ 
     self.__doc__ = doc 

    def __get__(self, obj, objtype=None): 
     if obj is None: 
      return self 
     if self.fget is None: 
      raise AttributeError("unreadable attribute") 
     return self.fget(obj.__class__) 

    def __set__(self, obj, value): 
     if self.fset is None: 
      raise AttributeError("can't set attribute") 
     self.fset(obj.__class__, value) 

    def __delete__(self, obj): 
     if self.fdel is None: 
      raise AttributeError("can't delete attribute") 
     self.fdel(obj.__class__) 

    def getter(self, fget): 
     return type(self)(fget, self.fset, self.fdel, self.__doc__) 

    def setter(self, fset): 
     return type(self)(self.fget, fset, self.fdel, self.__doc__) 

    def deleter(self, fdel): 
     return type(self)(self.fget, self.fset, fdel, self.__doc__) 


def classproperty_support(cls): 
    """ 
    Class decorator to add metaclass to our class. 
    Metaclass uses to add descriptors to class attributes, see: 
    http://stackoverflow.com/a/26634248/1113207 
    """ 
    class Meta(type): 
     pass 

    for name, obj in vars(cls).items(): 
     if isinstance(obj, classproperty): 
      setattr(Meta, name, property(obj.fget, obj.fset, obj.fdel)) 

    class Wrapper(cls, metaclass=Meta): 
     pass 
    return Wrapper 

नोट:: कोड का परीक्षण नहीं किया गया है बहुत, नोट करने के लिए के रूप में आप उम्मीद करता है, तो यह काम नहीं करता स्वतंत्र महसूस

यहाँ कार्यान्वयन है।

संबंधित मुद्दे