Python: лучший способ добавить в sys.path относительно текущего запущенного скрипта


103

У меня есть каталог, полный скриптов (допустим project/bin). У меня также есть библиотека, project/libи я хочу, чтобы скрипты автоматически загружали ее. Это то, что я обычно использую в начале каждого скрипта:

#!/usr/bin/python
from os.path import dirname, realpath, sep, pardir
import sys
sys.path.append(dirname(realpath(__file__)) + sep + pardir + sep + "lib")

# ... now the real code
import mylib

Это немного громоздко, некрасиво, и его нужно вставлять в начало каждого файла. Есть лучший способ сделать это?

На самом деле я надеюсь на что-то столь же гладкое, как это:

#!/usr/bin/python
import sys.path
from os.path import pardir, sep
sys.path.append_relative(pardir + sep + "lib")

import mylib

Или даже лучше, что-то, что не сломается, когда мой редактор (или кто-то другой, у кого есть доступ к фиксации) решит изменить порядок импорта в рамках процесса очистки:

#!/usr/bin/python --relpath_append ../lib
import mylib

Это не будет напрямую переноситься на платформы, отличные от posix, но сохранит все в чистоте.


Ответы:


25

Если вы не хотите редактировать каждый файл

  • Установите вашу библиотеку как обычную библиотеку python
    или
  • Установите PYTHONPATHна свойlib

или если вы хотите добавить одну строку в каждый файл, добавьте вверху оператор импорта, например

import import_my_lib

храните import_my_lib.pyв корзине и import_my_libможете правильно установить путь Python к тому, что libвы хотите


120

Вот что я использую:

import os, sys
sys.path.append(os.path.join(os.path.dirname(__file__), "lib"))

3
Я бы сделал sys.path.insert (0, ..), чтобы он не переопределился другими путями.
Джон Цзян

Неужели нет возможности заставить это работать автоматически?
Дени де Бернарди

1
Это немного рискованно. Если __file__это относительное имя файла относительно текущего рабочего каталога (например, setup.py), тогда os.path.dirname(__file__)будет пустая строка. Для этого и подобных проблем , поднятый Джоном Jiang , ekhumoro «S более универсальный раствор сильно предпочтительнее.
Сесил Карри,

31

Я использую:

import sys,os
sys.path.append(os.getcwd())

5
Я часто просто делаюsys.path.append('.')
кимбо

1
что, если сценарий запускается из другого каталога? например, из корневого каталога, указав полный системный путь? тогда os.getcwd () вернет "/"
obayhan

2
Никогда не делай этого. Текущий рабочий каталог (CWD) не обязательно будет тем, что вы думаете - особенно в непредвиденных крайних случаях, которые вы определенно должны были увидеть за милю. __file__Вместо этого просто ссылайтесь, как любой квази-вменяемый разработчик.
Сесил Карри

15

Создайте модуль-оболочку project/bin/lib, который содержит это:

import sys, os

sys.path.insert(0, os.path.join(
    os.path.dirname(os.path.dirname(os.path.realpath(__file__))), 'lib'))

import mylib

del sys.path[0], sys, os

Затем вы можете заменить весь мусор в верхней части ваших скриптов на:

#!/usr/bin/python
from lib import mylib

9

Если вы не хотите каким-либо образом изменять содержимое скрипта, добавьте текущий рабочий каталог .к $ PYTHONPATH (см. Пример ниже)

PYTHONPATH=.:$PYTHONPATH alembic revision --autogenerate -m "First revision"

И конец этому!


docs.python.org/3/tutorial/modules.html#the-module-search-path говорит: «sys.path инициализируется из следующих мест: -Каталог, содержащий входной сценарий». Думаю, это неправда.

Я склонен делать это, особенно в .envrc, так что с direnv это даже автоматически и изолированно.
EdvardM

8

Использование python 3.4+
Запрет на использование cx_freeze или использование в IDLE. 😃

import sys
from pathlib import Path

sys.path.append(Path(__file__).parent / "lib")

Совместимость с Python2: import pathlib import os sys.path.append (os.path.dirname ( file ))
майкл

6

Вы можете запустить сценарий python -mиз соответствующего корневого каталога. И передайте "путь модулей" в качестве аргумента.

Пример: $ python -m module.sub_module.main # Notice there is no '.py' at the end.


Другой пример:

$ tree  # Given this file structure
.
├── bar
│   ├── __init__.py
│   └── mod.py
└── foo
    ├── __init__.py
    └── main.py

$ cat foo/main.py
from bar.mod import print1
print1()

$ cat bar/mod.py
def print1():
    print('In bar/mod.py')

$ python foo/main.py  # This gives an error
Traceback (most recent call last):
  File "foo/main.py", line 1, in <module>
    from bar.mod import print1
ImportError: No module named bar.mod

$ python -m foo.main  # But this succeeds
In bar/mod.py

2
Для этого рекомендуется использовать переключатель m, а не
взлом

4

С каждым предоставленным ответом есть проблема, которую можно резюмировать как «просто добавьте это волшебное заклинание в начало вашего сценария. Посмотрите, что вы можете сделать с помощью всего лишь одной или двух строк кода». Они не будут работать во всех возможных ситуациях!

Например, в одном из таких магических заклинаний используется напильник . К сожалению, если вы упаковываете свой скрипт с помощью cx_Freeze или используете IDLE, это приведет к исключению.

Еще одно такое магическое заклинание использует os.getcwd (). Это будет работать только в том случае, если вы запускаете свой сценарий из командной строки и каталог, содержащий ваш сценарий, является текущим рабочим каталогом (то есть вы использовали команду cd для перехода в каталог до запуска сценария). Эх боги! Надеюсь, мне не придется объяснять, почему это не сработает, если ваш сценарий Python находится где-то в PATH, и вы запускали его, просто набрав имя файла сценария.

К счастью, есть магическое заклинание, которое сработает во всех случаях, которые я тестировал. К сожалению, магическое заклинание - это не просто пара строк кода.

import inspect
import os
import sys

# Add script directory to sys.path.
# This is complicated due to the fact that __file__ is not always defined.

def GetScriptDirectory():
    if hasattr(GetScriptDirectory, "dir"):
        return GetScriptDirectory.dir
    module_path = ""
    try:
        # The easy way. Just use __file__.
        # Unfortunately, __file__ is not available when cx_freeze is used or in IDLE.
        module_path = __file__
    except NameError:
        if len(sys.argv) > 0 and len(sys.argv[0]) > 0 and os.path.isabs(sys.argv[0]):
            module_path = sys.argv[0]
        else:
            module_path = os.path.abspath(inspect.getfile(GetScriptDirectory))
            if not os.path.exists(module_path):
                # If cx_freeze is used the value of the module_path variable at this point is in the following format.
                # {PathToExeFile}\{NameOfPythonSourceFile}. This makes it necessary to strip off the file name to get the correct
                # path.
                module_path = os.path.dirname(module_path)
    GetScriptDirectory.dir = os.path.dirname(module_path)
    return GetScriptDirectory.dir

sys.path.append(os.path.join(GetScriptDirectory(), "lib"))
print(GetScriptDirectory())
print(sys.path)

Как видите, это непростая задача!


1

Этот подходит мне лучше всего. Использование:

os.path.abspath('')

На Mac он должен напечатать что-то вроде:

'/Users/<your username>/<path_to_where_you_at>'

Чтобы получить путь abs к текущему wd, этот лучше, потому что теперь вы можете подняться, если хотите, например:

os.path.abspath('../')

И сейчас:

 '/Users/<your username>/'

Итак, если вы хотите импортировать utilsотсюда, '/Users/<your username>/'
все, что вам нужно сделать, это:

import sys
sys.path.append(os.path.abspath('../'))

0

Я вижу в вашем примере шебанг. Если вы запускаете свои сценарии bin как ./bin/foo.pyвместо python ./bin/foo.py, есть возможность использовать shebang для изменения $PYTHONPATHпеременной.

Однако вы не можете изменять переменные среды непосредственно в shebangs, поэтому вам понадобится небольшой вспомогательный скрипт. Поместите это python.shв свою binпапку:

#!/usr/bin/env bash
export PYTHONPATH=$PWD/lib
exec "/usr/bin/python" "$@"

А затем измените свой шебанг, ./bin/foo.pyчтобы быть#!bin/python.sh


0

Когда мы пытаемся запустить файл python с путем из терминала.

import sys
#For file name
file_name=sys.argv[0]
#For first argument
dir= sys.argv[1]
print("File Name: {}, argument dir: {}".format(file_name, dir)

Сохраните файл (test.py).

Система запуска.

Откройте терминал и перейдите в тот каталог, где находится файл сохранения. затем написать

python test.py "/home/saiful/Desktop/bird.jpg"

Нажмите Enter

Выход:

File Name: test, Argument dir: /home/saiful/Desktop/bird.jpg

0

Я использую:

from site import addsitedir

Затем можно использовать любой относительный каталог! addsitedir('..\lib') ; две точки означают перемещение (вверх) на один каталог первым.

Помните, что все зависит от вашего текущего рабочего каталога, с которого вы начинаете. Если C: \ Joe \ Jen \ Becky, то добавляет, что он ('.. \ lib') импортирует на ваш путь C: \ Joe \ Jen \ lib

C:\
  |__Joe
      |_ Jen
      |     |_ Becky
      |_ lib

Я ценю ответ, но это не добавляет путь относительно текущего запущенного скрипта. Он добавляет путь относительно текущего рабочего каталога
Джеймс Харр,
Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.