首页 > 解决方案 > TypeError: super(type, obj): obj 必须是类型的实例或子类型,仅当我的元类被导入时

问题描述

__init__我创建了一个元类,通过父类管理函数参数的继承。让我告诉你我的意思:

class A(metaclass= MagicMeta):
    def __init__(self, a, taunt = None):
        print(locals())
        self.a = a
        self.taunt = taunt

class B(A, metaclass=MagicMeta):
    def __init__(self, b):
        self.b = b

class Foo(B,metaclass=MagicMeta):
    def __init__(self,yolo, name ='empty', surname = None):

        print(yolo,a,b)
        self.name = name
        self.surname= surname

o =Foo(1,2,3,taunt='taunted')
o.taunt
>>> 'taunted'
o.b
>>> 2

我的元类在与我的类在同一个文件中写入时运行良好但是当我导入它时,我收到此错误: TypeError: super(type, obj): obj must be an instance or subtype of type when my metaclass is imported

我的元类:

import re
from inspect import Parameter
def get_args(f):
    args = list()
    kwargs = dict()
    for param in inspect.signature(f).parameters.values():
        if (param.kind == param.POSITIONAL_OR_KEYWORD):
            if param.default ==Parameter.empty:
                args.append(param.name)
            else:
                kwargs[param.name]= param.default 
    return args, kwargs 

def  compileKwargs(dct):
    string =""
    poke = False
    for k, o  in dct.items():
        if type(o) == str:
            string+= k+"='"+o+"', "
        else:           
            string+= k+"="+str(o)+", "

    return string

def  compileKwargs2(dct):
    string =""
    poke = False
    for k, o  in dct.items():
        if type(o) == str:
            string+= k+"='"+k+"', "
        else:           
            string+= k+"="+k+", "

    return string

def stringArgs(liste):
    return " ".join([e+"," for e in liste])

def compileArgs(liste1,liste2):
    liste1.extend([e for e in liste2 if e not in liste1])
    return liste1

def editFuncName(actual: str, replace:str):
    #print('EDITFUNCNAME')
    #print(actual)
    string = re.sub('(?<=def ).*?(?=\()',replace, actual)
    #print('string', string)
    return string

import inspect
from textwrap import dedent, indent
def processCode(code : list):
    string=""
    #print('processcode')
    for i,e  in enumerate(code):
        #print('row', e)
        #print('dedent', e)
        if i != 0:
            string+=indent(dedent(e),'\t')
        else :
            string+=dedent(e)
    return string
import types
class MagicMeta(type):
    def __init__(cls, name, bases, dct):
        #print(bases,dct)
        setattr(cls,'_CODE_', dict())

        #GET THE __init__ code function and its arg and kwargs
        # for the class and the inherited class
        func = cls.__init__
        cls._CODE_[func.__name__]= inspect.getsourcelines(func)
        args2 =get_args(cls.__bases__[0].__init__)
        
        setattr(cls,'_ARGS_', dict())
        cls._ARGS_[func.__name__]=[get_args(func), args2]

        lines = cls._CODE_['__init__']
        string= lines[0][0]
        arg, kwarg = cls._ARGS_['__init__'][0]
        arg2, kwarg2 = cls._ARGS_['__init__'][1]

        comparg = stringArgs(compileArgs(arg, arg2))
        #------------------------------------------------------

        #PROCESS arg and kwargs to manage it as string
        dct = {**kwarg ,**kwarg2}
        #print(dct)
        newargs = comparg + compileKwargs(dct)
        string = re.sub('(?<=\().*?(?=\))',newargs, string)
        print(type(arg2))
        print(arg2)
        superarg =stringArgs([a for a in arg2 if a != 'self']) + compileKwargs2(kwarg2)
        arg =stringArgs([a for a in arg2 if a != 'self'])
        printt = "print({})\n".format(arg)
        printtt = "print(locals())\n"
        print(superarg)
        #--------------------------------------------------------

        #ADD the super().__init__ in the __init__ function
        superx = "super({},self).{}({})\n".format(cls.__name__, func.__name__, superarg)
        #superx = "super().{}({})\n".format( func.__name__, superarg)
        print(superx)
        code = lines[0]
        #print('LINE DEF', code[0])
        #--------------------------------------------------------

        #BUILD the code of the new __init__ function
        code[0]= editFuncName(string, 'tempo')
        code.insert(1, printt)
        code.insert(2, "print(self, type(self))\n")
        if len(bases)>0:
            code.insert(3, superx)
 
        print('code:',code)
        codestr  = processCode(code)
        #print('précompile', codestr)
        #--------------------------------------------------------

        #REPLACE the __init__ function code
        comp = compile(codestr, '<string>','exec')
        #print(comp)
        exec(comp)
        cls.__init__ = types.MethodType(eval('tempo'), cls)
        #print(eval('tempo.__code__'))
        #--------------------------------------------------------

我会避免在每次需要时设置元类的代码

此外,我认为,对于深入了解python,这是一个很好的机会来了解为什么import会改变类错误行为,当它的内部代码被动态修改时

标签: pythonpython-3.xinheritancemetaclass

解决方案


所以我认为通过一些调整,你可以在不使用元类的情况下解决这个问题。看起来您想使用继承让您的 Foo 实例包含 b、a 和嘲讽。但是您正在使用有问题的位置参数传递它们。一种解决方案是摄取 *args 和 **kwargs,并在超级调用中将它们传递给祖先类。然后我们可以访问并删除 args[0] 来设置 b 和 a。这是令人担忧的,因为如果继承顺序发生变化,那么 b 和 a 是什么变化。例如:

class A:
  def __init__(self, *args):
    args = list(args)
    a = args.pop(0)
    self.a = a
    try:
        super().__init__(*args)
    except TypeError:
        pass

class B:
  def __init__(self, *args):
    args = list(args)
    b = args.pop(0)
    self.b = b
    try:
        super().__init__(*args)
    except TypeError:
        pass

class C(B,A):
    pass

class D(A, B):
    pass

c = C('a', 'b')
d = D('a', 'b')
>>> c.__dict__
{'b': 'a', 'a': 'b'}
>>> d.__dict__
{'a': 'a', 'b': 'b'}

我们不能依靠这种方法来可靠地设置 a 和 b 是什么。因此,我们应该使用关键字 args。此代码使用关键字参数来设置这些祖先参数:

class NewA:
    def __init__(self, *, a, taunt = None):
        self.a = a
        self.taunt = taunt

class NewB(NewA):
    def __init__(self, *, b, **kwargs):
        super().__init__(**kwargs)
        self.b = b

class NewFoo(NewB):
    def __init__(self, yolo, name ='empty', surname = None, **kwargs):
        super().__init__(**kwargs)
        self.name = name
        self.surname= surname

f = NewFoo(1, b=2, a=3,taunt='taunted')
>>> print(f.__dict__)
{'a': 3, 'taunt': 'taunted', 'b': 2, 'name': 'empty', 'surname': None}

在其中,我们使用 python3 中的一项新功能来要求命名参数。当我们尝试省略 a 时,我们得到:

>>> NewFoo(1, b=2, taunt='taunted')
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "<stdin>", line 3, in __init__
  File "<stdin>", line 3, in __init__
TypeError: __init__() missing 1 required keyword-only argument: 'a'

我们可以通过以下方式要求这些命名参数:

  • 使用 * 作为参数,它告诉 python 在它之后的所有参数都被命名
  • 对于那些命名的参数,不要设置默认值根据函数签名上的 python 文档 Parameters after “*” or “*identifier” are keyword-only parameters and may only be passed used keyword arguments.

推荐阅读