Załóżmy, że chcemy stworzyć rodzinę klas, które są różnymi implementacjami lub specjalizacjami nadrzędnej koncepcji. Załóżmy, że istnieje pewna domyślna implementacja niektórych właściwości pochodnych. Chcielibyśmy umieścić to w klasie podstawowej
class Math_Set_Base:
@property
def size(self):
return len(self.elements)
Tak więc podklasa automatycznie będzie mogła policzyć swoje elementy w tym dość głupim przykładzie
class Concrete_Math_Set(Math_Set_Base):
def __init__(self,*elements):
self.elements = elements
Concrete_Math_Set(1,2,3).size
# 3
Ale co jeśli podklasa nie chce używać tej wartości domyślnej? To nie działa:
import math
class Square_Integers_Below(Math_Set_Base):
def __init__(self,cap):
self.size = int(math.sqrt(cap))
Square_Integers_Below(7)
# Traceback (most recent call last):
# File "<stdin>", line 1, in <module>
# File "<stdin>", line 3, in __init__
# AttributeError: can't set attribute
Zdaję sobie sprawę, że istnieją sposoby na zastąpienie właściwości za pomocą właściwości, ale chciałbym tego uniknąć. Ponieważ celem klasy podstawowej jest ułatwienie życia użytkownikowi, aby nie dodawać wzdęć poprzez narzucenie (z wąskiego punktu widzenia podklasy) zwiniętej i zbędnej metody dostępu.
Czy da się to zrobić? Jeśli nie, jakie jest następne najlepsze rozwiązanie?
źródło
__get__()
,__set__()
i__delete__()
) i wywołują one,AttributeError
jeśli nie zapewnisz dla nich żadnej funkcji. Zobacz odpowiednik implementacji właściwości w języku Python .setter
lub__set__
od nieruchomości, będzie to działać. Ostrzegam jednak, że oznacza to również, że możesz łatwo zastąpić właściwość nie tylko na poziomie klasy (self.size = ...
), ale nawet na poziomie instancji (np.Concrete_Math_Set(1, 2, 3).size = 10
Byłoby równie ważne). Jedzenie dla myśli :)Square_Integers_Below(9).size = 1
jest również ważna, ponieważ jest to atrybut prosty. W tym szczególnym przypadku użycia „rozmiar” może to wydawać się trafne (zamiast tego należy użyć właściwości), ale w ogólnym przypadku „łatwo zastąpić obliczoną rekwizyt w podklasie”, w niektórych przypadkach może być dobre. Możesz także kontrolować dostęp do atrybutów za pomocą,__setattr__()
ale może to być przytłaczające.To będzie długa, wyczerpująca odpowiedź, która może być tylko komplementarna ... ale twoje pytanie zabrało mnie na przejażdżkę w dół króliczej nory, więc chciałbym również podzielić się swoimi odkryciami (i bólem).
Ostatecznie może się okazać, że ta odpowiedź nie jest pomocna w rzeczywistym problemie. W rzeczywistości doszedłem do wniosku, że wcale tego nie zrobiłbym. To powiedziawszy, tło do tego wniosku może cię trochę zabawić, ponieważ szukasz więcej szczegółów.
Rozwiązanie niektórych nieporozumień
Pierwsza odpowiedź, choć poprawna w większości przypadków, nie zawsze tak jest. Rozważmy na przykład tę klasę:
inst_prop
, będąc jednocześnieproperty
, jest nieodwołalnie atrybutem instancji:Wszystko zależy od tego, gdzie Twoje
property
jest zdefiniowane. Jeśli twoje@property
jest zdefiniowane w ramach klasy „scope” (lub tak naprawdę tonamespace
), staje się atrybutem klasy. W moim przykładzie sama klasa nie zdaje sobie z tego sprawy,inst_prop
dopóki nie zostanie utworzona . Oczywiście tutaj wcale nie jest bardzo przydatna.Ale najpierw zajmijmy się twoim komentarzem na temat rozwiązania dziedziczenia ...
Więc w jaki sposób dziedziczenie uwzględnia ten problem? W tym artykule omówiono nieco temat i kolejność rozwiązywania metod jest w pewnym stopniu powiązana, chociaż omawia ona głównie szerokość dziedziczenia zamiast głębokości.
W połączeniu z naszym ustaleniem, biorąc pod uwagę poniższe ustawienia:
Wyobraź sobie, co się dzieje, gdy te wiersze są wykonywane:
Wynik jest zatem następujący:
Zauważ jak:
self.world_view
był w stanie być zastosowany, podczas gdyself.culture
nie udało sięculture
nie istnieje wChild.__dict__
(mappingproxy
klasie, nie należy mylić z instancją__dict__
)culture
istniejec.__dict__
, nie jest przywoływany.Być może potrafisz zgadnąć, dlaczego -
world_view
został nadpisany przezParent
klasę jako nie-własność, więc równieżChild
był w stanie go zastąpić. Tymczasem, ponieważculture
jest dziedziczona, to istnieje tylko w obrębiemappingproxy
odGrandparent
:W rzeczywistości, jeśli spróbujesz usunąć
Parent.culture
:Zauważysz, że nawet nie istnieje
Parent
. Ponieważ obiekt bezpośrednio odwołuje się doGrandparent.culture
.A co z nakazem rozstrzygnięcia?
Dlatego jesteśmy zainteresowani obserwowaniem rzeczywistego zlecenia rozdzielczości, spróbujmy usunąć
Parent.world_view
:Zastanawiam się, jaki jest wynik?
Wrócił do Dziadka
world_view
property
, mimo że udało nam się wcześniej przypisaćself.world_view
! Ale co, jeśli zmienimy siłąworld_view
na poziomie klasy, podobnie jak inna odpowiedź? Co jeśli go usuniemy? Co jeśli przypisamy bieżący atrybut klasy jako właściwość?Wynik to:
Jest to interesujące, ponieważ
c.world_view
jest przywracane do atrybutu wystąpienia, podczas gdyChild.world_view
jest to ten, który przypisaliśmy. Po usunięciu atrybutu instancji następuje powrót do atrybutu klasy. Po ponownym przypisaniuChild.world_view
do właściwości natychmiast tracimy dostęp do atrybutu instancji.Dlatego możemy założyć następującą kolejność rozwiązywania :
property
, pobierz jego wartość za pomocągetter
lubfget
(więcej na ten temat później). Bieżąca klasa od pierwszej do podstawowej jako ostatnia.property
atrybut nieklasowy. Bieżąca klasa pierwsza do klasy podstawowej ostatnia.W takim przypadku usuńmy root
property
:Co daje:
Ta dah!
Child
teraz ma swoją własnąculture
opartą na silnym wstawianiu doc.__dict__
.Child.culture
nie istnieje oczywiście, ponieważ nigdy nie został zdefiniowany w atrybucieParent
lubChild
class, iGrandparent
został usunięty.Czy to jest podstawowa przyczyna mojego problemu?
Właściwie nie . Występujący błąd, który wciąż obserwujemy podczas przypisywania
self.culture
, jest zupełnie inny . Ale kolejność dziedziczenia stanowi tło dla odpowiedzi - która jestproperty
samą sobą.Oprócz poprzednio wspomnianej
getter
metody,property
masz również kilka zgrabnych sztuczek w rękawach. W tym przypadku najbardziej istotna jest metodasetter
lubfset
, która jest uruchamiana przezself.culture = ...
linię. Ponieważproperty
nie zaimplementowałeś żadnejsetter
anifget
funkcji, python nie wie, co robić, iAttributeError
zamiast tego rzuca (tjcan't set attribute
.).Jeśli jednak zastosowałeś
setter
metodę:Podczas tworzenia
Child
klasy otrzymasz:Zamiast odbierać
AttributeError
, aktualnie wywołujeszsome_prop.setter
metodę. Co daje ci większą kontrolę nad twoim obiektem ... dzięki naszym wcześniejszym ustaleniom wiemy, że musimy nadpisać atrybut klasy, zanim dotrze on do właściwości. Można to zaimplementować w klasie bazowej jako wyzwalacz. Oto nowy przykład:Co skutkuje w:
TA DAH! Możesz teraz zastąpić własny atrybut instancji dziedziczoną właściwością!
Więc problem rozwiązany?
... Nie całkiem. Problem z tym podejściem polega na tym, że nie możesz mieć właściwej
setter
metody. Są przypadki, w których chcesz ustawić swoje wartościproperty
. Ale teraz, gdy ustawiszself.culture = ...
to będzie zawsze zastąpić cokolwiek działać zdefiniowano wgetter
(który w tym przypadku naprawdę jest po prostu@property
owinięta część. Ty możesz dodać w środkach bardziej zróżnicowany, ale tak czy inaczej będzie to zawsze obejmować więcej niż tylkoself.culture = ...
. na przykład:To waaaaay bardziej skomplikowane niż w innych odpowiedzi,
size = None
na poziomie klasy.Możesz również rozważyć napisanie własnego deskryptora zamiast obsługi metod
__get__
i__set__
lub dodatkowych metod. Ale pod koniec dnia, gdyself.culture
jest przywoływane,__get__
zawsze będzie uruchamiane jako pierwsze, a kiedyself.culture = ...
jest przywoływane,__set__
zawsze będzie uruchamiane jako pierwsze. O ile próbowałem, nie można tego obejść.Sedno problemu, IMO
Problem, który tu widzę, polega na tym, że nie możesz mieć ciasta i też jesz.
property
oznacza deskryptor z wygodnym dostępem z metod takich jakgetattr
lubsetattr
. Jeśli chcesz, aby te metody osiągnęły inny cel, po prostu prosisz o kłopoty. Być może przemyślę to podejście:property
do tego?property
, czy jest jakiś powód, dla którego musiałbym go zastąpić?property
nie mają one zastosowania?property
s, czy osobna metoda byłaby dla mnie lepsza niż zwykłe ponowne przypisanie, ponieważ ponowne przypisanie może przypadkowo unieważnić teproperty
?W przypadku punktu 5 moim podejściem byłaby
overwrite_prop()
metoda w klasie bazowej, która nadpisze aktualny atrybut klasy, abyproperty
nie był już wyzwalany:Jak widać, choć wciąż nieco wymyślony, jest co najmniej bardziej jawny niż tajemniczy
size = None
. To powiedziawszy, ostatecznie nie zastąpiłbym w ogóle właściwości i ponownie rozważyłbym mój projekt od podstaw.Jeśli dotarłeś tak daleko - dziękuję za podróż ze mną. To było zabawne, małe ćwiczenie.
źródło
A
@property
jest zdefiniowane na poziomie klasy. Dokumentacja zawiera szczegółowe informacje na temat tego, jak to działa, ale wystarczy powiedzieć, że ustawienie lub zmusza właściwość do wywołania określonej metody. Jednakżeproperty
obiekt zarządzający tym procesem jest definiowany za pomocą własnej definicji klasy. Oznacza to, że jest zdefiniowany jako zmienna klasy, ale zachowuje się jak zmienna instancji.Jedną z konsekwencji tego jest to, że możesz swobodnie zmienić przypisanie na poziomie klasy :
I tak jak każda inna nazwa na poziomie klasy (np. Metody), możesz zastąpić ją w podklasie, po prostu jawnie definiując ją inaczej:
Kiedy tworzymy rzeczywistą instancję, zmienna instancji po prostu zacienia zmienną klasy o tej samej nazwie.
property
Przedmiot zazwyczaj używa niektórych shenanigans manipulować ten proces (tj stosujące pobierające i ustawiające metody), ale kiedy nazwa klasy poziomie nie jest zdefiniowana jako właściwość, nic szczególnego się nie dzieje, a więc działa jak można oczekiwać od każdej innej zmiennej.źródło
__dict__
? Czy istnieją też sposoby na zautomatyzowanie tego? Tak, to tylko jedna linijka, ale jest to coś bardzo tajemniczego, jeśli nie jesteś zaznajomiony z krwawymi szczegółami własności itp.# declare size to not be a @property
W ogóle nie potrzebujesz przypisania
size
.size
jest właściwością w klasie podstawowej, więc możesz zastąpić tę właściwość w klasie potomnej:Możesz (mikro) zoptymalizować to, obliczając pierwiastek kwadratowy:
źródło
size
jest to właściwość, a nie atrybut instancji, nie musisz robić nic wymyślnego.Wygląda na to, że chcesz zdefiniować
size
w swojej klasie:Inną opcją jest przechowywanie
cap
w klasie i obliczanie jej za pomocąsize
zdefiniowanej właściwości (która zastępuje właściwość klasy bazowejsize
).źródło
Sugeruję dodanie takiego setera:
W ten sposób możesz zastąpić domyślną
.size
właściwość w następujący sposób:źródło
Możesz także zrobić następną rzecz
źródło
_size
ani_size_call
tam. Mógłbyś wypalić wywołanie funkcjisize
jako warunek i użyćtry... except...
do przetestowania_size
zamiast brać dodatkowe odwołanie do klasy, które nie zostanie użyte. Niezależnie od tego uważam, że jest to jeszcze bardziej tajemnicze niż zwykłesize = None
nadpisywanie.