在 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 代码
谢谢你的帮助。
我在 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 代码
谢谢你的帮助。