Ist es pythonisch, in Funktionen zu importieren?

126

PEP 8 sagt:

  • Importe werden immer am Anfang der Datei platziert, direkt nach Modulkommentaren und Dokumentzeichenfolgen sowie vor Modulglobalen und -konstanten.

Gelegentlich verstoße ich gegen PEP 8. Manchmal importiere ich Dinge in Funktionen. In der Regel mache ich das, wenn es einen Import gibt, der nur innerhalb einer einzelnen Funktion verwendet wird.

Irgendwelche Meinungen?

BEARBEITEN (der Grund, warum ich das Importieren in Funktionen für eine gute Idee halte):

Hauptgrund: Es kann den Code klarer machen.

  • Wenn ich mir den Code einer Funktion anschaue, frage ich mich vielleicht: "Was ist Funktion / Klasse xxx?" (xxx wird innerhalb der Funktion verwendet). Wenn ich alle meine Importe oben im Modul habe, muss ich dort nachsehen, was xxx ist. Dies ist eher ein Problem bei der Verwendung from m import xxx. Das Sehen m.xxxin der Funktion sagt mir wahrscheinlich mehr. Je nachdem, was mist: Ist es ein bekanntes Top-Level-Modul / Paket ( import m)? Oder ist es ein Untermodul / Paket ( from a.b.c import m)?
  • In einigen Fällen kann das Verständnis der Funktion erleichtert werden, wenn diese zusätzlichen Informationen ("Was ist xxx?") In der Nähe des Verwendungsortes von xxx verwendet werden.
Codeape
quelle
2
und du machst das für die leistung?
Macarse
4
Ich denke, es macht den Code in einigen Fällen klarer. Ich würde vermuten, dass die Rohleistung beim Importieren in eine Funktion sinkt (da die import-Anweisung jedes Mal ausgeführt wird, wenn die Funktion aufgerufen wird).
Codeape
Sie können antworten "Was ist Funktion / Klasse xxx?" durch Verwendung der Import-XYZ-Syntax anstelle der Von-XYZ-Import-Abc-Syntax
Tom Leys
1
Wenn Klarheit der einzige Faktor ist, könnte U auch einen entsprechenden entsprechenden Kommentar hinzufügen. ;)
Lakshman Prasad
5
@becomingGuru: Sicher, aber Kommentare können nicht mehr mit der Realität
übereinstimmen

Antworten:

88

Auf lange Sicht werden Sie es zu schätzen wissen, dass die meisten Ihrer Importe ganz oben in der Datei stehen. Auf diese Weise können Sie auf einen Blick erkennen, wie kompliziert Ihr Modul ist, wenn es importiert werden muss.

Wenn ich einer vorhandenen Datei neuen Code hinzufüge, führe ich den Import normalerweise dort durch, wo er benötigt wird. Wenn der Code erhalten bleibt, mache ich die Dinge dauerhafter, indem ich die Importzeile an den Anfang der Datei verschiebe.

Ein weiterer Punkt, ich bevorzuge es, eine ImportErrorAusnahme zu erhalten, bevor Code ausgeführt wird - als Überprüfung der Integrität. Dies ist also ein weiterer Grund, oben zu importieren.

Ich benutze pyChecker, um nach nicht verwendeten Modulen zu suchen.

Peter Ericson
quelle
47

Es gibt zwei Fälle, in denen ich diesbezüglich gegen PEP 8 verstoße:

  • Zirkuläre Importe: Modul A importiert Modul B, aber etwas in Modul B benötigt Modul A (obwohl dies oft ein Zeichen dafür ist, dass ich die Module umgestalten muss, um die zirkuläre Abhängigkeit zu beseitigen).
  • Einfügen eines PDF-Haltepunkts: import pdb; pdb.set_trace()Dies ist praktisch, da ich nicht import pdbjedes Modul, das ich debuggen möchte, an die Spitze setzen möchte, und es ist leicht zu merken, den Import zu entfernen, wenn ich den Haltepunkt entferne.

Außerhalb dieser beiden Fälle ist es eine gute Idee, alles an die Spitze zu setzen. Es macht die Abhängigkeiten klarer.

Rick Copeland
quelle
7
Ich bin damit einverstanden, dass Abhängigkeiten in Bezug auf das gesamte Modul klarer werden. Aber ich glaube, dass es den Code auf Funktionsebene weniger klar machen kann, alles oben zu importieren. Wenn Sie sich den Code einer Funktion ansehen, fragen Sie sich möglicherweise: "Was ist Funktion / Klasse xxx?" (xxx wird innerhalb der Funktion verwendet). Und Sie müssen ganz oben in der Datei nachsehen, woher xxx kommt. Dies ist eher ein Problem bei der Verwendung von m import xxx. Wenn Sie m.xxx sehen, erfahren Sie mehr - zumindest, wenn kein Zweifel darüber besteht, was m ist.
Codeape
20

Hier sind die vier Importanwendungsfälle, die wir verwenden

  1. import(und from x import yund import x as y) oben

  2. Auswahlmöglichkeiten für den Import. Oben.

    import settings
    if setting.something:
        import this as foo
    else:
        import that as foo
  3. Bedingter Import. Wird mit JSON, XML-Bibliotheken und dergleichen verwendet. Oben.

    try:
        import this as foo
    except ImportError:
        import that as foo
  4. Dynamischer Import. Bisher haben wir nur ein Beispiel dafür.

    import settings
    module_stuff = {}
    module= __import__( settings.some_module, module_stuff )
    x = module_stuff['x']

    Beachten Sie, dass dieser dynamische Import keinen Code einbringt, sondern komplexe Datenstrukturen, die in Python geschrieben wurden. Es ist wie ein eingelegtes Datenelement, nur dass wir es von Hand eingelegt haben.

    Dies befindet sich mehr oder weniger auch oben in einem Modul


Folgendes tun wir, um den Code klarer zu gestalten:

  • Halten Sie die Module kurz.

  • Wenn ich alle meine Importe oben im Modul habe, muss ich dort nachsehen, um festzustellen, was ein Name ist. Wenn das Modul kurz ist, ist das einfach.

  • In einigen Fällen kann das Verständnis der Funktion erleichtert werden, wenn diese zusätzlichen Informationen in der Nähe des Verwendungsortes eines Namens vorhanden sind. Wenn das Modul kurz ist, ist das einfach.

S.Lott
quelle
Es ist natürlich eine sehr gute Idee, die Module kurz zu halten. Um jedoch den Vorteil zu haben, dass immer "Importinformationen" für Funktionen verfügbar sind, müsste die maximale Modullänge einen Bildschirm betragen (wahrscheinlich maximal 100 Zeilen). Und das wäre wahrscheinlich zu kurz, um in den meisten Fällen praktisch zu sein.
Codeape
Ich nehme an, Sie könnten dies auf ein logisches Extrem bringen. Ich denke, es könnte einen Gleichgewichtspunkt geben, an dem Ihr Modul "klein genug" ist, dass Sie keine ausgefallenen Importtechniken benötigen, um die Komplexität zu verwalten. Unsere durchschnittliche Modulgröße beträgt zufällig etwa 100 Zeilen.
S.Lott
8

Eines ist zu beachten: Unnötige Importe können zu Leistungsproblemen führen. Wenn dies also eine Funktion ist, die häufig aufgerufen wird, ist es besser, den Import ganz oben zu platzieren. Dies ist natürlich eine Optimierung. Wenn also ein gültiger Fall vorliegt, dass das Importieren innerhalb einer Funktion klarer ist als das Importieren am Anfang einer Datei, übertrifft dies in den meisten Fällen die Leistung.

Wenn Sie IronPython ausführen, wird mir gesagt, dass es besser ist, interne Funktionen zu importieren (da das Kompilieren von Code in IronPython langsam sein kann). Auf diese Weise können Sie möglicherweise einen Weg zum Importieren von Inside-Funktionen finden. Aber anders als das würde ich argumentieren, dass es sich einfach nicht lohnt, gegen Konventionen zu kämpfen.

In der Regel mache ich das, wenn es einen Import gibt, der nur innerhalb einer einzelnen Funktion verwendet wird.

Ein weiterer Punkt, den ich ansprechen möchte, ist, dass dies ein potenzielles Wartungsproblem sein kann. Was passiert, wenn Sie eine Funktion hinzufügen, die ein Modul verwendet, das zuvor nur von einer Funktion verwendet wurde? Denken Sie daran, den Import oben in die Datei einzufügen? Oder scannen Sie jede einzelne Funktion nach Importen?

FWIW, es gibt Fälle, in denen es sinnvoll ist, innerhalb einer Funktion zu importieren. Wenn Sie beispielsweise die Sprache in cx_Oracle festlegen möchten, müssen Sie vor dem Import eine NLS _LANG-Umgebungsvariable festlegen . Daher sehen Sie möglicherweise Code wie folgt:

import os

oracle = None

def InitializeOracle(lang):
    global oracle
    os.environ['NLS_LANG'] = lang
    import cx_Oracle
    oracle = cx_Oracle
Jason Baker
quelle
2
Ich stimme Ihrem Wartungsproblem zu. Refactoring-Code kann etwas problematisch sein. Wenn ich eine zweite Funktion hinzufüge, die ein Modul verwendet, das zuvor nur von einer Funktion verwendet wurde, verschiebe ich entweder den Import nach oben oder verstoße gegen meine eigene allgemeine Regel, indem ich das Modul auch in die zweite Funktion importiere.
Codeape
2
Ich denke, das Leistungsargument kann auch in die andere Richtung gehen. Das Importieren eines Moduls kann zeitaufwändig sein. Auf verteilten Dateisystemen wie Supercomputern kann das Importieren eines großen Moduls wie numpy einige Sekunden dauern. Wenn ein Modul nur für eine einzelne, selten verwendete Funktion benötigt wird, beschleunigt der Import in die Funktion den allgemeinen Fall erheblich.
Amaurea
6

Ich habe diese Regel schon einmal für Module gebrochen, die sich selbst testen. Das heißt, sie werden normalerweise nur zur Unterstützung verwendet, aber ich definiere eine Hauptleitung für sie, damit Sie ihre Funktionalität testen können, wenn Sie sie selbst ausführen. In diesem Fall importiere ich manchmal getoptund cmdnur in der Hauptsache, weil ich möchte, dass jemandem, der den Code liest, klar ist, dass diese Module nichts mit dem normalen Betrieb des Moduls zu tun haben und nur zum Testen enthalten sind.

Dan Lew
quelle
5

Aus der Frage nach dem zweimaligen Laden des Moduls - Warum nicht beides?

Ein Import am oberen Rand des Skripts zeigt die Abhängigkeiten an, und ein weiterer Import in der Funktion macht diese Funktion atomarer, ohne anscheinend einen Leistungsnachteil zu verursachen, da ein aufeinanderfolgender Import billig ist.

IljaBek
quelle
3

Solange es ist importund nicht from x import *, sollten Sie sie oben platzieren. Es fügt dem globalen Namespace nur einen Namen hinzu, und Sie bleiben bei PEP 8. Wenn Sie ihn später an einem anderen Ort benötigen, müssen Sie nichts verschieben.

Es ist keine große Sache, aber da es fast keinen Unterschied gibt, würde ich vorschlagen, das zu tun, was PEP 8 sagt.

Javier
quelle
3
Tatsächlich setzen from x import *wird in einer Funktion eines SyntaxWarning erzeugen, zumindest 2,5 beträgt.
Rick Copeland
3

Schauen Sie sich den alternativen Ansatz an, der in der SQLalchemie verwendet wird: Abhängigkeitsinjektion:

@util.dependencies("sqlalchemy.orm.query")
def merge_result(query, *args):
    #...
    query.Query(...)

Beachten Sie, wie die importierte Bibliothek in einem Dekorator deklariert und als Argument an die Funktion übergeben wird !

Dieser Ansatz macht den Code sauberer und funktioniert auch 4,5-mal schneller als eine importAnweisung!

Benchmark: https://gist.github.com/kolypto/589e84fbcfb6312532658df2fabdb796

kolypto
quelle
2

In Modulen, die beide 'normale' Module sind und ausgeführt werden können (dh einen if __name__ == '__main__':Abschnitt haben), importiere ich normalerweise Module, die nur verwendet werden, wenn das Modul innerhalb des Hauptabschnitts ausgeführt wird.

Beispiel:

def really_useful_function(data):
    ...


def main():
    from pathlib import Path
    from argparse import ArgumentParser
    from dataloader import load_data_from_directory

    parser = ArgumentParser()
    parser.add_argument('directory')
    args = parser.parse_args()
    data = load_data_from_directory(Path(args.directory))
    print(really_useful_function(data)


if __name__ == '__main__':
    main()
Codeape
quelle
1

Es gibt einen anderen (wahrscheinlich "Eck") Fall, in dem es für importselten verwendete Funktionen von Vorteil sein kann: Verkürzung der Startzeit.

Ich bin einmal mit einem ziemlich komplexen Programm auf diese Wand gestoßen, das auf einem kleinen IoT-Server ausgeführt wird und Befehle von einer seriellen Leitung akzeptiert und Operationen ausführt, möglicherweise sehr komplexe Operationen.

Platzieren von importAnweisungen oben in Dateien, damit alle Importe vor dem Start des Servers verarbeitet werden. da importListe aufgenommen jinja2, lxml, signxmlund andere „schwere Gewichte“ (und SoC war nicht sehr mächtig) Das bedeutete Minuten vor der ersten Anweisung tatsächlich ausgeführt.

OTOH platzierte die meisten Importe in Funktionen. Ich konnte den Server in Sekundenschnelle auf der seriellen Leitung "am Leben" halten. Als die Module tatsächlich benötigt wurden, musste ich natürlich den Preis bezahlen (Hinweis: Dies kann auch durch das Laichen einer Hintergrundaufgabe, die importin Leerlaufzeit ausgeführt wird , gemildert werden ).

ZioByte
quelle