在函数装饰器中命名mangling

时间:2018-05-15 19:55:45

标签: python python-3.x python-decorators

如果装饰器名称是__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 :(得分:2)

这是由于Python的名称错误。根据{{​​3}}

  

表单__spam的任何标识符(至少两个前导下划线,   最多一个尾随下划线)替换为文本   _classname__spam,其中classname是当前的类名,其中前导下划线被剥离。这种破坏是不加考虑的   到标识符的句法位置,只要它发生   在一个类的定义内。

强调补充。

如果解释程序在类@__decorator中看到A,它会忽略对decorator的绑定,用__decorator替换_A__decorator并尝试评估那个标识符,它为您提供NameError

答案 1 :(得分:0)

in this post所述,这是由于属性名称修饰所致。

OP发布的代码是一个有趣的案例,使我研究了名称管理的执行方式。事实证明,名称转换是在编译为Python字节码的过程中执行的,可以通过运行以下代码(在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名称上。如果您的目标是暗示不应直接使用module属性,则只需一个下划线就足够了。但是,如果您真的想要前导双下划线,则可以添加两个以上的下划线以防止以noted in documentation的形式出现:

  

任何形式的__spam标识符(至少两个下划线,   最多一个下划线)在文字上被替换为   _classname__spam,其中classname是当前的类名,其中前导下划线被去除。