更改__init__ class 功能代码后,不再允许"get source code"
After changing the __init__ class function code, not allow to "get source code" anymore
我创建了一个元 class,它在 __init__
函数参数中添加 args 和 kwargs 来自 inherited class 然后为 init inherited class instance
示例:
class A():
def __init__(self, a:int, taunt = None):
#print('init a')
self.a = a
self.test = None
class B(A, metaclass=MagicMeta):
def __init__(self, b:int):
#print('init b')
self.b = b
class Foo(B,metaclass=MagicMeta):
def __init__(self,yolo, name ='empty', surname = None):
self.name = name
self.surname= surname
#print(self.test)
def __str__(self):
return str(self.__class__) + ": " + str(self.__dict__)
x =Foo(yolo=1,a=2,b=3, name='name!')
print(x.a)
print(x.b)
print(x.name)
print(str(x))
print(inspect.getsourcelines(A.__init__))
inspect.getsourcelines(Foo.__init__)
> 2
> 3
> name!
> "<class '__main__.Foo'>: {}"
> ([' def __init__(self, a:int, taunt = None):\n', " print('init a')\n", ' self.a = a\n', ' self.test = None\n'], 2)
---------------------------------------------------------------------------
OSError Traceback (most recent call last)
in
4 print(x.name)
5 print(str(x))
----> 6 inspect.getsourcelines(Foo.__init__)
~/opt/anaconda3/lib/python3.8/inspect.py in getsourcelines(object)
965 raised if the source code cannot be retrieved."""
966 object = unwrap(object)
--> 967 lines, lnum = findsource(object)
968
969 if istraceback(object):
~/opt/anaconda3/lib/python3.8/inspect.py in findsource(object)
796 lines = linecache.getlines(file)
797 if not lines:
--> 798 raise OSError('could not get source code')
799
800 if ismodule(object):
OSError: could not get source code
第一个问题,Foo实例中的self
不应该为空,修改__init__
Foo class函数的代码后,无法读取不再
这里是 MagicMeta 代码:
import re
from inspect import Parameter
# get arg and kwargs of a function
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 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
# indent the string code
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):
setattr(cls,'_CODE_', dict())
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))
dct = {**kwarg ,**kwarg2}
#print(dct)
newargs = comparg + compileKwargs(dct)
string = re.sub('(?<=\().*?(?=\))',newargs, string)
superarg =stringArgs(arg2) + compileKwargs(kwarg2)
#print(superarg)
superx = "super({},self).{}({})\n".format(cls.__name__, func.__name__, superarg)
code = lines[0]
#print('LINE DEF', code[0])
code[0]= editFuncName(string, 'tempo')
code.insert(1, superx)
#print('code:',code)
codestr = processCode(code)
#print('précompile', codestr)
comp = compile(codestr, '<string>','exec')
#print(comp)
#exec the code to define the 'tempo' function which will replace __init__
exec(comp)
cls.__init__ = types.MethodType(eval('tempo'), cls)
#print(eval('tempo.__code__'))
getsourcelines
不会神奇地 de-compiles 和反向工程传入的函数来重新创建将编译回等效对象的源代码行。
它所做的是检查传入的函数及其模块中的属性,检索源、物理文件(通常是“. py" 文件),并获取字节码本身中的注释以获取实际的行号。
如果您像 运行 一些代码一样简单地使用编译的 .pyc
文件,从文件夹中删除源代码 .py
,它也会以同样的方式失败。
在您的例子中,.__init__
函数的源代码不在文件中,它在动态构建的字符串中,在 __init__
方法之后甚至不再存在元类退出。
但它是可以修复的 - 您只需将用于生成 __init__
方法的字符串保存为文件,并在编译该字符串的过程中添加该文件的路径。
如果像您一样传递 exec
一个字符串,它将不起作用 - 但如果您使用字符串调用 compile
,要在调用 exec 之前创建一个代码对象,compile
调用可以采用 filename
(实际上是路径)参数 - 它将作为源文件嵌入到代码对象中。然后你可以像你一样调用 exec
,但是将 compile
的 return 传递给它而不是 source-code 字符串。
只要该文件存在于磁盘上,getsourcelines()
就会return 以适当的偏移量向您提供其内容。
In [xxx]: import inspect
...
In [104]: bla = "def bla(): return 1"
In [105]: open("testx.py", "wt").write(bla)
Out[105]: 19
In [106]: b = compile(bla, "testx.py", "exec")
In [107]: exec(b)
In [108]: bla()
Out[108]: 1
In [109]: inspect.getsourcelines(bla)
Out[109]: (['def bla(): return 1\n'], 1)
In [110]: !rm testx.py
In [111]: inspect.getsourcelines(bla)
---------------------------------------------------------------------------
OSError Traceback (most recent call last)
<ipython-input-111-3459b1636cc6> in <module>
----> 1 inspect.getsourcelines(bla)
[...]
OSError: could not get source code
我认为这是一个 XY 问题。如果您使用的 Python 版本比 3.7.
旧版本,则使用 dataclasses
模块或关键字参数可以更轻松地解决您要解决的问题。
from dataclasses import dataclass
@dataclass
class A:
a: int = 0
@dataclass
class B(A):
b: int = 1
def __post_init__(self):
self.c = self.a + self.b
from dataclasses import field
from typing import List
@dataclass
class C(B):
foo: List[int] = field(default_factory=list)
# __repr__ is thrown in for free
assert str(C(a=2, b=3, foo=[1, 2, 3])) == 'C(a=2, b=3, foo=[1, 2, 3])'
# retain default args
assert C().a == 0
# using positional args and post_init
assert C(2, 3).c == 5
# mutable defaults
assert C().foo == [] and C().foo is not C().foo
使用 kwargs,您可以使用命名参数弹出每个 class 所需的参数,然后将其余的 kwargs 传递给父 classes 初始化函数,例如
class A:
def __init__(self, a: int = 0):
self.a = a
class B(A):
def __init__(self, *, b:int, **kwargs):
super().__init__(**kwargs)
self.b = b
class C(B):
def __init__(self, *, c: int = 2, **kwargs):
super().__init__(**kwargs)
self.c = c
obj = C(a=1, b=2, c=3)
assert vars(obj) == dict(a=1, b=2, c=3)
# retain defaults
obj2 = C(b=2)
assert obj2.a == 0
我创建了一个元 class,它在 __init__
函数参数中添加 args 和 kwargs 来自 inherited class 然后为 init inherited class instance
示例:
class A():
def __init__(self, a:int, taunt = None):
#print('init a')
self.a = a
self.test = None
class B(A, metaclass=MagicMeta):
def __init__(self, b:int):
#print('init b')
self.b = b
class Foo(B,metaclass=MagicMeta):
def __init__(self,yolo, name ='empty', surname = None):
self.name = name
self.surname= surname
#print(self.test)
def __str__(self):
return str(self.__class__) + ": " + str(self.__dict__)
x =Foo(yolo=1,a=2,b=3, name='name!')
print(x.a)
print(x.b)
print(x.name)
print(str(x))
print(inspect.getsourcelines(A.__init__))
inspect.getsourcelines(Foo.__init__)
> 2
> 3
> name!
> "<class '__main__.Foo'>: {}"
> ([' def __init__(self, a:int, taunt = None):\n', " print('init a')\n", ' self.a = a\n', ' self.test = None\n'], 2)
---------------------------------------------------------------------------
OSError Traceback (most recent call last)
in
4 print(x.name)
5 print(str(x))
----> 6 inspect.getsourcelines(Foo.__init__)
~/opt/anaconda3/lib/python3.8/inspect.py in getsourcelines(object)
965 raised if the source code cannot be retrieved."""
966 object = unwrap(object)
--> 967 lines, lnum = findsource(object)
968
969 if istraceback(object):
~/opt/anaconda3/lib/python3.8/inspect.py in findsource(object)
796 lines = linecache.getlines(file)
797 if not lines:
--> 798 raise OSError('could not get source code')
799
800 if ismodule(object):
OSError: could not get source code
第一个问题,Foo实例中的self
不应该为空,修改__init__
Foo class函数的代码后,无法读取不再
这里是 MagicMeta 代码:
import re
from inspect import Parameter
# get arg and kwargs of a function
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 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
# indent the string code
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):
setattr(cls,'_CODE_', dict())
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))
dct = {**kwarg ,**kwarg2}
#print(dct)
newargs = comparg + compileKwargs(dct)
string = re.sub('(?<=\().*?(?=\))',newargs, string)
superarg =stringArgs(arg2) + compileKwargs(kwarg2)
#print(superarg)
superx = "super({},self).{}({})\n".format(cls.__name__, func.__name__, superarg)
code = lines[0]
#print('LINE DEF', code[0])
code[0]= editFuncName(string, 'tempo')
code.insert(1, superx)
#print('code:',code)
codestr = processCode(code)
#print('précompile', codestr)
comp = compile(codestr, '<string>','exec')
#print(comp)
#exec the code to define the 'tempo' function which will replace __init__
exec(comp)
cls.__init__ = types.MethodType(eval('tempo'), cls)
#print(eval('tempo.__code__'))
getsourcelines
不会神奇地 de-compiles 和反向工程传入的函数来重新创建将编译回等效对象的源代码行。
它所做的是检查传入的函数及其模块中的属性,检索源、物理文件(通常是“. py" 文件),并获取字节码本身中的注释以获取实际的行号。
如果您像 运行 一些代码一样简单地使用编译的 .pyc
文件,从文件夹中删除源代码 .py
,它也会以同样的方式失败。
在您的例子中,.__init__
函数的源代码不在文件中,它在动态构建的字符串中,在 __init__
方法之后甚至不再存在元类退出。
但它是可以修复的 - 您只需将用于生成 __init__
方法的字符串保存为文件,并在编译该字符串的过程中添加该文件的路径。
如果像您一样传递 exec
一个字符串,它将不起作用 - 但如果您使用字符串调用 compile
,要在调用 exec 之前创建一个代码对象,compile
调用可以采用 filename
(实际上是路径)参数 - 它将作为源文件嵌入到代码对象中。然后你可以像你一样调用 exec
,但是将 compile
的 return 传递给它而不是 source-code 字符串。
只要该文件存在于磁盘上,getsourcelines()
就会return 以适当的偏移量向您提供其内容。
In [xxx]: import inspect
...
In [104]: bla = "def bla(): return 1"
In [105]: open("testx.py", "wt").write(bla)
Out[105]: 19
In [106]: b = compile(bla, "testx.py", "exec")
In [107]: exec(b)
In [108]: bla()
Out[108]: 1
In [109]: inspect.getsourcelines(bla)
Out[109]: (['def bla(): return 1\n'], 1)
In [110]: !rm testx.py
In [111]: inspect.getsourcelines(bla)
---------------------------------------------------------------------------
OSError Traceback (most recent call last)
<ipython-input-111-3459b1636cc6> in <module>
----> 1 inspect.getsourcelines(bla)
[...]
OSError: could not get source code
我认为这是一个 XY 问题。如果您使用的 Python 版本比 3.7.
旧版本,则使用dataclasses
模块或关键字参数可以更轻松地解决您要解决的问题。
from dataclasses import dataclass
@dataclass
class A:
a: int = 0
@dataclass
class B(A):
b: int = 1
def __post_init__(self):
self.c = self.a + self.b
from dataclasses import field
from typing import List
@dataclass
class C(B):
foo: List[int] = field(default_factory=list)
# __repr__ is thrown in for free
assert str(C(a=2, b=3, foo=[1, 2, 3])) == 'C(a=2, b=3, foo=[1, 2, 3])'
# retain default args
assert C().a == 0
# using positional args and post_init
assert C(2, 3).c == 5
# mutable defaults
assert C().foo == [] and C().foo is not C().foo
使用 kwargs,您可以使用命名参数弹出每个 class 所需的参数,然后将其余的 kwargs 传递给父 classes 初始化函数,例如
class A:
def __init__(self, a: int = 0):
self.a = a
class B(A):
def __init__(self, *, b:int, **kwargs):
super().__init__(**kwargs)
self.b = b
class C(B):
def __init__(self, *, c: int = 2, **kwargs):
super().__init__(**kwargs)
self.c = c
obj = C(a=1, b=2, c=3)
assert vars(obj) == dict(a=1, b=2, c=3)
# retain defaults
obj2 = C(b=2)
assert obj2.a == 0