【问题标题】:How to change the code of a class' method with a decorator?如何使用装饰器更改类方法的代码?
【发布时间】:2021-10-24 01:06:03
【问题描述】:

我正在创建一个装饰器,用于创建特定于模块的使用类(仅接受特定模块中的类的实例化),并且我正在尝试更改该类的 __init__ 方法中的代码。我尝试将一些编译后的代码添加到对象的 __init__ 方法的 __code__ 属性中,如下所示:

klass.__init__.__code__ += hook

我的完整装饰器:

def ModuleSpecificUse(klass=None, module=__name__):
    def decorator(cls):
        hook = compile("""\nif __name__ != module:
        raise ValueError("cannot use object")""", __name__ + ".py", "exec")
        klass.__init__.__code__ += hook

        return klass

    return decorator

然后我尝试像这样使用ModuleSpecificUse 装饰器:

@ModuleSpecificUse(module="foo")
class C:
    pass

得到了这个错误:

Traceback (most recent call last):
  File "C:/.../test.py", line 13, in <module>
    class C:
  File "C:/.../test.py", line 5, in decorator
    klass.__init__.__code__ += hook
AttributeError: 'method-wrapper' object has no attribute '__code__'

ModuleSpecificUse 装饰器的理想用途是首先创建一个由ModuleSpecificUse 装饰的类:

@ModuleSpecificUse(module="some_module")
class C:
    <code>

如果模块不是some_module,则应该引发异常:

ValueError: cannot use object

【问题讨论】:

  • ModuleSpecificUse 的典型用法是什么样的?几乎可以肯定,有比尝试修改函数的 __code__ 属性更好的方法。
  • 我认为可以装饰方法来做你想做的事(尽管不是以这种方式)。请提供minimal reproducible example 以说明如果它有效,将如何使用它 - 以及其他人可以用作测试用例的东西。
  • 更具体地说:您希望通过限制可以实例化类的模块来避免什么错误
  • 我认为@chepner 的意思是这听起来很像XY Problem
  • 如果C 的定义首先会引发错误,为什么还要在some_module.py 中定义它?如果您确实 允许定义类而不是引发错误,会发生什么? (无论如何,真正想要颠覆您的支票的人只会将其他参数传递给ModuleSpecificUse。)

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


【解决方案1】:

我认为以下内容会按您说的做。它被实现为一个类装饰器,将类替换为从它派生的类,该类具有自己的__init__(),首先进行模块名称检查,如果不是指定的则引发异常,否则调用超类'__init__() .

派生类的__init__() 通过调用堆栈“返回”并检查调用者全局命名空间中__file__ 变量的值来确定正在调用的模块的名称。是的,这有点像你可以在 hacky 方面调用的内容;¬)

test_class_deco.py

from pathlib import Path
import sys

def ModuleSpecificUse(allowed_module=None):
    if not allowed_module:
        allowed_module = Path(__file__).stem

    def decorator(cls):
        class Class(cls):
            def __init__(self, *args, **kwargs):
                nonlocal allowed_module

                namespace = sys._getframe(1).f_globals  # Caller's globals.
                module_file = namespace.get('__file__')
                module_name = Path(module_file).stem

                if module_name != allowed_module:
                    raise ValueError(f'only {allowed_module!r} module can create instances '
                                     f'of class {cls.__name__}')
                super().__init__(*args, **kwargs)

        return Class

    return decorator



@ModuleSpecificUse()  # Note must use of trailing ().
class C:
    pass

@ModuleSpecificUse(allowed_module="baz")
class D:
    pass


if __name__ == '__main__':

    try:
        c = C()
    except ValueError as exc:
        print('Error: Exception {exc!r} should NOT have been raised.')
    else:
        print('No problem creating instance of class C.')

    try:
        d = D()
    except ValueError as exc:
        print(f'Exception {exc!r}\n    raised AS EXPECTED when trying to create '
              f'instance of class D.')
    else:
        print('Error: A ValueError exception should have been raised.')

baz.py - 表明baz 模块可以创建D 类的实例,

from test_class_deco import D

try:
    d = D()
except ValueError as exc:
    print('Error: Exception {exc!r} should NOT have been raised.')
else:
    print('No problem creating instance of class D.')

【讨论】:

    猜你喜欢
    • 2022-08-15
    • 2014-01-14
    • 2020-01-11
    • 2019-02-03
    相关资源
    最近更新 更多