Otomasi impor Python

UntukSetelah
import math import os.path import requests # 100500 other imports print(math.pi) print(os.path.join('my', 'path')) print(requests.get) 
 import smart_imports smart_imports.all() print(math.pi) print(os_path.join('my', 'path')) print(requests.get) 
Kebetulan bahwa sejak 2012 saya telah mengembangkan browser open source, menjadi satu-satunya programmer. Dengan Python dengan sendirinya. Peramban bukanlah hal yang termudah, sekarang di bagian utama proyek ini terdapat lebih dari 1000 modul dan lebih dari 120.000 baris kode Python. Secara total, ini akan menjadi satu setengah kali lebih banyak dengan proyek satelit.

Pada titik tertentu, saya bosan mengotak-atik lantai impor di awal setiap file, dan saya memutuskan untuk menangani masalah ini untuk selamanya. Jadi perpustakaan smart_import lahir ( github , pypi ).

Idenya cukup sederhana. Setiap proyek kompleks pada akhirnya membentuk perjanjiannya sendiri untuk menamai semuanya. Jika perjanjian ini diubah menjadi aturan yang lebih formal, maka setiap entitas dapat diimpor secara otomatis dengan nama variabel yang terkait dengannya.

Misalnya, Anda tidak perlu menulis import math untuk mengakses math.pi - kita math.pi dapat memahami bahwa dalam hal ini math adalah modul perpustakaan standar.

Impor pintar mendukung Python> = 3.5. Perpustakaan sepenuhnya dicakup oleh tes, cakupan> 95% . Saya sudah menggunakannya sendiri selama satu tahun sekarang.

Untuk detailnya, saya mengundang Anda ke Cat.

Bagaimana cara kerjanya secara umum?


Jadi, kode dari gambar header berfungsi sebagai berikut:

  1. Selama panggilan ke smart_imports.all() perpustakaan membangun AST dari modul dari mana panggilan itu dibuat;
  2. Temukan variabel yang tidak diinisialisasi;
  3. Kami menjalankan nama masing-masing variabel melalui urutan aturan yang mencoba menemukan modul (atau atribut modul) yang diperlukan untuk impor berdasarkan nama. Jika aturan telah menemukan entitas yang diperlukan, aturan berikut ini tidak dicentang.
  4. Modul yang ditemukan dimuat, diinisialisasi dan ditempatkan di namespace global (atau atribut yang diperlukan dari modul ini ditempatkan di sana).

Variabel yang tidak diinisialisasi dicari di seluruh kode, termasuk sintaks baru.

Impor otomatis diaktifkan hanya untuk komponen proyek yang secara eksplisit memanggil smart_imoprts.all() . Selain itu, penggunaan impor pintar tidak melarang penggunaan impor konvensional. Ini memungkinkan Anda untuk mengimplementasikan perpustakaan secara bertahap, serta menyelesaikan dependensi siklik yang rumit.

Pembaca yang teliti akan memperhatikan bahwa modul AST dibangun dua kali:

  • CPython membangunnya untuk pertama kali selama impor modul;
  • Kali kedua smart_import membangunnya saat panggilan ke smart_imports.all() .

AST benar-benar dapat dibangun hanya sekali (untuk ini Anda perlu mengintegrasikan ke dalam proses impor modul menggunakan kait impor diimplementasikan dalam PEP-0302 , tetapi solusi ini memperlambat impor.

Mengapa menurut Anda begitu?
Membandingkan kinerja dua implementasi (dengan dan tanpa kait), saya sampai pada kesimpulan bahwa ketika mengimpor modul, CPython membangun AST dalam struktur data internal (C-shh). Mengubahnya ke struktur data Python lebih mahal daripada membangun pohon dari sumber menggunakan modul ast .

Tentu saja, AST dari setiap modul dibangun dan dianalisis hanya sekali per peluncuran.

Aturan Impor Default


Perpustakaan dapat digunakan tanpa konfigurasi tambahan. Secara default, itu mengimpor modul sesuai dengan aturan berikut:

  1. Secara kebetulan, nama itu mencari modul di sebelah yang sekarang (di direktori yang sama).
  2. Periksa modul perpustakaan standar:
    • dengan pencocokan tepat nama untuk paket tingkat atas;
    • untuk paket dan modul bersarang, periksa nama majemuk, ganti titik dengan garis bawah. Misalnya, os.path akan diimpor jika variabel os_path .
  3. Dengan pencocokan nama yang tepat, ia mencari paket pihak ketiga yang diinstal. Misalnya, permintaan paket terkenal.

Performa


Impor pintar tidak memengaruhi kinerja program, tetapi meningkatkan waktu yang diperlukan untuk memulai.

Karena pembangunan kembali AST, waktu lari pertama meningkat sekitar 1,5-2 kali. Untuk proyek kecil ini tidak signifikan. Dalam proyek-proyek besar, waktu start-up menderita dari struktur ketergantungan antara modul daripada dari waktu impor modul tertentu.

Ketika impor pintar menjadi populer, saya menulis ulang pekerjaan dari AST ke C - ini akan secara signifikan mengurangi biaya startup.

Untuk mempercepat pemuatan, hasil pemrosesan modul AST dapat di-cache pada sistem file. Caching diaktifkan di konfigurasi. Tentu saja, cache dinonaktifkan ketika Anda mengubah sumbernya.

Waktu start-up dipengaruhi oleh daftar aturan pencarian modul dan urutannya. Karena beberapa aturan menggunakan fungsionalitas Python standar untuk mencari modul. Anda dapat mengecualikan pengeluaran ini dengan secara eksplisit menunjukkan korespondensi nama dan modul menggunakan aturan "Nama Khusus" (lihat di bawah).

Konfigurasi


Konfigurasi default telah dijelaskan sebelumnya. Seharusnya cukup untuk bekerja dengan perpustakaan standar dalam proyek-proyek kecil.

Konfigurasi default
 { "cache_dir": null, "rules": [{"type": "rule_local_modules"}, {"type": "rule_stdlib"}, {"type": "rule_predefined_names"}, {"type": "rule_global_modules"}] } 


Jika perlu, konfigurasi yang lebih kompleks dapat diletakkan pada sistem file.

Contoh konfigurasi kompleks (dari browser).

Selama panggilan ke smart_import.all() perpustakaan menentukan posisi modul panggilan pada sistem file dan mulai mencari file smart_imports.json dalam arah dari direktori saat ini ke root. Jika file tersebut ditemukan, itu dianggap sebagai konfigurasi untuk modul saat ini.

Anda dapat menggunakan beberapa konfigurasi berbeda (menempatkannya di direktori yang berbeda).

Tidak ada banyak opsi konfigurasi sekarang:

 { //     AST. //     null —   . "cache_dir": null|"string", //       . "rules": [] } 

Aturan Impor


Urutan menentukan aturan dalam konfigurasi menentukan urutan aplikasi mereka. Aturan pertama yang berfungsi menghentikan pencarian lebih lanjut untuk impor.

Dalam contoh-contoh konfigurasi, aturan rule_predefined_names akan sering muncul di rule_predefined_names , perlu bahwa fungsi bawaan (misalnya, print ) dikenali dengan benar.

Aturan 1: Nama yang Ditentukan sebelumnya


Aturan ini memungkinkan Anda untuk mengabaikan nama yang telah ditentukan seperti __file__ dan fungsi __file__ seperti print .

Contoh
 # : # { # "rules": [{"type": "rule_predefined_names"}] # } import smart_imports smart_imports.all() #        __file__ #        print(__file__) 

Aturan 2: Modul Lokal


Cek apakah ada modul dengan nama yang ditentukan di sebelah modul saat ini (di direktori yang sama). Jika ada, impor saja.

Contoh
 # : # { # "rules": [{"type": "rule_predefined_names"}, # {"type": "rule_local_modules"}] # } # #    : # # my_package # |-- __init__.py # |-- a.py # |-- b.py # b.py import smart_imports smart_imports.all() #    "a.py" print(a) 

Aturan 3: Modul Global


Mencoba mengimpor modul secara langsung dengan nama. Misalnya, modul permintaan .

Contoh
 # : # { # "rules": [{"type": "rule_predefined_names"}, # {"type": "rule_global_modules"}] # } # #    # # pip install requests import smart_imports smart_imports.all() #    requests print(requests.get('http://example.com')) 

Aturan 4: Nama Khusus


Sesuai dengan nama modul tertentu atau atributnya. Kepatuhan ditunjukkan dalam aturan konfigurasi.

Contoh
 # : # { # "rules": [{"type": "rule_predefined_names"}, # {"type": "rule_custom", # "variables": {"my_import_module": {"module": "os.path"}, # "my_import_attribute": {"module": "random", "attribute": "seed"}}}] # } import smart_imports smart_imports.all() #       #        print(my_import_module) print(my_import_attribute) 

Aturan 5: Modul Standar


Periksa apakah namanya adalah modul perpustakaan standar. Misalnya matematika atau os.path yang berubah menjadi os_path .

Ini bekerja lebih cepat daripada aturan untuk mengimpor modul global, karena memeriksa keberadaan modul pada daftar yang di-cache. Daftar untuk setiap versi Python berasal dari sini: github.com/jackmaney/python-stdlib-list

Contoh
 # : # { # "rules": [{"type": "rule_predefined_names"}, # {"type": "rule_stdlib"}] # } import smart_imports smart_imports.all() print(math.pi) 

Aturan 6: Impor dengan Awalan


Mengimpor modul dengan nama, dari paket yang terkait dengan awalannya. Lebih mudah digunakan ketika Anda memiliki beberapa paket yang digunakan di seluruh kode. Sebagai contoh, modul paket utils dapat diakses dengan awalan utils_ .

Contoh
 # : # { # "rules": [{"type": "rule_predefined_names"}, # {"type": "rule_prefix", # "prefixes": [{"prefix": "utils_", "module": "my_package.utils"}]}] # } # #    : # # my_package # |-- __init__.py # |-- utils # |-- |-- __init__ # |-- |-- a.py # |-- |-- b.py # |-- subpackage # |-- |-- __init__ # |-- |-- c.py # c.py import smart_imports smart_imports.all() print(utils_a) print(utils_b) 

Aturan 7: Modul dari paket induk


Jika Anda memiliki sub paket dengan nama yang sama di berbagai bagian proyek (misalnya, tests atau migrations ), Anda dapat memperbolehkan mereka mencari modul untuk diimpor dengan nama dalam paket induk.

Contoh
 # : # { # "rules": [{"type": "rule_predefined_names"}, # {"type": "rule_local_modules_from_parent", # "suffixes": [".tests"]}] # } # #    : # # my_package # |-- __init__.py # |-- a.py # |-- tests # |-- |-- __init__ # |-- |-- b.py # b.py import smart_imports smart_imports.all() print(a) 

Aturan 8: Mengikat paket lain


Untuk modul dari paket tertentu, ini memungkinkan pencarian impor dengan nama dalam paket lain (ditentukan dalam konfigurasi). Dalam kasus saya, aturan ini bermanfaat untuk kasus-kasus ketika saya tidak ingin memperluas pekerjaan aturan sebelumnya (Modul dari paket induk) ke seluruh proyek.

Contoh
 # : # { # "rules": [{"type": "rule_predefined_names"}, # {"type": "rule_local_modules_from_namespace", # "map": {"my_package.subpackage_1": ["my_package.subpackage_2"]}}] # } # #    : # # my_package # |-- __init__.py # |-- subpackage_1 # |-- |-- __init__ # |-- |-- a.py # |-- subpackage_2 # |-- |-- __init__ # |-- |-- b.py # a.py import smart_imports smart_imports.all() print(b) 

Menambahkan Aturan Anda Sendiri


Menambahkan aturan Anda sendiri cukup sederhana:

  1. Kami mewarisi dari kelas smart_imports.rules.BaseRule .
  2. Kami menyadari logika yang diperlukan.
  3. Daftarkan aturan menggunakan metode smart_imports.rules.register
  4. Tambahkan aturan ke konfigurasi.
  5. ???
  6. Untung

Contoh dapat ditemukan dalam implementasi aturan saat ini.

Untung


Daftar impor multiline di awal setiap sumber telah hilang.

Jumlah baris menurun. Sebelum browser beralih ke impor cerdas, ada 6688 baris yang bertanggung jawab untuk mengimpor. Setelah transisi, 2084 tetap (dua baris smart_import per file + 130 impor, dipanggil secara eksplisit dari fungsi dan tempat yang serupa).

Bonus yang bagus adalah standarisasi nama dalam proyek. Kode menjadi lebih mudah dibaca dan lebih mudah untuk ditulis. Tidak perlu memikirkan nama-nama entitas yang diimpor - ada beberapa aturan yang jelas yang mudah diikuti.

Rencana pengembangan


Saya suka ide mendefinisikan properti kode dengan nama variabel, jadi saya akan mencoba mengembangkannya baik dalam impor pintar maupun dalam proyek lain.

Mengenai impor cerdas, saya berencana:

  1. Tambahkan dukungan untuk versi Python baru.
  2. Jelajahi kemungkinan mengandalkan praktik komunitas saat ini pada jenis anotasi kode.
  3. Jelajahi kemungkinan membuat impor malas.
  4. Terapkan utilitas untuk pembuatan otomatis konfigurasi dari kode sumber dan refactoring sumber untuk menggunakan smart_import.
  5. Tulis ulang bagian dari kode C untuk mempercepat pekerjaan dengan AST.
  6. Untuk mengembangkan integrasi dengan linter dan IDE jika mereka memiliki masalah dengan analisis kode tanpa impor eksplisit.

Selain itu, saya tertarik dengan pendapat Anda tentang perilaku default perpustakaan dan aturan impor.

Terima kasih telah mengatasi lembaran teks ini :-D

Source: https://habr.com/ru/post/id459930/


All Articles