Supposons que nous voulons créer une famille de classes qui sont différentes implémentations ou spécialisations d'un concept global. Supposons qu'il existe une implémentation par défaut plausible pour certaines propriétés dérivées. Nous voudrions mettre cela dans une classe de base
class Math_Set_Base:
@property
def size(self):
return len(self.elements)
Ainsi, une sous-classe pourra automatiquement compter ses éléments dans cet exemple plutôt stupide
class Concrete_Math_Set(Math_Set_Base):
def __init__(self,*elements):
self.elements = elements
Concrete_Math_Set(1,2,3).size
# 3
Mais que se passe-t-il si une sous-classe ne veut pas utiliser cette valeur par défaut? Cela ne fonctionne pas:
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
Je me rends compte qu'il existe des moyens de remplacer une propriété par une propriété, mais j'aimerais éviter cela. Parce que le but de la classe de base est de rendre la vie aussi simple que possible à son utilisateur, de ne pas ajouter de ballonnement en imposant une (du point de vue étroit de la sous-classe) une méthode d'accès compliquée et superflue.
Peut-on le faire? Sinon, quelle est la meilleure solution suivante?
la source
len(self.elements)
en tant qu'implémentation. Cette implémentation très spécifique impose un contrat à toutes les instances de la classe, à savoir qu'elles fournissentelements
ce qui est un conteneur de taille . Cependant, votreSquare_Integers_Below
classe ne semble pas se comporter de cette façon (peut-être qu'elle génère ses membres de manière dynamique), elle doit donc définir son propre comportement.len(self.elements)
n'est pas une implémentation par défaut appropriée pour les ensembles mathématiques car il existe "de nombreux" ensembles qui n'ont même pas de cardinalité finie. En général, si une sous-classe ne veut pas utiliser le comportement de ses classes de base, elle doit le remplacer au niveau de la classe. Les attributs d'instance, comme son nom l'indique, fonctionnent au niveau de l'instance et sont donc régis par le comportement de classe.Réponses:
Une propriété est un descripteur de données qui a priorité sur un attribut d'instance du même nom. Vous pouvez définir un descripteur non-données avec une
__get__()
méthode unique : un attribut d'instance a priorité sur le descripteur non-données du même nom, voir la documentation . Le problème ici est que lanon_data_property
définition ci-dessous est uniquement à des fins de calcul (vous ne pouvez pas définir un setter ou un deleter) mais cela semble être le cas dans votre exemple.Cependant, cela suppose que vous avez accès à la classe de base pour effectuer ces modifications.
la source
__get__()
,__set__()
et__delete__()
) et elles soulèvent unAttributeError
si vous ne leur fournissez aucune fonction. Voir l' équivalent Python de l'implémentation d'une propriété .setter
ou__set__
de la propriété, cela fonctionnera. Cependant, je vous préviens que cela signifie également que vous pouvez facilement remplacer la propriété non seulement au niveau de la classe (self.size = ...
) mais même au niveau de l'instance (par exemple,Concrete_Math_Set(1, 2, 3).size = 10
serait également valide).Square_Integers_Below(9).size = 1
est également valide car c'est un attribut simple. Dans ce cas d'utilisation particulier de "taille", cela peut sembler gênant (utiliser une propriété à la place), mais dans le cas général "remplacer un accessoire calculé facilement dans la sous-classe", cela pourrait être bon dans certains cas. Vous pouvez également contrôler l'accès aux attributs avec__setattr__()
mais cela peut être écrasant.Ce sera une réponse de longue haleine qui ne servira peut-être qu'à être complémentaire ... mais votre question m'a amené à faire un tour dans le terrier du lapin, donc j'aimerais partager mes conclusions (et ma douleur) également.
Vous pourriez finalement trouver cette réponse non utile à votre problème réel. En fait, ma conclusion est que - je ne ferais pas ça du tout. Cela dit, le contexte de cette conclusion pourrait vous divertir un peu, car vous recherchez plus de détails.
Remédier à certaines idées fausses
La première réponse, bien que correcte dans la plupart des cas, n'est pas toujours le cas. Par exemple, considérez cette classe:
inst_prop
, tout en étant unproperty
, est irrévocablement un attribut d'instance:Tout dépend où votre
property
est défini en premier lieu. Si votre@property
est défini dans la classe "scope" (ou vraiment, lanamespace
), il devient un attribut de classe. Dans mon exemple, la classe elle-même n'en connaît aucuneinst_prop
jusqu'à ce qu'elle soit instanciée. Bien sûr, ce n'est pas du tout utile en tant que propriété ici.Mais d'abord, abordons votre commentaire sur la résolution d'héritage ...
Alors, comment l'héritage entre-t-il exactement dans ce problème? Cet article suivant plonge un peu dans le sujet, et l' ordre de résolution des méthodes est quelque peu lié, bien qu'il traite principalement de l'étendue de l'héritage au lieu de la profondeur.
Combiné avec notre constat, compte tenu de la configuration ci-dessous:
Imaginez ce qui se passe lorsque ces lignes sont exécutées:
Le résultat est donc:
Remarquez comment:
self.world_view
a pu être appliqué, mais aself.culture
échouéculture
n'existe pas dansChild.__dict__
(lemappingproxy
de la classe, à ne pas confondre avec l'instance__dict__
)culture
existe enc.__dict__
, il n'est pas référencé.Vous pourriez peut-être deviner pourquoi - a
world_view
été écrasé par laParent
classe en tant que non-propriété, vous avez doncChild
pu le remplacer également. Pendant ce temps,culture
étant hérité, il n'existe qu'au seinmappingproxy
deGrandparent
:En fait, si vous essayez de supprimer
Parent.culture
:Vous remarquerez qu'il n'existe même pas pour
Parent
. Parce que l'objet fait directement référence àGrandparent.culture
.Alors, qu'en est-il de l'ordre de résolution?
Nous sommes donc intéressés à observer l'ordre de résolution réel, essayons
Parent.world_view
plutôt de le supprimer :Vous vous demandez quel est le résultat?
Il est revenu à celui de Grands-parents
world_view
property
, même si nous avions réussi à assigner l'self.world_view
avant! Mais que se passe-t-il si nous changeons avec forceworld_view
au niveau de la classe, comme l'autre réponse? Et si nous le supprimons? Et si nous attribuons l'attribut de classe actuel à une propriété?Le résultat est:
Ceci est intéressant car
c.world_view
est restauré à son attribut d'instance, tandis queChild.world_view
c'est celui que nous avons attribué. Après avoir supprimé l'attribut d'instance, il revient à l'attribut de classe. Et après avoir réaffecté leChild.world_view
à la propriété, nous perdons instantanément l'accès à l'attribut d'instance.Par conséquent, nous pouvons supposer l'ordre de résolution suivant :
property
, récupérez sa valeur viagetter
oufget
(plus de détails plus loin). La classe actuelle en premier à la classe de base en dernier.property
attribut non- classe. La classe actuelle en premier à la classe de base en dernier.Dans ce cas, supprimons la racine
property
:Qui donne:
Et voilà!
Child
a maintenant leur propreculture
basé sur l'insertion énergique dansc.__dict__
.Child.culture
n'existe pas, bien sûr, car il n'a jamais été défini dansParent
ou l'Child
attribut class, et celuiGrandparent
-ci a été supprimé.Est-ce la cause première de mon problème?
En fait, non . L'erreur que vous obtenez, que nous observons toujours lors de l'attribution
self.culture
, est totalement différente . Mais l'ordre d'héritage définit la toile de fond de la réponse - qui est leproperty
lui - même.Outre la
getter
méthode mentionnée précédemment ,property
ayez également quelques astuces soignées dans ses manches. La plus pertinente dans ce cas est la méthode,setter
oufset
, qui est déclenchée par laself.culture = ...
ligne. Étant donné que vousproperty
n'avez implémenté aucune fonctionsetter
ni aucunefget
fonction, python ne sait pas quoi faire et lanceAttributeError
plutôt une (c'est-à-direcan't set attribute
).Si toutefois vous avez implémenté une
setter
méthode:Lors de l'instanciation du
Child
cours, vous obtiendrez:Au lieu de recevoir un
AttributeError
, vous appelez maintenant lasome_prop.setter
méthode. Ce qui vous donne plus de contrôle sur votre objet ... avec nos résultats précédents, nous savons que nous devons avoir un attribut de classe écrasé avant qu'il n'atteigne la propriété. Cela pourrait être implémenté dans la classe de base comme déclencheur. Voici un nouvel exemple:Ce qui se traduit par:
ET VOILÀ! Vous pouvez maintenant remplacer votre propre attribut d'instance sur une propriété héritée!
Alors, problème résolu?
... Pas vraiment. Le problème avec cette approche est que vous ne pouvez plus avoir de
setter
méthode appropriée . Dans certains cas, vous souhaitez définir des valeurs sur votreproperty
. Mais maintenant, chaque fois que vous le définissez,self.culture = ...
il remplacera toujours la fonction que vous avez définie dans legetter
(qui, dans ce cas, n'est vraiment que la@property
partie encapsulée. Vous pouvez ajouter des mesures plus nuancées, mais d'une manière ou d'une autre, cela impliquera toujours plus que justeself.culture = ...
. par exemple:C'est waaaaay plus compliqué que l'autre réponse,
size = None
au niveau de la classe.Vous pouvez également envisager d'écrire votre propre descripteur à la place pour gérer les méthodes
__get__
and__set__
ou supplémentaires. Mais à la fin de la journée, quandself.culture
est référencé, le__get__
sera toujours déclenché en premier, et quandself.culture = ...
est référencé,__set__
sera toujours déclenché en premier. Il n'y a pas moyen de le contourner autant que j'ai essayé.Le nœud du problème, l'OMI
Le problème que je vois ici est - vous ne pouvez pas avoir votre gâteau et le manger aussi.
property
est conçu comme un descripteur avec un accès pratique à partir de méthodes commegetattr
ousetattr
. Si vous souhaitez également que ces méthodes atteignent un objectif différent, vous demandez simplement des ennuis. Je repenserais peut-être l'approche:property
pour ça?property
, y a-t-il une raison pour laquelle je devrais l'écraser?property
- ci ne s'appliquent pas?property
s, une méthode distincte me servirait-elle mieux qu'une simple réaffectation, car la réaffectation peut accidentellement annuler leproperty
s?Pour le point 5, mon approche serait d'avoir une
overwrite_prop()
méthode dans la classe de base qui écraserait l'attribut de classe actuel afin que laproperty
volonté ne soit plus déclenchée:Comme vous pouvez le voir, bien que toujours un peu artificiel, il est au moins plus explicite qu'un cryptique
size = None
. Cela dit, en fin de compte, je n'écraserais pas du tout la propriété et reconsidérerais ma conception à la racine.Si vous êtes arrivé jusqu'ici - merci d'avoir fait ce voyage avec moi. C'était un petit exercice amusant.
la source
A
@property
est défini au niveau de la classe. La documentation explique en détail comment cela fonctionne, mais il suffit de dire que la définition ou l' obtention de la propriété se résume à appeler une méthode particulière. Cependant, l'property
objet qui gère ce processus est défini avec la propre définition de la classe. C'est-à-dire qu'elle est définie comme une variable de classe mais se comporte comme une variable d'instance.Une conséquence de cela est que vous pouvez le réaffecter librement au niveau de la classe :
Et comme tout autre nom de niveau classe (par exemple, les méthodes), vous pouvez le remplacer dans une sous-classe en le définissant simplement de manière explicite différemment:
Lorsque nous créons une instance réelle, la variable d'instance masque simplement la variable de classe du même nom. L'
property
objet utilise normalement quelques manigances pour manipuler ce processus (c'est-à-dire appliquer des getters et des setters) mais lorsque le nom au niveau de la classe n'est pas défini en tant que propriété, rien de spécial ne se produit, et il agit donc comme vous vous attendez de toute autre variable.la source
__dict__
? Existe-t-il également des moyens d'automatiser cela? Oui, c'est juste une ligne mais c'est le genre de chose qui est très énigmatique à lire si vous n'êtes pas familier avec les détails sanglants des propriétés, etc.# declare size to not be a @property
Vous n'avez pas besoin du tout de devoir
size
.size
est une propriété de la classe de base, vous pouvez donc remplacer cette propriété dans la classe enfant:Vous pouvez (micro) optimiser cela en précalculant la racine carrée:
la source
size
est une propriété et non pas un attribut d'instance, vous ne devez pas faire quoi que ce soit de fantaisie.Il semble que vous souhaitiez définir
size
dans votre classe:Une autre option consiste à stocker
cap
dans votre classe et à le calculer avecsize
défini comme une propriété (qui remplace la propriété de la classe de basesize
).la source
Je suggère d'ajouter un setter comme ceci:
De cette façon, vous pouvez remplacer la
.size
propriété par défaut comme suit:la source
Vous pouvez aussi faire la prochaine chose
la source
_size
ou_size_call
là. Vous auriez pu incorporer l'appel de fonction danssize
comme condition, et l'utilisertry... except...
pour tester au_size
lieu de prendre une référence de classe supplémentaire qui ne sera pas utilisée. Quoi qu'il en soit, je pense que c'est encore plus cryptique que la simplesize = None
réécriture en premier lieu.Je pense qu'il est possible de définir une propriété d'une classe de base sur une autre propriété d'une classe dérivée, à l'intérieur de la classe dérivée, puis d'utiliser la propriété de la classe de base avec une nouvelle valeur.
Dans le code initial, il existe une sorte de conflit entre le nom
size
de la classe de base et l'attributself.size
de la classe dérivée. Cela peut être visible si nous remplaçons le nomself.size
de la classe dérivée parself.length
. Cela produira:Ensuite, si nous remplaçons le nom de la méthode
size
parlength
dans toutes les occurrences du programme, cela entraînera la même exception:Le code fixe, ou de toute façon, une version qui fonctionne d'une manière ou d'une autre, consiste à conserver exactement le même code, à l'exception de la classe
Square_Integers_Below
, qui définira la méthodesize
de la classe de base, sur une autre valeur.Et puis, lorsque nous exécutons tout le programme, la sortie est:
J'espère que cela a été utile d'une manière ou d'une autre.
la source