【问题标题】:Python3.5: Class initialization using inheritancePython3.5:使用继承进行类初始化
【发布时间】:2018-02-02 22:03:06
【问题描述】:

我最近偶然发现了 Python 中的元类,并决定使用它们来简化一些功能。 (使用 Python 3.5)

简而言之,我正在编写一个定义类的模块,例如必须注册和初始化的“组件”(我的意思是我需要初始化实际的类,而不是实例)。

我可以轻松注册课程:

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"""

在这种情况下,我正在注册组件的类,因为它允许我稍后引用它们,而无需它们的实际引用。

但是类缺乏自我初始化的能力(至少在 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 字段在每个子类之间共享......所以添加到一个列表会添加到每个子类。 RIP。

所以下一个想法是实现一种初始化器:

class BaseManager(Component):
    @classmethod
    def classinit(cls):
        cls.SubManagers = []

但是现在,我需要一种在类创建后调用此方法的方法。所以让我们再次使用元类来做这件事:

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)

我认为自己已经完成了这个。有点优雅的方式来做IMO。 classinit() 将从正在创建的每个类中调用(与在父级上调用的 3.6 __init_subclass__ 不同)。至少我喜欢它,直到 Python 3.5 在 RuntimeError: super(): empty __class__ cell 中哭泣...

我读到它是因为我从元类的 __init__ 方法中调用了一个方法,并且虽然创建了这个类(因此我愿意将代码放在 __init__ 中,以初始化已经创建的东西),但它缺少这个 @ 987654331@细胞,至少在那一刻……

我尝试在 Python 3.6 中运行完全相同的代码,它运行正常,所以我猜是有问题但得到了修复......

我真正的问题是:

  • 我们真的可以在 Python 3.5 中用元类初始化类吗?
  • 有没有办法在初始化过程中避免 super() 的使用限制?
  • 为什么它在 3.6 中工作?
  • 如果一切都失败了,那么仍然提供类初始化并允许调用 super(...) 的最佳做法是什么? (我是否需要明确地引用超类?)

提前感谢您的帮助。

编辑:

目标是能够以“简单”的方式派生组件并能够相对于其父级初始化每个类:

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

管理器是一个问题,我有多个依赖于组件及其初始化类的能力的概念。

【问题讨论】:

  • 我不明白@Manager2.RegisterSubManager 是如何工作的,因为Manager2 不是Manager1 的子类。
  • 我的错误,已修复

标签: python metaprogramming python-3.5 metaclass


【解决方案1】:

TL;DR - 如果您尝试从元类 __new____init__ 方法使用对 super 的空调用,您确实会拥有 RuntimeError: super(): empty __class__ cell...:在此阶段隐式“魔术”变量 @987654326 super 内部使用的@ 尚未创建。 (在验证这一点时,我发现这已在 Python 3.6 中修复 - 即:可以从 Python 3.6 中元类的 __init__ 调用使用无参数 super 的类方法,但在 3.5 中会产生此错误)

如果您现在只能这样做,只需硬编码对超类方法的调用,就像在 Python 中创建 super 之前需要它一样。 (使用 super 的详细形式也不行)。

--

您的倒数第二个想法,即使用类方法作为类装饰器进行注册,可以通过使用简单的 Python 名称修改自动创建具有元类的 SubManagers 属性来自动创建每个管理器类唯一的SubManagers 属性通过在其__dict__ 中检查一个类自己的命名空间(也可以在没有元类的情况下完成)

使用元类,只需在你的元类末尾添加这两行__init__

if getattr(cls, "SubManagers") and not "SubManagers" in cls.__dict__:
    cls.SubManagers = []

如果您的类装饰器方法排除了元类,您不需要为此使用元类 - 更改您的注册方法以执行上述“自己的”子管理器列表创建:

@classmethod
def RegisterSubManager(cls, manager):
   if not "SubManagers" in cls.__dict__:
       cls.SubManagers = []
   cls.SubManagers.append(manager)

【讨论】:

  • 感谢您的回复。我一直在寻找一种解决方法(如果有的话),以允许在子类中自动解决类似super() 的问题,但我想我会在子类中使用显式父类调用。另一方面,注册装饰器只适用于管理器,而不是所有组件:不同的组件作为类似单例的类工作,可能需要不同的初始化(写下来几乎听起来不像我这样使用类的好主意,但在 3.6 等完美世界中,它可以正常工作……只是在 3.5 中不行:()。
  • 也许你可以做一个简单的类装饰器来调用initclass 方法——在元类运行后应用装饰器,然后super() 应该可以工作。
  • 正在这样做,尝试使用元类:p 来避免它。至少现在组件(不是管理器)注册在没有装饰器的情况下工作。但是我对在metaclass.__init__ 事物中运行方法的这种限制感到有点沮丧......而且在下一个版本的Python(3.6)中它已被修复......我真的想到了一些纯Python的解决方法是可能的,即使我找到的最接近的方法有点......铁杆:stackoverflow.com/a/4885951/7983255。这个链接有办法吗? (我必须说我有点被推翻了)
【解决方案2】:

如果您想要为您的 Manager 类型提供额外的行为,也许您希望它们拥有自己的、更精致的元类,而不是仅仅使用从 Component 继承的元类。尝试编写一个继承自MetaComponentMetaManager 元类。您甚至可以将类方法从 Manager1 移动到元类中(它们成为普通方法):

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

【讨论】:

  • 我喜欢这个想法,但我希望最终开发人员能够编写自己的管理器和他们自己的初始化(同时引用super().__init__ 以便基础cls.SubManagers 也被初始化)。你的想法完成了工作,但它没有提供我想要的模块化,更特别的是它确实使事情变得有点复杂:)。感谢您花时间和我一起寻找答案!我也在挖掘,我们永远不知道我们会发现什么! (实际上我为此浪费了一些时间,但是一旦完成,我将介绍一个关于 Python 的相当有趣的观点)
  • 虽然我从没想过在元类中放置类方法,但我觉得这很酷,感觉确实“自然”! (但我会为绝对需要元类的类保留这种定义风格,我不觉得经理会在我的情况下......)
  • 我不确定我是否理解您的担忧。为什么你认为你不能在某处使用super?您不需要捏造classinit 方法,元类的__init__ 已经处理初始化子管理器列表。
  • 简而言之,我有需要初始化的管理器类,但是每个子类 (class SubManagerX(Manager)) 可能希望像普通管理器一样初始化自己,并为自己加上一些额外的初始化。因此,为了能够在保留旧行为的同时初始化子管理器,我需要能够从子管理器classinit() 调用super().classinit(),因为管理器可以相互派生。但是如果我只使用元类进行初始化,每次有人想要子类化它会有点痛苦......
  • 好吧,在某个时候,也许您应该重新考虑使用类和元类,而不是使用类和实例。用户自定义实例的行为比自定义类创建更容易(除非他们习惯于编写自己的元类)。
【解决方案3】:

好的,所以经过一些实验,我设法提供了一个“修复”以允许类初始化,允许使用super()

首先,模块“修复”初始化方法:

# ./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: https://stackoverflow.com/a/4885951/7983255
    """

    # 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"""

公平地说,我对它的完成感到满意。希望这对想要初始化类的人也有帮助(至少在 Python3.6 之前的环境中......)。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2014-11-23
    • 2017-10-21
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多