Python3.5:使用继承

时间:2017-08-24 21:54:29

标签: python metaprogramming python-3.5 metaclass

我最近偶然发现了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__中,以初始化已创建的东西),它缺少这个__class__单元格,至少在那一刻......

我尝试在Python 3.6中运行完全相同的代码,并且它有效,所以我猜错了但是修复了......

我真正的问题是:

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

提前感谢您的帮助。

修改

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

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

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

3 个答案:

答案 0 :(得分:1)

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

如果这是你现在唯一的方法,只需硬编码超类方法,就像在Python中创建super之前需要的那样。 (使用详细形式的super也不会起作用。)

-

使用classmethod作为类装饰器进行注册的倒数第二个想法可以通过使用简单的Python名称自动创建带有元类的SubManagers属性来自动创建每个管理器。通过在SubManagers中检查一个类'自己的命名空间来确定类唯一__dict__属性(并且可以在没有元类的情况下完成)

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

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

如果你的class-decorator方法排除了元类,你不需要只使用元类 - 更改你的register方法来执行上面的“自己的”子管理器列表创建:

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

答案 1 :(得分:1)

如果您想要为Manager类型添加额外行为,或许您希望他们拥有自己的,更精确的元类,而不是仅使用他们从Component继承的那个。尝试编写一个继承自MetaManager的{​​{1}}元类。您甚至可以将类方法从MetaComponent移动到元类中(它们成为常规方法):

Manager1

答案 2 :(得分:0)

好的,经过一些实验,我设法提供了一个"修复"为了允许类初始化,允许使用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之前的env中)。