İç fonksiyonları içe aktarmak pitonik mi?


126

PEP 8 diyor ki:

  • İçe aktarmalar her zaman, herhangi bir modül yorumundan ve belge dizgisinden hemen sonra ve modül globalleri ve sabitlerinden önce olmak üzere dosyanın en üstüne yerleştirilir.

Bazı durumlarda, PEP 8'i ihlal ediyorum. Bazen işlevlerin içindeki öğeleri içe aktarıyorum. Genel bir kural olarak, bunu yalnızca tek bir işlev içinde kullanılan bir içe aktarma varsa yaparım.

Herhangi bir görüş?

DÜZENLE (işlevleri içe aktarmayı düşünmemin nedeni iyi bir fikir olabilir):

Ana neden: Kodu daha net hale getirebilir.

  • Bir işlevin koduna bakarken kendime şunu sorabilirim: "İşlev / sınıf xxx nedir?" (işlev içinde kullanılan xxx). Tüm içe aktarmalarım modülün üst kısmına gelirse, xxx'nin ne olduğunu belirlemek için oraya bakmam gerekir. Bu daha çok kullanırken bir sorundur from m import xxx. m.xxxFonksiyonu görmek muhtemelen bana daha fazlasını anlatıyor. Neyin molduğuna bağlı olarak : İyi bilinen bir üst düzey modül / paket ( import m) mi? Yoksa bir alt modül / paket ( from a.b.c import m) mi?
  • Bazı durumlarda, fazladan bilginin ("xxx nedir?") Xxx kullanılan yere yakın olması, işlevin anlaşılmasını kolaylaştırabilir.

2
ve bunu performans için mi yapıyorsun?
Macarse

4
Bazı durumlarda kodu daha net hale getirdiğini hissediyorum. Bir fonksiyonu içe aktarırken ham performans düşüşlerini tahmin ediyorum (çünkü import ifadesi fonksiyon her çağrıldığında çalışacaktır).
kod

"İşlev / sınıf xxx nedir?" xyz'den içe aktarma abc sözdizimi yerine içe aktarma xyz sözdizimini kullanarak
Tom Leys

1
Tek faktör netlikse, U bu etkiyle ilgili bir yorum da içerebilir. ;)
Lakshman Prasad

5
@becomingGuru: Elbette, ancak yorumlar gerçeklikle
uyumlu olmayabilir

Yanıtlar:


88

Uzun vadede, içe aktarmalarınızın çoğunun dosyanın en üstünde olmasını takdir edeceğinizi düşünüyorum, bu şekilde, modülünüzün içeri aktarması gerekenlere göre ne kadar karmaşık olduğunu bir bakışta anlayabilirsiniz.

Mevcut bir dosyaya yeni kod eklersem, genellikle içe aktarımı gerektiği yerde yaparım ve ardından kod kalırsa, içe aktarma satırını dosyanın en üstüne taşıyarak işleri daha kalıcı hale getiririm.

Bir başka nokta, ImportErrorherhangi bir kod çalıştırılmadan önce bir istisna almayı tercih ederim - bir akıl sağlığı kontrolü olarak, bu yüzden en üstte içe aktarmak için başka bir neden.

pyCheckerKullanılmayan modülleri kontrol etmek için kullanıyorum .


47

Bu bağlamda PEP 8'i ihlal ettiğim iki durum var:

  • Dairesel içe aktarmalar: modül A, modül B'yi içe aktarır, ancak modül B'deki bir şey, modül A'ya ihtiyaç duyar (ancak bu genellikle döngüsel bağımlılığı ortadan kaldırmak için modülleri yeniden düzenlemem gerektiğinin bir işaretidir)
  • Bir pdb kesme noktası eklemek: import pdb; pdb.set_trace()Bu kullanışlı b / c import pdbHata ayıklamak isteyebileceğim her modülün en üstüne koymak istemiyorum ve kesme noktasını kaldırdığımda içe aktarmayı kaldırmayı hatırlamak kolay.

Bu iki durumun dışında, her şeyi en üste koymak iyi bir fikirdir. Bağımlılıkları daha net hale getirir.


7
Modüle bir bütün olarak bakıldığında bağımlılıkları daha net hale getirdiğini kabul ediyorum. Ancak, en üstteki her şeyi içe aktarmak için işlev düzeyinde kodu daha az net hale getirebileceğine inanıyorum. Bir işlevin koduna bakarken kendinize şunu sorabilirsiniz: "İşlev / sınıf xxx nedir?" (xxx, işlevin içinde kullanılır). Ve xxx'in nereden geldiğini görmek için dosyanın en üstüne bakmalısın. Bu daha çok m import xxx'den kullanılırken bir sorundur. M.xxx'i görmek size daha fazlasını anlatır - en azından m'nin ne olduğu konusunda hiçbir şüpheniz yoksa.
kod

20

İşte kullandığımız dört ithal kullanım örneği

  1. import(ve from x import yve import x as y) üstündeki

  2. İthalat Seçenekleri. Tepede.

    import settings
    if setting.something:
        import this as foo
    else:
        import that as foo
  3. Koşullu İthalat. JSON, XML kitaplıkları ve benzerleriyle kullanılır. Tepede.

    try:
        import this as foo
    except ImportError:
        import that as foo
  4. Dinamik Alma. Şimdiye kadar bunun sadece bir örneğine sahibiz.

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

    Bu dinamik içe aktarmanın kod getirmediğini, ancak Python'da yazılmış karmaşık veri yapılarını getirdiğini unutmayın. El ile toparlamamız dışında, turşu bir veri parçası gibi.

    Bu aynı zamanda bir modülün tepesinde aşağı yukarı


Kodu daha net hale getirmek için yaptıklarımız:

  • Modülleri kısa tutun.

  • Tüm içe aktarmalarımı modülün en üstünde tutuyorsam, adın ne olduğunu belirlemek için oraya gitmem gerekiyor. Modül kısaysa, bunu yapmak kolaydır.

  • Bazı durumlarda, bir ismin kullanıldığı yere yakın bu ekstra bilgiye sahip olmak, işlevin anlaşılmasını kolaylaştırabilir. Modül kısaysa, bunu yapmak kolaydır.


Modülleri kısa tutmak elbette çok iyi bir fikir. Ancak, mevcut fonksiyonlar için her zaman "içe aktarma bilgisine" sahip olmanın avantajını elde etmek için, maksimum modül uzunluğu bir ekran olmalıdır (muhtemelen maksimum 100 satır). Ve bu muhtemelen çoğu durumda pratik olamayacak kadar kısa olacaktır.
kod kaseti

Sanırım bunu mantıksal bir aşırılığa taşıyabilirsin. Modülünüzün "yeterince küçük" olduğu ve karmaşıklığı yönetmek için süslü içe aktarma tekniklerine ihtiyaç duymadığınız bir denge noktası olabileceğini düşünüyorum. Ortalama modül boyutumuz - tesadüfen - yaklaşık 100 satırdır.
S.Lott

8

Unutulmaması gereken bir şey var: gereksiz ithalat performans sorunlarına neden olabilir. Dolayısıyla, bu sıklıkla çağrılacak bir işlevse, içe aktarmayı en üste koymanız daha iyi olur. Tabii bu olan bir işlev içinde ithal bir dosyanın en üstünde ithal daha olduğunu açıkça ifade edilmesi için geçerli bir durum var eğer öyleyse, bir optimizasyon çoğu durumda koz performans söyledi.

IronPython yapıyorsanız, içerideki işlevleri içe aktarmanın daha iyi olduğu söylendi (çünkü IronPython'da kod derlemek yavaş olabilir). Böylece, o zaman iç işlevleri içe aktarmanın bir yolunu bulabilirsin. Ama bunun dışında, geleneğe karşı savaşmanın buna değmediğini iddia ediyorum.

Genel bir kural olarak, bunu yalnızca tek bir işlev içinde kullanılan bir içe aktarma varsa yaparım.

Vurgulamak istediğim bir diğer nokta da bunun potansiyel bir bakım sorunu olabileceğidir. Daha önce yalnızca bir işlev tarafından kullanılmış bir modülü kullanan bir işlev eklerseniz ne olur? İçe aktarımı dosyanın en üstüne eklemeyi hatırlayacak mısınız? Yoksa ithalat için her bir işlevi mi tarayacaksınız?

FWIW, bir işlevin içine aktarmanın anlamlı olduğu durumlar vardır. Örneğin, cx_Oracle'da dili ayarlamak istiyorsanız , içe aktarılmadan önce bir NLS _LANG ortam değişkeni ayarlamanız gerekir . Bu nedenle, aşağıdaki gibi bir kod görebilirsiniz:

import os

oracle = None

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

2
Bakım sorununuzla aynı fikirdeyim. Kodu yeniden düzenleme biraz sorunlu olabilir. Daha önce yalnızca bir işlev tarafından kullanılan bir modülü kullanan ikinci bir işlev eklersem - ya içe aktarmayı en üste taşır ya da ikinci işlevdeki modülü içe aktararak kendi genel kuralımı bozarım.
kod

2
Bence performans argümanı başka yöne de gidebilir. Bir modülü içe aktarmak zaman alıcı olabilir. Süper bilgisayarlardaki gibi dağıtılmış dosya sistemlerinde, numpy gibi büyük bir modülü içe aktarmak birkaç saniye sürebilir. Bir modüle yalnızca nadiren kullanılan tek bir işlev için ihtiyaç duyuluyorsa, işlevin içeri aktarılması genel durumu önemli ölçüde hızlandıracaktır.
amaurea

6

Kendi kendini test eden modüller için bu kuralı daha önce ihlal ettim. Yani, normalde sadece destek için kullanılırlar, ancak onlar için bir ana tanımlıyorum, böylece onları kendi başlarına çalıştırırsanız, işlevselliklerini test edebilirsiniz. Bu durumda bazen içe aktarıyorum getoptve cmdsadece esas olarak, çünkü kodu okuyan birine, bu modüllerin modülün normal çalışmasıyla hiçbir ilgisi olmadığı ve sadece test için dahil edildiklerinin açık olmasını istiyorum.


5

Modülü iki kez yüklemeyle ilgili sorudan geliyor - Neden ikisi birden olmasın?

Komut dosyasının üst kısmındaki bir içe aktarma, bağımlılıkları ve işlevdeki başka bir içe aktarma ile bu işlevi daha atomik hale getirirken, görünürde herhangi bir performans dezavantajına neden olmaz, çünkü ardışık içe aktarma ucuzdur.


3

Olduğu importve olmadığı sürece from x import *, onları en üste koymalısın. Global isim alanına sadece bir isim ekler ve siz PEP 8'e bağlı kalırsınız. Ayrıca, daha sonra başka bir yerde ihtiyacınız olursa, herhangi bir şeyi hareket ettirmeniz gerekmez.

Önemli değil, ancak neredeyse hiçbir fark olmadığından, PEP 8'in söylediği şeyi yapmayı öneririm.


3
Aslında, from x import *bir işlevin içine koymak , en az 2.5'te bir SyntaxWarning oluşturacaktır.
Rick Copeland

3

Sqlalchemy'de kullanılan alternatif yaklaşıma bir göz atın: bağımlılık ekleme:

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

İçe aktarılan kitaplığın bir dekoratörde nasıl bildirildiğine ve işleve argüman olarak nasıl aktarıldığına dikkat edin !

Bu yaklaşım, kodu daha temiz hale getirir ve ayrıca bir ifadeden 4,5 kat daha hızlı çalışır import!

Karşılaştırma: https://gist.github.com/kolypto/589e84fbcfb6312532658df2fabdb796


2

Hem 'normal' modüller olan hem de yürütülebilen modüllerde (yani bir if __name__ == '__main__': bölüme sahip) modüllerde, genellikle yalnızca ana bölüm içindeki modülü çalıştırırken kullanılan modülleri içe aktarırım.

Misal:

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

importNadiren kullanılan işlevlerin içinde yararlı olabileceği başka bir (muhtemelen "köşe") durum daha var: başlangıç ​​süresini kısaltın.

Bir seri hattan komutları kabul eden ve muhtemelen çok karmaşık işlemler gerçekleştiren küçük bir IoT sunucusunda çalışan oldukça karmaşık bir programla bu duvara bir kez çarptım.

Tüm içe aktarımların sunucu başlatılmadan önce işlenmesi importanlamına gelen ifadelerin dosyaların üstüne yerleştirilmesi ; çünkü liste dahil , , ve diğer "ağır ağırlıklar" (ve SoC çok güçlü değildi) bu demek dakika ilk talimat önce aslında idam edildi.importjinja2lxmlsignxml

OTOH, çoğu ithalatı işlevlere yerleştirerek, sunucunun seri hatta saniyeler içinde "canlı" olmasını sağladım. Tabii ki, modüllere gerçekten ihtiyaç duyulduğunda bedelini ödemem gerekiyordu (Not: bu, importboşta kalma süresinde s yapan bir arka plan görevi oluşturarak da hafifletilebilir ).

Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.