Formulation du problème
Vous créez un namedtuple
objet dans votre code et vous souhaitez écraser l'une des valeurs d'attribut comme vous en avez l'habitude pour les objets normaux :
from collections import namedtuple Car = namedtuple('Car', 'speed color') porsche = Car(300, 'gold') porsche.speed = 400
Mais, de manière inattendue, Python lève un AttributeError: can't set attribute
. ?
Traceback (most recent call last): File "C:\Users\xcent\Desktop\code.py", line 5, in <module> porsche.speed = 400 AttributeError: can't set attribute
Que pouvez-vous faire? ? Comprenons la raison pour laquelle cette erreur se produit - après cela, vous apprendrez les meilleurs correctifs.
Pourquoi cette erreur se produit-elle ?
Tout namedtuple
l'objet est aussi un tuple
(relation de sous-classe ), il est donc immuable — vous ne pouvez pas le modifier après la création.
Par exemple, vous ne pouvez pas modifier un tuple ('Alice', 'Bob')
à ('Ann', 'Bob')
sans en créer un nouveau.
Si vous tentez de modifier un namedtuple
valeur d'attribut de l'objet, vous essayez de modifier un objet immuable. Par conséquent, Python lève le AttributeError: can't set attribute
.
Correctif #1 :utilisez la méthode namedtuple._replace()
Le moyen le plus simple de réparer le AttributeError:can't set attribute
est de créer un nouveau namedtuple
objet avec le namedtuple._replace()
méthode. Le résultat est un nouveau namedtuple
objet avec tous les attributs copiés sauf celui qui vient d'être transmis.
Par exemple, si vous souhaitez modifier l'attribut x
de l'objet tuple nommé n
à la valeur 42
, appelez le n._replace(x=42)
.
Voici le correctif appliqué à notre exemple précédent :
from collections import namedtuple Car = namedtuple('Car', 'speed color') porsche = Car(300, 'gold') porsche = porsche._replace(speed=400)
Vous pouvez voir dans la suite print()
déclaration indiquant que la valeur d'attribut d'origine pour speed
est passé de 300 à 400.
print(porsche) # Car(speed=300, color='gold')
Notez que cela est généralement inefficace car tous les autres attributs doivent être copiés juste pour mettre à jour un seul attribut !
Pouvons-nous résoudre ce problème ? Bien sûr !
Correctif #2 : n'utilisez pas de tuples nommés mais des classes
Les tuples nommés sont des objets immuables, vous ne pouvez donc pas modifier les valeurs d'attribut. Mais les classes Python standard sont modifiables, elles peuvent donc être modifiées arbitrairement. Plus précisément, vous pouvez modifier la valeur d'un attribut existant et même ajouter dynamiquement un nouvel attribut à un objet existant.
Le code suivant accomplit la même chose que notre exemple de code, mais il le fait plus efficacement en utilisant des classes Python standard plutôt que namedtuples
:
class Car: def __init__(self, speed, color): self.speed = speed self.color = color porsche = Car(300, 'gold') porsche.speed = 400
Confirmons que la valeur de l'attribut est passée à 400 :
print(porsche.speed) # 400
Vous pouvez même ajouter un attribut supplémentaire, par exemple price
, à l'objet comme ceci :
porsche.price = 100000 print(porsche.price) # 100000
Correctif #3 :Utiliser des dictionnaires
Pour les applications légères, vous pouvez également utiliser un dictionnaire simple au lieu de tuples nommés ou de classes. Les dictionnaires sont conçus pour contenir clé :valeur au lieu de attribut :valeur paires. Par exemple, vous pouvez créer un dictionnaire {'key_1': 'value_1', 'key_2': 'value_2'}
pour stocker deux clés au lieu d'attributs. Au lieu de mettre à jour un attribut, vous mettriez maintenant à jour une clé en utilisant dict['key_1'] = 'new_value'
.
Voici cette solution appliquée à notre problème précédent :une certaine simplification s'est produite !
porsche = {'speed': 300, 'color': 'gold'} porsche['speed'] = 400
Vérifions si l'attribut a changé :
print(porsche['speed']) # 400
En effet, la sortie a changé et aucun message d'erreur n'existe.
Correctif #4 :si vous devez utiliser des tuples nommés, utilisez un attribut mutable
Si vous devez utiliser namedtuples
— par exemple pour mettre à jour un attribut existant avec une nouvelle valeur — et vous ne pouvez pas en créer un nouveau avec _replace()
pour des raisons d'efficacité, procédez comme suit :
Créez le nametuple
les attributs de l'objet en tant qu'objets modifiables tels que des listes. Vous pouvez toujours modifier le contenu d'une liste car elle est modifiable. Donc, sémantiquement, vous modifiez la valeur d'un namedtuple
attribut sans violer réellement le critère d'immuabilité—le namedtuple
pointe toujours vers le même objet modifiable (par exemple, une liste) en mémoire.
Voici cette solution appliquée à l'exemple de problème :
from collections import namedtuple Car = namedtuple('Car', 'speed color') porsche = Car([300], 'gold') porsche.speed[0] = 400
Bien que vous ne trouviez peut-être pas cette solution particulièrement sexy, elle fonctionne à merveille :
print(porsche.speed) # [400]
La valeur d'origine de la vitesse a changé et aucune erreur ne s'est produite.