Почему должна быть создана новая глобальная переменная для ссылки на текущий экземпляр класса в "exec"? - PullRequest
0 голосов
/ 25 ноября 2018

У меня есть класс, содержащий ~ 20 методов, и в def __init__(self, ...): мне нужно вызвать многие из этих методов (~ 9), но я не хотел вызывать каждый отдельный метод по одному.

Итак, я выбрал легкий путь и создал два списка со списками, которые используют exec для вызова каждого метода:

[exec("self.create%s()" % x) for x in "ArticleObjects SeriesObjects ArticleList SearchList".split(" ")]
[exec("self.compile%sPage(self)" % x) for x in "About Screenshots Search Contact Articles".split(" ")]

Когда я запустил этот код с помощью python3 filename.py, я получил ошибку,что читается:

NameError: name 'self' is not defined

Путем проб и ошибок я нашел это;чтобы заставить этот код работать, мне пришлось создать копию self с именем instance и сделать новую переменную instance глобальной переменной, а затем вызвать метод, используя ClassName.methodName(instance) вместо self.methodName():

С рабочим кодом:

global instance; instance = self
[exec("ClassNamecreate%s(instance)" % x) for x in "ArticleObjects SeriesObjects ArticleList SearchList".split(" ")]
[exec("ClassName.compile%sPage(instance)" % x) for x in "About Screenshots Search Contact Articles".split(" ")]

Почему это так?Почему переменная self не определена в exec, хотя она доступна для области, в которую вызывается exec?

Обновление: Я использую Python 3.6.7

Ответы [ 3 ]

0 голосов
/ 25 ноября 2018

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

class Test:
    def __init__(self):
        for f in (self.createA, self.createB, self.createC):
            f()

    def createA(self):
        print("A")

    def createB(self):
        print("B")

    def createC(self):
        print("C")
0 голосов
/ 25 ноября 2018

Здесь есть много хороших предложений о том, как избежать выражения exec (что обычно плохо), но чтобы ответить на ваш вопрос о , почему это происходит, это больше связано с пониманием списка.Понимания списка создают новую область видимости, и когда вы вызываете exec без аргумента globals или locals, он использует функцию locals():

Примечание. Локальные значения по умолчанию действуют так, как описано ниже для функции locals ()

Источник

Здесь вы можете увидеть, как выглядят результаты функции locals() из понимания списка:

class Sample:
    def __init__(self):
        k = 4
        print(locals())
        exec("print(locals())")
        [print(locals()) for x in range(1)]
        [exec("print(locals())") for x in range(1)]
Sample()

output:

{'k': 4, 'self': <__main__.Sample object at 0x00000000030295C0>}
{'k': 4, 'self': <__main__.Sample object at 0x00000000030295C0>}
{'x': 0, '.0': <range_iterator object at 0x00000000030019F0>}
{'x': 0, '.0': <range_iterator object at 0x00000000030019F0>}

Итак, locals() одинаково внутри или снаружи exec.Это понимание списка, которое меняет его.Только когда вы находитесь за пределами выражения exec, интерпретатор может пройти мимо локальных представлений списка и найти себя во внешней области видимости.Нет такой удачи, как только вы позвоните exec.

0 голосов
/ 25 ноября 2018

Использование getattr проще (и обычно безопаснее), чем exec.Попробуйте что-то вроде этого:

def __init__(self):
    suffixes = ["ArticleObjects", "SeriesObjects", ...]
    for suffix in suffixes:
        method = getattr(self, "create" + suffix)
        method()
...