2017-08-24 83 views
1

我最近偶然发现了Python中的元类,并决定使用它们来简化某些功能。 (使用Python 3.5)Python3.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字段在每个子类中共享...因此,添加到列表中会添加到每个子类中。安息。

所以接下来的想法是实现一种initializator的:

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

但现在,我需要一种方法来调用类的创建后此方法。因此,让我们再次用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) 

我会认为我自己这样做。有点做一个优雅的方式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 

经理们一个问题,我有多个概念,这些概念依赖于组件以及它们初始化类的能力。

+0

我不明白'@ Manager2.RegisterSubManager'可能可以工作,因为'Manager2'不是'Manager1'的子类。 – Blckknght

+0

我的错误,修正了 – WKnight02

回答

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以上版本中)。

1

TL; DR - 你确实有RuntimeError: super(): empty __class__ cell...,如果你尝试从元类使用空调用super__new____init__方法:在这个阶段隐含的“魔术”变量__class__内部使用由super还没有尚未创建。 (在验证这一点,我刚刚发现了这个已被固定在Python 3.6 - 那就是:用参super classmethods可以被称为元类的__init__在Python 3.6,但产量在3.5这个错误),如果是这样的

现在只有一件事情,就是硬编码调用超类方法,就像在Python中创建super之前所需的那样。 (使用详细形式的super也不适用)。

-

你的倒数第二个想法,用classmethods作为装饰类登记的,可以由通过使用自动创建SubManagers属性与元类的工作,一个简单的Python名称重整自动

:通过检查一个类的自己的命名空间中的 __dict__(它可以不用元类进行为好)

使用元类,就在你的元类__init__的末尾添加这两条线创建的每一个经理级的独特SubManagers属性

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

如果你的类,装饰方法,否则妨碍元类,你不需要只使用一个元类为 - 改变你的注册方法执行上述“自己” submanager列表的创建:

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

谢谢你的回应。我正在寻找更多的解决方法,如果有的话,允许在子类中使用自动的类似'super()'的解决方案,但我想我会在子类中使用显式的父类调用。另一方面,注册装饰器对于管理者来说只是一件事情,并不是所有的组件:不同的组件都像单一类的类一样工作,并且可能需要不同的初始化(把它写下来几乎听起来像是不像我这样使用类,但是在一个完美的世界中,例如3.6,它正在工作......只是不在3.5':(')。 – WKnight02

+0

也许你可以做一个简单的类装饰器来调用initclass方法 - 装饰器应用于元类之后已经运行了,'super()'应该可以工作。 – jsbueno

+0

是这样做的,试着避免使用元类':p'。至少现在组件(非管理器)注册没有装饰器,但我有点在'metaclass .__ init__'事物中对运行方法的这种限制感到郁闷......还有一个事实是,在下一个版本的Python(3.6)中它已经修复了......我真的认为一些纯Python的解决方法是可能的,甚至这是我发现的最接近的方法有点... Hardcore:https://stackoverflow.com/a/4885951/7983255。有没有办法从这个链接? (我有点被推翻,我必须说) – WKnight02

1

如果您需要Manager类型的额外行为,也许您希望它们拥有自己的更精炼的元类,而不是仅使用它们从Component继承的元类。尝试编写从MetaComponent继承的MetaManager元类。你甚至可以摆脱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 
+0

我喜欢这个想法,但我希望最终开发者能够编写自己的管理器和他们自己的初始化(同时引用'super().__ init__',这样基类'cls.SubManagers'也被初始化)。你的想法完成了这项工作,但它不能提供我想要的模块性,更特别的是它使事情变得复杂一些:)。感谢您的时间和我一起寻找答案!我还在挖,我们永远不知道我们会发现什么! (我实际上已经失去了一点时间,但一旦完成,我将会介绍一个关于Python的相当有趣的观点) – WKnight02

+0

尽管我从未考虑过将类方法放在元类中,但我觉得这很酷,它确实感到“自然”! (但是我会保留这个定义样式,这些样式绝对需要元类,我不觉得经理人会在我的情况下......) – WKnight02

+0

我不确定我是否理解你的关注。你为什么认为你不能在某处使用'super'?您不需要对'classinit'方法进行修改,元类的'__init__'已经处理初始化子管理器列表。 – Blckknght