Какая альтернатива execfile в Python 3?

352

Похоже, они отменили в Python 3 все простой способ быстро загрузить скрипт, удалив execfile()

Есть ли очевидная альтернатива, по которой я скучаю?

RS
источник
1
reloadвернулся, так как imp.reload, начиная с 3.2.
Дугал
18
Если вы используете Python в интерактивном режиме, рассмотрите возможность использования IPython: %run script_nameработает со всеми версиями Python.
Майкл
1
Начиная с 3.4 impесть importlib (который должен быть импортирован): importlib.reload(mod_name)импортирует и выполняет mod_name.
П.
3
что не так с runfile ("filename.py")?
Мусомер
1
Спасибо @mousomer !! Я точно искал функциональность, runfile()так как мне нужно было запустить скрипт Python, который выполняется в своем собственном пространстве имен (в отличие от выполнения в вызывающем пространстве имен). Мое приложение: добавьте каталог вызываемого скрипта в системный путь ( sys.path) с помощью __file__атрибута: если мы используем execfile()или его эквивалент в Python 3 ( exec(open('file.py').read())), включенный скрипт запускается в пространстве имен вызывающего и, следовательно, __file__преобразуется в имя вызывающего файла.
мастропи

Ответы:

389

Согласно документации , вместо

execfile("./filename") 

использование

exec(open("./filename").read())

Видеть:

Педро Вагнер
источник
54
Есть идеи, почему они так поступили? Это гораздо более многословно, чем раньше. Кроме того, это не работает для меня на Python3.3. Я получаю «Нет такого файла или каталога», когда я исполняю (open ('./ some_file'). Read ()). Я попытался включить расширение «.py», а также исключить «./»
JoeyC
25
Менее тривиально, это не обеспечивает номера строк при возникновении исключений, как execfile ().
KDN
35
Вам также понадобится closeэтот дескриптор файла. Еще одна причина, чтобы не нравиться изменения с Python 2.
Ребс
3
@ Ребз, вам не нужно закрывать дескриптор файла в этом примере, это будет сделано автоматически (по крайней мере, в обычном CPython)
17
4
@ Rebs в CPython-объектах собирают мусор, как только их счетчик ссылок становится равным 0, только циклические ссылки могут задерживать это ( stackoverflow.com/questions/9449489/… ). В этом случае это должно произойти сразу после возврата read (). И файловые объекты закрываются при удалении (примечание: я понимаю, что эта ссылка явно говорит: «всегда закрывайте файлы», что, в общем-то, является хорошей практикой)
Тихо
219

Вы просто должны прочитать файл и выполнить код самостоятельно. 2to3 текущий заменяет

execfile("somefile.py", global_vars, local_vars)

в виде

with open("somefile.py") as f:
    code = compile(f.read(), "somefile.py", 'exec')
    exec(code, global_vars, local_vars)

(Вызов компиляции не является строго необходимым, но он связывает имя файла с объектом кода, что немного облегчает отладку.)

Видеть:

Бенджамин Петерсон
источник
3
Это работает для меня. Однако я заметил, что вы записали локальные и глобальные аргументы в неправильном порядке. Это на самом деле: exec (object [, globals [, localals]]). Конечно, если вы перевернули аргументы в оригинале, то 2to3 выдаст именно то, что вы сказали. :)
Натан Шивели-Сандерс
3
Было приятно узнать, что, если вы можете опустить global_vars и local_vars, замена python3 здесь также работает под python2. Несмотря на то, execчто это утверждение в python2, оно exec(code)работает, потому что парены просто игнорируются.
Медмунд
2
+1 за использование компиляции. Мой "somefile.py"содержали inspect.getsourcefile(lambda _: None)который терпел неудачу без Компилировать, потому что inspectмодуль не может определить , где код был откуда.
ArtOfWarfare
16
Это ... действительно ужасно Есть идеи, почему они избавились от execfile () в 3.x? execfile также облегчает передачу аргументов командной строки.
aneccodeal
3
open("somefile.py")может быть неправильным, если somefile.pyиспользуется кодировка символов, отличная от locale.getpreferredencoding(). tokenize.open()может быть использован вместо
Jfs
73

Хотя exec(open("filename").read())часто приводится в качестве альтернативы execfile("filename"), он пропускает важные детали, которые execfileподдерживаются.

Следующая функция для Python3.x максимально приближена к тому, чтобы иметь такое же поведение, как и непосредственное выполнение файла. Это соответствует бегу python /path/to/somefile.py.

def execfile(filepath, globals=None, locals=None):
    if globals is None:
        globals = {}
    globals.update({
        "__file__": filepath,
        "__name__": "__main__",
    })
    with open(filepath, 'rb') as file:
        exec(compile(file.read(), filepath, 'exec'), globals, locals)

# execute the file
execfile("/path/to/somefile.py")

Ноты:

  • Использует двоичное чтение, чтобы избежать проблем кодирования
  • Гарантированное закрытие файла (Python3.x предупреждает об этом)
  • Определяет __main__, что некоторые скрипты зависят от этого, чтобы проверить, загружаются ли они как модуль или нет, например.if __name__ == "__main__"
  • Параметр __file__лучше подходит для сообщений об исключениях, и некоторые сценарии используют __file__для получения путей к другим файлам относительно них.
  • Принимает необязательные аргументы globals & locals, изменяя их на месте, как это execfileделается, так что вы можете получить доступ к любым переменным, определенным путем чтения переменных после запуска.

  • В отличие от Python2, execfileэто не изменяет текущее пространство имен по умолчанию. Для этого вы должны явно указать globals()& locals().

ideasman42
источник
68

Как недавно было предложено в списке рассылки python-dev , модуль runpy может быть жизнеспособной альтернативой. Цитата из этого сообщения:

https://docs.python.org/3/library/runpy.html#runpy.run_path

import runpy
file_globals = runpy.run_path("file.py")

Есть тонкие различия в execfile:

  • run_pathвсегда создает новое пространство имен. Он выполняет код как модуль, поэтому нет различий между глобальными и локальными (поэтому есть только init_globalsаргумент). Глобалы возвращаются.

    execfileвыполняется в текущем пространстве имен или заданном пространстве имен. Семантика localsи globals, если дано, были похожи на локальные и глобальные переменные внутри определения класса.

  • run_path может выполнять не только файлы, но также яйца и каталоги (подробности см. в документации).

Йонас Шефер
источник
1
По какой-то причине он выводит на экран много информации, которую не просили напечатать (« встроенные » и т. Д. В Anaconda Python 3). Есть ли способ отключить это, чтобы визуализировалась только та информация, которую я выводил с помощью print ()?
Джон Донн
Возможно ли также получить все переменные в текущем рабочем пространстве вместо того, чтобы все они были сохранены file_globals? Это избавит от необходимости вводить file_globals['...']для каждой переменной.
Адриан
1
«Кроме того, любые функции и классы, определенные в исполняемом коде, не гарантированно будут работать правильно после того, как функция runpy вернулась» Стоит отметить, в зависимости от вашего
варианта
@Adriaan Выполнить "globals (). Update (file_globals)". Лично мне больше всего нравится это решение, потому что я могу отлавливать ошибки, прежде чем принять решение обновить текущее рабочее пространство.
Рон Каминский
@nodakai Спасибо за информацию, я пропустил это. Никогда еще не было таких проблем, интересно, что может вызвать это.
Рон Каминский
21

Этот лучше, так как он берет глобальные и локальные от вызывающей стороны:

import sys
def execfile(filename, globals=None, locals=None):
    if globals is None:
        globals = sys._getframe(1).f_globals
    if locals is None:
        locals = sys._getframe(1).f_locals
    with open(filename, "r") as fh:
        exec(fh.read()+"\n", globals, locals)
Ноам
источник
На самом деле, этот ближе к py2 execfile. Это даже сработало для меня при использовании pytests, где другие решения, опубликованные выше, потерпели неудачу. Спасибо! :)
Boriel
17

Вы можете написать свою собственную функцию:

def xfile(afile, globalz=None, localz=None):
    with open(afile, "r") as fh:
        exec(fh.read(), globalz, localz)

Если вам действительно нужно ...

Эван Фосмарк
источник
1
-1: исполнительная оценка не работает таким образом. Код не работает ни в одной версии Python.
nosklo
6
-1: значения параметров по умолчанию оцениваются во время определения функции, делая оба globalsи localsуказывают на глобальное пространство имен для модуля, содержащего определение, execfile()а не на глобальное и локальное пространство имен вызывающей стороны. Правильный подход состоит в том, чтобы использовать в Noneкачестве значения по умолчанию и определять глобальные и локальные значения вызывающего абонента с помощью возможностей самоанализа inspectмодуля.
Свен Марнач
12

Если скрипт, который вы хотите загрузить, находится в том же каталоге, что и тот, который вы запускаете, возможно, «import» выполнит эту работу?

Если вам нужно динамически импортировать код , стоит обратить внимание на встроенную функцию __ import__ и модуль imp .

>>> import sys
>>> sys.path = ['/path/to/script'] + sys.path
>>> __import__('test')
<module 'test' from '/path/to/script/test.pyc'>
>>> __import__('test').run()
'Hello world!'

test.py:

def run():
        return "Hello world!"

Если вы используете Python 3.1 или более позднюю версию , вы также должны взглянуть на importlib .

ascobol
источник
Это был правильный ответ для меня. Этот блог хорошо объясняет importlib dev.to/0xcrypto/dynamic-importing-stuff-in-python--1805
Ник Брэди,
9

Вот что у меня было ( fileуже назначен путь к файлу с исходным кодом в обоих примерах):

execfile(file)

Вот что я заменил:

exec(compile(open(file).read(), file, 'exec'))

Моя любимая часть: вторая версия прекрасно работает как в Python 2, так и в 3, то есть нет необходимости добавлять в зависящую от версии логику.

ArtOfWarfare
источник
5

Обратите внимание, что приведенный выше шаблон не будет работать, если вы используете объявления кодировки PEP-263, которые не являются ascii или utf-8. Вам нужно найти кодировку данных и правильно ее кодировать, прежде чем передать ее в exec ().

class python3Execfile(object):
    def _get_file_encoding(self, filename):
        with open(filename, 'rb') as fp:
            try:
                return tokenize.detect_encoding(fp.readline)[0]
            except SyntaxError:
                return "utf-8"

    def my_execfile(filename):
        globals['__file__'] = filename
        with open(filename, 'r', encoding=self._get_file_encoding(filename)) as fp:
            contents = fp.read()
        if not contents.endswith("\n"):
            # http://bugs.python.org/issue10204
            contents += "\n"
        exec(contents, globals, globals)
Эрик
источник
3
Что такое «вышеописанная модель»? Пожалуйста, используйте ссылки при ссылке на другие сообщения в StackOverflow. Термины относительного позиционирования, такие как «выше», не работают, так как существует 3 различных способа сортировки ответов (по голосам, по дате или по активности), и наиболее распространенный (по голосам) является изменчивым. Со временем ваши посты и посты вокруг вашего получат разные оценки, что означает, что они будут реорганизованы, и такие сравнения будут менее полезны.
ArtOfWarfare
Очень хороший момент. И учитывая, что я написал этот ответ почти шесть месяцев назад, я предполагаю, что под «вышеуказанным шаблоном» я подразумевал stackoverflow.com/a/2849077/165082 (который, к сожалению, вы должны нажать, чтобы решить), или, что еще лучше, ответ Ноама:
Эрик
2
Как правило, когда я хочу сослаться на другие ответы на тот же вопрос из своего ответа, я набираю «Ответ Ноама» (например) и связываю текст с ответом, на который я ссылаюсь, только на случай, если ответ отсоединится от пользователь в будущем, IE, потому что пользователь меняет имя своей учетной записи, или публикация становится общей вики, потому что в нее было внесено слишком много правок.
ArtOfWarfare
Как получить URL-адрес определенного «ответа» в сообщении, исключая название ответа автора?
DevPlayer
Просмотр источника и получить идентификатор. Например, ваш вопрос будет stackoverflow.com/questions/436198/… . Я все за лучший метод, но ничего не вижу, когда нахожу возле комментария
Эрик
4

Кроме того, хотя вы и не являетесь чистым Python-решением, если вы используете IPython (как вам, вероятно, следует в любом случае), вы можете сделать:

%run /path/to/filename.py

Что одинаково легко.

RS
источник
1

Я просто новичок здесь, так что, может быть, это просто удача, если я нашел это:

После попытки запустить скрипт из приглашения интерпретатора >>> с помощью команды

    execfile('filename.py')

для которого я получил "NameError: имя 'execfile' не определено", я попробовал очень простой

    import filename

это сработало хорошо :-)

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

Я использую Ubuntu 16.014 LTS x64. Python 3.5.2 (по умолчанию, 17 ноября 2016 г., 17:05:23) [GCC 5.4.0 20160609] в Linux

Клод
источник
0

Для меня самый чистый подход - использовать importlibи импортировать файл как модуль по пути, например так:

from importlib import util

def load_file(name, path):
    spec = util.spec_from_file_location(name, path)
    module = util.module_from_spec(spec)
    spec.loader.exec_module(module)
    return module

Пример использования

Давайте иметь файл foo.py:

print('i got imported')
def hello():
    print('hello from foo')

Теперь просто импортируйте и используйте его как обычный модуль:

>>> foo = load_file('foo', './foo.py')
i got imported
>>> foo.hello()
hello from foo

Я предпочитаю эту технику по сравнению с прямыми подходами, например, exec(open(...))потому что она не загромождает ваши пространства имен или излишне портит $PATH.

Арминий
источник