【问题标题】:Inserting a dictionary into a python class using a decorator使用装饰器将字典插入 python 类
【发布时间】:2019-03-26 12:18:46
【问题描述】:

我正在尝试设计一个可以接受类名(不是对象)的包装器,并将字典插入到类的每个实例中。下面是我在包装现有函数时如何实现这一点的 sn-p。

def insert_fn_into_class(cls):
    """Decorator function that consumes a function and inserts it into the cls class."""
    def decorator(func):
        @wraps(func)
        def wrapper(self, *args, **kwargs):
            return func(*args, **kwargs)
        setattr(cls, f'prefix_{func.__name__}', wrapper)

    return decorator

如何使用类似的模板来装饰字典并将其插入到 cls 类中。既然字典是不可调用的,那么如何设计这样的包装器呢?

更新

感谢您对此提出的建设性反馈。 一位 stack-overflow 用户正确地指出,我没有解释我为什么要这样做。所以这里是:

  1. 我正在尝试构建一个框架,该框架本质上可以使用一堆用户定义的函数并扩展现有的类。所以我有一个类A,以及用户定义的函数f_1f_2、...、f_n,我想将它们注入到A类中,这样这个类obj_a = A()的实例就可以调用这些函数,例如obj_a.f_1()。我已经设法使用类似于上面的代码 sn-p 的装饰器函数来实现这一点。
  2. 现在,每个用户定义的函数都有一些重复的代码,如果我的基类A 的所有实例都可以访问用户定义的字典,我认为我可以删除这些代码。我实现这一目标的思考过程是尝试修改我现有的包装函数以将其添加到类中。但是,我知道字典不可调用,因此是个问题。

我希望这足够详细。

更新 2

看起来还有进一步阐述的余地。这是我之前描述的各种组件的外观示例。

module_a.py

class BaseClass():
    def __init__(self):
        ...

    def base_class_method(self):
        ...
    
    def run(self):
        getattr(self, 'prefix_user_fn_1')()

def extend(cls=BaseClass):
    def decorator(func):
        def wrapper(self, *args, **kwargs):
            return func(*args, **kwargs)
        setattr(cls, f'prefix_{func.__name__}', wrapper)
    return decorator

user_defined_functions.py

from module_a import BaseClass, extend

@extend()
def user_fn_1():
    dict_ = {'a':'b', 'c':'d'}
    ...

@extend()
def user_fn_2():
    dict_ = {'my':'dict', 'c':'d'}
    ...

main.py

from module_a import BaseClass

b = BaseClass()
b.run()

每个用户函数都包含一个常用字典的子集。为了利用这一点,我认为如果可以将其作为 BaseClass 动态注入的属性的一部分进行访问会很方便。

modified_user_defined_functions.py

# THIS WILL NOT WORK FOR OBVIOUS REASONS
from module_a import BaseClass, extend, common_config, insert_config_into_class

# @common_config ?? Is this even a good idea?
dict_ = {'my':'dict', 'a':'b', 'c':'d'}
insert_config_into_class(BaseClass, dict_)

@extend()
def user_fn_1(self):
    print(self.dict_)
    # do something with self.dict_
    ...

要合并这一点,我可能必须将BaseClass 上的运行方法更改为如下所示:

modified_module_a.py

...
class BaseClass():
    ...
    
    def run(self):
        getattr(self, 'prefix_user_fn_1')(self)

更新 3 - 潜在解决方案

def shared_config(data, cls=BaseClass):
    setattr(cls, 'SHARED_DICT', data)

这个解决方案有效,但在某种程度上回答了我的问题,我想我可以说,当一个简单的函数可能实现这一点时,我可能会通过为此编写一个装饰器来过度设计我的解决方案。

但是,原始问题的一个方面仍然存在 - 这是一个好方法吗?

【问题讨论】:

  • 在我看来是一个典型的 XY 问题。不要询问您认为的解决方案是什么(实际上这几乎是不可能的 - 除非专门为此设计,否则一个类对其实例一无所知),请解释您试图用这个“解决方案”解决的真正问题"。
  • 如果我没有说清楚,我很抱歉,但代码 sn-p 是我试图接近解决方案的设计。
  • 是的,但解决方案是什么?这只是“XY 问题”的定义:你问认为什么是解决方案,而不是解释真正的问题(“我怎么做 X?”=>“你为什么想要做 X ?” => “因为我需要它来解决 Y” => “啊,好吧 - 但这不是解决 Y 的方法,你想要 W 而不是”。所以如果你期望一个有用的答案,请解释真正的问题。
  • @brunodesthuilliers 谢谢你。我完全同意这一点。我修改了问题以反映对问题的更好构造的描述。
  • 这个字典是只读的还是必须是可写的?你可以发布一个示例使用吗? (dict + 几个使用它的“用户定义”函数等)?您的问题现在更有意义,但缺乏认真回答的背景(请记住,我们根本不了解您的项目!)。

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


【解决方案1】:

你提问的方式给了我一个暗示,你不太明白 python 如何使用对象引用。

装饰器不适用于不可调用的对象,因此需要澄清

接受类名(不是对象)

不是很清楚。而且你想附加到类的东西没有区别——字典或函数,无论如何你只会操纵抽象引用。

除此之外,看看这个sn-p:

def patcher(cls):
    def wrapper(*args, **kwargs):
        instance = cls(*args, **kwargs)
        instance.payload = {'my': 'dict'}
         return instance
    return wrapper

@patcher
class A:
    pass

它是否满足您的需求?

【讨论】:

  • 感谢您的回复。是的,装饰器并不意味着与不可调用的对象一起使用,这就是为什么我的问题的最后一行是它的原因。另外,你是对的,我应该更清楚 - 我正在尝试解决这个问题而不必实例化类。
【解决方案2】:

我想我可以说我可能过度设计了我的解决方案

嗯,确实有点……FWIW the whole thing looks quite overcomplicated

第一件事:如果你将你的 dict 定义为一个全局(模块)名称,那么该模块中的所有函数都可以直接访问它:

# simple.py

SHARED_DATA = {"foo": "bar", "answer": 42}

def func1():
    print SHARED_DATA["foo"]

def func2():
    print SHARED_DATA["bar"]

所以我并没有真正看到在从另一个模块导入的类中“注入”它的意义,只是为了从这些函数中访问它。

现在和你的装饰师一起:

def extend(cls=BaseClass):
    def decorator(func):
        def wrapper(self, *args, **kwargs):
            return func(*args, **kwargs)
        setattr(cls, f'prefix_{func.__name__}', wrapper)
    return decorator

如果目标是使函数成为类的属性但不将其转换为实例方法,则可以使用staticmethod

def extend(cls=BaseClass):
    def decorator(func):
        setattr(cls, f'prefix_{func.__name__}', staticmethod(func))
        return func
    return decorator

现在,如果您有其他(无法解释的)理由将SHARED_DICT 设为(BaseClass 或其他类的)类属性,您确实可以提供配置功能:

# module_a
def configure(cls, data):
    cls.SHARED_DATA = data


# less_simple.py

from module_a import BaseClass, configure

SHARED_DATA = {"foo": "bar", "answer": 42}
configure(BaseClass, SHARED_DATA)

def func1():
    print SHARED_DATA["foo"]

def func2():
    print SHARED_DATA["bar"]

但请注意,您仍然不需要通过 BaseClassself 从模块的函数中访问此字典。

可以当然将类或实例传递给你使用定义的函数,但在这里,不需要一个奇怪的装置 - 要么将它们设为类方法,要么直接将它们设置为类,Python 将负责将类或实例作为第一个参数注入(这里是一个使它们成为实例方法的示例):

# module_a
def configure(cls, data):
    cls.SHARED_DATA = data

def extend(cls=BaseClass):
    def decorator(func):
        setattr(cls, f'prefix_{func.__name__}', func)
        return func
    return decorator

# rube_goldberg.py

from module_a import BaseClass, configure, extend

SHARED_DATA = {"foo": "bar", "answer": 42}
configure(BaseClass, SHARED_DATA)

@extend
def func1(self):
    print SHARED_DATA["foo"]

@extend
def func2(self):
    print SHARED_DATA["bar"]

请注意,从装饰函数 POV 来看,这仍然完全没用 - 它们根本不需要 self 来访问 SHARED_DATA,但现在它们不能在没有 BaseClass 的情况下执行实例作为第一个参数,因此用户无法直接测试它们。

现在,您的问题中可能还有其他事情没有提及,但到目前为止,您似乎正在努力使简单的事情变得复杂;-)

【讨论】:

  • 哇。谢谢你的全面分析。对此,我真的非常感激。我需要通过 BaseClass 访问共享数据的原因是因为 BaseClass 上的某些操作也需要访问它。有一些“无法解释”的原因,但是关于我们所讨论的所有内容如何有助于更大的系统的信息太多了。实际上,这就是为什么我在提出问题时从最细微的层次开始的原因。不管怎样,谢谢。我今天确实学到了一些新东西。
猜你喜欢
  • 2011-03-18
  • 2021-07-16
  • 1970-01-01
  • 2011-03-09
  • 2021-01-05
  • 2017-12-08
  • 2011-04-28
相关资源
最近更新 更多