Sous-classement: est-il possible de remplacer une propriété avec un attribut conventionnel?

14

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?

Paul Panzer
la source
Cela indique probablement un problème avec la hiérarchie des classes elle-même. La meilleure solution est alors de retravailler cette hiérarchie. Vous mentionnez qu ' "il existe une implémentation par défaut plausible pour certaines propriétés dérivées" avec l'exemple d'ensembles mathématiques et 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 fournissent elementsce qui est un conteneur de taille . Cependant, votre Square_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.
a_guest il y a
Le remplacement n'est nécessaire que parce qu'il a hérité du (mauvais) comportement en premier lieu. 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.
a_guest il y a

Réponses:

6

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 la non_data_propertydé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.

import math

class non_data_property:
    def __init__(self, fget):
        self.__doc__ = fget.__doc__
        self.fget = fget

    def __get__(self, obj, cls):
        if obj is None:
            return self
        return self.fget(obj)

class Math_Set_Base:
    @non_data_property
    def size(self, *elements):
        return len(self.elements)

class Concrete_Math_Set(Math_Set_Base):
    def __init__(self, *elements):
        self.elements = elements


class Square_Integers_Below(Math_Set_Base):
    def __init__(self, cap):
        self.size = int(math.sqrt(cap))

print(Concrete_Math_Set(1, 2, 3).size) # 3
print(Square_Integers_Below(1).size) # 1
print(Square_Integers_Below(4).size) # 2
print(Square_Integers_Below(9).size) # 3

Cependant, cela suppose que vous avez accès à la classe de base pour effectuer ces modifications.

Arkelis
la source
C'est assez proche de la perfection. Donc, cela signifie une propriété même si vous ne définissez que le getter ajoute implicitement un setter qui ne fait rien à part bloquer l'affectation? Intéressant. J'accepterai probablement cette réponse.
Paul Panzer
Oui, selon les documents, une propriété définit toujours les trois méthodes de descripteur ( __get__(), __set__()et __delete__()) et elles soulèvent un AttributeErrorsi vous ne leur fournissez aucune fonction. Voir l' équivalent Python de l'implémentation d'une propriété .
Arkelis
1
Tant que vous ne vous souciez pas du setterou __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 = 10serait également valide).
Matière
Et Square_Integers_Below(9).size = 1est é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.
Arkelis
1
Je ne suis pas en désaccord sur le fait qu'il pourrait y avoir un cas d'utilisation valable pour ceux-ci, je voulais juste mentionner la mise en garde car cela peut compliquer les efforts de débogage à l'avenir. Tant que vous et OP êtes conscients des implications, tout va bien.
r.ook
10

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

  1. self.world_viewa pu être appliqué, mais a self.cultureéchoué
  2. culturen'existe pas dans Child.__dict__(le mappingproxyde la classe, à ne pas confondre avec l'instance __dict__)
  3. 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 :

  1. 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.
  2. Sinon, s'il existe un attribut d'instance, récupérez la valeur d'attribut d'instance.
  3. 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:

  1. Ai-je vraiment besoin d'un propertypour ça?
  2. Une méthode pourrait-elle me servir différemment?
  3. Si j'ai besoin d'un property, y a-t-il une raison pour laquelle je devrais l'écraser?
  4. La sous-classe appartient-elle vraiment à la même famille si celles property- ci ne s'appliquent pas?
  5. 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.

tour
la source
2
Wow, merci beaucoup! Cela me prendra un peu de temps pour digérer cela, mais je suppose que je l'ai demandé.
Paul Panzer
6

A @propertyest 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' propertyobjet 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 :

print(Math_Set_Base.size)
# <property object at 0x10776d6d0>

Math_Set_Base.size = 4
print(Math_Set_Base.size)
# 4

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:

class Square_Integers_Below(Math_Set_Base):
    # explicitly define size at the class level to be literally anything other than a @property
    size = None

    def __init__(self,cap):
        self.size = int(math.sqrt(cap))

print(Square_Integers_Below(4).size)  # 2
print(Square_Integers_Below.size)     # None

Lorsque nous créons une instance réelle, la variable d'instance masque simplement la variable de classe du même nom. L' propertyobjet 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.

Green Cloak Guy
la source
Merci, c'est d'une simplicité rafraîchissante. Cela signifie-t-il que même s'il n'y a jamais eu de propriétés nulle part dans ma classe et ses ancêtres, il cherchera tout d'abord dans l'arborescence d'héritage entière un nom de niveau de classe avant même de déranger la recherche de __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.
Paul Panzer
@PaulPanzer Je n'ai pas examiné les procédures derrière cela, donc je ne peux pas vous donner moi-même une réponse satisfaisante. Vous pouvez essayer de l'explorer à partir du code source de Cpython si vous le souhaitez. En ce qui concerne l'automatisation du processus, je ne pense pas qu'il existe un bon moyen de le faire à part ne pas en faire une propriété en premier lieu, ou simplement ajouter un commentaire / docstring pour permettre à ceux qui lisent votre code de savoir ce que vous faites . Quelque chose comme# declare size to not be a @property
Green Cloak Guy
4

Vous n'avez pas besoin du tout de devoir size. sizeest une propriété de la classe de base, vous pouvez donc remplacer cette propriété dans la classe enfant:

class Math_Set_Base:
    @property
    def size(self):
        return len(self.elements)

    # size = property(lambda self: self.elements)


class Square_Integers_Below(Math_Set_Base):

    def __init__(self, cap):
        self._cap = cap

    @property
    def size(self):
        return int(math.sqrt(self._cap))

    # size = property(lambda self: int(math.sqrt(self._cap)))

Vous pouvez (micro) optimiser cela en précalculant la racine carrée:

class Square_Integers_Below(Math_Set_Base):

    def __init__(self, cap):
        self._size = int(math.sqrt(self._cap))

    @property
    def size(self):
        return self._size
chepner
la source
C'est un bon point, remplacez simplement une propriété parent par une propriété enfant! +1
r.ook
C'est en quelque sorte la chose la plus simple à faire, mais j'étais intéressé et j'ai demandé spécifiquement des moyens pour autoriser un remplacement de propriété non afin de ne pas imposer l'inconvénient d'avoir à écrire une propriété de mémorisation où une simple affectation ferait le emploi.
Paul Panzer
Je ne sais pas pourquoi vous voudriez compliquer votre code comme ça. Pour le petit prix de reconnaissance qui sizeest une propriété et non pas un attribut d'instance, vous ne devez pas faire quoi que ce soit de fantaisie.
chepner
Mon cas d'utilisation est beaucoup de classes enfants avec beaucoup d'attributs; ne pas avoir à écrire 5 lignes au lieu d'une pour tout cela justifie une certaine fantaisie, OMI.
Paul Panzer
2

Il semble que vous souhaitiez définir sizedans votre classe:

class Square_Integers_Below(Math_Set_Base):
    size = None

    def __init__(self, cap):
        self.size = int(math.sqrt(cap))

Une autre option consiste à stocker capdans votre classe et à le calculer avec sizedéfini comme une propriété (qui remplace la propriété de la classe de base size).

Uri
la source
2

Je suggère d'ajouter un setter comme ceci:

class Math_Set_Base:
    @property
    def size(self):
        try:
            return self._size
        except:
            return len(self.elements)

    @size.setter
    def size(self, value):
        self._size = value

De cette façon, vous pouvez remplacer la .sizepropriété par défaut comme suit:

class Concrete_Math_Set(Math_Set_Base):
    def __init__(self,*elements):
        self.elements = elements

class Square_Integers_Below(Math_Set_Base):
    def __init__(self,cap):
        self.size = int(math.sqrt(cap))

print(Concrete_Math_Set(1,2,3).size) # 3
print(Square_Integers_Below(7).size) # 2
Amir
la source
1

Vous pouvez aussi faire la prochaine chose

class Math_Set_Base:
    _size = None

    def _size_call(self):
       return len(self.elements)

    @property
    def size(self):
        return  self._size if self._size is not None else self._size_call()

class Concrete_Math_Set(Math_Set_Base):
    def __init__(self, *elements):
        self.elements = elements


class Square_Integers_Below(Math_Set_Base):
    def __init__(self, cap):
        self._size = int(math.sqrt(cap))
Ryabchenko Alexander
la source
C'est bien, mais vous n'en avez pas vraiment besoin _sizeou _size_calllà. Vous auriez pu incorporer l'appel de fonction dans sizecomme condition, et l'utiliser try... except... pour tester au _sizelieu 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 simple size = Noneréécriture en premier lieu.
r.ook le
0

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 sizede la classe de base et l'attribut self.sizede la classe dérivée. Cela peut être visible si nous remplaçons le nom self.sizede la classe dérivée par self.length. Cela produira:

3
<__main__.Square_Integers_Below object at 0x000001BCD56B6080>

Ensuite, si nous remplaçons le nom de la méthode sizepar lengthdans toutes les occurrences du programme, cela entraînera la même exception:

Traceback (most recent call last):
  File "C:/Users/Maria/Downloads/so_1.2.py", line 24, in <module>
    Square_Integers_Below(7)
  File "C:/Users/Maria/Downloads/so_1.2.py", line 21, in __init__
    self.length = int(math.sqrt(cap))
AttributeError: can't set attribute

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éthode sizede la classe de base, sur une autre valeur.

class Square_Integers_Below(Math_Set_Base):

    def __init__(self,cap):
        #Math_Set_Base.__init__(self)
        self.length = int(math.sqrt(cap))
        Math_Set_Base.size = self.length

    def __repr__(self):
        return str(self.size)

Et puis, lorsque nous exécutons tout le programme, la sortie est:

3
2

J'espère que cela a été utile d'une manière ou d'une autre.

Sofia190
la source