在Python中使用绑定属性创建函数工厂

问题描述 投票:0回答:3

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

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

给出属性名称?

谢谢。

python function factory
3个回答
2
投票
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

0
投票

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

关于性能,您应该直接使用__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__是一个存储操作。


0
投票

好了经过大量的实验,我找到了一个解决方案,虽然它很不优雅。

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代码

谢谢你的帮助。

© www.soinside.com 2019 - 2024. All rights reserved.