Название искажения в функции декораторов - PullRequest
0 голосов
/ 15 мая 2018

Я не могу использовать декоратор внутри класса, объявленного в том же модуле, что и декоратор, если имя декоратора имеет тип __double_leading_underscore.

Проще объяснить на примере:

# Just a pass-through
def __decorator(fn):
  return fn

decorator = __decorator

class A(object):
  @decorator
  def test(self):
    return 1

print(A().test())
# Prints 1

Если я изменю @decorator на @__decorator:

class A(object):
  @__decorator
  def test(self):
    return 1

Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "<stdin>", line 2, in A
NameError: name '_A__decorator' is not defined

Он пытается найти __decorator внутри класса.

Есть ли способ сохранить соглашение об именах, но ссылаться на модуль вместо класса?

Ответы [ 2 ]

0 голосов
/ 08 октября 2018

Как отмечалось в этом посте это связано с искажением имени атрибута.

Код, опубликованный OP, является интересным случаем, который заставил меня исследовать, как осуществляется управление именами.Оказывается, искажение имени выполняется во время компиляции в байт-код Python, что можно увидеть, выполнив этот код ( run в Python 3.7 ):

import dis

# I want the source code, not just class object.
a_def = '''
class A:
    __mangled = 'aiya!'

    def p(self):
        print(self.__mangled)
'''

print(dis.dis(a_def, depth=2))  # In Python 3.7 they added `depth` argument so nested `code object`s will be printed.

Байт-код:

  2           0 LOAD_BUILD_CLASS
              2 LOAD_CONST               0 (<code object A at 0x7f4b3f6ddd20, file "<dis>", line 2>)
              4 LOAD_CONST               1 ('A')
              6 MAKE_FUNCTION            0
              8 LOAD_CONST               1 ('A')
             10 CALL_FUNCTION            2
             12 STORE_NAME               0 (A)
             14 LOAD_CONST               2 (None)
             16 RETURN_VALUE

Disassembly of <code object A at 0x7f4b3f6ddd20, file "<dis>", line 2>:
  2           0 LOAD_NAME                0 (__name__)
              2 STORE_NAME               1 (__module__)
              4 LOAD_CONST               0 ('A')
              6 STORE_NAME               2 (__qualname__)

  3           8 LOAD_CONST               1 ('aiya!')
             10 STORE_NAME               3 (_A__mangled)

  5          12 LOAD_CONST               2 (<code object p at 0x7f4b3f6dde40, file "<dis>", line 5>)
             14 LOAD_CONST               3 ('A.p')
             16 MAKE_FUNCTION            0
             18 STORE_NAME               4 (p)
             20 LOAD_CONST               4 (None)
             22 RETURN_VALUE

Disassembly of <code object p at 0x7f4b3f6dde40, file "<dis>", line 5>:
  6           0 LOAD_GLOBAL              0 (print)
              2 LOAD_FAST                0 (self)
              4 LOAD_ATTR                1 (_A__mangled)
              6 CALL_FUNCTION            1
              8 POP_TOP
             10 LOAD_CONST               0 (None)
             12 RETURN_VALUE

Это объясняет, почему __decorator в теле класса вызывает поиск _A__decorator, потому что он "жестко закодирован" в байт-коде.

Единственный способ вызвать __decorator в теле класса - этоиспользуйте один из следующих вызовов:

import sys

__mangled = '???'

class A():    
    # All following calls look horrible.
    print(globals()['__mangled'])
    print(eval('__mangled'))
    this_module = sys.modules['__main__']
    print(getattr(this_module, '__mangled'))

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

Любой идентификатор вида __spam (как минимум два ведущихподчеркивания, самое большее одно заключительное подчеркивание ) текстуально заменяется на _classname__spam, где classname - это имя текущего класса с удаленными начальными подчеркиваниями.

0 голосов
/ 16 мая 2018

Это из-за искажения имени в Python. Согласно документам

Любой идентификатор вида __spam (как минимум два ведущих символа подчеркивания, не более одного завершающего подчеркивания) текстуально заменяется на _classname__spam, где classname - это текущее имя класса с начальными символами подчеркивания. Это искажение сделано без учета к синтаксической позиции идентификатора, пока это происходит в пределах определения класса.

Акцент добавлен.

Когда интерпретатор видит @__decorator в классе A, он игнорирует привязку к decorator, текстуально заменяет __decorator на _A__decorator и пытается оценить этот идентификатор, который дает вам NameError.

...