Python 确定函数是否包含特定方法
我想写一些代码来检查学生提交的内容,以确保给定函数包含Python 确定函数是否包含特定方法,python,code-inspection,Python,Code Inspection,我想写一些代码来检查学生提交的内容,以确保给定函数包含np.random.choice 例如: import numpy as np def checkme(z, x=[1,2,3], y=4): tmp = np.random.choice(x, size=y) if z: print("z") return(list(tmp)) 我已经看到我可以使用像 tmp = inspect.signature(checkme) for param in tm
np.random.choice
例如:
import numpy as np
def checkme(z, x=[1,2,3], y=4):
tmp = np.random.choice(x, size=y)
if z:
print("z")
return(list(tmp))
我已经看到我可以使用像
tmp = inspect.signature(checkme)
for param in tmp.parameters.values():
print(param.name, ",", param.default)
来确定参数和值,这很好,但我想更进一步,确保函数体包含特定的函数或方法。所以在上面,我想确保学生的代码包括np.random.choice
如何访问函数体以“检查”并确定这是真是假?您可以使用另一种检查方法,
inspect.getsource
,它将以字符串形式获取函数的源代码
import inspect
import numpy as np
def checkme(z, x=[1,2,3], y=4):
tmp = np.random.choice(x, size=y)
if z:
print("z")
return(list(tmp))
code = inspect.getsource(checkme)
lines = code.split("\n")
for line in lines:
print(line, "np.random.choice" in line)
输出:
# def checkme(z, x=[1,2,3], y=4): False
# tmp = np.random.choice(x, size=y) True
# if z: False
# print("z") False
# return(list(tmp)) False
# False
我拆分代码字符串以检查调用该方法的确切行
这种方法的问题在于别名
例如,如果您的学生以其他形式输入numpy
import numpy as mynp
import numpy
import numpy as npy
当然,对于注释代码:
# np.random.choice
等
有关类inspect的一些详细信息。您可以执行以下操作:
import inspect
f = 'XXX' # Your student's submission is stored in XXX.py
assert('np.random.choice' in inspect.getsource(__import__(f).checkme))
您可以检查函数是否被调用,而不是检查源代码。您可以应用装饰器来执行此检查:
import numpy as np
# Create your decorator
def iscalled_decorator(func):
def wrapper(*args, **kwargs):
global iscalled
iscalled = True
return func(*args, **kwargs)
return wrapper
# Decorate np.random.choice
np.random.choice = iscalled_decorator(np.random.choice)
# Import your student's function
f = 'XXX'
checkme = __import__(f).checkme
# Set a flag iscalled and call the function
iscalled = False
checkme(3, [1,2,3], 4)
# Check if the flag is True
assert(iscalled)
假设您想通过手头的函数(即使是编译的
pyc
形式)和not通过字符串搜索操作(我想您已经考虑过了)来实现这一点,那么您可以使用
形式为y=np.random.choice(x)
的调用将编译成如下内容(输出):
8 0加载\u全局0(np)
2负载属性1(随机)
4加载方式2(选择)
6加载速度1(x)
8调用方法1
10商店2(y)
假设您的学生使用全局导入numpy作为np
,则这些指令及其参数的顺序应始终相同。第三个LOAD\u方法
可能变成LOAD\u ATTR
,具体取决于加载方法的方式
实际调用更难检测,它可能变成call\u METHOD
、call\u FUNCTION\u EX
、call\u FUNCTION\u KW
或call\u FUNCTION
,具体取决于执行方式。检查被调用的函数是否确实是您想要的函数也不是那么简单,就像上面的例子中那样,这是显而易见的。检查是否进行了实际调用仍然是可能的,但需要跟踪Python堆栈并对指令及其参数进行真正的解析,如果希望深入研究,可以进行检查
我将仅限于检查np.random.choice
是否实际加载到了checked函数中。可以使用以下代码执行此操作:
导入dis
def zip3(g):
尝试:
a、 b,c=下一个(g),下一个(g),下一个(g)
而1:
产量a、b、c
a、 b,c=b,c,next(g)
除停止迭代外:
通过
def检查(func):
对于zip3中的a、b、c(dis.get_指令(func)):
如果a.opname=='LOAD_GLOBAL'和a.argval=='np':
如果b.opname=='LOAD_ATTR'和b.argval=='random':
如果c.opname in('LOAD_ATTR','LOAD_METHOD')和c.argval=='choice':
返回真值
返回错误
检查(检查我)#->正确
注意:操作码可能会根据Python版本而变化,但我假设您将在同一Python版本下运行所有测试,因此您可以调整匹配项以满足您的需要(使用
dis.dis()
进行检查)。当然,使用这种方法,您将无法捕获更复杂的内容,如a=np;b=a.随机;b、 选项(x)
或将numpy作为任何对象导入
,但对于字符串匹配也是如此。您可以临时用包装器替换要检查的方法,该包装器将让您知道是否通过全局变量(或您选择的其他对象)调用了该方法。我认为这是唯一真正的解决方案,因为像我建议的那样,检查字符串匹配和检查取消组合代码都很容易出错,并且不可避免地会遗漏边缘情况
下面是一个例子:
类检查器:
def检查(自检、func、funcargs):
实随机选择=np.random.choice
self.called=False
def包装(*args,**kwargs):
self.called=True
返回实数\u np\u随机选项(*args,**kwargs)
np.random.choice=包装器
func(*funcargs)
np.random.choice=real\u np\u random\u choice
回叫
Checker().检查(checkme,(3,[1,2,3],4))#->True
我在这里使用一个类只是因为我需要以某种方式将结果从包装器中取出。当然,这也可以通过全局变量来实现
检查给定模块的给定方法是否被调用的更通用的解决方案是:
类检查器:
定义初始化(自身、模块、方法):
self.module=模块
self.method=方法
def检查(自检、func、funcargs):
real_method=getattr(self.module,self.method)
self.called=False
def包装(*args,**kwargs):
self.called=True
返回实数方法(*args,**kwargs)
setattr(self.module、self.method、包装器)
func(*funcargs)
setattr(self.module、self.method、real_方法)
回叫
c=检查程序(np.random,'choice')
打印(c.检查(检查我,(3,[1,2,3],4)))
最后,100%确定的唯一方法就是这样做。Monkeypatch检查被调用函数是否被调用。Python的本质是,即使Python在没有实际运行代码的情况下也不知道使用了什么。