J'essaie de convertir une classe de "données" creuse assez longue en un tuple nommé. Ma classe ressemble actuellement à ceci:
class Node(object):
def __init__(self, val, left=None, right=None):
self.val = val
self.left = left
self.right = right
Après la conversion, namedtuple
il ressemble à ceci:
from collections import namedtuple
Node = namedtuple('Node', 'val left right')
Mais il y a un problème ici. Ma classe d'origine m'a permis de passer juste une valeur et a pris soin de la valeur par défaut en utilisant des valeurs par défaut pour les arguments nommés / mot-clé. Quelque chose comme:
class BinaryTree(object):
def __init__(self, val):
self.root = Node(val)
Mais cela ne fonctionne pas dans le cas de mon tuple nommé refactorisé car il s'attend à ce que je passe tous les champs. Je peux bien sûr remplacer les occurrences de Node(val)
to Node(val, None, None)
mais ce n'est pas à mon goût.
Existe-t-il donc une bonne astuce qui peut réussir ma réécriture sans ajouter beaucoup de complexité de code (métaprogrammation) ou devrais-je simplement avaler la pilule et continuer avec la «recherche et remplacement»? :)
Node
qu'il est. Pourquoi convertir en tuple nommé?Node
classes actuelles et autres sont de simples objets de valeur contenant des données avec un tas de champs différents (Node
c'est juste l'un d'entre eux). Ces déclarations de classe ne sont rien de plus que le bruit de ligne à mon humble avis et ont donc voulu les supprimer. Pourquoi entretenir quelque chose qui n'est pas nécessaire? :).debug_print()
méthode qui parcourt l'arbre et l'imprime?BinaryTree
classe.Node
et d'autres détenteurs de données ne nécessitent pas de telles méthodes spéciales, étant donné que les tuples nommés ont une représentation__str__
et une__repr__
représentation décentes . :)Réponses:
Python 3.7
Utilisez le paramètre par défaut .
Ou mieux encore, utilisez la nouvelle bibliothèque de classes de données , qui est beaucoup plus agréable que namedtuple.
Avant Python 3.7
Définissez
Node.__new__.__defaults__
les valeurs par défaut.Avant Python 2.6
Définissez
Node.__new__.func_defaults
les valeurs par défaut.Ordre
Dans toutes les versions de Python, si vous définissez moins de valeurs par défaut qu'il n'en existe dans le tuple nommé, les valeurs par défaut sont appliquées aux paramètres les plus à droite. Cela vous permet de conserver certains arguments comme arguments requis.
Wrapper pour Python 2.6 à 3.6
Voici un wrapper pour vous, qui vous permet même (facultativement) de définir les valeurs par défaut sur autre chose que
None
. Cela ne prend pas en charge les arguments requis.Exemple:
la source
isinstance
... tous les avantages, pas les inconvénients ... dommage que vous ayez été un peu en retard la fête. C'est la meilleure réponse.(None)
n'est pas un tuple, c'estNone
. Si vous utilisez à la(None,)
place, cela devrait fonctionner correctement.Node.__new__.__defaults__= (None,) * len(Node._fields)
J'ai sous-classé namedtuple et remplacé la
__new__
méthode:Cela préserve une hiérarchie de types intuitive, contrairement à la création d'une fonction d'usine déguisée en classe.
la source
__new__
n'est pas appelé lorsqu'il_replace
est utilisé.Enveloppez-le dans une fonction.
la source
isinstance(Node('val'), Node)
: il lèvera désormais une exception, plutôt que de renvoyer True. Bien que plus explicite, la réponse de @ justinfay (ci-dessous) préserve correctement les informations de hiérarchie de types, c'est donc probablement une meilleure approche si d'autres vont interagir avec les instances de Node.def make_node(...):
plutôt qu'en prétendant qu'il s'agit d'une définition de classe. De cette façon, les utilisateurs ne sont pas tentés de vérifier le polymorphisme de type sur la fonction mais utilisent la définition de tuple elle-même.isinstance
incorrecte.Avec
typing.NamedTuple
Python 3.6.1+, vous pouvez fournir à la fois une valeur par défaut et une annotation de type à un champ NamedTuple. À utilisertyping.Any
si vous n'avez besoin que de l'ancien:Usage:
De plus, au cas où vous auriez besoin à la fois de valeurs par défaut et d'une mutabilité facultative, Python 3.7 va avoir des classes de données (PEP 557) qui peuvent dans certains (beaucoup?) Remplacer les nommés.
Sidenote: une particularité de la spécification actuelle des annotations (expressions après
:
pour les paramètres et les variables et après->
pour les fonctions) en Python est qu'elles sont évaluées au moment de la définition * . Ainsi, puisque "les noms de classe sont définis une fois que le corps entier de la classe a été exécuté", les annotations'Node'
dans les champs de classe ci-dessus doivent être des chaînes pour éviter NameError.Ce type d'indices de type est appelé "référence avant" ( [1] , [2] ), et avec PEP 563 Python 3.7+ va avoir une
__future__
importation (à activer par défaut dans 4.0) qui permettra d'utiliser des références avant sans devis, reportant leur évaluation.* AFAICT seules les annotations de variables locales ne sont pas évaluées au moment de l'exécution. (source: PEP 526 )
la source
left
etright
(c'est-à-direNode
) est le même type que la classe en cours de définition et doit donc être écrit sous forme de chaînes.my_list: List[T] = None
self.my_list = my_list if my_list is not None else []
? Ne pouvons-nous pas utiliser des paramètres par défaut comme celui-ci?typing.NamedTuple
. Mais avec les classes de données, vous pouvez utiliser desField
objets avec undefault_factory
attr. pour cela, en remplaçant votre idiome parmy_list: List[T] = field(default_factory=list)
.Voici un exemple directement issu de la documentation :
Ainsi, l'exemple du PO serait:
Cependant, j'aime mieux certaines des autres réponses données ici. Je voulais juste ajouter ceci pour être complet.
la source
_
méthode (ce qui signifie fondamentalement une méthode privée) pour quelque chosereplace
qui semble assez utile ..*args
. Il se peut simplement qu'il ait été ajouté à la langue avant que beaucoup de ces choses soient normalisées._
préfixe est d'éviter la collision avec les noms des champs de tuple définis par l'utilisateur (citation appropriée du document: "Tout identifiant Python valide peut être utilisé pour un nom de champ à l'exception des noms commençant par un trait de soulignement."). En ce qui concerne la chaîne séparée par des espaces, je pense que c'est juste pour enregistrer quelques frappes (et vous pouvez passer une séquence de chaînes si vous préférez)._
cela a beaucoup de sens alors.Je ne suis pas sûr qu'il existe un moyen simple de nommer le double intégré. Il y a un joli module appelé recordtype qui a cette fonctionnalité:
la source
recordtype
cela semble certainement intéressant pour les travaux futurs. +1recordtype
c'est mutable alors qu'ilnamedtuple
ne l'est pas. Cela peut être important si vous voulez que l'objet soit lavable (ce qui, je suppose, ne l'est pas, car il a commencé en tant que classe).Voici une version plus compacte inspirée de la réponse de justinfay:
la source
Node(1, 2)
ne fonctionne pas avec cette recette, mais fonctionne dans la réponse de @ justinfay. Sinon, c'est assez chouette (+1).Dans python3.7 +, il y a un tout nouvel argument defaults = keyword.
Exemple d'utilisation:
la source
Court, simple et ne conduit pas les gens à
isinstance
mal utiliser :la source
Un exemple légèrement étendu pour initialiser tous les arguments manquants avec
None
:la source
Python 3.7: introduction de
defaults
param dans la définition namedtuple.Exemple comme indiqué dans la documentation:
Lisez plus ici .
la source
Vous pouvez également utiliser ceci:
Cela vous donne essentiellement la possibilité de construire n'importe quel tuple nommé avec une valeur par défaut et de remplacer uniquement les paramètres dont vous avez besoin, par exemple:
la source
Combiner les approches de @Denis et @Mark:
Cela devrait prendre en charge la création du tuple avec des arguments positionnels et également avec des cas mixtes. Cas de test:
mais prend également en charge TypeError:
la source
Je trouve cette version plus facile à lire:
Ce n'est pas aussi efficace que cela nécessite la création de l'objet deux fois, mais vous pouvez changer cela en définissant le duple par défaut à l'intérieur du module et en demandant simplement à la fonction de faire la ligne de remplacement.
la source
Puisque vous utilisez en
namedtuple
tant que classe de données, vous devez savoir que python 3.7 introduira un@dataclass
décorateur à cet effet - et bien sûr, il a des valeurs par défaut.Un exemple de la documentation :
Beaucoup plus propre, lisible et utilisable que le piratage
namedtuple
. Il n'est pas difficile de prévoir que l'utilisation denamedtuple
s diminuera avec l'adoption de 3.7.la source
Inspiré par cette réponse à une autre question, voici ma solution proposée basée sur une métaclasse et utilisant
super
(pour gérer correctement les futurs sous-calculs). C'est assez similaire à la réponse de justinfay .Ensuite:
la source
La réponse de jterrace à l'utilisation de recordtype est excellente, mais l'auteur de la bibliothèque recommande d'utiliser son projet namedlist , qui fournit à la fois des implémentations mutable (
namedlist
) et immutable (namedtuple
).la source
Voici une réponse générique courte et simple avec une belle syntaxe pour un tuple nommé avec des arguments par défaut:
Usage:
Minifié:
la source
En utilisant la
NamedTuple
classe de maAdvanced Enum (aenum)
bibliothèque et en utilisant laclass
syntaxe, c'est assez simple:Le seul inconvénient potentiel est l'exigence d'une
__doc__
chaîne pour tout attribut avec une valeur par défaut (c'est facultatif pour les attributs simples). En cours d'utilisation, il ressemble à:Les avantages que cela a sur
justinfay's answer
:est la simplicité, tout en étant
metaclass
basé au lieu deexec
basé.la source
Une autre solution:
Usage:
la source
Voici une version moins flexible, mais plus concise du wrapper de Mark Lodato: il prend les champs et les valeurs par défaut comme un dictionnaire.
Exemple:
la source
dict
n'a aucune garantie de commande.