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.