Чи імпортувати всередині функцій пітонічно?


126

PEP 8 говорить:

  • Імпорт завжди розміщується у верхній частині файлу, одразу після будь-яких коментарів модулів та доктрингів, а також перед глобальними модулями та константами.

За професією я порушую PEP 8. Деколи я імпортую речі всередині функцій. Як правило, я це роблю, якщо є імпорт, який використовується лише в межах однієї функції.

Будь-які думки?

EDIT (причина, по якій я відчуваю імпорт у функції, може бути хорошою ідеєю):

Основна причина: Це може зробити код яснішим.

  • Дивлячись на код функції, я можу запитати себе: "Що таке функція / клас xxx?" (xxx використовується у межах функції). Якщо у мене весь імпорт у верхній частині модуля, я мушу піти туди, щоб визначити, що таке ххх. Це більше питання при використанні from m import xxx. Бачити m.xxxу функції, ймовірно, мені більше розповідає. Залежно від того, що mтаке: чи це відомий модуль / пакет верхнього рівня ( import m)? Або це підмодуль / пакет ( from a.b.c import m)?
  • У деяких випадках наявність додаткової інформації ("Що таке ххх?") Близько до того, де використовується ххх, може полегшити розуміння функції.

2
і ти це робиш для виконання?
Макарс

4
Я вважаю, що це робить код яснішим у деяких випадках. Я б здогадався, що при роботі з імпортом у функції падає сира продуктивність (оскільки оператор import виконує кожен раз, коли функція викликається).
кодап

Ви можете відповісти "Що таке функція / клас xxx?" використовуючи синтаксис імпорту xyz, а не синтаксис від xyz import abc abc
Том Лейс,

1
Якщо чіткість є єдиним фактором, U може також включити відповідний коментар для цього. ;)
Лакшман Прасад

5
@becomingGuru: Звичайно, але коментарі можуть вийти із синхронізації з реальністю ...
codeape

Відповіді:


88

Зрештою, я думаю, ви оціните, що більшість ваших імпортів знаходяться у верхній частині файлу, так ви зможете з першого погляду сказати, наскільки складний ваш модуль тим, що йому потрібно імпортувати.

Якщо я додаю новий код до наявного файлу, то зазвичай імпортую там, де це потрібно, і тоді, якщо код залишається, я зроблю все більш постійним, переміщуючи рядок імпорту у верхню частину файлу.

Ще один момент, який я вважаю за краще отримати ImportError виняток перед запуском будь-якого коду - як перевірку правильності, тому це ще одна причина для імпорту вгорі.

Я використовую pyCheckerдля перевірки на невикористані модулі.


47

Є два випадки, коли я порушую PEP 8 з цього приводу:

  • Круговий імпорт: модуль A імпортує модуль B, але щось у модулі B потребує модуля A (хоча це часто є ознакою того, що мені потрібно переробляти модулі для усунення кругової залежності)
  • Вставлення точки розриву pdb: import pdb; pdb.set_trace()Це зручно b / c, я не хочу ставити import pdbу верхній частині кожного модуля, який, можливо, хочу налагодити, і легко запам’ятати, щоб видалити імпорт, коли я видаляю точку зламу.

Поза цими двома випадками, гарно все поставити на вершину. Це робить залежності зрозумілішими.


7
Я погоджуюсь, що це робить зрозумілішими залежності щодо модуля в цілому. Але я вважаю, що це може зробити код менш зрозумілим на рівні функцій, щоб імпортувати все вгорі. Дивлячись на код функції, ви можете запитати себе: "Що таке функція / клас xxx?" (xxx використовується всередині функції). І вам потрібно подивитися в самому верху файлу, щоб побачити, звідки походить xxx. Це більше проблеми при використанні з m import xxx. Побачивши m.xxx, ви розповідаєте більше - принаймні, якщо немає сумнівів щодо того, що таке m.
кодап

20

Ось чотири випадки використання імпорту, які ми використовуємо

  1. importfrom x import yі import x as y) вгорі

  2. Вибір для імпорту. На вершині.

    import settings
    if setting.something:
        import this as foo
    else:
        import that as foo
  3. Умовний імпорт. Використовується з бібліотеками JSON, XML тощо. На вершині.

    try:
        import this as foo
    except ImportError:
        import that as foo
  4. Динамічний імпорт. Поки ми маємо лише один приклад цього.

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

    Зауважте, що цей динамічний імпорт не вводить код, але містить складні структури даних, написані на Python. Це щось на зразок маринованих фрагментів даних, за винятком того, що ми маринували їх вручну.

    Це також більш-менш у верхній частині модуля


Ось що ми робимо, щоб зробити код більш зрозумілим:

  • Тримайте модулі короткими.

  • Якщо у мене весь імпорт у верхній частині модуля, я мушу піти туди, щоб визначити, що таке ім’я. Якщо модуль короткий, це легко зробити.

  • У деяких випадках наявність додаткової інформації, близької до того, де використовується ім'я, може полегшити розуміння функції. Якщо модуль короткий, це легко зробити.


Зберігання модулів короткими, звичайно, дуже хороша ідея. Але для того, щоб завжди мати «імпорт інформації» для наявних функцій, максимальна довжина модуля повинна бути одним екраном (можливо, максимум 100 рядків). І це, мабуть, буде занадто коротким, щоб бути практичним у більшості випадків.
codeape

Я гадаю, ви могли б сприйняти це до логічної крайності. Я думаю, що може бути точка балансу, коли ваш модуль "досить малий", що вам не потрібні фантазійні методи імпорту для управління складністю. Наш середній розмір модуля - випадково - близько 100 рядків.
S.Lott

8

Варто пам’ятати: зайвий імпорт може спричинити проблеми з роботою. Отже, якщо це функція, яку викличуть часто, вам краще просто поставити імпорт вгорі. Звичайно , це є оптимізацією, так що якщо є дійсний випадок , який буде зроблено , що імпорт всередині функції є більш ясним , ніж імпорт в верхній частині файлу, що козирі продуктивності в більшості випадків.

Якщо ви робите IronPython, мені кажуть, що краще імпортувати внутрішні функції (оскільки компіляція коду в IronPython може бути повільною). Таким чином, ви можете отримати спосіб імпортувати всередину функції тоді. Але крім цього я б заперечував, що боротися з конвенцією просто не варто.

Як правило, я це роблю, якщо є імпорт, який використовується лише в межах однієї функції.

Я також хочу зазначити, що це може бути потенційною проблемою утримання. Що станеться, якщо ви додасте функцію, яка використовує модуль, який раніше використовувався лише однією функцією? Чи пам’ятаєте ви додати імпорт у верхню частину файлу? Або ви збираєтесь сканувати кожну функцію для імпорту?

FWIW, є випадки, коли є сенс імпортувати всередину функції. Наприклад, якщо ви хочете встановити мову в cx_Oracle, вам потрібно встановити _змінну середовища NLS LANG перед її імпортом. Таким чином, ви можете побачити такий код:

import os

oracle = None

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

2
Я згоден з вашим питанням обслуговування. Код рефакторингу може бути трохи проблематичним. Якщо я додаю другу функцію, яка використовує модуль, який раніше використовувався лише однією функцією - я або переміщую імпорт до верху, або порушую власне загальне правило, імпортуючи модуль також у другу функцію.
кодап

2
Я думаю, що аргумент ефективності може піти і іншим шляхом. Імпорт модуля може зайняти багато часу. У таких розподілених файлових системах, як на суперкомп'ютерах, імпорт великого модуля на зразок нуме може зайняти кілька секунд. Якщо модуль потрібен лише для однієї, рідко використовуваної функції, то імпорт у функцію значно прискорить загальний випадок.
amaurea

6

Раніше я порушив це правило для модулів, які проходять самотестування. Тобто вони зазвичай просто використовуються для підтримки, але я визначаю для них головне, так що якщо ви запускаєте їх самостійно, ви можете перевірити їх функціональність. У такому випадку я іноді імпортую getoptі cmdлише в основному, тому що я хочу, щоб хтось, хто читає код, зрозумів, що ці модулі не мають нічого спільного з нормальною роботою модуля і включаються лише для тестування.


5

Виходячи з питання про завантаження модуля двічі - Чому б не обидва?

Імпорт у верхній частині сценарію вказуватиме на залежності та інший імпорт у функції, що зробить цю функцію більш атомною, але, здавалося б, не спричиняючи жодних недоліків у роботі, оскільки послідовний імпорт є дешевим.


3

Поки це є importі ні from x import *, ви повинні поставити їх у верхній частині. Це додає лише одне ім’я до глобального простору імен, і ви дотримуєтесь PEP 8. Плюс, якщо пізніше вам це потрібно десь в іншому місці, вам не доведеться нічого рухати.

Це не велика справа, але оскільки різниці майже немає, я б запропонував зробити те, що говорить PEP 8.


3
Насправді, введення from x import *функції всередині функції генерує SyntaxWarning, принаймні, у 2,5.
Рік Коупленд

3

Погляньте на альтернативний підхід, який використовується в sqlalchemy: введення залежності:

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

Зауважте, як імпортована бібліотека оголошується в декораторі та передається як аргумент функції !

Такий підхід робить код чистішим, а також працює в 4,5 рази швидше, ніж importзаява!

Орієнтир: https://gist.github.com/kolypto/589e84fbcfb6312532658df2fabdb796


2

У модулях, які є "нормальними" модулями і можуть виконуватися (тобто мати if __name__ == '__main__': -секцію), я зазвичай імпортую модулі, які використовуються лише під час виконання модуля всередині основного розділу.

Приклад:

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

Є ще один (ймовірно, «кутовий») випадок, коли це може бути корисним для importвсередині рідко використовуваних функцій: скоротити час запуску.

Я потрапив у цю стіну один раз досить складною програмою, що працює на невеликому сервері IoT, приймаючи команди з послідовного рядка та виконуючи операції, можливо, дуже складні операції.

Розміщення importоператорів у верхній частині файлів означало, що весь імпорт обробляється до запуску сервера; оскільки importсписок включені jinja2, lxml, signxmlта інші «важкі ваги» (і SoC був не дуже потужним) це означали хвилини до першої команди була фактично виконані.

OTOH розміщуючи більшість імпортів у функціях, мені вдалося за лічені секунди зробити сервер «живим» на послідовної лінії Звичайно, коли модулі насправді потрібні, я повинен був заплатити ціну (Примітка: також це можна пом'якшити, нерестуючи фонове завдання, яке виконується importв простої).

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.