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

在Python中创建具有绑定属性的函数工厂,python,function,factory,Python,Function,Factory,我在python中遇到这个问题已经有一段时间了: 我需要在类中创建一个方法列表,这些方法执行几乎相同的操作,但使用不同的实例成员变量。 所以我的第一次尝试是这样的: from functools import partial class Operations: def __init__(self): self.a = 10 self.b = 20 self.c = 30 self.add_operation_1 = pa

我在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
def expected_function(self):
    self.a = self.a + self.b
如您所见,我使用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!!!
这次我没有使用partials,而是使用闭包。 主要优点是
getattr
仅在创建实例对象时执行一次,而不是在调用方法时执行,但我找不到摆脱
setattr
的方法。 作为一个副作用,这并不像我预期的那样有效
getattr
在开始时获取属性的值,因此返回的函数不会看到对这些属性的任何更改

所以现在我有点困了。 有没有一种方法可以生成这样的方法:

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
def expected_function(self):
    self.a = self.a + self.b
给定属性名称


谢谢。

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

关于性能,您应该通过直接使用_dict___属性而不是使用setattr/getattr来获得一些性能

要了解为什么直接访问uu dict_uuu比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转换为函数调用,而写入uuu dict uuuuu是一个存储操作。

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

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
关于性能,您应该通过直接使用_dict___属性而不是使用setattr/getattr来获得一些性能

要了解为什么直接访问uu dict_uuu比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转换为函数调用,而写入uu dict_uu是一个存储操作。

好的,在做了大量实验后,我找到了一个解决方案,尽管这很不合理

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']
因为这可以在初始化时进行评估,所以它完全符合我的需要。最大的权衡是优雅、可读性、错误处理等。 我认为Paul Cornelius解决方案对于这个用例来说已经足够好了。 虽然我可以考虑使用Jija模板来生成Python代码< /P>
谢谢你的帮助。

好的,在做了大量实验后,我找到了一个解决方案,尽管这是不公平的

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']
因为这可以在初始化时进行评估,所以它完全符合我的需要。最大的权衡是优雅、可读性、错误处理等。 我认为Paul Cornelius解决方案对于这个用例来说已经足够好了。 虽然我可以考虑使用Jija模板来生成Python代码< /P>
感谢您的帮助。

“主要优点是getattr只执行一次”-这不是优点。这是一个bug。“主要的优点是getattr只执行一次”-这不是一个优点。这是一个bug。我喜欢这样,但不是直接使用
\uuuu dict\uuuu
,而是做一些类似
namespace=vars(self)
的事情,然后至少
namespace[s1]+=namespace[s2]
,因为性能似乎是一个问题,您正在避免额外的属性查找,而且我觉得它只是读nicer@PaulCornelius这是一个优雅的解决方案。虽然使用索引时会生成额外的字节码:
BINARY\u SUBSCR
。这是因为python需要将字符串“s1”转换为属性s1。有没有一种方法可以在初始化时进行转换而不是函数调用?我不知道将字符串s1转换为属性s1是什么意思
v[s1]
使用字符串s1在字典中查找键。您必须找到要添加的两个值,并找到答案的放置位置-我不认为这是可以避免的。如果你对每一个额外的字节码都很着迷,那你就麻烦大了。我喜欢这样,但不要直接使用
\uuuu dict\uuuu
,至少要做
namespace=vars(self)
然后
namespace[s1]+=namespace[s2]
,因为性能似乎是个问题,您正在避免额外的属性查找,而且我认为它只是读取nicer@PaulCornelius这是一个优雅的解决方案。虽然使用索引时会生成额外的字节码:
BINARY\u SUBSCR
。这是因为python需要将字符串“s1”转换为属性s1。有没有一种方法可以在初始化时进行转换而不是函数调用?我不知道将字符串s1转换为属性s1是什么意思
v[s1]
使用字符串s1在字典中查找键。您必须找到要添加的两个值,并找到答案的放置位置-我不认为这是可以避免的。如果你要为每一个额外的字节码发狂,你就有麻烦了。