TypeError: super(type, obj): obj 必须是类型的实例或子类型,仅当我的元类被导入时
Posted
技术标签:
【中文标题】TypeError: super(type, obj): obj 必须是类型的实例或子类型,仅当我的元类被导入时【英文标题】:TypeError: super(type, obj): obj must be an instance or subtype of type ONLY when my metaclass is imported 【发布时间】:2021-03-13 23:30:48 【问题描述】:我创建了一个元类,通过父类管理__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会改变类的错误行为,当它的内部代码被动态修改时
【问题讨论】:
你为什么要使用元类来处理直接使用super
已经很好处理的东西? MagicMeta
做了什么?
我给你一个例子,它自动处理,通过__init__
函数继承arg和kwargs,如果有一天,我更新__init__
A类的arg,B和Foo 类将自动更新,而无需重写它们。您可以注意到 taunt
kwarg 没有在 Foo 和 B __init__
函数中设置,但我仍然可以在 Foo 实例中设置它。
试试吧,在__init__
类函数中添加一个新的kwarg,并设置它,在一个新的Foo实例__init__
【参考方案1】:
所以我认为通过一些调整,你可以在不使用元类的情况下解决这个问题。看起来您想使用继承让您的 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 在它之后的所有参数都被命名 对于那些命名参数不要设置默认值 每the python docs on function signaturesParameters after “*” or “*identifier” are keyword-only parameters and may only be passed used keyword arguments.
【讨论】:
我知道我们可以用 kwargs 和 args 解决这个问题,但它并没有解释为什么文件中的元类运行良好但从其他文件导入时却没有的原因.理解它可能很有用。以上是关于TypeError: super(type, obj): obj 必须是类型的实例或子类型,仅当我的元类被导入时的主要内容,如果未能解决你的问题,请参考以下文章
python中super出现的TypeError: must be type, not classobj 原因及解决
super() 为新型类引发“TypeError:必须是类型,而不是 classobj”