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在没有实际运行代码的情况下也不知道使用了什么。