Изменение доступа к атрибуту для модуля __main__ (подробности разрешения имени) - PullRequest
0 голосов
/ 17 декабря 2018

Информация, полученная из документов

Относительно разрешение имен документация не совсем ясна.Он использует термины scope и namespace , но не дает точных сведений о том, как они вступают в силу и когда в точности возникает NameError:

Когдаимя используется в блоке кода, оно разрешается с использованием ближайшей области видимости.Набор всех таких областей видимости для блока кода называется средой блока.

Если имя не найдено вообще, возникает исключение NameError.

Этооднако не объясняет, где именно имя ищется.Относительно пространств имен мы получаем следующую информацию:

Имена разрешаются в пространстве имен верхнего уровня путем поиска в глобальном пространстве имен, то есть в пространстве имен модуля, содержащего блок кода, [...]

И далее, относительно __main__:

Пространство имен для модуля создается автоматически при первом импорте модуля.Основной модуль для скрипта всегда называется __main__.

В этой части документа далее указывается, что

'__main__' являетсяимя области, в которой выполняется код верхнего уровня.

Соответствующий код

Объединяя вышеприведенные операторы, я предполагаю, что всякий раз, когда имя должно быть разрешено в "среда сценариев верхнего уровня " (" пространство имен верхнего уровня ") это происходит путем проверки sys.modules['__main__'] (аналогично тому, как работает доступ к атрибутам для модулей и как он может быть изменен, как указано в PEP 562 ).Однако следующий фрагмент показывает, что это не так:

import sys

class Wrapper:
    def __init__(self):
        self.main = sys.modules['__main__']

    def __getattr__(self, name):
        try:
            return getattr(self.main, name)
        except AttributeError:
            return 'Fallback for "{}"'.format(name)

sys.modules['__main__'] = Wrapper()
print(undefined)

, что повышает NameError: name 'undefined' is not defined.

С другой стороны, мы можем добавлять имена, изменяя sys.modules['__main__'].__dict__ или используя setattr:

import sys

# Either ...
sys.modules['__main__'].__dict__['undefined'] = 'not anymore'
# Or ...
setattr(sys.modules['__main__'], 'undefined', 'not anymore')

print(undefined)  # Works.

Так что я подозревал, что, возможно, это атрибут __dict__ модуля (или эквивалентно __builtins__.globals), который проверяется напрямую, обходя getattr объекта модуля.Однако расширение приведенного выше примера показывает, что это не так:

import sys

class Wrapper:
    def __init__(self):
        self.main = sys.modules['__main__']

    def __getattr__(self, name):
        try:
            return getattr(self.main, name)
        except AttributeError:
            return 'Fallback for "{}"'.format(name)

    @property
    def __dict__(self):
        class D:
            def __contains__(*args):
                return True

            def __getitem__(__, item):
                return getattr(self, item)

        return D()

sys.modules['__main__'] = Wrapper()
sys.modules['builtins'].globals = lambda: sys.modules['__main__'].__dict__
print(globals()['undefined'])  # Works.
print(undefined)               # Raises NameError.

Вопросы

  1. Какое точное определение областей и пространств имен?
  2. Как точно разрешаются имена (какие шаги предпринимаются и какие ресурсы проверяются, чтобы определить, существует ли имя)?
  3. Каким образом для разрешения имен используются области ипространства имен?
  4. Почему в приведенном выше примере с использованием Wrapper происходит сбой (хотя он работает при «общем» доступе к атрибутам модуля, согласно PEP 562 )?

1 Ответ

0 голосов
/ 17 декабря 2018

Это очень интересный вопрос, так как у меня нет четкого ответа, давайте проведем несколько экспериментов.

Сначала давайте немного изменим ваш код:

# file main.py
import sys
print(sys.modules['__main__'])
class Wrapper:
    def __init__(self):
        self.main = sys.modules['__main__']

    def __getattr__(self, name):
        try:
            return getattr(self.main, name)
        except AttributeError:
            return 'Fallback for "{}"'.format(name)

sys.modules['__main__'] = Wrapper()
print(sys.modules['__main__'])
print(undefined)

Он напечатает

<module '__main__' from 'main.py'>
<__main__.Wrapper object at 0x000001F87601BE48>
Traceback (most recent call last):
  File "main.py", line 15, in <module>
    print(undefined)
NameError: name 'undefined' is not defined

Таким образом, у нас все еще есть __main__ в качестве модуля, и Wrapper класс находится внутри него.

Документы говорят:

Модуль __name__ устанавливается равным __main__ при чтении из стандартного ввода, скрипта или из интерактивного приглашения.

Так что это означает, что наша строка sys.modules['__main__'] = Wrapper() подразумеваетсязаменить уже загруженный модуль чем-то внутри этого модуля (!!).

OTOH, импортируя main.py из REPL (другой случай, когда создается модуль __main__), полностью портится все так что в это время происходит некоторая замена.

Подводя итог:

Насколько я вижу, требуется немного глубокой темной магии, чтобы изменить __main__ изнутриработающий модуль, может быть, если мы используем importlib.reload и связываемся с кэшированными модулями?

Делаем этоиз другого модуля кажется нормальным, но (пример) портится с вещами, и разрешение имен нарушается, т. е. класс Wapper не разрешает предыдущие имена так, как вы считаете.

PD.

Извините, если это не тот опытный ответ, который вы хотели, и он больше похож на комментарий.Я сделал это в качестве эксперимента, чтобы проверить вашу гипотезу и, возможно, найти некоторые результаты.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...