72 votes

Python : utiliser "..%(var)s.." % locals() est-elle une bonne pratique ?

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

J'ai l'impression qu'il 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, de compter les paramètres et de conserver les % correspondant aux positions à l'intérieur du tuple.

Vous aimez ? L'utilisez-vous ou l'utiliseriez-vous ? Oui/Non, veuillez expliquer.

90voto

Alex Martelli Points 330805

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

Cependant, pour les grandes applications ayant une longue durée de vie et de nombreux mainteneurs, cette pratique peut conduire à 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 certaines des questions en jeu, car elles ne sont peut-être pas évidentes pour quiconque ne porte 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", votre chaîne de format ne serait pas codée en dur - ou, si c'était le cas, elle se présenterait sous une forme telle que _('Hello {name}.') où le _ vient de gettext ou des cadres i18n / L10n similaires. Le fait est qu'une telle application (ou les modules réutilisables qui peuvent être utilisés dans de telles applications) doit supporter l'internationalisation (AKA i18n) et la localisation (AKA L10n) : vous voulez que votre application soit capable d'émettre "Hello Paul" dans certains pays et cultures, "Hola Paul" dans d'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ée en dur, elle se trouve dans une sorte de base de données. À toutes fins utiles, imaginez que cette chaîne de format soit toujours une variable, et non une chaîne littérale.

Donc, ce que vous avez est essentiellement

formatstring.format(**locals())

et vous ne pouvez pas trivialement vérifier exactement ce que les noms locaux que le formatage va utiliser. Il faudrait ouvrir et parcourir la base de données L10N, identifier les chaînes de formatage qui vont être utilisées ici dans différents paramètres, et les vérifier toutes.

Donc, en pratique, vous n'avez pas connaître quels noms locaux vont être utilisés - ce qui nuit terriblement à la maintenance de la fonction. Vous n'avez pas le droit de renommer ou de supprimer une variable locale, car cela pourrait nuire à l'expérience des utilisateurs ayant une combinaison obscure (pour vous) de langues, de paramètres locaux et de préférences.

Si vous disposez d'excellents tests d'intégration/de régression, la rupture sera détectée avant la sortie de la version bêta - mais le service d'assurance qualité vous criera dessus et la sortie sera retardée... et, soyons honnêtes, si vous visez une couverture à 100 % avec unité est raisonnable, il ne l'est vraiment pas avec les intégration une fois que vous considérez l'explosion combinatoire des paramètres [[pour L10N et pour de nombreuses autres raisons]] et des versions supportées de toutes les dépendances. Ainsi, vous ne pouvez pas allègrement risquer des ruptures parce que "elles seront détectées par l'AQ" (si vous le faites, vous ne ferez pas long feu dans un environnement qui développe de grandes applications ou des composants réutilisables;-).

Ainsi, en pratique, vous ne supprimerez jamais la variable locale "name", même si les responsables de l'expérience utilisateur ont depuis longtemps remplacé ce message d'accueil par un "Welcome, Dread Overlord !" plus approprié. (et des versions L10n appropriées). Tout cela parce que vous avez choisi locals() ...

Vous accumulez donc des déchets à cause de la façon dont vous avez réduit votre capacité à maintenir et à modifier votre code - et peut-être que cette variable locale "nom" n'existe que parce qu'elle a été extraite d'une base de données ou autre, donc la conserver (ou une autre variable locale) n'est pas seulement un déchet, elle réduit aussi vos performances. Est-ce que la commodité de surface de locals() qui vaut que ?-)

Mais attendez, il y a pire ! Parmi les nombreux services utiles qu'un lint -comme un programme (comme, par exemple, pylint ) peut faire pour vous, c'est de vous avertir des variables locales inutilisées (j'aimerais qu'il puisse le faire aussi pour les globales inutilisées, mais, pour les composants réutilisables, c'est juste un peu trop difficile;-). De cette façon, vous attraperez la plupart des erreurs d'orthographe occasionnelles telles que if ...: nmae = ... très rapidement et à peu de frais, plutôt que de voir un test unitaire échouer et de faire un travail de détective pour le découvrir. por qué il s'est brisé (vous faire ont des tests unitaires obsessionnels et envahissants qui serait vous finirez par l'attraper, n'est-ce pas ?) -- lint vous signalera une variable locale inutilisée nmae et vous le réparerez immédiatement.

Mais si vous avez dans votre code un blah.format(**locals()) ou, de manière équivalente, une blah % locals() ... vous êtes SOL, mon pote!-) Comment la pauvre charpie va-t-elle savoir si nmae est en fait une variable inutilisée, ou bien elle est utilisée par n'importe quelle fonction ou méthode externe à laquelle vous passez la commande locals() à ? Il ne peut pas - soit il va quand même avertir (provoquant un effet de "cri du loup" qui vous conduit finalement à ignorer ou à désactiver ces avertissements), soit il ne va jamais avertir (avec le même effet final : aucun avertissement;-).

Comparez cela à l'alternative "l'explicite est meilleur que l'implicite"... :

blah.format(name=name)

Il n'y a plus aucun souci d'entretien, de performance ou de peluches, c'est le bonheur ! Vous faites comprendre immédiatement à toutes les personnes concernées (peluches comprises;-) exactement ce que Les variables locales sont utilisées, et dans quel but exactement.

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

Donc, en résumant : " le connaître !" Hmm, je veux dire, "connais-toi toi-même !". Et par "toi-même", je veux dire "le but et la portée de ton code". S'il s'agit d'une chose unique, qui ne sera jamais traduite en anglais et en français, qui n'aura pas besoin de maintenance ultérieure, qui ne sera jamais réutilisée dans un contexte plus large, etc, etc. locals() Si vous savez autre chose, ou même si vous n'êtes pas tout à fait certain, optez pour la prudence et rendez les choses plus explicites - subissez le petit inconvénient d'expliquer exactement ce que vous allez faire, et profitez de tous les avantages qui en découlent.

D'ailleurs, ce n'est là qu'un des exemples où Python s'efforce de prendre en charge à la fois la programmation "petite, ponctuelle, exploratoire, voire interactive" (en autorisant et en prenant en charge des commodités hasardeuses qui vont bien au-delà de l'utilisation de l'ordinateur) et le développement de l'informatique. locals() -- pense à import * , eval , exec et bien d'autres façons de réduire en bouillie les espaces de noms et de risquer des impacts sur la maintenance pour des raisons de commodité), ainsi que des applications et des composants "importants, réutilisables et d'entreprise". Il peut faire du bon travail dans les deux cas, mais seulement si vous vous connaissez et si vous évitez d'utiliser les parties "pratiques" sauf si vous êtes absolument certain de pouvoir vous les offrir. 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 mainteneurs humains, etc ".

Rappelez-vous, "Les espaces de noms sont une idée géniale - faisons-en plus !" est la conclusion du Zen de Python... mais Python, en tant que "langage pour adultes consentants", laisse vous définir les limites de ce que cela implique, en fonction de votre environnement de développement, de vos objectifs et de vos pratiques. Utilisez ce pouvoir de manière responsable !-)

1 votes

Une excellente réponse. Je pense que la plupart des programmes ne sont pas internationalisés, et donc que ce n'est pas un problème dans un très grand nombre de cas. Mais dans ces cas, oui, l'interpolation de chaînes de caractères est mauvaise.

5 votes

@Paul, je ne suis pas d'accord : l'interpolation de chaînes de caractères est excellent , notamment pour le support i18n/L10n -- cela doit juste se faire sur des variables explicitement nommées ! Le problème n'est pas avec l'interpolation, c'est avec le passage de la variable locals() à des fonctions ou méthodes externes. De plus, la prise en charge croissante de l'Unicode par Python (la chaîne de caractères par défaut dans le langage 3.* ) est exactement une tentative d'aider à changer le fait que "la plupart des programmes ne sont pas i18n'd" -- beaucoup d'autres devrait être, qu'actuellement sont Au fur et à mesure que l'Internet (via les smartphones, les netbooks, etc.) se développe en Chine, les hypothèses anglocentriques deviennent de plus en plus étranges ;-).

2 votes

Je pense qu'il y a la possibilité de bricoler avec locales/gettext pour insérer {self} , {password} ou d'autres objets que vous ne souhaitez pas voir apparaître dans la chaîne de format. Cela peut constituer un risque pour la sécurité. Il est préférable d'être explicite pour le code réel

10voto

S.Lott Points 207588

Jamais de la vie. Le contexte du formatage n'est pas clair : locals pourrait inclure presque n'importe quelle variable. self.__dict__ n'est pas aussi vague. Parfaitement horrible pour 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 imposer à votre organisation des problèmes de maintenance futurs comme celui-là ?

0 votes

Le contexte est la fonction dans laquelle apparaît locals(). Si c'est une fonction courte et agréable, vous pouvez simplement regarder les variables. Si c'est une fonction très longue, elle doit être remaniée. En quoi self.__dict__ est-il plus clair ?

6 votes

Je ne comprends pas pourquoi le fait de référencer un nom de variable locale dans une chaîne de format n'est pas plus clair que le fait de référencer un nom de variable locale dans le code.

0 votes

self.__dict__ est basée sur la définition de la classe - qui est souvent liée à la __init__() et soigneusement documentée dans la chaîne de documents. locals() est souvent une collection de noms assez aléatoire.

10voto

Andrew Hare Points 159332

Je pense qu'il s'agit d'un excellent modèle, car vous tirez parti de la fonctionnalité intégrée pour réduire le code que vous devez écrire. Je le trouve personnellement assez pythonique.

Je n'écris jamais le 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.

0 votes

J'aime l'utiliser au début d'une fonction, lorsque j'ai besoin d'un dict construit à partir des paramètres d'entrée. C'est assez efficace et je pense que c'est aussi pythique. Il peut être mal utilisé parfois, je peux le comprendre.

10voto

pfctdayelise Points 1736

En ce qui concerne le "cousin", au lieu de obj.__dict__ il est beaucoup mieux avec le nouveau formatage des chaînes :

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

Je l'utilise beaucoup pour répression les méthodes, par exemple

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

8voto

RedGlyph Points 6046

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

  • étant plus lisible que "%0s".
  • étant indépendant de l'ordre des arguments
  • pas contraignant d'utiliser tous les arguments de la chaîne de caractères

J'aurais tendance à privilégier str.format(), puisque c'est ce qu'il faut faire en Python 3 (d'après le document PEP 3101 ), et est déjà disponible depuis la 2.6. Avec locals() cependant, vous devriez faire ça :

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

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X