通过动态设置方法的奇怪行为
strange behavioiur by dynamically setting methods
我发现动态添加方法到 class 时的“奇怪”行为。当我一个一个地(非迭代地)完成时,一切正常 [Sample 1] 但是当我尝试循环任务时,似乎所有方法都指向最后一个的主体添加方法 [示例 2.A]。在进一步尝试 [Sample 2.B] 中,我再次得到相同的“奇怪”结果。
你能帮我找出这种行为的根源是什么以及如何解决它吗?
我正在使用 python 3.9.6
这里是代码示例:
测试函数
def a(): print('a()')
def a1(): print('a1()')
def a2(p='p'): print('a2({})'.format(p))
示例 1: 一种方法(正确行为)
class Test1(object): pass
setattr(Test1, a.__name__, lambda self, *args, **kwargs: a(*args, **kwargs))
setattr(Test1, a1.__name__, lambda self, *args, **kwargs: a1(*args, **kwargs))
setattr(Test1, a2.__name__, lambda self, *args, **kwargs: a2(*args, **kwargs))
Test1().a()
Test1().a1()
Test1().a2()
输出
a()
a1()
a2(p)
示例 2.A:“奇怪”行为 - 迭代方法
class Test2(object): pass
for f in [a, a1, a2]:
setattr(Test2, f.__name__, lambda self, *args, **kwargs: f(*args, **kwargs))
# print(f.__name__, hasattr(Test2, f.__name__)) # debug: correct output
# eval("Test2().{}()".format(f.__name__)) # debug: correct output
Test2().a()
Test2().a1()
Test2().a2()
print(dir(Test2)) # the methods have right signature but same body!
输出
a2(p)
a2(p)
a2(p)
[..., 'a', 'a1', 'a2']
示例 2.B:“奇怪”行为 - 工厂方法
Test3 = type('Test3', (object,), {f.__name__: lambda self, *args, **kwargs: f(*args, **kwargs) for f in [a, a1, a2]})
Test3().a()
Test3().a1()
Test3().a2()
print(dir(Test3)) # the methods have right signature but same body!
输出
a2(p)
a2(p)
a2(p)
[..., 'a', 'a1', 'a2']
我写了一个更简化的例子来解释这里发生的事情。
funcs = []
for i in range(5):
print(i + 1)
funcs.append(lambda: i + 1)
print("exec lambdas")
for func in funcs:
print(func())
输出
1
2
3
4
5
exec lambdas
5
5
5
5
5
现在这显示了与您的问题相同的行为。在设置 lambda 时,我们看到 i 的值在增加。然而,当我稍后调用 lambdas 时,它们都是 return 相同的值。
这里的问题是我告诉 lambda,当我打电话给你时,我希望你 return i + 1
的值。然而,当我开始执行 lambdas 函数时,循环已经完成。循环后 i
的值现在是 4。因为所有的 lambda 都说同样的事情,return i + 1
的值和 i
的值现在是 4 ,lambda return 的所有执行都是相同的值,因为它们都引用 i
并且在循环结束时 i
被设置为 4.
你的情况也一样。您正在设置在循环期间评估的属性 f.__name__
。然后创建一个 lambda,稍后执行时将计算 f。当你在循环之后执行 lambda 时,f
将指向 a2
所以你所有的属性 a
a1
a2
都将在循环之后调用并评估 f
意味着 f 指向它设置的最后一项,即 a2
感谢 Chris Doyle 的考虑,我提出了问题的解决方案(至少在语法方面)。
示例2.A的解决方案:“奇怪”行为 - 迭代方法
class Test2(object): pass
for f in [a, a1, a2]:
setattr(Test2, f.__name__, \
eval('lambda self,*args, **kwargs: {}(*args, **kwargs)'.format(f.__name__))) # ok
Test2().a()
Test2().a1()
Test2().a2()
输出
a()
a1()
a2(p)
备注
- 重要的是
eval
作用于完整的 lambda
表达式,如果它只是 return 值,eval("f(*args, **kwargs)"
,那么问题与之前相同
- 对于无
eval
的解决方案,传递迭代变量 f
作为匿名函数的键值参数
setattr(Test2, f.__name__, lambda self, f=f, *args, **kwargs: f(*args, **kwargs))
示例2.B的解决方案:“奇怪”行为 - 工厂方法
d = {f.__name__: eval('lambda self, *args, **kwargs: f(*args, **kwargs)', dict(f=f, )) for f in [a, a1, a2]} # ok
Test3 = type('Test3', (object,), d)
Test3().a()
Test3().a1()
Test3().a2()
输出
a()
a1()
a2(p)
备注
对于无 eval
的解决方案,它与上面
的论证相同
d = {f.__name__: lambda self, f=f, *args, **kwargs: f(*args, **kwargs) for f in [a, a1, a2]}
我想问题的根源是与嵌套范围的某种冲突。如果有人想在这一点上添加一些评论,那么问题就可以认为已经解决了。
我当然也对不同的解决方案持开放态度...也许使用 nonlocal
关键字或更奇特的后缀:)
我发现动态添加方法到 class 时的“奇怪”行为。当我一个一个地(非迭代地)完成时,一切正常 [Sample 1] 但是当我尝试循环任务时,似乎所有方法都指向最后一个的主体添加方法 [示例 2.A]。在进一步尝试 [Sample 2.B] 中,我再次得到相同的“奇怪”结果。
你能帮我找出这种行为的根源是什么以及如何解决它吗?
我正在使用 python 3.9.6
这里是代码示例:
测试函数
def a(): print('a()')
def a1(): print('a1()')
def a2(p='p'): print('a2({})'.format(p))
示例 1: 一种方法(正确行为)
class Test1(object): pass
setattr(Test1, a.__name__, lambda self, *args, **kwargs: a(*args, **kwargs))
setattr(Test1, a1.__name__, lambda self, *args, **kwargs: a1(*args, **kwargs))
setattr(Test1, a2.__name__, lambda self, *args, **kwargs: a2(*args, **kwargs))
Test1().a()
Test1().a1()
Test1().a2()
输出
a()
a1()
a2(p)
示例 2.A:“奇怪”行为 - 迭代方法
class Test2(object): pass
for f in [a, a1, a2]:
setattr(Test2, f.__name__, lambda self, *args, **kwargs: f(*args, **kwargs))
# print(f.__name__, hasattr(Test2, f.__name__)) # debug: correct output
# eval("Test2().{}()".format(f.__name__)) # debug: correct output
Test2().a()
Test2().a1()
Test2().a2()
print(dir(Test2)) # the methods have right signature but same body!
输出
a2(p)
a2(p)
a2(p)
[..., 'a', 'a1', 'a2']
示例 2.B:“奇怪”行为 - 工厂方法
Test3 = type('Test3', (object,), {f.__name__: lambda self, *args, **kwargs: f(*args, **kwargs) for f in [a, a1, a2]})
Test3().a()
Test3().a1()
Test3().a2()
print(dir(Test3)) # the methods have right signature but same body!
输出
a2(p)
a2(p)
a2(p)
[..., 'a', 'a1', 'a2']
我写了一个更简化的例子来解释这里发生的事情。
funcs = []
for i in range(5):
print(i + 1)
funcs.append(lambda: i + 1)
print("exec lambdas")
for func in funcs:
print(func())
输出
1
2
3
4
5
exec lambdas
5
5
5
5
5
现在这显示了与您的问题相同的行为。在设置 lambda 时,我们看到 i 的值在增加。然而,当我稍后调用 lambdas 时,它们都是 return 相同的值。
这里的问题是我告诉 lambda,当我打电话给你时,我希望你 return i + 1
的值。然而,当我开始执行 lambdas 函数时,循环已经完成。循环后 i
的值现在是 4。因为所有的 lambda 都说同样的事情,return i + 1
的值和 i
的值现在是 4 ,lambda return 的所有执行都是相同的值,因为它们都引用 i
并且在循环结束时 i
被设置为 4.
你的情况也一样。您正在设置在循环期间评估的属性 f.__name__
。然后创建一个 lambda,稍后执行时将计算 f。当你在循环之后执行 lambda 时,f
将指向 a2
所以你所有的属性 a
a1
a2
都将在循环之后调用并评估 f
意味着 f 指向它设置的最后一项,即 a2
感谢 Chris Doyle 的考虑,我提出了问题的解决方案(至少在语法方面)。
示例2.A的解决方案:“奇怪”行为 - 迭代方法
class Test2(object): pass
for f in [a, a1, a2]:
setattr(Test2, f.__name__, \
eval('lambda self,*args, **kwargs: {}(*args, **kwargs)'.format(f.__name__))) # ok
Test2().a()
Test2().a1()
Test2().a2()
输出
a()
a1()
a2(p)
备注
- 重要的是
eval
作用于完整的lambda
表达式,如果它只是 return 值,eval("f(*args, **kwargs)"
,那么问题与之前相同 - 对于无
eval
的解决方案,传递迭代变量f
作为匿名函数的键值参数setattr(Test2, f.__name__, lambda self, f=f, *args, **kwargs: f(*args, **kwargs))
示例2.B的解决方案:“奇怪”行为 - 工厂方法
d = {f.__name__: eval('lambda self, *args, **kwargs: f(*args, **kwargs)', dict(f=f, )) for f in [a, a1, a2]} # ok
Test3 = type('Test3', (object,), d)
Test3().a()
Test3().a1()
Test3().a2()
输出
a()
a1()
a2(p)
备注
对于无 eval
的解决方案,它与上面
d = {f.__name__: lambda self, f=f, *args, **kwargs: f(*args, **kwargs) for f in [a, a1, a2]}
我想问题的根源是与嵌套范围的某种冲突。如果有人想在这一点上添加一些评论,那么问题就可以认为已经解决了。
我当然也对不同的解决方案持开放态度...也许使用 nonlocal
关键字或更奇特的后缀:)