如何在动态创建 class 时使用 super() 传递函数?
How to pass function with super() when creating class dynamically?
假设我有这个代码:
class StaticParent:
def _print(self):
print("I'm StaticParent")
class StaticChild(StaticParent):
def _print(self):
print('StaticChild saying: ')
super()._print()
def _parent_print_proto(self):
print("I'm DynamicParent")
def _child_print_proto(self):
print('DynamicChild saying: ')
super()._print()
DynamicParent = type('DynamicParent', tuple([]), {"_print": _parent_print_proto})
DynamicChild = type('DynamicChild', (DynamicParent,), {"_print": _child_print_proto})
sc = StaticChild()
sc._print()
dc = DynamicChild()
dc._print()
它的输出是:
StaticChild saying:
I'm StaticParent
DynamicChild saying:
Traceback (most recent call last):
File "/tmp/example.py", line 28, in <module>
dc._print()
File "/tmp/example.py", line 17, in _child_print_proto
super()._print()
RuntimeError: super(): __class__ cell not found
所以问题是如何为调用 super()
的许多 类 创建原型方法?
PS 我尝试用 lambda 实现方法,但它也不起作用:
DynamicChildWithLambda = type('DynamicChild', (DynamicParent,), {"_print": lambda self : print('Lambda saying: ', super()._print())})
Traceback (most recent call last):
File "/tmp/example.py", line 30, in <module>
dcwl._print()
File "/tmp/example.py", line 23, in <lambda>
DynamicChildWithLambda = type('DynamicChild', (DynamicParent,), {"_print": lambda self : print('Lambda saying: ', super()._print())})
RuntimeError: super(): __class__ cell not found
PS2 我也这样试过:
class StaticParent:
def _print(self):
print("I'm StaticParent")
def _child_print_proto(self):
print('DynamicChild saying: ')
super(StaticParent, self)._print()
DynamicChild = type('DynamicChild', (StaticParent,), {"_print": _child_print_proto})
dc = DynamicChild()
dc._print()
DynamicChild saying:
Traceback (most recent call last):
File "/tmp/example.py", line 13, in <module>
dc._print()
File "/tmp/example.py", line 8, in _child_print_proto
super(StaticParent, self)._print()
AttributeError: 'super' object has no attribute '_print'
在 class 中定义的方法会得到一个伪造的闭包作用域,该作用域会自动提供它在 no-arg super()
中定义的 class。当在 class 之外定义时,它不能执行此操作(因为在您定义方法时显然没有定义 class)。但是您仍然可以通过 old-fashioned 方式进行闭包,方法是实际编写一个您手动定义 __class__
的闭包函数:
class StaticParent:
def _print(self):
print("I'm StaticParent")
class StaticChild(StaticParent):
def _print(self):
print('StaticChild saying: ')
super()._print()
def _parent_print_proto(self):
print("I'm DynamicParent")
# Nesting allows us to make the inner function have an appropriate __class__
# defined for use by no-arg super
def _make_child_print_proto(cls):
__class__ = cls
def _child_print_proto(self):
print('DynamicChild saying: ')
super()._print()
return _child_print_proto
DynamicParent = type('DynamicParent', tuple([]), {"_print": _parent_print_proto})
DynamicChild = type('DynamicChild', (DynamicParent,), {})
# Need DynamicChild to exist to use it as __class__, bind after creation
DynamicChild._print = _make_child_print_proto(DynamicChild)
sc = StaticChild()
sc._print()
dc = DynamicChild()
dc._print()
是的,它很老套而且很糟糕。在实际代码中,我只使用不太常见的显式 two-arg super
:
def _child_print_proto(self):
print('DynamicChild saying: ')
super(DynamicChild, self)._print()
这就是 super()
所做的一切; Python 隐藏了它在闭包作用域中定义为 __class__
的 class,super()
拉出它和第一个位置参数,假定为 self
,并且隐含地two-arg 表单明确地做同样的事情。
明确一点:你不能手动将self.__class__
作为第一个参数传递给two-argsuper()
,到模拟在闭包作用域中绑定的 __class__
。 它似乎有效,并且确实有效,直到您使用该方法实际创建 class 的子项并尝试调用它的方法(super()
的全部要点是你可能有一个任意复杂的 class 层次结构来导航;你不能只说“哦,但我的 class 足够特别再也不会被 subclassed").如果你做一些像添加这样简单的事情:
class DynamicGrandChild(DynamicChild):
pass
dgc = DynamicGrandChild()
dgc._print()
到 self.__class__
-使用来自 Epsi95's answer 的代码,您将看到:
StaticChild saying:
I'm StaticParent
DynamicChild saying:
I'm DynamicParent
DynamicChild saying:
DynamicChild saying:
DynamicChild saying:
DynamicChild saying:
DynamicChild saying:
DynamicChild saying:
DynamicChild saying:
DynamicChild saying:
... repeats a thousand times or so ...
DynamicChild saying:
DynamicChild saying:
Traceback (most recent call last):
File ".code.tio", line 31, in <module>
dgc._print()
File ".code.tio", line 15, in _child_print_proto
super(self.__class__, self)._print()
File ".code.tio", line 15, in _child_print_proto
super(self.__class__, self)._print()
File ".code.tio", line 15, in _child_print_proto
super(self.__class__, self)._print()
[Previous line repeated 994 more times]
File ".code.tio", line 14, in _child_print_proto
print('DynamicChild saying: ')
RecursionError: maximum recursion depth exceeded while calling a Python object
super()
在设计继承时使用。 super(self.__class__, self)
仅在您 破坏 继承时使用。唯一安全的方法是对 class 进行某种静态链接(该方法将附加到 而不是 运行 时间类型调用它的任何实例),我上面的两个解决方案是执行此操作的两种合理方法(实际上只有一个其他选项,它正在关闭,并且仍然传递 class 和 self
明确地,例如,而不是定义 __class__
,只需使用 super(cls, self)
来明确地使用闭包变量;不够明显,并且有点两全其美)。
所以,我说“有三种方法”,但实际上有一种 稍微 更好(但也不太便携,因为 API 用于 types.FunctionType
随着时间的推移发生了变化,即使通常定义的闭包没有)解决方案,它使您可以创建一个实用函数来将任意函数绑定到任意 classes,而不是要求您将每个这样的函数包装在一个 closure-maker。那就是直接将函数重建为闭包:
import types
# Define function normally, with super(). The function can't actually be used as is though
def _child_print_proto(self):
print('DynamicChild saying: ')
super()._print()
# Utility function binding arbitrary function to arbitrary class
def bind_to_class(cls, func):
# This translates as:
# Make a new function using the same code object as the passed function,
# but tell it it has one closure scoped variable named __class__, and
# provide the class as the value to associate with it.
# This requires Python 3.8 or later (code objects didn't have a replace method
# until then, so doing this would be even uglier than it already is)
return types.FunctionType(func.__code__.replace(co_freevars=('__class__',)), func.__globals__, closure=(types.CellType(cls),))
DynamicParent = type('DynamicParent', tuple([]), {"_print": _parent_print_proto})
DynamicChild = type('DynamicChild', (DynamicParent,), {})
# Rebind the function to a new function that believes it was defined in DynamicChild
DynamicChild._print = bind_to_class(DynamicChild, _child_print_proto)
就像我说的,它是 super-ugly 并且需要在 3.8 之前重写,但它确实有一个轻微的优势,允许您使用 bind_to_class
和任意 super()
使用函数来将它们绑定到任意 classes。我仍然认为手动调用 two-arg super
是 safe/obvious 的方法,但现在您已经有了所有选项。
假设我有这个代码:
class StaticParent:
def _print(self):
print("I'm StaticParent")
class StaticChild(StaticParent):
def _print(self):
print('StaticChild saying: ')
super()._print()
def _parent_print_proto(self):
print("I'm DynamicParent")
def _child_print_proto(self):
print('DynamicChild saying: ')
super()._print()
DynamicParent = type('DynamicParent', tuple([]), {"_print": _parent_print_proto})
DynamicChild = type('DynamicChild', (DynamicParent,), {"_print": _child_print_proto})
sc = StaticChild()
sc._print()
dc = DynamicChild()
dc._print()
它的输出是:
StaticChild saying:
I'm StaticParent
DynamicChild saying:
Traceback (most recent call last):
File "/tmp/example.py", line 28, in <module>
dc._print()
File "/tmp/example.py", line 17, in _child_print_proto
super()._print()
RuntimeError: super(): __class__ cell not found
所以问题是如何为调用 super()
的许多 类 创建原型方法?
PS 我尝试用 lambda 实现方法,但它也不起作用:
DynamicChildWithLambda = type('DynamicChild', (DynamicParent,), {"_print": lambda self : print('Lambda saying: ', super()._print())})
Traceback (most recent call last):
File "/tmp/example.py", line 30, in <module>
dcwl._print()
File "/tmp/example.py", line 23, in <lambda>
DynamicChildWithLambda = type('DynamicChild', (DynamicParent,), {"_print": lambda self : print('Lambda saying: ', super()._print())})
RuntimeError: super(): __class__ cell not found
PS2 我也这样试过:
class StaticParent:
def _print(self):
print("I'm StaticParent")
def _child_print_proto(self):
print('DynamicChild saying: ')
super(StaticParent, self)._print()
DynamicChild = type('DynamicChild', (StaticParent,), {"_print": _child_print_proto})
dc = DynamicChild()
dc._print()
DynamicChild saying:
Traceback (most recent call last):
File "/tmp/example.py", line 13, in <module>
dc._print()
File "/tmp/example.py", line 8, in _child_print_proto
super(StaticParent, self)._print()
AttributeError: 'super' object has no attribute '_print'
在 class 中定义的方法会得到一个伪造的闭包作用域,该作用域会自动提供它在 no-arg super()
中定义的 class。当在 class 之外定义时,它不能执行此操作(因为在您定义方法时显然没有定义 class)。但是您仍然可以通过 old-fashioned 方式进行闭包,方法是实际编写一个您手动定义 __class__
的闭包函数:
class StaticParent:
def _print(self):
print("I'm StaticParent")
class StaticChild(StaticParent):
def _print(self):
print('StaticChild saying: ')
super()._print()
def _parent_print_proto(self):
print("I'm DynamicParent")
# Nesting allows us to make the inner function have an appropriate __class__
# defined for use by no-arg super
def _make_child_print_proto(cls):
__class__ = cls
def _child_print_proto(self):
print('DynamicChild saying: ')
super()._print()
return _child_print_proto
DynamicParent = type('DynamicParent', tuple([]), {"_print": _parent_print_proto})
DynamicChild = type('DynamicChild', (DynamicParent,), {})
# Need DynamicChild to exist to use it as __class__, bind after creation
DynamicChild._print = _make_child_print_proto(DynamicChild)
sc = StaticChild()
sc._print()
dc = DynamicChild()
dc._print()
是的,它很老套而且很糟糕。在实际代码中,我只使用不太常见的显式 two-arg super
:
def _child_print_proto(self):
print('DynamicChild saying: ')
super(DynamicChild, self)._print()
这就是 super()
所做的一切; Python 隐藏了它在闭包作用域中定义为 __class__
的 class,super()
拉出它和第一个位置参数,假定为 self
,并且隐含地two-arg 表单明确地做同样的事情。
明确一点:你不能手动将self.__class__
作为第一个参数传递给two-argsuper()
,到模拟在闭包作用域中绑定的 __class__
。 它似乎有效,并且确实有效,直到您使用该方法实际创建 class 的子项并尝试调用它的方法(super()
的全部要点是你可能有一个任意复杂的 class 层次结构来导航;你不能只说“哦,但我的 class 足够特别再也不会被 subclassed").如果你做一些像添加这样简单的事情:
class DynamicGrandChild(DynamicChild):
pass
dgc = DynamicGrandChild()
dgc._print()
到 self.__class__
-使用来自 Epsi95's answer 的代码,您将看到:
StaticChild saying:
I'm StaticParent
DynamicChild saying:
I'm DynamicParent
DynamicChild saying:
DynamicChild saying:
DynamicChild saying:
DynamicChild saying:
DynamicChild saying:
DynamicChild saying:
DynamicChild saying:
DynamicChild saying:
... repeats a thousand times or so ...
DynamicChild saying:
DynamicChild saying:
Traceback (most recent call last):
File ".code.tio", line 31, in <module>
dgc._print()
File ".code.tio", line 15, in _child_print_proto
super(self.__class__, self)._print()
File ".code.tio", line 15, in _child_print_proto
super(self.__class__, self)._print()
File ".code.tio", line 15, in _child_print_proto
super(self.__class__, self)._print()
[Previous line repeated 994 more times]
File ".code.tio", line 14, in _child_print_proto
print('DynamicChild saying: ')
RecursionError: maximum recursion depth exceeded while calling a Python object
super()
在设计继承时使用。 super(self.__class__, self)
仅在您 破坏 继承时使用。唯一安全的方法是对 class 进行某种静态链接(该方法将附加到 而不是 运行 时间类型调用它的任何实例),我上面的两个解决方案是执行此操作的两种合理方法(实际上只有一个其他选项,它正在关闭,并且仍然传递 class 和 self
明确地,例如,而不是定义 __class__
,只需使用 super(cls, self)
来明确地使用闭包变量;不够明显,并且有点两全其美)。
所以,我说“有三种方法”,但实际上有一种 稍微 更好(但也不太便携,因为 API 用于 types.FunctionType
随着时间的推移发生了变化,即使通常定义的闭包没有)解决方案,它使您可以创建一个实用函数来将任意函数绑定到任意 classes,而不是要求您将每个这样的函数包装在一个 closure-maker。那就是直接将函数重建为闭包:
import types
# Define function normally, with super(). The function can't actually be used as is though
def _child_print_proto(self):
print('DynamicChild saying: ')
super()._print()
# Utility function binding arbitrary function to arbitrary class
def bind_to_class(cls, func):
# This translates as:
# Make a new function using the same code object as the passed function,
# but tell it it has one closure scoped variable named __class__, and
# provide the class as the value to associate with it.
# This requires Python 3.8 or later (code objects didn't have a replace method
# until then, so doing this would be even uglier than it already is)
return types.FunctionType(func.__code__.replace(co_freevars=('__class__',)), func.__globals__, closure=(types.CellType(cls),))
DynamicParent = type('DynamicParent', tuple([]), {"_print": _parent_print_proto})
DynamicChild = type('DynamicChild', (DynamicParent,), {})
# Rebind the function to a new function that believes it was defined in DynamicChild
DynamicChild._print = bind_to_class(DynamicChild, _child_print_proto)
就像我说的,它是 super-ugly 并且需要在 3.8 之前重写,但它确实有一个轻微的优势,允许您使用 bind_to_class
和任意 super()
使用函数来将它们绑定到任意 classes。我仍然认为手动调用 two-arg super
是 safe/obvious 的方法,但现在您已经有了所有选项。