web-dev-qa-db-fra.com

Python: est-ce que l'utilisation de "..% (var) s .."% locals () est une bonne pratique?

J'ai découvert ce motif (ou anti-motif) et j'en suis très content.

Je pense que c'est très agile:

def example():
    age = ...
    name = ...
    print "hello %(name)s you are %(age)s years old" % locals()

Parfois j'utilise son cousin:

def example2(obj):
    print "The file at %(path)s has %(length)s bytes" % obj.__dict__

Je n'ai pas besoin de créer un tuple artificiel et de compter les paramètres et de conserver les positions correspondantes% s à l'intérieur du tuple.

Aimes-tu? L'utiliseriez-vous/l'utiliseriez-vous? Oui/Non, veuillez expliquer.

70
flybywire

C'est correct pour les petites applications et les scripts prétendument "ponctuels", en particulier avec l'amélioration vars mentionnée par @ kaizer.se et la version .format Mentionnée par @RedGlyph.

Cependant, pour les grandes applications avec une longue durée de maintenance et de nombreux mainteneurs, cette pratique peut entraîner des maux de tête en matière de maintenance, et je pense que c'est de là que vient la réponse de @ S.Lott. Permettez-moi d'expliquer certains des problèmes impliqués, car ils peuvent ne pas être évidents pour quiconque n'a pas les cicatrices du développement et de la maintenance de grandes applications (ou de composants réutilisables pour de telles bêtes).

Dans une application "sérieuse", vous n'auriez pas votre chaîne de format codée en dur - ou, si vous l'aviez fait, ce serait sous une forme telle que _('Hello {name}.'), où le _ Vient from gettext ou des frameworks i18n/L10n similaires. Le fait est qu'une telle application (ou des modules réutilisables qui peuvent arriver à être utilisés dans de telles applications) doit prendre en charge l'internationalisation (AKA i18n) et la localisation (AKA L10n): vous voulez que votre application puisse émettre "Hello Paul" dans certains pays et cultures, "Hola Paul" dans certains autres, "Ciao Paul" dans d'autres encore, et ainsi de suite. Ainsi, la chaîne de format est plus ou moins automatiquement remplacée par une autre au moment de l'exécution, en fonction des paramètres de localisation actuels; au lieu d'être codé en dur, il vit dans une sorte de base de données. À toutes fins utiles, imaginez que la chaîne de formatage soit toujours une variable, pas une chaîne littérale.

Donc, ce que vous avez est essentiellement

formatstring.format(**locals())

et vous ne pouvez pas vérifier trivialement exactement quoi noms locaux que le formatage va utiliser. Vous devrez ouvrir et parcourir la base de données L10N, identifier les chaînes de format qui seront utilisées ici dans différents paramètres, les vérifier toutes.

Donc, dans la pratique, vous ne savez quels noms locaux vont être utilisés - ce qui limite horriblement la maintenance de la fonction. Vous n'osez pas renommer ou supprimer une variable locale, car cela pourrait horriblement briser l'expérience utilisateur pour les utilisateurs avec une combinaison obscure (pour vous) de langue, de paramètres régionaux et de préférences.

Si vous avez de superbes tests d'intégration/de régression, la rupture sera interceptée avant la version bêta - mais le contrôle qualité vous hurlera dessus et la sortie sera retardée ... et, soyons honnêtes, tout en visant une couverture à 100% avec unit tests est raisonnable, ce n'est vraiment pas le cas avec intégration tests, une fois que vous considérez l'explosion combinatoire des paramètres [[pour L10N et pour bien d'autres raisons]] et versions prises en charge de toutes les dépendances. Donc, vous ne risquez pas allègrement de risquer des ruptures parce que "ils seront pris au contrôle qualité" (si vous le faites, vous risquez de ne pas durer longtemps dans un environnement qui développe de grandes applications ou des composants réutilisables ;-).

Ainsi, dans la pratique, vous ne supprimerez jamais la variable locale "nom" même si les gens de l'expérience utilisateur ont depuis longtemps changé ce message d'accueil en un "Welcome, Dread Overlord!" Plus approprié! (et de manière appropriée les versions L10n de celui-ci). Tout cela parce que vous avez choisi locals()...

Donc, vous accumulez de la cruauté à cause de la façon dont vous avez limité votre capacité à maintenir et à modifier votre code - et peut-être que cette variable locale "name" n'existe que parce qu'elle a été extraite d'une base de données ou autre, alors gardez-la (ou un autre local) n'est pas seulement cruel, il réduit également vos performances. La commodité de surface de locals() vaut-elle que ? -)

Mais attendez, il y a pire! Parmi les nombreux services utiles qu'un programme de type lintcomme, par exemple, --- (pylint ) peut faire pour vous, est de vous avertir des variables locales inutilisées (j'aimerais pouvoir le faire pour globals inutilisés aussi, mais, pour les composants réutilisables, c'est juste un peu trop difficile ;-). De cette façon, vous attraperez la plupart des fautes d'orthographe occasionnelles telles que if ...: nmae = ... Très rapidement et à moindre coût, plutôt qu'en voyant une pause de test unitaire et en effectuant un travail de détective pour savoir pourquoi il s'est cassé (vous faites avez des tests unitaires obsessionnels et omniprésents qui pourraient attraper cela finalement, non? -) - lint va vous parler d'une variable locale inutilisée nmae et vous la corrigerez immédiatement.

Mais si vous avez dans votre code un blah.format(**locals()), ou de manière équivalente un blah % locals()... vous êtes SOL, mon pote! -) Comment une mauvaise charpie va-t-elle savoir si nmae est en fait une variable inutilisée, ou en fait elle est utilisée par n'importe quelle fonction ou méthode externe à laquelle vous passez locals()? Il ne peut pas - soit il va avertir de toute façon (provoquant un effet "cry wolf" qui finit par vous amener à ignorer ou désactiver de tels avertissements), ou il ne va jamais avertir (avec le même effet final: pas d'avertissement ;-) .

Comparez cela à l'alternative "explicite vaut mieux qu'implicite" ...:

blah.format(name=name)

Là - aucun des soucis d'entretien, de performance et de problème de peluches ne s'applique plus; félicité! Vous le faites immédiatement comprendre à toutes les personnes concernées (charpie incluse ;-) exactement quoi les variables locales sont utilisées, et exactement à quelles fins.

Je pourrais continuer, mais je pense que ce post est déjà assez long ;-).

Donc, résumant: " γνῶθι σεαυτόν !" Hmm, je veux dire, "connais-toi toi-même!". Et par "toi-même", j'entends en fait "le but et la portée de votre code". Si c'est une chose unique ou à peu près, qui ne sera jamais i18n'd et L10n'd, n'aura guère besoin de maintenance future, ne sera jamais réutilisée dans un contexte plus large, etc., etc., alors continuez et utilisez locals() pour sa petite mais belle commodité; si vous savez le contraire, ou même si vous n'êtes pas tout à fait certain, faites preuve de prudence et rendez les choses plus explicites - souffrez du petit inconvénient de préciser exactement ce que vous allez faire et profitez de tous les avantages qui en découlent.

BTW, ce n'est qu'un des exemples où Python s'efforce de prendre en charge à la fois la programmation "petite, ponctuelle, exploratoire, peut-être interactive" (en autorisant et en prenant en charge des commodités risquées qui vont bien au-delà de locals() - pensez à import *, eval, exec, et à plusieurs autres façons de faire exploser les espaces de noms et de risquer des impacts de maintenance pour des raisons de commodité) , ainsi que des applications et des composants "grands, réutilisables, d'entreprise". Il peut faire un très bon travail dans les deux, mais seulement si vous "vous connaissez" et évitez d'utiliser les éléments "pratiques" sauf lorsque vous êtes absolument certain vous pouvez en fait les payer. Le plus souvent, la considération clé est: "Qu'est-ce que cela fait à mes espaces de noms, et à la conscience de leur formation et de leur utilisation par le compilateur, lint & c, les lecteurs et responsables humains, et ainsi de suite?" .

Rappelez-vous, "Les espaces de noms sont une excellente idée - faisons-en plus!" c'est ainsi que le Zen de Python conclut ... mais Python, en tant que "langage pour adultes consentants", permet vous définissez les limites de ce que cela implique, en conséquence de votre environnement de développement, de vos objectifs et de vos pratiques. Utilisez ce pouvoir de manière responsable! -)

87
Alex Martelli

Jamais en un million d'années. On ne sait pas quel est le contexte du formatage: locals pourrait inclure presque n'importe quelle variable. self.__dict__ n'est pas aussi vague. Parfaitement horrible de laisser les futurs développeurs se gratter la tête sur ce qui est local et ce qui ne l'est pas.

C'est un mystère intentionnel. Pourquoi assaillir votre organisation de futurs maux de tête en matière de maintenance?

10
S.Lott

Je pense que c'est un excellent modèle car vous tirez parti des fonctionnalités intégrées pour réduire le code que vous devez écrire. Personnellement, je trouve cela assez pythonique.

Je n'écris jamais de code que je n'ai pas besoin d'écrire - moins de code est mieux que plus de code et cette pratique d'utilisation de locals() par exemple me permet d'écrire moins de code et est également très facile à lire et à comprendre.

10
Andrew Hare

En ce qui concerne le "cousin", au lieu de obj.__dict__, c'est beaucoup mieux avec le nouveau formatage de chaîne:

def example2(obj):
    print "The file at {o.path} has {o.length} bytes".format(o=obj)

J'utilise beaucoup ceci pour les méthodes repr, par exemple.

def __repr__(self):
    return "{s.time}/{s.place}/{s.warning}".format(s=self)
10
pfctdayelise

Les "%(name)s" % <dictionary> ou encore mieux, les "{name}".format(<parameters>) ont le mérite de

  • étant plus lisible que "% 0s"
  • être indépendant de l'ordre des arguments
  • pas obligé d'utiliser tous les arguments de la chaîne

J'aurais tendance à favoriser le str.format (), car cela devrait être le moyen de le faire dans Python 3 (selon PEP 3101 ), et c'est déjà disponible à partir de la version 2.6. Avec locals(), vous devrez faire ceci:

print("hello {name} you are {age} years old".format(**locals()))
8
RedGlyph

L'utilisation de la fonction intégrée vars([object]) ( documentation ) peut améliorer l'apparence du second:

def example2(obj):
    print "The file at %(path)s has %(length)s bytes" % vars(obj)

L'effet est bien sûr le même.

6
u0b34a0f6ae

Il existe maintenant un moyen officiel de le faire, à partir de Python 3.6.0: littéraux de chaîne formatés .

Cela fonctionne comme ceci:

f'normal string text {local_variable_name}'

Par exemple. au lieu de ceux-ci:

"hello %(name)s you are %(age)s years old" % locals()
"hello {name} you are {age} years old".format(**locals())
"hello {} you are {} years old".format(name, age)

fais juste ceci:

f"hello {name} you are {age} years old"

Voici l'exemple officiel:

>>> name = "Fred"
>>> f"He said his name is {name}."
'He said his name is Fred.'
>>> width = 10
>>> precision = 4
>>> value = decimal.Decimal("12.34567")
>>> f"result: {value:{width}.{precision}}"  # nested fields
'result:      12.35'

Référence:

1
Jacktose