Ist "Importmodul" ein besserer Codierungsstil als "aus der Modulimportfunktion"?

68

Sei from module import functionder FMIF-Codierungsstil.

Nennen import modulewir den IM-Codierungsstil.

Nennen from package import modulewir den FPIM-Codierungsstil.

Warum wird IM + FPIM als besserer Codierungsstil als FMIF angesehen? (In diesem Beitrag finden Sie die Inspiration für diese Frage.)

Hier sind einige Kriterien, die mich dazu veranlassen, FMIF IM vorzuziehen:

  1. Kürze des Codes: Es ermöglicht mir, kürzere Funktionsnamen zu verwenden und so die Konvention von 80 Spalten pro Zeile einzuhalten.
  2. Lesbarkeit: chisquare(...)erscheint besser lesbar als scipy.stats.stats.chisquare(...). Obwohl dies ein subjektives Kriterium ist, denke ich, dass die meisten Menschen zustimmen würden.
  3. Einfache Umleitung: Wenn ich FMIF verwenden und aus irgendeinem Grunde zu einem späteren Zeitpunkt will Python umleiten zu definieren , functionaus alt_modulestatt moduleIch brauche nur eine Zeile zu ändern: from alt_module import function. Wenn ich IM verwenden würde, müsste ich viele Codezeilen ändern.
Mir ist klar, dass FPIM dazu beiträgt, die ersten beiden Probleme aufzuheben, aber was ist mit dem dritten?

Ich interessiere mich für alle Gründe, warum IM + FPIM besser als FMIF ist, aber insbesondere würde ich mich für die Ausarbeitung der folgenden hier genannten Punkte interessieren :

Vorteile für IM:

  1. Leichtes Verspotten / Injizieren in Tests. (Ich bin mit Spott nicht sehr vertraut, obwohl ich kürzlich erfahren habe, was der Begriff bedeutet. Können Sie Code anzeigen, der zeigt, wie IM hier besser ist als FMIF?)
  2. Möglichkeit für ein Modul, sich durch Neudefinition einiger Einträge flexibel zu ändern. (Ich muss etwas falsch verstehen, denn dies scheint ein Vorteil von FMIF gegenüber IM zu sein. Siehe meinen dritten Grund für FMIF oben.)
  3. vorhersehbares und kontrollierbares Verhalten bei der Serialisierung und Wiederherstellung Ihrer Daten. (Ich verstehe wirklich nicht, wie sich die Wahl von IM oder FMIF auf dieses Problem auswirkt. Bitte erläutern Sie dies.)
  4. Ich verstehe, dass FMIF "meinen Namespace verschmutzt", aber abgesehen davon, dass es sich um eine negativ klingende Phrase handelt, weiß ich nicht zu schätzen, wie sehr dies den Code auf konkrete Weise verletzt.
PS. Während ich diese Frage schrieb, erhielt ich eine Warnung, dass die Frage subjektiv erscheint und wahrscheinlich geschlossen wird. Bitte schließen Sie es nicht. Ich suche keine subjektive Meinung, sondern konkrete Codierungssituationen, in denen IM + FPIM nachweislich besser ist als FMIF.

Danke vielmals.

unutbu
quelle
2
Nebenbei bemerkt, obwohl es wirklich keine großen Auswirkungen auf Ihre Codierungspraktiken haben sollte, jedes '.' Punktaufruf in Python ist buchstäblich jedes Mal eine Operation, wenn Sie es tun. Wenn Sie also jemals etwas in einer Schleife tun, möchten Sie sicherstellen, dass Ihr lokaler Code so etwas wie rand = random.uniform ausführt, wenn Sie IM oder FPIM ausführen und die Effizienz ein Problem darstellt.
Namey

Antworten:

46

Die Negative, die Sie für IM / FPIM auflisten, können häufig durch geeignete Verwendung einer asKlausel verbessert werden . from some.package import mymodulewithalongname as mymodkann Ihren Code sinnvoll verkürzen und seine Lesbarkeit verbessern. Wenn Sie ihn mymodulewithalongnamein somethingcompletelydifferentmorgen umbenennen , kann die asKlausel als einzelne Anweisung zum Bearbeiten verwendet werden.

Betrachten Sie Ihren Pro-FMIF-Punkt 3 (nennen Sie ihn R für die Umleitung) und Ihren Pro-FPIM-Punkt 2 (nennen Sie ihn F für Flexibilität): R bedeutet, den Verlust der Integrität von Modulgrenzen zu erleichtern, während F ihn verstärkt. Mehrere Funktionen, Klassen und Variablen in einem Modul sollen häufig zusammenarbeiten: Sie sollten nicht unabhängig voneinander auf unterschiedliche Bedeutungen umgeschaltet werden. Betrachten Sie beispielsweise das Modul randomund seine Funktionen seedund uniform: Wenn Sie den Import nur eines Moduls auf ein anderes Modul umstellen würden, würden Sie die normale Verbindung zwischen Aufrufen von seedund Ergebnissen von Aufrufen von unterbrechen uniform. Wenn ein Modul gut gestaltet ist, mit Zusammenhalt und Integrität, ist die Erleichterung von R, die Grenzen des Moduls aufzubrechen, tatsächlich negativ - es macht es einfacher, etwas zu tun, das Ihnen besser gehtnicht tun.

Umgekehrt ist , was ermöglicht , F koordinierte Schalt gekoppelter Funktionen, Klassen und Variablen (so allgemein von Entitäten , die gehören zusammen, durch Modularität). Um beispielsweise das Testen wiederholbar zu machen (FPIM Pro-Punkt 1), verspotten Sie beide seedund randomdas randomModul. Wenn Ihr Code FPIM folgt, sind Sie fertig, die Koordination ist garantiert. Wenn Sie jedoch Code haben, der die Funktionen direkt importiert hat, müssen Sie jedes dieser Module suchen und das Verspotten immer und immer wieder wiederholen. Um Tests perfekt wiederholbar zu machen, ist normalerweise auch ein "koordiniertes Verspotten" von Datums- und Zeitfunktionen erforderlich. Wenn Sie sie from datetime import datetimein einigen Modulen verwenden, müssen Sie sie alle finden und verspotten (sowie alle, die dies tun)from time import timeusw.), um sicherzustellen, dass alle Zeiten empfangen werden, in denen die verschiedenen Teile des Systems fragen: "Wie spät ist es jetzt?" sind vollkommen konsistent (wenn Sie FPIM verwenden, verspotten Sie nur die beiden relevanten Module).

Ich mag FPIM, weil es wirklich nicht viel Mehrwert gibt, wenn ein mehrfach qualifizierter Name anstelle eines einfach qualifizierten Namens verwendet wird (während der Unterschied zwischen Barennamen und qualifizierten Namen sehr groß ist - Sie erhalten mit einem qualifizierten Namen so viel mehr Kontrolle, sei es einzeln oder multiplizieren, als Sie jemals mit einem Barennamen können!).

Na ja, ich kann nicht den ganzen Arbeitstag darauf verwenden, auf jeden Ihrer Punkte zu antworten - Ihre Frage sollte wahrscheinlich ein halbes Dutzend Fragen sein ;-). Ich hoffe, dass dies zumindest "Warum ist F besser als R" und einige der Verspottungs- / Testprobleme anspricht - es läuft darauf hinaus , gut gestaltete Modularität (über F) zu erhalten und zu verbessern, anstatt sie zu untergraben (über R).

Alex Martelli
quelle
random.seed wirkt sich auf random.uniform aus, aber FMIF ermöglicht es einem, sich zu ändern, seedohne sich zu ändern uniform. FMIF ermöglicht es dem Code, auf subtile Weise zu brechen, wenn Sie nicht vorsichtig sind! FPIM erfordert eine verwandte Funktion, um verwandt zu bleiben. Dies verhindert, dass der oben genannte Fehler jemals auftritt. Ah, ich verstehe endlich. Vielen Dank. Mann, ich muss viel Code umschreiben ... :)
unutbu
Beim zweiten Gedanken kann ich ein Python-Skript schreiben, um die Arbeit für mich zu erledigen!
Unutbu
1
@unutbu, ja, besonders wenn Ihr Lieblingseditor leistungsstark und Python-skriptfähig ist (z. B. vim), können einige "Massenbearbeitungen" sinnvoll automatisiert werden (stellen Sie jedoch sicher, dass Sie alle Unit-Tests anschließend erneut ausführen ;-).
Alex Martelli
15
Ich stelle fest, dass Ihre Antwort FMIF verwendet : from question.FMIF import point_2 as F, point_3 as R.
Intuitiert
Gute Antwort. Ein bisschen dicht vielleicht? In dieser Antwort zu "Best Practice importieren" finden Sie eine einfachere Formulierung der meisten dieser Punkte.
Lutz Prechelt
18

Der klassische Text dazu stammt, wie so oft, von Fredrik Lundh, dem Effbot. Sein Rat: Verwenden Sie immer Import - außer wenn Sie nicht sollten.

Mit anderen Worten, sei vernünftig. Persönlich finde ich, dass alles, was mehrere Module tief ist, über importiert wird from x.y.z import a- das Hauptbeispiel sind Django-Modelle. Aber wie alles andere ist es eine Frage des Stils, und Sie sollten eine konsistente haben - insbesondere bei Modulen wie datetime, bei denen sowohl das Modul als auch die darin enthaltene Klasse dasselbe heißen. Müssen Sie schreiben datetime.datetime.now()oder nur datetime.now()? (In meinem Code immer der erstere.)

Die Punkte 1 und 2 in Ihrer Fragenliste scheinen dasselbe Problem zu sein. Aufgrund der dynamischen Natur von Python ist es ziemlich einfach, ein Element im Namespace eines Moduls zu ersetzen, unabhängig davon, welche der Methoden Sie verwenden. Die Schwierigkeit tritt auf, wenn sich eine Funktion in einem Modul auf eine andere bezieht, die Sie verspotten möchten. In diesem Fall bedeutet der Import des Moduls anstelle der Funktionen, dass Sie dies tun können module.function_to_replace = myreplacementfuncund alles transparent funktioniert - dies ist jedoch über FPIM genauso einfach wie über IM.

Ich verstehe auch nicht, wie Punkt 3 irgendetwas mit irgendetwas zu tun hat. Ich denke, Ihr Punkt 4 basiert jedoch auf einem Missverständnis. Keine der von Ihnen angegebenen Methoden verschmutzt Ihren Namespace. Das bedeutet , dass from module import *Sie überhaupt keine Ahnung haben, was Sie importieren, und dass Funktionen in Ihrem Code angezeigt werden können, ohne dass dem Leser ein Hinweis gegeben wird, woher sie stammen. Das ist schrecklich und sollte unter allen Umständen vermieden werden.

Daniel Roseman
quelle
In fast allen meinen Skripten habe ich, import datetime, now = datetime.datetime.nowdamit ich es bequem tun kann print(now(), 'reading file X')...
gerrit
@gerrit: Normalerweise keine gute Idee, wenn Sie mehr als 1 oder 2 Funktionen eines Moduls verwenden: Es verschmutzt den Namespace und zwingt Sie jedes Mal, von der Code- zur Moduldeklaration hin und her zu wechseln, wenn Sie fragen, woher diese somefunc()in Zeile 2345 stammt. ""
MestreLion
@MestreLion Ich mache es für genau eine Funktion (Klassenmethode) und das ist datetime.datetime.now. Ansonsten verwende ich from X import Ynur für Pakete, so dass Ynoch ein Modul ist.
Gerrit
@gerrit: Ich habe es nie datetime.dametime.now()oft genug benutzt, um es nervig zu finden oder eine Ausnahme zu rechtfertigen. Aber wenn man darüber nachdenkt, ist es schade, dass der "kanonische" Weg, die aktuelle Zeit zu erhalten, in Python
MestreLion
6

Tolle Antworten hier (ich habe sie alle positiv bewertet), und hier sind meine Gedanken zu diesem Thema:

Adressieren Sie zunächst jede Ihrer Kugeln:

(Angeblich) Vorteile von FMIF:

  • Kürze des Codes: Kürzere Funktionsnamen helfen dabei, die 80 Spalten pro Zeile einzuhalten.

Vielleicht, aber Modulnamen sind normalerweise kurz genug, so dass dies nicht relevant ist. Sicher, es ist datetimeaber auch os, re, sysetc. Und Python hat innerhalb freie Zeilenumbrüche { [ (. Und für verschachtelte Module gibt es immer assowohl IM als auch FPIM

  • Lesbarkeit: chisquare (...) erscheint besser lesbar als scipy.stats.stats.chisquare (...).

Entschieden widersprechen. Beim Lesen von Fremdcode (oder meines eigenen Codes nach einigen Monaten) ist es schwierig zu wissen, woher die einzelnen Funktionen stammen. Qualifizierte Namen ersparen mir das Hin- und Hergehen von Zeile 2345 zum Moduldeklarationsheader. Und es gibt Ihnen auch Kontext : " chisquare? Was ist das? Oh, es ist von scypy? Ok, dann ein paar mathematische Sachen" . Und noch einmal, Sie können immer abkürzen scipy.stats.stats as scypyst. scypyst.chisquare(...)ist kurz genug mit allen Vorteilen eines qualifizierten Namens.

import os.path as osp ist ein weiteres gutes Beispiel, wenn man bedenkt, dass es sehr häufig vorkommt, 3 oder mehr seiner Funktionen in einem einzigen Aufruf zu verketten: join (expanduser (), basename (splitext ())) usw.

  • Einfache Umleitung: Einzeilige Neudefinition einer Funktion aus dem Altmodul anstelle des Moduls.

Wie oft möchten Sie eine einzelne Funktion, aber nicht das gesamte Modul neu definieren? Modulgrenzen und Funktionskoordination sollten erhalten bleiben, und Alex hat dies bereits ausführlich erklärt. Für die meisten (alle?) Real-World-Szenarien ist, wenn dies alt_module.xein praktikabler Ersatz ist module.x, wahrscheinlich alt_moduleselbst ein Rückgang der Alternative für module, sodass sowohl IM als auch FPIM wie FMIF Einzeiler sind, vorausgesetzt, Sie verwenden as.

  • Mir ist klar, dass FPIM dazu beiträgt, die ersten beiden Probleme aufzuheben ...

Tatsächlich asist es dasjenige, das die ersten beiden Probleme (und das dritte) mildert, nicht FPIM. Sie können IM auch dafür verwenden: import some.long.package.path.x as xfür das gleiche Ergebnis wie FPIM.


Keiner der oben genannten ist wirklich ein Profi von FMIF. Und die Gründe, warum ich IM / FPIM bevorzuge, sind:

Der Einfachheit und Konsistenz halber importiere ich beim Importieren von IM oder FPIM immer ein Modul , kein Objekt aus einem Modul. Denken Sie daran, dass FMIF (ab-) zum Importieren von Funktionen, Klassen, Variablen oder sogar anderen Modulen verwendet werden kann! Denken Sie an das Durcheinander von from somemodule import sys, somevar, os, SomeClass, datetime, someFunc.

Wenn Sie mehr als ein einzelnes Objekt von einem Modul möchten, verschmutzt FMIF Ihren Namespace stärker als IM oder FPIM, das einen einzelnen Namen verwendet, unabhängig davon, wie viele Objekte Sie verwenden möchten. Und solche Objekte haben einen qualifizierten Namen, der ein Vorteil und kein Nachteil ist: Wie ich in Ausgabe 2 gesagt habe, verbessert IMHO a die Lesbarkeit.

Alles hängt von Konsistenz, Einfachheit und Organisation ab. "Module importieren, keine Objekte" ist ein gutes, einfaches Denkmodell.

MestreLion
quelle
5

Wie Alex Martelli verwende ich gerne asbeim Importieren einer Funktion.

Eine Sache, die ich getan habe, ist, ein Präfix für alle Funktionen zu verwenden, die aus demselben Modul importiert wurden:

from random import seed as r_seed
from random import random as r_random

r_seedist kürzer zu tippen als random.seed, behält aber die Modulgrenzen etwas bei. Jemand, der beiläufig auf Ihren Code schaut, kann sehen r_seed()und r_random()haben die Chance zu erkennen, dass sie verwandt sind.

Natürlich können Sie immer einfach tun:

import random as r

und dann verwenden Sie r.random()und r.seed(), was der ideale Kompromiss für diesen Fall sein kann. Ich verwende den Präfixtrick nur, wenn ich eine oder zwei Funktionen aus einem Modul importiere. Wenn ich viele Funktionen aus demselben Modul verwenden möchte, importiere ich das Modul, möglicherweise mit einem as, um den Namen zu verkürzen.

steveha
quelle
1

Ich stimme MestreLion hier am meisten zu (und damit eine Gegenstimme).

Meine Perspektive: Ich überprüfe häufig Code, mit dem ich nicht vertraut bin, und es ist ziemlich frustrierend, nicht zu wissen, aus welchem ​​Modul eine Funktion stammt, wenn ich nur die Funktion betrachte.

Code wird einmal geschrieben und viele Male gelesen, sodass Lesbarkeit und Wartbarkeit die einfache Eingabe vereinfachen.

In ähnlicher Weise wird Code normalerweise nicht zum Nutzen des Codierers geschrieben, sondern zum Nutzen einer anderen Entität.

Ihr Code sollte für jemanden lesbar sein, der Python besser kennt als Sie, aber mit dem Code nicht vertraut ist.

Vollständige Pfadimporte können IDE auch dabei helfen, Sie auf die richtige Quelle der Funktion oder des Objekts zu verweisen, die Sie betrachten.

Aus all diesen und den von MestreLion festgestellten Gründen komme ich zu dem Schluss, dass es die beste Vorgehensweise ist, den vollständigen Pfad zu importieren und zu verwenden.

Aaron Hall
quelle