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:
class Foo:
def __init__(self):
self.name = 'Foo!'
@property
def inst_prop():
return f'Retrieving {self.name}'
self.inst_prop = inst_prop
inst_prop, tout en étant un property, est irrévocablement un attribut d'instance:
>>> Foo.inst_prop
Traceback (most recent call last):
File "<pyshell#60>", line 1, in <module>
Foo.inst_prop
AttributeError: type object 'Foo' has no attribute 'inst_prop'
>>> Foo().inst_prop
<property object at 0x032B93F0>
>>> Foo().inst_prop.fget()
'Retrieving Foo!'
Tout dépend où votre propertyest défini en premier lieu. Si votre @propertyest défini dans la classe "scope" (ou vraiment, la namespace), il devient un attribut de classe. Dans mon exemple, la classe elle-même n'en connaît aucune inst_propjusqu'à 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:
@property
def some_prop(self):
return "Family property"
class Grandparent:
culture = some_prop
world_view = some_prop
class Parent(Grandparent):
world_view = "Parent's new world_view"
class Child(Parent):
def __init__(self):
try:
self.world_view = "Child's new world_view"
self.culture = "Child's new culture"
except AttributeError as exc:
print(exc)
self.__dict__['culture'] = "Child's desired new culture"
Imaginez ce qui se passe lorsque ces lignes sont exécutées:
print("Instantiating Child class...")
c = Child()
print(f'c.__dict__ is: {c.__dict__}')
print(f'Child.__dict__ is: {Child.__dict__}')
print(f'c.world_view is: {c.world_view}')
print(f'Child.world_view is: {Child.world_view}')
print(f'c.culture is: {c.culture}')
print(f'Child.culture is: {Child.culture}')
Le résultat est donc:
Instantiating Child class...
can't set attribute
c.__dict__ is: {'world_view': "Child's new world_view", 'culture': "Child's desired new culture"}
Child.__dict__ is: {'__module__': '__main__', '__init__': <function Child.__init__ at 0x0068ECD8>, '__doc__': None}
c.world_view is: Child's new world_view
Child.world_view is: Parent's new world_view
c.culture is: Family property
Child.culture is: <property object at 0x00694C00>
Remarquez comment:
self.world_viewa pu être appliqué, mais a self.cultureéchoué
culturen'existe pas dans Child.__dict__(le mappingproxyde la classe, à ne pas confondre avec l'instance __dict__)
- Même s'il
cultureexiste en c.__dict__, il n'est pas référencé.
Vous pourriez peut-être deviner pourquoi - a world_viewété écrasé par la Parentclasse en tant que non-propriété, vous avez donc Childpu le remplacer également. Pendant ce temps, cultureétant hérité, il n'existe qu'au sein mappingproxydeGrandparent :
Grandparent.__dict__ is: {
'__module__': '__main__',
'culture': <property object at 0x00694C00>,
'world_view': <property object at 0x00694C00>,
...
}
En fait, si vous essayez de supprimer Parent.culture:
>>> del Parent.culture
Traceback (most recent call last):
File "<pyshell#67>", line 1, in <module>
del Parent.culture
AttributeError: 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_viewplutôt de le supprimer :
del Parent.world_view
print(f'c.world_view is: {c.world_view}')
print(f'Child.world_view is: {Child.world_view}')
Vous vous demandez quel est le résultat?
c.world_view is: Family property
Child.world_view is: <property object at 0x00694C00>
Il est revenu à celui de Grands-parents world_view property, même si nous avions réussi à assigner l' self.world_viewavant! Mais que se passe-t-il si nous changeons avec force world_viewau 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é?
Child.world_view = "Child's independent world_view"
print(f'c.world_view is: {c.world_view}')
print(f'Child.world_view is: {Child.world_view}')
del c.world_view
print(f'c.world_view is: {c.world_view}')
print(f'Child.world_view is: {Child.world_view}')
Child.world_view = property(lambda self: "Child's own property")
print(f'c.world_view is: {c.world_view}')
print(f'Child.world_view is: {Child.world_view}')
Le résultat est:
# Creating Child's own world view
c.world_view is: Child's new world_view
Child.world_view is: Child's independent world_view
# Deleting Child instance's world view
c.world_view is: Child's independent world_view
Child.world_view is: Child's independent world_view
# Changing Child's world view to the property
c.world_view is: Child's own property
Child.world_view is: <property object at 0x020071B0>
Ceci est intéressant car c.world_viewest restauré à son attribut d'instance, tandis que Child.world_viewc'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é le Child.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 :
- Si un attribut de classe existe et qu'il s'agit d'un
property, récupérez sa valeur via getterou fget(plus de détails plus loin). La classe actuelle en premier à la classe de base en dernier.
- Sinon, s'il existe un attribut d'instance, récupérez la valeur d'attribut d'instance.
- Sinon, récupérez l'
propertyattribut non- classe. La classe actuelle en premier à la classe de base en dernier.
Dans ce cas, supprimons la racine property:
del Grandparent.culture
print(f'c.culture is: {c.culture}')
print(f'Child.culture is: {Child.culture}')
Qui donne:
c.culture is: Child's desired new culture
Traceback (most recent call last):
File "<pyshell#74>", line 1, in <module>
print(f'Child.culture is: {Child.culture}')
AttributeError: type object 'Child' has no attribute 'culture'
Et voilà! Childa maintenant leur propre culturebasé sur l'insertion énergique dans c.__dict__. Child.culturen'existe pas, bien sûr, car il n'a jamais été défini dans Parentou l' Childattribut class, et celui Grandparent-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 le propertylui - même.
Outre la getterméthode mentionnée précédemment , propertyayez également quelques astuces soignées dans ses manches. La plus pertinente dans ce cas est la méthode, setterou fset, qui est déclenchée par la self.culture = ...ligne. Étant donné que vous propertyn'avez implémenté aucune fonction setterni aucune fgetfonction, python ne sait pas quoi faire et lance AttributeErrorplutôt une (c'est-à-dire can't set attribute).
Si toutefois vous avez implémenté une setterméthode:
@property
def some_prop(self):
return "Family property"
@some_prop.setter
def some_prop(self, val):
print(f"property setter is called!")
# do something else...
Lors de l'instanciation du Childcours, vous obtiendrez:
Instantiating Child class...
property setter is called!
Au lieu de recevoir un AttributeError, vous appelez maintenant la some_prop.settermé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:
class Grandparent:
@property
def culture(self):
return "Family property"
# add a setter method
@culture.setter
def culture(self, val):
print('Fine, have your own culture')
# overwrite the child class attribute
type(self).culture = None
self.culture = val
class Parent(Grandparent):
pass
class Child(Parent):
def __init__(self):
self.culture = "I'm a millennial!"
c = Child()
print(c.culture)
Ce qui se traduit par:
Fine, have your own culture
I'm a millennial!
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 setterméthode appropriée . Dans certains cas, vous souhaitez définir des valeurs sur votre property. Mais maintenant, chaque fois que vous le définissez, self.culture = ...il remplacera toujours la fonction que vous avez définie dans le getter(qui, dans ce cas, n'est vraiment que la @propertypartie encapsulée. Vous pouvez ajouter des mesures plus nuancées, mais d'une manière ou d'une autre, cela impliquera toujours plus que juste self.culture = .... par exemple:
class Grandparent:
# ...
@culture.setter
def culture(self, val):
if isinstance(val, tuple):
if val[1]:
print('Fine, have your own culture')
type(self).culture = None
self.culture = val[0]
else:
raise AttributeError("Oh no you don't")
# ...
class Child(Parent):
def __init__(self):
try:
# Usual setter
self.culture = "I'm a Gen X!"
except AttributeError:
# Trigger the overwrite condition
self.culture = "I'm a Boomer!", True
C'est waaaaay plus compliqué que l'autre réponse, size = Noneau 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, quand self.cultureest référencé, le __get__sera toujours déclenché en premier, et quand self.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. propertyest conçu comme un descripteur avec un accès pratique à partir de méthodes comme getattrou setattr. Si vous souhaitez également que ces méthodes atteignent un objectif différent, vous demandez simplement des ennuis. Je repenserais peut-être l'approche:
- Ai-je vraiment besoin d'un
propertypour ça?
- Une méthode pourrait-elle me servir différemment?
- Si j'ai besoin d'un
property, y a-t-il une raison pour laquelle je devrais l'écraser?
- La sous-classe appartient-elle vraiment à la même famille si celles
property- ci ne s'appliquent pas?
- Si j'ai besoin d'écraser un / tous les
propertys, une méthode distincte me servirait-elle mieux qu'une simple réaffectation, car la réaffectation peut accidentellement annuler le propertys?
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 la propertyvolonté ne soit plus déclenchée:
class Grandparent:
# ...
def overwrite_props(self):
# reassign class attributes
type(self).size = None
type(self).len = None
# other properties, if necessary
# ...
# Usage
class Child(Parent):
def __init__(self):
self.overwrite_props()
self.size = 5
self.len = 10
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.
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 fournissentelementsce qui est un conteneur de taille . Cependant, votreSquare_Integers_Belowclasse 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.