2010-07-08 11 views

Odpowiedz

36

The property deskryptor zawsze zwraca się, gdy uzyskuje się dostęp z klasy (np. gdy instance jest None w metodzie __get__).

Jeśli to nie to, co chcesz, to można napisać nowy deskryptor, że zawsze używa obiektu klasy (owner) zamiast instancji:

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

To nie jest atrybut tylko do odczytu. Deskryptory w języku Python celowo nie przechwytują wywołań na poziomie klasy do 'setattr' i' delattr' z ich metodami '__set__' i' __delete__'. Więc 'Foo.number = 6' może nadal działać. – HardlyKnowEm

47

To sprawi Foo.numbertylko do odczytu właściwość:

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 

Objaśnienie: zwykle podczas korzystania @property scenariusz wygląda następująco:

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

Właściwość zdefiniowane w Foo jest tylko do odczytu w odniesieniu do jego wystąpień. Oznacza to, że foo.number = 6 podniosłoby AttributeError.

Analogicznie, jeśli chcesz, aby Foo.number podniósł AttributeError, musisz ustawić właściwość zdefiniowaną w type(Foo). Stąd potrzeba metaklasu.


Należy zauważyć, że ta wartość tylko do odczytu nie jest odporna na ataki hakerów. Nieruchomość może być zapisywalny przez zmianę Foo Klasa:

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

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

wydruki

6 

lub, jeśli chcesz, aby Foo.number nieruchomości ustawiane,

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) 

drukuje również 6

+0

@unutbu: Czy można wykonać odwrotność: oznacza to, że Foo.number jest zapisywalny bez odbudowy klasy Foo. – user2284570

+0

@ user2284570: Możesz dokonać zapisu 'Foo.number', zmieniając metaclass' Foo' z 'MetaFoo' na coś innego. Możesz ustawić 'numer' jako właściwość możliwą do ustawienia lub po prostu nie wspominać o tym, w takim przypadku ustawienie' Foo.number' sprawi, że 'Foo.number' będzie normalnym atrybutem klasy. Dodałem trochę kodu powyżej, aby pokazać, co mam na myśli. – unutbu

+0

@unutbu: I czy będę w stanie zmienić jego metaklass? Czy to działałoby dla obiektów kodu? – user2284570

12

zgadzam się z unubtu's answer; wydaje się działać, jednak nie działa z tą dokładną składnią na Python 3 (w szczególności, Python 3.4 jest tym, z czym walczyłem). Oto jak utworzyć wzór w Pythonie 3.4, aby to wszystko działa, jak się wydaje:

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

Problem z powyższych rozwiązań jest to, że nie będzie działać dostępu do zmiennych klasy z instancji zmiennej:

print(Foo.number) 
# 4 

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

Ponadto korzystając metaklasa wyraźny jest nie tak ładne, jak przy użyciu zwykłego dekoratora property.

Próbowałem rozwiązać ten problem. Oto jak to działa teraz:

@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 

Jak widać, mamy @classproperty który działa tak samo jak @property dla zmiennych klasowych. Jedyne, czego potrzebujemy, to dodatkowy dekorator klasy @classproperty_support.

Rozwiązanie działa również dla właściwości klasy tylko do odczytu.

Oto realizacja:

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 

Uwaga: kod nie jest testowany dużo, nie krępuj się, aby pamiętać, jeśli to nie działa zgodnie z oczekiwaniami.

Powiązane problemy