PEP8 - імпорт не у верхній частині файлу з sys.path


75

Проблема

PEP8 має правило щодо розміщення імпорту у верхній частині файлу:

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

Однак у певних випадках я можу захотіти зробити щось на зразок:

import sys
sys.path.insert("..", 0)

import my_module

У цьому випадку pep8утиліта командного рядка позначає мій код:

Імпорт рівня модуля E402 не у верхній частині файлу

Який найкращий спосіб досягти відповідності PEP8 sys.pathмодифікаціям?

Чому

У мене є цей код, оскільки я дотримуюся структури проекту, наведеної в Посібнику автостопа по Python .

У цьому посібнику випливає, що у мене є my_moduleпапка, окрема від testsпапки, обидві вони знаходяться в одному каталозі. Якщо я хочу отримати доступ my_moduleз tests, я думаю, мені потрібно додати ..доsys.path


Чому б вам не написати setup.pyі не встановити my_module для тестування?
— jonrsharpe

Тому що це трохи менш зручно. Припускаю, що міг би, але волію ні.
— Люк Тейлор,

Для кого? Якщо ви коли-небудь захочете насправді використовувати цей проект де завгодно, це найпростіший спосіб запустити його і запустити.
— jonrsharpe

9
З PEP8 : "Однак знайте, коли бути непослідовними - іноді рекомендації щодо стилю просто не застосовуються. Якщо ви сумніваєтесь, використовуйте найкраще рішення". Бувають випадки, коли доводиться порушувати відповідність PEP8, і це нормально.
— SethMMorton

1
@jonrsharpe Це гарна звичка братися за майбутні речі, якими я поділюсь. (Хоча я бачу вашу думку, у такому випадку я можу використовувати setup.py). Я буду це пам’ятати.
— Люк Тейлор,

Відповіді:


63

Часто у мене є кілька файлів із тестами в підкаталозі foo/testsмого проекту, поки модулі, які я тестую, знаходяться foo/src. Для запуску тестів foo/testsбез помилок імпорту я створюю файл, foo/tests/pathmagic.pyякий виглядає так;

"""Path hack to make tests work."""

import os
import sys

bp = os.path.dirname(os.path.realpath('.')).split(os.sep)
modpath = os.sep.join(bp + ['src'])
sys.path.insert(0, modpath)

Потім я використовую у кожному тестовому файлі

import pathmagic  # noqa

як перший імпорт. У «noqa» коментар запобігає pycodestyle/ pep8від скаржачись невикористаного імпорту.


2
Це круто, але проблема все ще має "імпортований, але невикористаний [F401]".
— Chung-Yen Hung

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

3
@ Chung-YenHung Майте на увазі, що попередження pycodestyle / pep8 є дорадчими, а не синтаксичними помилками або винятками. Ви можете ігнорувати їх. Я оновив свою відповідь, додавши коментар "noqa" після імпорту.
— Роланд Сміт

Одним з величезних недоліків цього є перетворення тестів у пакет, що pathmagicможе бути важливим. Більшість учасників тестів python вважають, що ваші тести - це колекція файлів, які не ввімкнено sys.path, і зміна яких може призвести до проблем, подивіться, як pytestсправи з цими проблемами docs.pytest.org/en/latest/…
— Meitham,

1
@Meitham Хоча в моїй відповіді згадується, як я використовував це для запуску тестів тоді, питання не в запуску тестів. (З тих пір я перейшов до pytest для своїх тестів.) В інших ситуаціях цей механізм все ще корисний.
— Роланд Сміт

82

Якщо є лише декілька імпортів, ви можете просто ігнорувати PEP8 у цих importрядках:

import sys
sys.path.insert("..", 0)
import my_module  # noqa: E402

6
Я вважаю за краще бути більш явним, вказуючи порушене правило, як # noqa: E402наприклад. ( джерело )
— Макс Гудрідж

@MaxGoodridge справді! Відредагована відповідь, щоб додати правило.
— astorga

10

Є ще одне обхідне рішення.

import sys
... all your other imports...

sys.path.insert("..", 0)
try:
    import my_module
except:
    raise

1
Я впевнений, що це не спрацює. Причиною є те, що можуть бути деякі модулі, імпортовані в розділі "... весь ваш імпорт ...", для яких може знадобитися спочатку встановити PYTHONPATH.
— darkdefender27,

2
@ darkdefender27 ідея полягає в тому, щоб розмістити весь імпорт, який вимагає ПІТОНПАТУ, всередині tryтіла, а все інше (що не залежить від нього) вгорі.
— astorga

або навіть простіше:if 1: import module
— stason

4

Я щойно боровся з подібним запитанням, і, думаю, знайшов дещо приємніше рішення, ніж прийнята відповідь.

Створіть pathmagicмодуль, який виконує фактичну маніпуляцію sys.path, але вносьте зміни в контекстному менеджері :

"""Path hack to make tests work."""

import os
import sys

class context:
    def __enter__(self):
        bp = os.path.dirname(os.path.realpath('.')).split(os.sep)
        modpath = os.sep.join(bp + ['src'])
        sys.path.insert(0, modpath)

    def __exit__(self, *args):
        pass

Потім у своїх тестових файлах (або там, де це потрібно) ви робите:

import pathmagic

with pathmagic.context():
    import my_module
    # ...

Таким чином, ви не отримуєте скарг від flake8 / pycodestyle, вам не потрібні спеціальні коментарі, і структура, схоже, має сенс.

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


EDIT : Щойно побачив набагато простіший фокус у відповіді на інше запитання : додайте assert pathmagicпід імпорт, щоб уникнути noqaкоментарів.


Все це справді досягнуто - це позбавлення від наступного спеціального коментаря за рахунок того, що потрібно використовувати менеджер контексту - на мій погляд, дещо туманний компроміс. Що стосується прибирання в __exit__блоці, то для того, щоб зробити це правильно належним чином, потрібно буде просто видалити доданий шлях (якщо він все ще є там), оскільки відновлення всього попереднього значення до того, що було на момент введення контексту, також скасує будь-які інші зміни, які можуть були з якоїсь причини зроблені до нього за допомогою іншого коду (виконуваного в тому ж контексті).
— martineau

@martineau справді, це справа смаку. Можливо, я трохи упереджений до спеціальних коментарів, оскільки моя поточна база коду містить занадто багато коментарів до безлічі інструментів статичного аналізу та редакторів, які використовують різні команди. Також узгодили свій другий пункт.
— іцадок

Проблемним, якщо pathmagic.context()його можна назвати довільною кількістю разів (наприклад, при запуску всіх тестів). Повернення також є проблематичним: будь-який імпорт пізніше (наприклад, зроблений на вимогу) може не вдатися.
— ivan_pozdeev

4

Ви вже пробували таке:

import sys
from importlib import import_module

sys.path.insert("..", 0)

# import module
my_mod = import_module('my_module')

# get method or function from my_mod
my_method = getattr(my_mod , 'my_method')

2

Щоб відповідати pep8, слід включити шлях проекту до шляху python, щоб виконати відносний / абсолютний імпорт.

Для цього ви можете поглянути на цю відповідь: Постійно додайте каталог до PYTHONPATH


3
Я не хочу робити цей каталог глобально доступним для всіх моїх скриптів Python, оскільки це може спричинити конфлікти.
— Люк Тейлор,

2
Ви можете використовувати пакет docs.python.org/3/library/pkgutil.html для використання просторів імен. Якщо ви вважаєте, що ваше рішення є найкращим, ви не зобов'язані слідувати пеп8. Pep8 - це лише поради та найкращі практики, це не означає, що ви повинні дотримуватися кожного правила, щоразу та скрізь.
— Pierre Barre


0

Ця проблема вже має кілька рішень, які працюють, але у випадку, якщо у вас є кілька непочаткових імпортів, і ви не хочете коментувати кожен # noqa: E402(або використовувати одну з інших пропозицій, наступне працює для цілого блоку:

import sys
sys.path.insert("..", 0)

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