Używanie property () w metodach klas


173

Mam klasę z dwiema metodami klas (przy użyciu funkcji classmethod ()) do pobierania i ustawiania tego, co jest zasadniczo zmienną statyczną. Próbowałem użyć z nimi funkcji property (), ale powoduje to błąd. Udało mi się odtworzyć błąd w tłumaczu:

class Foo(object):
    _var = 5
    @classmethod
    def getvar(cls):
        return cls._var
    @classmethod
    def setvar(cls, value):
        cls._var = value
    var = property(getvar, setvar)

Mogę zademonstrować metody klas, ale nie działają one jako właściwości:

>>> f = Foo()
>>> f.getvar()
5
>>> f.setvar(4)
>>> f.getvar()
4
>>> f.var
Traceback (most recent call last):
  File "<stdin>", line 1, in ?
TypeError: 'classmethod' object is not callable
>>> f.var=5
Traceback (most recent call last):
  File "<stdin>", line 1, in ?
TypeError: 'classmethod' object is not callable

Czy jest możliwe użycie funkcji property () z funkcjami dekorowanymi metodą klas?

Odpowiedzi:


90

Właściwość jest tworzona w klasie, ale wpływa na instancję. Więc jeśli chcesz mieć właściwość classmethod, utwórz właściwość w metaklasie.

>>> class foo(object):
...     _var = 5
...     class __metaclass__(type):  # Python 2 syntax for metaclasses
...         pass
...     @classmethod
...     def getvar(cls):
...         return cls._var
...     @classmethod
...     def setvar(cls, value):
...         cls._var = value
...     
>>> foo.__metaclass__.var = property(foo.getvar.im_func, foo.setvar.im_func)
>>> foo.var
5
>>> foo.var = 3
>>> foo.var
3

Ale ponieważ i tak używasz metaklasy, będzie lepiej czytać, jeśli po prostu przeniesiesz tam metody klas.

>>> class foo(object):
...     _var = 5
...     class __metaclass__(type):  # Python 2 syntax for metaclasses
...         @property
...         def var(cls):
...             return cls._var
...         @var.setter
...         def var(cls, value):
...             cls._var = value
... 
>>> foo.var
5
>>> foo.var = 3
>>> foo.var
3

lub, używając metaclass=...składni Pythona 3 i metaklasy zdefiniowanej poza treścią fooklasy oraz metaklasy odpowiedzialnej za ustawienie początkowej wartości _var:

>>> class foo_meta(type):
...     def __init__(cls, *args, **kwargs):
...         cls._var = 5
...     @property
...     def var(cls):
...         return cls._var
...     @var.setter
...     def var(cls, value):
...         cls._var = value
...
>>> class foo(metaclass=foo_meta):
...     pass
...
>>> foo.var
5
>>> foo.var = 3
>>> foo.var
3

1
Wydaje się, że to nie działa dla mnie w Pythonie 3.2. Jeśli zmienię foo .__ metaclass __. Var = property (foo.getvar.im_func, foo.setvar.im_func) na foo .__ metaclass __. Var = property (foo.getvar .__ func__, foo.setvar .__ func__) I get "AttributeError: type obiekt „foo” nie ma atrybutu „var” ”podczas wykonywania„ foo.var ”.
Michael Kelley,

Podwójna korekta SIGH : działa w Pythonie 2.7, ale nie w Pythonie 3.2.
Michael Kelley

@MichaelKelley - To dlatego, że składnia metaklas zmieniła się w Pythonie 3.x
mac

1
Nie jestem pewien, czy rozumiem, w jaki sposób można by to napisać w Pythonie 3.x?
SylvainD

8
@Josay: Najpierw musisz zdefiniować metaklasę, a następnie zdefiniować klasę, używając nowej class Foo(metaclass=...)składni.
Kevin,

69

Czytając informacje o wydaniu Pythona 2.2 , znajduję następujące informacje.

Metoda get [właściwości] nie zostanie wywołana, gdy do właściwości uzyskuje się dostęp jako atrybut klasy (Cx), a nie jako atrybut instancji (C (). X). Jeśli chcesz przesłonić operację __get__ dla właściwości, gdy jest używana jako atrybut klasy, możesz podklasować właściwość - jest to sam typ nowego stylu - aby rozszerzyć jej metodę __get__ lub możesz zdefiniować typ deskryptora od podstaw, tworząc nowy -style, która definiuje metody __get__, __set__ i __delete__.

UWAGA: Poniższa metoda w rzeczywistości nie działa w przypadku seterów, tylko metody pobierające.

Dlatego uważam, że zalecanym rozwiązaniem jest utworzenie klasy ClassProperty jako podklasy właściwości.

class ClassProperty(property):
    def __get__(self, cls, owner):
        return self.fget.__get__(None, owner)()

class foo(object):
    _var=5
    def getvar(cls):
        return cls._var
    getvar=classmethod(getvar)
    def setvar(cls,value):
        cls._var=value
    setvar=classmethod(setvar)
    var=ClassProperty(getvar,setvar)

assert foo.getvar() == 5
foo.setvar(4)
assert foo.getvar() == 4
assert foo.var == 4
foo.var = 3
assert foo.var == 3

Jednak setery w rzeczywistości nie działają:

foo.var = 4
assert foo.var == foo._var # raises AssertionError

foo._var jest niezmieniona, po prostu nadpisałeś właściwość nową wartością.

Możesz również użyć ClassPropertyjako dekoratora:

class foo(object):
    _var = 5

    @ClassProperty
    @classmethod
    def var(cls):
        return cls._var

    @var.setter
    @classmethod
    def var(cls, value):
        cls._var = value

assert foo.var == 5

20
Nie sądzę, aby część ustawiająca właściwości ClassProperty faktycznie działała tak, jak opisano: podczas gdy wszystkie potwierdzenia w przykładzie przechodzą, na końcu foo._var == 4 (nie 3, jak sugerowano). Ustawienie właściwości powoduje jej ubijanie. Kiedy omawiano właściwości klas na python-dev , zwrócono uwagę, że chociaż metody pobierające są trywialne, to setery są trudne (niemożliwe?) Bez metaklasy
Gabriel Grant

4
@Gabriel Całkowicie poprawne. Nie mogę uwierzyć, że nikt nie zwracał na to uwagi przez dwa lata.
agf

Nie jestem też pewien, dlaczego nie używasz tu po prostu self.fget(owner)i nie usuwasz potrzeby używania go @classmethodw ogóle? (to co classmethod robi , tłumaczyć .__get__(instance, owner)(*args, **kwargs)do function(owner, *args, **kwargs)połączeń za pośrednictwem pośrednika; właściwości nie potrzebują pośrednika).
Martijn Pieters

W Twojej demonstracji brakuje jakiejkolwiek rzeczywistej transformacji zarówno w funkcji pobierającej, jak i ustawiającej, która zgrabnie wykazałaby, że twoje foo.var = 3przypisanie w rzeczywistości nie przechodzi przez właściwość , a zamiast tego po prostu zamieniło obiekt właściwości na fooliczbę całkowitą. Jeśli dodałeś assert isinstance(foo.__dict__['var'], ClassProperty)wywołania między swoimi asercjami, zobaczysz, że po foo.var = 3wykonaniu zakończy się niepowodzeniem .
Martijn Pieters

1
Klasy Python nie obsługują deskryptor wiążące ustawienie na samej klasy , tylko na uzyskanie (tak instance.attr, instance.attr = valuei del instance.attrbędzie wszystko wiążą deskryptor znaleziono type(instance), ale jednocześnie classobj.attrwiąże, classobj.attr = valuei del classobj.attrczy nie i zamiast wymienić lub usunąć sam obiekt deskryptora). Potrzebujesz metaklasy do obsługi ustawiania i usuwania (czyniąc obiekt klasy instancją, a metaklasę typem).
Martijn Pieters

56

Mam nadzieję, że ten nieskomplikowany @classpropertydekorator tylko do odczytu pomógłby komuś, kto szuka właściwości klas.

class classproperty(object):

    def __init__(self, fget):
        self.fget = fget

    def __get__(self, owner_self, owner_cls):
        return self.fget(owner_cls)

class C(object):

    @classproperty
    def x(cls):
        return 1

assert C.x == 1
assert C().x == 1

2
Czy to działa z podklasami? (czy podklasa może zastąpić classproperties?)
zakdances

1
Umm tak? class D(C): x = 2; assert D.x == 2
dtheodor

Chciałbym to działało kiedy używam go w .formatjak "{x}".format(**dict(self.__class__.__dict__, **self.__dict__)):(
2rs2ts

@Nathan Nie tylko ... kiedy to ustawisz, nadpisujesz cały xdostęp 10. Podoba mi się to podejście, ponieważ jest schludne i proste, ale brzmi jak anty
Michele d'Amico

Łatwo naprawione: dodaj, __set__co podnosi an, ValueErroraby zapobiec zastąpieniu.
Kiran Jonnalagadda

30

Czy jest możliwe użycie funkcji property () z funkcjami dekorowanymi metodą klas?

Nie.

Jednak metoda klasowa jest po prostu metodą związaną (funkcją częściową) klasy dostępnej z instancji tej klasy.

Ponieważ instancja jest funkcją klasy i możesz wyprowadzić klasę z instancji, możesz uzyskać dowolne pożądane zachowanie z właściwości klasy za pomocą property:

class Example(object):
    _class_property = None
    @property
    def class_property(self):
        return self._class_property
    @class_property.setter
    def class_property(self, value):
        type(self)._class_property = value
    @class_property.deleter
    def class_property(self):
        del type(self)._class_property

Ten kod może służyć do testowania - powinien przejść bez generowania błędów:

ex1 = Example()
ex2 = Example()
ex1.class_property = None
ex2.class_property = 'Example'
assert ex1.class_property is ex2.class_property
del ex2.class_property
assert not hasattr(ex1, 'class_property')

Zwróć uwagę, że w ogóle nie potrzebowaliśmy metaklas - i tak nie masz bezpośredniego dostępu do metaklasy za pośrednictwem instancji jej klas.

pisanie @classpropertydekoratora

Rzeczywiście można stworzyć classpropertydekorator w ciągu zaledwie kilku linii kodu przez instacji property(nie jest to realizowane w C, ale można zobaczyć równoważne Python tutaj ):

class classproperty(property):
    def __get__(self, obj, objtype=None):
        return super(classproperty, self).__get__(objtype)
    def __set__(self, obj, value):
        super(classproperty, self).__set__(type(obj), value)
    def __delete__(self, obj):
        super(classproperty, self).__delete__(type(obj))

Następnie potraktuj dekorator tak, jakby był metodą klasową połączoną z właściwością:

class Foo(object):
    _bar = 5
    @classproperty
    def bar(cls):
        """this is the bar attribute - each subclass of Foo gets its own.
        Lookups should follow the method resolution order.
        """
        return cls._bar
    @bar.setter
    def bar(cls, value):
        cls._bar = value
    @bar.deleter
    def bar(cls):
        del cls._bar

A ten kod powinien działać bez błędów:

def main():
    f = Foo()
    print(f.bar)
    f.bar = 4
    print(f.bar)
    del f.bar
    try:
        f.bar
    except AttributeError:
        pass
    else:
        raise RuntimeError('f.bar must have worked - inconceivable!')
    help(f)  # includes the Foo.bar help.
    f.bar = 5

    class Bar(Foo):
        "a subclass of Foo, nothing more"
    help(Bar) # includes the Foo.bar help!
    b = Bar()
    b.bar = 'baz'
    print(b.bar) # prints baz
    del b.bar
    print(b.bar) # prints 5 - looked up from Foo!

    
if __name__ == '__main__':
    main()

Ale nie jestem pewien, jak dobrze byłoby to zalecane. Stary artykuł z listy mailingowej sugeruje, że to nie powinno działać.

Doprowadzenie nieruchomości do pracy na zajęciach:

Wadą powyższego jest to, że „właściwość klasy” nie jest dostępna z poziomu klasy, ponieważ po prostu nadpisałaby deskryptor danych z klasy __dict__.

Możemy jednak nadpisać to właściwością zdefiniowaną w metaklasie __dict__. Na przykład:

class MetaWithFooClassProperty(type):
    @property
    def foo(cls):
        """The foo property is a function of the class -
        in this case, the trivial case of the identity function.
        """
        return cls

Następnie instancja klasy metaklasy może mieć właściwość, która uzyskuje dostęp do właściwości klasy przy użyciu zasady przedstawionej już w poprzednich sekcjach:

class FooClassProperty(metaclass=MetaWithFooClassProperty):
    @property
    def foo(self):
        """access the class's property"""
        return type(self).foo

Teraz widzimy obie instancję

>>> FooClassProperty().foo
<class '__main__.FooClassProperty'>

i klasą

>>> FooClassProperty.foo
<class '__main__.FooClassProperty'>

mieć dostęp do właściwości klasy.


3
Jasna, zwięzła, dokładna: taka powinna być akceptowana odpowiedź.
pfabri

25

Python 3!

Stare pytanie, wiele widoków, bardzo potrzebuję jednego, prawdziwego Pythona 3 sposobu.

Na szczęście z metaclasskwargiem jest to łatwe :

class FooProperties(type):

    @property
    def var(cls):
        return cls._var

class Foo(object, metaclass=FooProperties):
    _var = 'FOO!'

Następnie, >>> Foo.var

'BLA!'


1
to znaczy, że nie ma prostego wyjścia z pudełka
mehmet

@mehmet Czy to nie jest proste? Foojest instancją swojej metaklasy i @propertymoże być używany w jej metodach tak samo, jak w przypadku instancji Foo.
OJFord,

2
trzeba było zdefiniować inną klasę dla klasy, co oznacza podwójną złożoność przy założeniu, że metaklasa nie nadaje się do ponownego użycia.
mehmet

Metoda klasowa działa zarówno z klasy, jak i instancji. Ta właściwość działa tylko z klasy. Nie sądzę, żeby o to się prosiło.
Aaron Hall

1
@AaronHall Jeśli to ważne, łatwo je dodać Foo.__new__. Chociaż w tym momencie warto albo zamiast tego użyć getattribute, albo zapytać, czy udawanie, że istnieje funkcja języka, jest naprawdę podejściem, które w ogóle chcesz przyjąć.
OJFord

16

Nie ma rozsądnego sposobu, aby ten system „właściwości klasy” działał w Pythonie.

Oto jeden nierozsądny sposób, aby to zadziałało. Z pewnością możesz uczynić to bardziej płynnym, zwiększając ilość magii metaklas.

class ClassProperty(object):
    def __init__(self, getter, setter):
        self.getter = getter
        self.setter = setter
    def __get__(self, cls, owner):
        return getattr(cls, self.getter)()
    def __set__(self, cls, value):
        getattr(cls, self.setter)(value)

class MetaFoo(type):
    var = ClassProperty('getvar', 'setvar')

class Foo(object):
    __metaclass__ = MetaFoo
    _var = 5
    @classmethod
    def getvar(cls):
        print "Getting var =", cls._var
        return cls._var
    @classmethod
    def setvar(cls, value):
        print "Setting var =", value
        cls._var = value

x = Foo.var
print "Foo.var = ", x
Foo.var = 42
x = Foo.var
print "Foo.var = ", x

Problem polega na tym, że Python nazywa właściwościami „deskryptorami”. Nie ma krótkiego i łatwego sposobu na wyjaśnienie, jak działa tego rodzaju metaprogramowanie, więc muszę wskazać wam deskryptor Howto .

Musisz zrozumieć tego rodzaju rzeczy tylko wtedy, gdy implementujesz dość zaawansowany framework. Na przykład utrwalanie obiektów przezroczystych lub system RPC lub rodzaj języka specyficznego dla domeny.

Jednak w komentarzu do poprzedniej odpowiedzi mówisz, że ty

trzeba zmodyfikować atrybut, który w sposób widoczny dla wszystkich instancji klasy oraz w zakresie, z którego wywoływane są te metody klasy, nie zawiera odniesień do wszystkich instancji klasy.

Wydaje mi się, że to, czego naprawdę chcesz, to wzorzec projektowy Observer .


Podoba mi się pomysł z przykładowym kodem, ale wydaje się, że w praktyce byłby trochę niezgrabny.
Mark Roddy,

To, co próbuję osiągnąć, to dość proste ustawienie i uzyskanie pojedynczego atrybutu, który jest używany jako flaga do modyfikowania zachowania wszystkich instancji, więc myślę, że Observer byłby przesadzony z powodu tego, co próbuję zrobić. Gdyby chodziło o wiele atrybutów, byłbym bardziej skłonny.
Mark Roddy,

Wydaje się, że zwykłe upublicznienie funkcji i bezpośrednie wywołanie ich było prostym rozwiązaniem. Byłem ciekawy, czy robię coś złego, czy też próbuję zrobić to niemożliwe. Przy okazji przepraszam za wiele komentarzy. Limit 300 znaków jest do niczego.
Mark Roddy

Fajną rzeczą w tym przykładzie kodu jest to, że możesz zaimplementować wszystkie niezgrabne bity raz, a następnie odziedziczyć je. Po prostu przenieś _var do klasy pochodnej. class D1 (Foo): _var = 21 class D2 (Foo) _var = "Hello" D1.var 21 D2.var Witam
Thomas L Holaday

6

Ustawienie go tylko w klasie meta nie pomaga, jeśli chcesz uzyskać dostęp do właściwości klasy za pośrednictwem obiektu, dla którego utworzono instancję, w tym przypadku musisz również zainstalować normalną właściwość na obiekcie (która jest wysyłana do właściwości klasy). Myślę, że to jest nieco bardziej jasne:

#!/usr/bin/python

class classproperty(property):
    def __get__(self, obj, type_):
        return self.fget.__get__(None, type_)()

    def __set__(self, obj, value):
        cls = type(obj)
        return self.fset.__get__(None, cls)(value)

class A (object):

    _foo = 1

    @classproperty
    @classmethod
    def foo(cls):
        return cls._foo

    @foo.setter
    @classmethod
    def foo(cls, value):
        cls.foo = value

a = A()

print a.foo

b = A()

print b.foo

b.foo = 5

print a.foo

A.foo = 10

print b.foo

print A.foo

3

Połowa rozwiązania, __set__ w klasie nadal nie działa. Rozwiązaniem jest niestandardowa klasa właściwości implementująca zarówno właściwość, jak i metodę statyczną

class ClassProperty(object):
    def __init__(self, fget, fset):
        self.fget = fget
        self.fset = fset

    def __get__(self, instance, owner):
        return self.fget()

    def __set__(self, instance, value):
        self.fset(value)

class Foo(object):
    _bar = 1
    def get_bar():
        print 'getting'
        return Foo._bar

    def set_bar(value):
        print 'setting'
        Foo._bar = value

    bar = ClassProperty(get_bar, set_bar)

f = Foo()
#__get__ works
f.bar
Foo.bar

f.bar = 2
Foo.bar = 3 #__set__ does not

3

Ponieważ muszę zmodyfikować atrybut, który w taki sposób, który jest widoczny dla wszystkich wystąpień klasy, oraz w zakresie, z którego te metody są wywoływane, nie ma odwołań do wszystkich wystąpień klasy.

Czy masz dostęp do co najmniej jednej instancji tej klasy? Mogę wtedy wymyślić sposób, aby to zrobić:

class MyClass (object):
    __var = None

    def _set_var (self, value):
        type (self).__var = value

    def _get_var (self):
        return self.__var

    var = property (_get_var, _set_var)

a = MyClass ()
b = MyClass ()
a.var = "foo"
print b.var

2

Spróbuj, a zadanie zostanie wykonane bez konieczności zmiany / dodawania dużej ilości istniejącego kodu.

>>> class foo(object):
...     _var = 5
...     def getvar(cls):
...         return cls._var
...     getvar = classmethod(getvar)
...     def setvar(cls, value):
...         cls._var = value
...     setvar = classmethod(setvar)
...     var = property(lambda self: self.getvar(), lambda self, val: self.setvar(val))
...
>>> f = foo()
>>> f.var
5
>>> f.var = 3
>>> f.var
3

propertyFunkcja wymaga dwóch callableargumentów. daj im opakowania lambda (które przekazuje instancję jako pierwszy argument) i wszystko jest w porządku.


Jak wskazuje Florian Bösch, składnia wymagana (przez biblioteki innych firm lub starszy kod) to foo.var.
Thomas L Holaday

2

Oto rozwiązanie, które powinno działać zarówno w przypadku dostępu za pośrednictwem klasy, jak i dostępu za pośrednictwem instancji używającej metaklasy.

In [1]: class ClassPropertyMeta(type):
   ...:     @property
   ...:     def prop(cls):
   ...:         return cls._prop
   ...:     def __new__(cls, name, parents, dct):
   ...:         # This makes overriding __getattr__ and __setattr__ in the class impossible, but should be fixable
   ...:         dct['__getattr__'] = classmethod(lambda cls, attr: getattr(cls, attr))
   ...:         dct['__setattr__'] = classmethod(lambda cls, attr, val: setattr(cls, attr, val))
   ...:         return super(ClassPropertyMeta, cls).__new__(cls, name, parents, dct)
   ...:

In [2]: class ClassProperty(object):
   ...:     __metaclass__ = ClassPropertyMeta
   ...:     _prop = 42
   ...:     def __getattr__(self, attr):
   ...:         raise Exception('Never gets called')
   ...:

In [3]: ClassProperty.prop
Out[3]: 42

In [4]: ClassProperty.prop = 1
---------------------------------------------------------------------------
AttributeError                            Traceback (most recent call last)
<ipython-input-4-e2e8b423818a> in <module>()
----> 1 ClassProperty.prop = 1

AttributeError: can't set attribute

In [5]: cp = ClassProperty()

In [6]: cp.prop
Out[6]: 42

In [7]: cp.prop = 1
---------------------------------------------------------------------------
AttributeError                            Traceback (most recent call last)
<ipython-input-7-e8284a3ee950> in <module>()
----> 1 cp.prop = 1

<ipython-input-1-16b7c320d521> in <lambda>(cls, attr, val)
      6         # This makes overriding __getattr__ and __setattr__ in the class impossible, but should be fixable
      7         dct['__getattr__'] = classmethod(lambda cls, attr: getattr(cls, attr))
----> 8         dct['__setattr__'] = classmethod(lambda cls, attr, val: setattr(cls, attr, val))
      9         return super(ClassPropertyMeta, cls).__new__(cls, name, parents, dct)

AttributeError: can't set attribute

Działa to również z seterem zdefiniowanym w metaklasie.


1

Po przeszukaniu różnych miejsc znalazłem metodę definiowania właściwości klasy poprawnej w Pythonie 2 i 3.

from future.utils import with_metaclass

class BuilderMetaClass(type):
    @property
    def load_namespaces(self):
        return (self.__sourcepath__)

class BuilderMixin(with_metaclass(BuilderMetaClass, object)):
    __sourcepath__ = 'sp'        

print(BuilderMixin.load_namespaces)

Mam nadzieję, że to może komuś pomóc :)


1
Jeśli ta metoda jest czymś, co gdzieś znalazłeś, dobrze byłoby podać link (patrz Jak odwoływać się do materiałów napisanych przez innych )
Andrew Myers

-27

Oto moja sugestia. Nie używaj metod klasowych.

Poważnie.

Jaki jest powód używania w tym przypadku metod klasowych? Dlaczego nie mieć zwykłego przedmiotu zwykłej klasy?


Jeśli chcesz po prostu zmienić wartość, właściwość nie jest zbyt pomocna, prawda? Po prostu ustaw wartość atrybutu i skończ z tym.

Właściwość powinna być używana tylko wtedy, gdy jest coś do ukrycia - coś, co może się zmienić w przyszłej implementacji.

Może twój przykład jest znacznie okrojony i jest jakieś piekielne obliczenia, które przerwałeś. Ale wygląda na to, że nieruchomość nie wnosi znaczącej wartości.

Techniki "prywatności" oparte na Javie (w Pythonie nazwy atrybutów zaczynające się od _) nie są zbyt pomocne. Od kogo prywatne? Kwestia prywatności jest trochę mglista, gdy masz źródło (tak jak w Pythonie).

Metody pobierające i ustawiające w stylu EJB oparte na Javie (często wykonywane jako właściwości w Pythonie) służą do ułatwienia prymitywnej introspekcji w Javie, a także do przekazywania testów za pomocą kompilatora języka statycznego. Wszystkie te metody pobierające i ustawiające nie są tak pomocne w Pythonie.


14
Ponieważ muszę zmodyfikować atrybut, który w taki sposób, który jest widoczny dla wszystkich wystąpień klasy, oraz w zakresie, z którego te metody są wywoływane, nie ma odwołań do wszystkich wystąpień klasy.
Mark Roddy,
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.