Python3.5: Class 使用继承初始化
Python3.5: Class initialization using inheritance
我最近在 Python 中偶然发现了 metaclasses 并决定使用它们来简化某些功能。 (使用 Python 3.5)
简而言之,我正在编写一个定义 class 的模块,例如必须注册和初始化的 "components"(我的意思是我需要初始化实际的 class,不是实例)。
我可以轻松注册class:
class MetaComponent(type):
def __init__(cls, *args, **kargs):
super().__init__(*args, **kargs)
RegisterComponent(cls)
class BaseComponent(metaclass=MetaComponent):
pass
class Component(BaseComponent):
"""This is the actual class to use when writing components"""
在这种情况下,我正在注册组件的 class,因为它允许我稍后引用它们,而无需实际引用。
但是 classes 缺乏自我初始化的能力(至少在 Python 3.5 中是这样),并且可能导致一些问题,例如:
class Manager(Component):
SubManagers = []
@classmethod
def ListSubManagers(cls):
for manager in cls.SubManagers:
print(manager)
@classmethod
def RegisterSubManager(cls, manager):
cls.SubManagers.append(manager)
return manager
@Manager1.RegisterSubManager
class Manager2(Manager):
pass
@Manager2.RegisterSubManager
class Manager3(Manager):
pass
# Now the fun:
Manager1.ListSubManagers()
# Displays:
# > Manager2
# > Manager3
现在,这是一个问题,因为我们的想法是让每个经理都有一个唯一的子经理列表。但是 SubManager
字段在每个子classes 之间共享...因此添加到一个列表会添加到每个子列表。安息吧。
所以下一个想法是实现一种初始化器:
class BaseManager(Component):
@classmethod
def classinit(cls):
cls.SubManagers = []
但是现在,我需要一种在 class 创建后调用此方法的方法。因此,让我们再次使用 metaclasses 执行此操作:
class MetaComponent(type):
def __init__(cls, *args, **kargs):
super().__init__(*args, **kargs):
cls.classinit(**kargs)
RegisterComponent(cls)
class BaseComponent(metaclass=MetaComponent):
@classmethod
def classinit(cls, **kargs):
print('BASE', cls)
class Component(BaseComponent):
@classmethod
def classinit(cls, **kargs):
super().classinit(**kargs) # Being able to use super() is the goal
print('COMPONENT', cls)
我认为自己已经完成了。在我看来,这是一种优雅的方式。 classinit()
将从每个正在创建的 class 中调用(与在父级上调用的 3.6 __init_subclass__
不同)。至少我喜欢它,直到Python 3.5在RuntimeError: super(): empty __class__ cell
哭了...
我读到它是因为我从 metaclass 的 __init__
方法中调用了一个方法,尽管 class 已创建(因此我愿意将代码放入__init__
,初始化已经创建的东西),它缺少这个 __class__
单元格,至少在那个时候...
我在 Python 3.6 中尝试了 运行 完全相同的代码,并且成功了,所以我猜是出了什么问题,但已修复...
我真正的问题是:
- Can we truly initialize classes with metaclasses in Python 3.5 ?
- Is there a way to avoid the use-restriction of super() in the initialization procedure?
- Why is it working in 3.6?
- If everything should fail, what would be the best course of action to still provide the class initialization, and allowing for super(...) calls? (Like do I need to refer to the super class explicitly ?)
提前感谢您的帮助。
编辑:
目标是能够派生组件并能够以 "easy" 方式相对于其父级初始化每个组件的 class:
class Manager(Component):
def classinit(cls, **kargs):
cls.SubManagers = []
@classmethod
def RegisterSubManager(cls, manager):
cls.SubManagers.append(manager)
return manager
@Manager.RegisterSubManager
class EventManager(Manager):
def classinit(cls, **kargs):
super().classinit(**kargs) # keep the old behaviour
cls.Events = []
# ...
@EventManager.RegisterSubManager
class InputManager(EventManager):
def classinit(cls, **kargs):
super().classinit(**kargs) # again, keep old behaviour
cls.Inputs = []
# use parts of EventManager, but define specialized methods
# for input management
管理人员是一个问题,我有多个概念取决于组件及其初始化组件的能力 class。
TL;DR - 如果您尝试使用元class __new__
或 [=15] 对 super
的空调用,您确实会得到 RuntimeError: super(): empty __class__ cell...
=] 方法:在此阶段,super
内部使用的隐式 "magic" 变量 __class__
尚未创建。 (在验证这一点时,我刚刚发现这已在 Python 3.6 中修复 - 即:class 使用无参数 super
的方法可以从元 class 中调用__init__
在 Python 3.6 中,但在 3.5 中产生此错误)
如果这是你现在唯一的方法,只需硬编码对 superclass 方法的调用,就像在 [=40= 中创建 super
之前需要它一样]. (使用详细形式 super
也不会起作用)。
--
倒数第二个想法,即使用 class 方法作为 class 装饰器进行注册,可以通过使用 meta[=44] 自动创建 SubManagers
属性来实现=] 通过使用一个简单的 Python 名称修改来自动创建每个管理器 class 唯一的 SubManagers
属性,方法是检查一个 class 在其 __dict__
中自己的名称空间(也可以在没有 metaclass 的情况下完成)
使用 metaclasses,只需在 metaclass __init__
的末尾添加这两行:
if getattr(cls, "SubManagers") and not "SubManagers" in cls.__dict__:
cls.SubManagers = []
如果您的 class-装饰器方法以其他方式排除 metaclasses,您不需要为此使用 metaclass - 更改您的注册方法以执行"own" 副经理列表创建上方:
@classmethod
def RegisterSubManager(cls, manager):
if not "SubManagers" in cls.__dict__:
cls.SubManagers = []
cls.SubManagers.append(manager)
如果您想要 Manager
类型的额外行为,也许您希望它们拥有自己的、更精致的元class,而不是仅仅使用它们从 Component
。尝试编写继承自 MetaComponent
的 MetaManager
元 class。您甚至可以将 class 方法从 Manager1
移动到 metaclass 中(在那里它们成为普通方法):
class MetaManager(MetaComponent):
def __init__(cls, *args, **kwargs):
super().__init__(*args, **kwargs)
cls.SubManagers = [] # each class gets its own SubManagers list
def ListSubManagers(cls):
for manager in cls.SubManagers:
print(manager)
def RegisterSubManager(cls, manager):
cls.SubManagers.append(manager)
return manager
class Manager(Component, metaclass=MetaManager): # inherit from this to get the metaclass
pass
class Manager1(Manager):
pass
@Manager1.RegisterSubManager
class Manager2(Manager):
pass
@Manager2.RegisterSubManager
class Manager3(Manager):
pass
好的,经过一些实验,我设法提供了一个 "fix" 以允许 class 初始化,允许使用 super()
.
首先,模块要"fix"初始化方法:
# ./PythonFix.py
import inspect
import types
def IsCellEmpty(cell):
"""Lets keep going, deeper !"""
try:
cell.cell_contents
return False
except ValueError:
return True
def ClosureFix(cls, functionContainer):
"""This is where madness happens.
I didn't want to come here. But hey, lets get mad.
Had to do this to correct a closure problem occuring in
Python < 3.6, joy.
Huge thanks:
"""
# Is the class decorated with @classmethod somehow
isclassmethod = inspect.ismethod(functionContainer) and functionContainer.__self__ is cls
if isclassmethod:
function = functionContainer.__func__
else:
function = functionContainer
# Get cells and prepare a cell holding ref to __class__
ClosureCells = function.__closure__ or ()
ClassCell_Fix = (lambda: cls).__closure__[0]
# Shortcut
c = function.__code__
HasClassFreevar = '__class__' in c.co_freevars
HasEmptyCells = any(IsCellEmpty(cell) for cell in ClosureCells)
if HasClassFreevar and not HasEmptyCells: # No fix required.
return classmethod(function)
Freevars_Fixed = c.co_freevars
Closure_Fixed = ClosureCells
if not HasClassFreevar:
Freevars_Fixed += ('__class__',)
Closure_Fixed += (ClassCell_Fix,)
elif HasEmptyCells: # This is silly, but for what I'm doing its ok.
Closure_Fixed = tuple(ClassCell_Fix if IsCellEmpty(cell) else cell for cell in ClosureCells)
# Now the real fun begins
PyCode_fixedFreevars = types.CodeType(
c.co_argcount, c.co_kwonlyargcount, c.co_nlocals,
c.co_stacksize, c.co_flags, c.co_code, c.co_consts, c.co_names,
c.co_varnames, c.co_filename, c.co_name, c.co_firstlineno,
c.co_lnotab, Freevars_Fixed, c.co_cellvars
)
# Lets fetch the last closure to add our __class__ fix
FixedFunction = types.FunctionType(
PyCode_fixedFreevars, function.__globals__, function.__name__,
function.__defaults__, Closure_Fixed
)
# Lets rewrap it so it is an actual classmethod (which it should be):
return classmethod(FixedFunction)
现在,组件代码:
class MetaComponent(type):
def __init__(cls:type, *args, **kargs) -> None:
super().__init__(*args, **kargs)
if hasattr(cls, 'classinit'):
cls.classinit = PythonFix.ClosureFix(cls, cls.classinit)
cls.classinit(**kargs)
RegisterComponent(cls)
def classinit(cls:type, **kargs) -> None:
"""The default classinit method."""
pass
class Component(metaclass=MetaComponent):
"""This class self registers, inherit from this"""
公平地说,我对它的完成很满意。希望这对想要初始化 classes 的人也有帮助(至少在 Python3.6 之前的环境中......)。
我最近在 Python 中偶然发现了 metaclasses 并决定使用它们来简化某些功能。 (使用 Python 3.5)
简而言之,我正在编写一个定义 class 的模块,例如必须注册和初始化的 "components"(我的意思是我需要初始化实际的 class,不是实例)。
我可以轻松注册class:
class MetaComponent(type):
def __init__(cls, *args, **kargs):
super().__init__(*args, **kargs)
RegisterComponent(cls)
class BaseComponent(metaclass=MetaComponent):
pass
class Component(BaseComponent):
"""This is the actual class to use when writing components"""
在这种情况下,我正在注册组件的 class,因为它允许我稍后引用它们,而无需实际引用。
但是 classes 缺乏自我初始化的能力(至少在 Python 3.5 中是这样),并且可能导致一些问题,例如:
class Manager(Component):
SubManagers = []
@classmethod
def ListSubManagers(cls):
for manager in cls.SubManagers:
print(manager)
@classmethod
def RegisterSubManager(cls, manager):
cls.SubManagers.append(manager)
return manager
@Manager1.RegisterSubManager
class Manager2(Manager):
pass
@Manager2.RegisterSubManager
class Manager3(Manager):
pass
# Now the fun:
Manager1.ListSubManagers()
# Displays:
# > Manager2
# > Manager3
现在,这是一个问题,因为我们的想法是让每个经理都有一个唯一的子经理列表。但是 SubManager
字段在每个子classes 之间共享...因此添加到一个列表会添加到每个子列表。安息吧。
所以下一个想法是实现一种初始化器:
class BaseManager(Component):
@classmethod
def classinit(cls):
cls.SubManagers = []
但是现在,我需要一种在 class 创建后调用此方法的方法。因此,让我们再次使用 metaclasses 执行此操作:
class MetaComponent(type):
def __init__(cls, *args, **kargs):
super().__init__(*args, **kargs):
cls.classinit(**kargs)
RegisterComponent(cls)
class BaseComponent(metaclass=MetaComponent):
@classmethod
def classinit(cls, **kargs):
print('BASE', cls)
class Component(BaseComponent):
@classmethod
def classinit(cls, **kargs):
super().classinit(**kargs) # Being able to use super() is the goal
print('COMPONENT', cls)
我认为自己已经完成了。在我看来,这是一种优雅的方式。 classinit()
将从每个正在创建的 class 中调用(与在父级上调用的 3.6 __init_subclass__
不同)。至少我喜欢它,直到Python 3.5在RuntimeError: super(): empty __class__ cell
哭了...
我读到它是因为我从 metaclass 的 __init__
方法中调用了一个方法,尽管 class 已创建(因此我愿意将代码放入__init__
,初始化已经创建的东西),它缺少这个 __class__
单元格,至少在那个时候...
我在 Python 3.6 中尝试了 运行 完全相同的代码,并且成功了,所以我猜是出了什么问题,但已修复...
我真正的问题是:
- Can we truly initialize classes with metaclasses in Python 3.5 ?
- Is there a way to avoid the use-restriction of super() in the initialization procedure?
- Why is it working in 3.6?
- If everything should fail, what would be the best course of action to still provide the class initialization, and allowing for super(...) calls? (Like do I need to refer to the super class explicitly ?)
提前感谢您的帮助。
编辑:
目标是能够派生组件并能够以 "easy" 方式相对于其父级初始化每个组件的 class:
class Manager(Component):
def classinit(cls, **kargs):
cls.SubManagers = []
@classmethod
def RegisterSubManager(cls, manager):
cls.SubManagers.append(manager)
return manager
@Manager.RegisterSubManager
class EventManager(Manager):
def classinit(cls, **kargs):
super().classinit(**kargs) # keep the old behaviour
cls.Events = []
# ...
@EventManager.RegisterSubManager
class InputManager(EventManager):
def classinit(cls, **kargs):
super().classinit(**kargs) # again, keep old behaviour
cls.Inputs = []
# use parts of EventManager, but define specialized methods
# for input management
管理人员是一个问题,我有多个概念取决于组件及其初始化组件的能力 class。
TL;DR - 如果您尝试使用元class __new__
或 [=15] 对 super
的空调用,您确实会得到 RuntimeError: super(): empty __class__ cell...
=] 方法:在此阶段,super
内部使用的隐式 "magic" 变量 __class__
尚未创建。 (在验证这一点时,我刚刚发现这已在 Python 3.6 中修复 - 即:class 使用无参数 super
的方法可以从元 class 中调用__init__
在 Python 3.6 中,但在 3.5 中产生此错误)
如果这是你现在唯一的方法,只需硬编码对 superclass 方法的调用,就像在 [=40= 中创建 super
之前需要它一样]. (使用详细形式 super
也不会起作用)。
--
倒数第二个想法,即使用 class 方法作为 class 装饰器进行注册,可以通过使用 meta[=44] 自动创建 SubManagers
属性来实现=] 通过使用一个简单的 Python 名称修改来自动创建每个管理器 class 唯一的 SubManagers
属性,方法是检查一个 class 在其 __dict__
中自己的名称空间(也可以在没有 metaclass 的情况下完成)
使用 metaclasses,只需在 metaclass __init__
的末尾添加这两行:
if getattr(cls, "SubManagers") and not "SubManagers" in cls.__dict__:
cls.SubManagers = []
如果您的 class-装饰器方法以其他方式排除 metaclasses,您不需要为此使用 metaclass - 更改您的注册方法以执行"own" 副经理列表创建上方:
@classmethod
def RegisterSubManager(cls, manager):
if not "SubManagers" in cls.__dict__:
cls.SubManagers = []
cls.SubManagers.append(manager)
如果您想要 Manager
类型的额外行为,也许您希望它们拥有自己的、更精致的元class,而不是仅仅使用它们从 Component
。尝试编写继承自 MetaComponent
的 MetaManager
元 class。您甚至可以将 class 方法从 Manager1
移动到 metaclass 中(在那里它们成为普通方法):
class MetaManager(MetaComponent):
def __init__(cls, *args, **kwargs):
super().__init__(*args, **kwargs)
cls.SubManagers = [] # each class gets its own SubManagers list
def ListSubManagers(cls):
for manager in cls.SubManagers:
print(manager)
def RegisterSubManager(cls, manager):
cls.SubManagers.append(manager)
return manager
class Manager(Component, metaclass=MetaManager): # inherit from this to get the metaclass
pass
class Manager1(Manager):
pass
@Manager1.RegisterSubManager
class Manager2(Manager):
pass
@Manager2.RegisterSubManager
class Manager3(Manager):
pass
好的,经过一些实验,我设法提供了一个 "fix" 以允许 class 初始化,允许使用 super()
.
首先,模块要"fix"初始化方法:
# ./PythonFix.py
import inspect
import types
def IsCellEmpty(cell):
"""Lets keep going, deeper !"""
try:
cell.cell_contents
return False
except ValueError:
return True
def ClosureFix(cls, functionContainer):
"""This is where madness happens.
I didn't want to come here. But hey, lets get mad.
Had to do this to correct a closure problem occuring in
Python < 3.6, joy.
Huge thanks:
"""
# Is the class decorated with @classmethod somehow
isclassmethod = inspect.ismethod(functionContainer) and functionContainer.__self__ is cls
if isclassmethod:
function = functionContainer.__func__
else:
function = functionContainer
# Get cells and prepare a cell holding ref to __class__
ClosureCells = function.__closure__ or ()
ClassCell_Fix = (lambda: cls).__closure__[0]
# Shortcut
c = function.__code__
HasClassFreevar = '__class__' in c.co_freevars
HasEmptyCells = any(IsCellEmpty(cell) for cell in ClosureCells)
if HasClassFreevar and not HasEmptyCells: # No fix required.
return classmethod(function)
Freevars_Fixed = c.co_freevars
Closure_Fixed = ClosureCells
if not HasClassFreevar:
Freevars_Fixed += ('__class__',)
Closure_Fixed += (ClassCell_Fix,)
elif HasEmptyCells: # This is silly, but for what I'm doing its ok.
Closure_Fixed = tuple(ClassCell_Fix if IsCellEmpty(cell) else cell for cell in ClosureCells)
# Now the real fun begins
PyCode_fixedFreevars = types.CodeType(
c.co_argcount, c.co_kwonlyargcount, c.co_nlocals,
c.co_stacksize, c.co_flags, c.co_code, c.co_consts, c.co_names,
c.co_varnames, c.co_filename, c.co_name, c.co_firstlineno,
c.co_lnotab, Freevars_Fixed, c.co_cellvars
)
# Lets fetch the last closure to add our __class__ fix
FixedFunction = types.FunctionType(
PyCode_fixedFreevars, function.__globals__, function.__name__,
function.__defaults__, Closure_Fixed
)
# Lets rewrap it so it is an actual classmethod (which it should be):
return classmethod(FixedFunction)
现在,组件代码:
class MetaComponent(type):
def __init__(cls:type, *args, **kargs) -> None:
super().__init__(*args, **kargs)
if hasattr(cls, 'classinit'):
cls.classinit = PythonFix.ClosureFix(cls, cls.classinit)
cls.classinit(**kargs)
RegisterComponent(cls)
def classinit(cls:type, **kargs) -> None:
"""The default classinit method."""
pass
class Component(metaclass=MetaComponent):
"""This class self registers, inherit from this"""
公平地说,我对它的完成很满意。希望这对想要初始化 classes 的人也有帮助(至少在 Python3.6 之前的环境中......)。