Apakah pythonic untuk mengimpor fungsi di dalam?


126

PEP 8 mengatakan:

  • Impor selalu diletakkan di bagian atas file, tepat setelah komentar modul dan docstrings, dan sebelum global modul dan konstanta.

Kadang-kadang, saya melanggar PEP 8. Beberapa kali saya mengimpor barang di dalam fungsi. Sebagai aturan umum, saya melakukan ini jika ada impor yang hanya digunakan dalam satu fungsi.

Ada opini?

EDIT (alasan saya merasa mengimpor fungsi bisa menjadi ide yang bagus):

Alasan utama: Ini dapat membuat kode lebih jelas.

  • Saat melihat kode fungsi, saya mungkin bertanya pada diri sendiri: "Apa itu fungsi / kelas xxx?" (xxx digunakan di dalam fungsi). Jika saya memiliki semua impor saya di bagian atas modul, saya harus melihat ke sana untuk menentukan apa itu xxx. Ini lebih merupakan masalah saat menggunakan from m import xxx. Melihat m.xxxfungsinya mungkin memberi tahu saya lebih banyak. Tergantung pada apa mitu: Apakah itu modul / package ( import m) tingkat atas yang terkenal ? Atau apakah itu sub-modul / paket ( from a.b.c import m)?
  • Dalam beberapa kasus, memiliki informasi tambahan ("Apa itu xxx?") Yang dekat dengan tempat xxx digunakan dapat membuat fungsi lebih mudah dipahami.

2
dan Anda melakukannya untuk kinerja?
Macarse

4
Saya merasa itu membuat kode lebih jelas dalam beberapa kasus. Saya kira kinerja mentah turun saat mengimpor dalam suatu fungsi (karena pernyataan impor akan dijalankan setiap kali fungsi dipanggil).
codeape

Anda bisa menjawab "Apa itu function / class xxx?" dengan menggunakan sintaks import xyz daripada sintaks dari xyz import abc
Tom Leys

1
Jika kejelasan adalah satu-satunya faktor, U bisa juga memasukkan komentar yang relevan, untuk efek itu. ;)
Lakshman Prasad

5
@becomingGuru: Tentu, tetapi komentar bisa tidak sinkron dengan kenyataan ...
codeape

Jawaban:


88

Dalam jangka panjang, saya pikir Anda akan menghargai memiliki sebagian besar impor Anda di bagian atas file, dengan cara itu Anda dapat mengetahui sekilas betapa rumitnya modul Anda dengan apa yang perlu diimpor.

Jika saya menambahkan kode baru ke file yang sudah ada, saya biasanya akan melakukan impor di tempat yang diperlukan dan kemudian jika kode tetap ada, saya akan membuatnya lebih permanen dengan memindahkan baris impor ke bagian atas file.

Satu hal lagi, saya lebih suka mendapatkan ImportErrorpengecualian sebelum kode apa pun dijalankan - sebagai pemeriksaan kewarasan, jadi itulah alasan lain untuk mengimpor di bagian atas.

Saya gunakan pyCheckeruntuk memeriksa modul yang tidak digunakan.


47

Ada dua kesempatan di mana saya melanggar PEP 8 dalam hal ini:

  • Impor melingkar: modul A mengimpor modul B, tetapi sesuatu di modul B memerlukan modul A (meskipun ini sering kali merupakan tanda bahwa saya perlu merefaktor modul untuk menghilangkan ketergantungan melingkar)
  • Memasukkan breakpoint pdb: import pdb; pdb.set_trace()Ini berguna b / c Saya tidak ingin meletakkan import pdbdi bagian atas setiap modul yang mungkin ingin saya debug, dan mudah diingat untuk menghapus impor ketika saya menghapus breakpoint.

Di luar dua kasus ini, ada baiknya untuk menempatkan semuanya di atas. Itu membuat dependensi lebih jelas.


7
Saya setuju itu membuat dependensi lebih jelas berkaitan dengan modul secara keseluruhan. Tapi saya percaya itu bisa membuat kode kurang jelas di tingkat fungsi untuk mengimpor semua yang ada di atas. Saat Anda melihat kode suatu fungsi, Anda mungkin bertanya pada diri sendiri: "Apa itu function / class xxx?" (xxx digunakan di dalam fungsi). Dan Anda harus melihat di bagian paling atas file untuk melihat dari mana xxx berasal. Ini lebih merupakan masalah saat menggunakan dari m import xxx. Melihat m.xxx memberi tahu Anda lebih banyak - setidaknya jika tidak ada keraguan tentang apa itu m.
codeape

20

Berikut adalah empat kasus penggunaan impor yang kami gunakan

  1. import(dan from x import ydan import x as y) di atas

  2. Pilihan untuk Impor. Di atas.

    import settings
    if setting.something:
        import this as foo
    else:
        import that as foo
  3. Impor Bersyarat. Digunakan dengan JSON, pustaka XML dan sejenisnya. Di atas.

    try:
        import this as foo
    except ImportError:
        import that as foo
  4. Impor Dinamis. Sejauh ini, kami hanya memiliki satu contoh tentang ini.

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

    Perhatikan bahwa impor dinamis ini tidak membawa kode, tetapi membawa struktur data kompleks yang ditulis dengan Python. Ini seperti potongan data yang diawetkan kecuali kita membuat acar dengan tangan.

    Ini juga, kurang lebih, di bagian atas modul


Inilah yang kami lakukan untuk membuat kode lebih jelas:

  • Buat modul tetap pendek.

  • Jika saya memiliki semua impor saya di bagian atas modul, saya harus melihat ke sana untuk menentukan apa itu nama. Jika modulnya pendek, itu mudah dilakukan.

  • Dalam beberapa kasus, memiliki informasi tambahan yang dekat dengan tempat nama digunakan dapat membuat fungsi lebih mudah dipahami. Jika modulnya pendek, itu mudah dilakukan.


Menjaga modul tetap singkat tentu saja merupakan ide yang sangat bagus. Tetapi untuk mendapatkan keuntungan dari selalu memiliki "informasi impor" untuk fungsi yang tersedia, panjang modul maksimum harus menjadi satu layar (mungkin maks 100 baris). Dan itu mungkin terlalu singkat untuk menjadi praktis dalam banyak kasus.
codeape

Saya kira Anda dapat mengambil ini secara ekstrim logis. Saya pikir mungkin ada titik keseimbangan di mana modul Anda "cukup kecil" sehingga Anda tidak memerlukan teknik impor mewah untuk mengelola kompleksitas. Ukuran modul rata-rata kami - secara kebetulan - sekitar 100 baris.
S. Lotot

8

Satu hal yang perlu diingat: impor yang tidak perlu dapat menyebabkan masalah kinerja. Jadi jika ini adalah fungsi yang akan sering dipanggil, lebih baik Anda meletakkan impor di atas. Tentu saja ini adalah pengoptimalan, jadi jika ada kasus yang valid untuk dibuat bahwa mengimpor di dalam fungsi lebih jelas daripada mengimpor di bagian atas file, yang mengalahkan kinerja dalam banyak kasus.

Jika Anda melakukan IronPython, saya diberitahu bahwa lebih baik mengimpor fungsi di dalam (karena mengkompilasi kode di IronPython bisa lambat). Dengan demikian, Anda mungkin bisa mendapatkan cara dengan mengimpor fungsi di dalam. Tapi selain itu, saya berpendapat bahwa melawan konvensi itu tidak layak.

Sebagai aturan umum, saya melakukan ini jika ada impor yang hanya digunakan dalam satu fungsi.

Hal lain yang ingin saya sampaikan adalah bahwa ini mungkin masalah pemeliharaan potensial. Apa yang terjadi jika Anda menambahkan fungsi yang menggunakan modul yang sebelumnya hanya digunakan oleh satu fungsi? Apakah Anda akan ingat untuk menambahkan impor ke bagian atas file? Atau apakah Anda akan memindai setiap fungsi untuk impor?

FWIW, ada kasus di mana masuk akal untuk mengimpor di dalam fungsi. Misalnya, jika Anda ingin menyetel bahasa di cx_Oracle, Anda perlu menyetel _variabel lingkungan NLS LANG sebelum diimpor. Jadi, Anda mungkin melihat kode seperti ini:

import os

oracle = None

def InitializeOracle(lang):
    global oracle
    os.environ['NLS_LANG'] = lang
    import cx_Oracle
    oracle = cx_Oracle

2
Saya setuju dengan masalah pemeliharaan Anda. Kode refactoring bisa jadi sedikit bermasalah. Jika saya menambahkan fungsi kedua yang menggunakan modul yang sebelumnya hanya digunakan oleh satu fungsi - saya memindahkan impor ke atas, atau saya melanggar aturan umum saya sendiri dengan mengimpor modul di fungsi kedua juga.
codeape

2
Saya pikir argumen kinerja bisa sebaliknya juga. Mengimpor modul bisa memakan waktu. Pada sistem file terdistribusi seperti yang ada di superkomputer, mengimpor modul besar seperti numpy dapat memakan waktu beberapa detik. Jika sebuah modul hanya diperlukan untuk satu fungsi yang jarang digunakan, maka mengimpor fungsi tersebut akan mempercepat kasus umum secara signifikan.
amaurea

6

Saya telah melanggar aturan ini sebelumnya untuk modul yang menguji sendiri. Artinya, mereka biasanya hanya digunakan untuk dukungan, tetapi saya menetapkan yang utama untuk mereka sehingga jika Anda menjalankannya sendiri, Anda dapat menguji fungsionalitasnya. Dalam hal ini saya terkadang mengimpor getoptdan cmdhanya di main, karena saya ingin menjelaskan kepada seseorang yang membaca kode bahwa modul ini tidak ada hubungannya dengan operasi normal modul dan hanya disertakan untuk pengujian.


5

Berasal dari pertanyaan tentang memuat modul dua kali - Mengapa tidak keduanya?

Impor di bagian atas skrip akan menunjukkan ketergantungan dan impor lain dalam fungsi dengan membuat fungsi ini lebih atomic, sementara tampaknya tidak menyebabkan kerugian kinerja, karena impor berurutan itu murah.


3

Selama importdan tidak from x import *, Anda harus meletakkannya di atas. Ia hanya menambahkan satu nama ke namespace global, dan Anda tetap menggunakan PEP 8. Selain itu, jika nanti Anda membutuhkannya di tempat lain, Anda tidak perlu memindahkan apa pun.

Ini bukan masalah besar, tetapi karena hampir tidak ada perbedaan, saya sarankan untuk melakukan apa yang dikatakan PEP 8.


3
Sebenarnya, memasukkan ke from x import *dalam fungsi akan menghasilkan SyntaxWarning, setidaknya di 2.5.
Rick Copeland

3

Lihatlah pendekatan alternatif yang digunakan dalam sqlalchemy: injeksi ketergantungan:

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

Perhatikan bagaimana perpustakaan yang diimpor dideklarasikan di dekorator, dan diteruskan sebagai argumen ke fungsi !

Pendekatan ini membuat kode lebih bersih, dan juga bekerja 4,5 kali lebih cepat daripada sebuah importpernyataan!

Tolok ukur: https://gist.github.com/kolypto/589e84fbcfb6312532658df2fabdb796


2

Dalam modul yang merupakan modul 'normal' dan dapat dijalankan (yaitu memiliki if __name__ == '__main__':-section), saya biasanya mengimpor modul yang hanya digunakan saat menjalankan modul di dalam bagian utama.

Contoh:

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()

1

Ada kasus lain (mungkin "sudut") di mana mungkin bermanfaat importdi dalam fungsi yang jarang digunakan: mempersingkat waktu startup.

Saya menabrak tembok itu sekali dengan program yang agak kompleks yang berjalan di server IoT kecil yang menerima perintah dari jalur serial dan melakukan operasi, mungkin operasi yang sangat kompleks.

Menempatkan importpernyataan di atas file berarti semua impor diproses sebelum server dimulai; sejak importdaftar termasuk jinja2, lxml, signxmldan "berat bobot" lainnya (dan SoC tidak sangat kuat) ini berarti menit sebelum instruksi pertama benar-benar dieksekusi.

OTOH menempatkan sebagian besar impor dalam fungsi Saya dapat membuat server "hidup" pada baris serial dalam hitungan detik. Tentu saja ketika modul benar-benar dibutuhkan saya harus membayar harganya (Catatan: ini juga dapat dikurangi dengan menelurkan tugas latar belakang yang dilakukan importdalam waktu idle).

Dengan menggunakan situs kami, Anda mengakui telah membaca dan memahami Kebijakan Cookie dan Kebijakan Privasi kami.
Licensed under cc by-sa 3.0 with attribution required.