在 Python 中创建具有绑定属性的函数工厂

Create a function factory with bound attributes in Python

我在 python 中遇到这个问题有一段时间了: 我需要在 class 中创建一个方法列表,它们做几乎相同的事情但具有不同的实例成员变量。 所以我的第一次尝试是这样的:

from functools import partial


class Operations:
    def __init__(self):
        self.a = 10
        self.b = 20
        self.c = 30
        self.add_operation_1 = partial(self.generic_add_1, 'a', 'b')
        self.add_operation_2 = partial(self.generic_add_1, 'a', 'c')

    def generic_add_1(self, first, second):
        first_value = getattr(self, first)
        second_value = getattr(self, second)
        setattr(self, first, first_value + second_value)


instance = Operations()
instance.add_operation_1()
print(instance.a)
# Should print 30
instance.add_operation_2()
print(instance.a)
# Should print 60

如您所见,我使用 getattr 和 setattr 来引用我需要更改的属性。

这行得通,但真的很慢,因为 partial 只保留参数,当函数被调用时,它会将它们传递给原始函数。另外,我对此不确定,但是 getattr 和 setattr 并不比使用 object.property

这样的东西慢一点

所以我成功进行了第二次尝试:

class Operations:
    def __init__(self):
        self.a = 10
        self.b = 20
        self.c = 30
        self.add_operation_1 = self.generic_add('a', 'b')
        self.add_operation_2 = self.generic_add('a', 'c')

    def generic_add(self, first, second):
        first_value = getattr(self, first)
        second_value = getattr(self, second)

        def real_operation():
            setattr(self, first, first_value + second_value)

        return real_operation


instance = Operations()
instance.add_operation_1()
print(instance.a)

# Should print 30

instance.add_operation_2()
print(instance.a)
# Should print 60 but returns 40 instead!!!

这次我没有使用局部而是闭包。 主要优点是 getattr 只在创建实例对象时执行一次,而不是在调用方法时执行一次,但我找不到摆脱 setattr 的方法。 作为副作用,这并不像我预期的那样有效。 getattr 在开头获取 属性 的值,因此返回的函数不会看到对这些属性的任何更改。

所以现在我有点卡住了。 有没有办法生成这样的方法:

def expected_function(self):
    self.a = self.a + self.b

给定属性名称?

谢谢。

正如 dospro 在他的评论中所指出的,getattr 只被执行一次是一个错误。您的闭包将在后续调用中使用过时的值。

关于性能,您应该直接使用 __dict__ 属性而不是使用 setattr / getattr。

要了解为什么直接访问 __dict__ 比 getattr / setattr 更快,我们可以查看生成的字节码:

self.__dict__['a'] = 1

0 LOAD_CONST               1 (1)
2 LOAD_FAST                0 (self)
4 LOAD_ATTR                0 (__dict__)
6 LOAD_CONST               2 ('a')
8 STORE_SUBSCR

setattr(self, 'a', 1)

0 LOAD_GLOBAL              0 (setattr)
2 LOAD_FAST                0 (self)
4 LOAD_CONST               1 ('a')
6 LOAD_CONST               2 (1)
8 CALL_FUNCTION            3
10 POP_TOP

setattr 转换为函数调用,而写入 __dict__ 是一个存储操作。

def function_generate(v, s1, s2):
    def f():
        v[s1] += v[s2]
    return f

class Operations:
    def __init__(self):
        self.a = 10
        self.b = 20
        self.c = 30
        namespace = vars(self)
        self.add_operation_1 = function_generate(namespace, 'a', 'b')
        self.add_operation_2 = function_generate(namespace, 'a', 'c')


instance = Operations()
instance.add_operation_1()
print(instance.a)
# Should print 30
instance.add_operation_2()
print(instance.a)
# Should print 60

好的,在做了很多实验之后我找到了一个解决方案,虽然它很不优雅。

def generic_eval_add(self, first, second):
        my_locals = {
            'self': self
        }
        string_code = """def real_operation():
    self.{0} = self.{0} + self.{1}""".format(first, second)

        print(string_code)
        exec(string_code, my_locals)

        return my_locals['real_operation']

由于这可以在初始化时进行评估,因此它正是我所需要的。最大的权衡是优雅、可读性、错误处理等。 Y 认为 Paul Cornelius 解决方案对于这个用例来说已经足够好了。 尽管我可能会考虑使用 jinja 模板来生成 python 代码

谢谢你的帮助。