在 python 中识别纯函数
Identifying pure functions in python
我有一个装饰器 @pure
将函数注册为纯函数,例如:
@pure
def rectangle_area(a,b):
return a*b
@pure
def triangle_area(a,b,c):
return ((a+(b+c))(c-(a-b))(c+(a-b))(a+(b-c)))**0.5/4
接下来,我要识别一个新定义的纯函数
def house_area(a,b,c):
return rectangle_area(a,b) + triangle_area(a,b,c)
显然 house_area
是纯函数,因为它只调用纯函数。
如何自动发现所有纯函数(可能使用 ast
)
假设运算符都是纯的,那么基本上你只需要检查所有的函数调用。这确实可以用 ast 模块来完成。
首先我将 pure
装饰器定义为:
def pure(f):
f.pure = True
return f
添加一个表明它是纯函数的属性,允许提前跳过或 "forcing" 一个函数以识别为纯函数。如果您需要像 math.sin
这样的函数来标识为纯函数,这将很有用。 此外,您无法向内置函数添加属性。
@pure
def sin(x):
return math.sin(x)
总而言之。使用 ast
模块访问所有节点。然后对于每个 Call
节点检查被调用的函数是否是纯函数。
import ast
class PureVisitor(ast.NodeVisitor):
def __init__(self, visited):
super().__init__()
self.pure = True
self.visited = visited
def visit_Name(self, node):
return node.id
def visit_Attribute(self, node):
name = [node.attr]
child = node.value
while child is not None:
if isinstance(child, ast.Attribute):
name.append(child.attr)
child = child.value
else:
name.append(child.id)
break
name = ".".join(reversed(name))
return name
def visit_Call(self, node):
if not self.pure:
return
name = self.visit(node.func)
if name not in self.visited:
self.visited.append(name)
try:
callee = eval(name)
if not is_pure(callee, self.visited):
self.pure = False
except NameError:
self.pure = False
然后检查函数是否有pure
属性。如果没有获取代码并检查是否所有函数调用都可以归类为纯。
import inspect, textwrap
def is_pure(f, _visited=None):
try:
return f.pure
except AttributeError:
pass
try:
code = inspect.getsource(f.__code__)
except AttributeError:
return False
code = textwrap.dedent(code)
node = compile(code, "<unknown>", "exec", ast.PyCF_ONLY_AST)
if _visited is None:
_visited = []
visitor = PureVisitor(_visited)
visitor.visit(node)
return visitor.pure
请注意,print(is_pure(lambda x: math.sin(x)))
不起作用,因为 inspect.getsource(f.__code__)
returns 逐行编码。因此 getsource
返回的源将包括 print
和 is_pure
调用,从而产生 False
。 除非这些函数被覆盖。
要验证它是否有效,请通过以下方式对其进行测试:
print(house_area) # Prints: True
列出当前模块中的所有函数:
import sys, types
for k in dir(sys.modules[__name__]):
v = globals()[k]
if isinstance(v, types.FunctionType):
print(k, is_pure(v))
visited
列表跟踪哪些功能已经过纯验证。这有助于规避与递归相关的问题。由于代码未执行,评估将递归访问 factorial
.
@pure
def factorial(n):
return 1 if n == 1 else n * factorial(n - 1)
请注意,您可能需要修改以下代码。选择另一种方式从函数名称中获取函数。
try:
callee = eval(name)
if not is_pure(callee, self.visited):
self.pure = False
except NameError:
self.pure = False
我有一个装饰器 @pure
将函数注册为纯函数,例如:
@pure
def rectangle_area(a,b):
return a*b
@pure
def triangle_area(a,b,c):
return ((a+(b+c))(c-(a-b))(c+(a-b))(a+(b-c)))**0.5/4
接下来,我要识别一个新定义的纯函数
def house_area(a,b,c):
return rectangle_area(a,b) + triangle_area(a,b,c)
显然 house_area
是纯函数,因为它只调用纯函数。
如何自动发现所有纯函数(可能使用 ast
)
假设运算符都是纯的,那么基本上你只需要检查所有的函数调用。这确实可以用 ast 模块来完成。
首先我将 pure
装饰器定义为:
def pure(f):
f.pure = True
return f
添加一个表明它是纯函数的属性,允许提前跳过或 "forcing" 一个函数以识别为纯函数。如果您需要像 math.sin
这样的函数来标识为纯函数,这将很有用。 此外,您无法向内置函数添加属性。
@pure
def sin(x):
return math.sin(x)
总而言之。使用 ast
模块访问所有节点。然后对于每个 Call
节点检查被调用的函数是否是纯函数。
import ast
class PureVisitor(ast.NodeVisitor):
def __init__(self, visited):
super().__init__()
self.pure = True
self.visited = visited
def visit_Name(self, node):
return node.id
def visit_Attribute(self, node):
name = [node.attr]
child = node.value
while child is not None:
if isinstance(child, ast.Attribute):
name.append(child.attr)
child = child.value
else:
name.append(child.id)
break
name = ".".join(reversed(name))
return name
def visit_Call(self, node):
if not self.pure:
return
name = self.visit(node.func)
if name not in self.visited:
self.visited.append(name)
try:
callee = eval(name)
if not is_pure(callee, self.visited):
self.pure = False
except NameError:
self.pure = False
然后检查函数是否有pure
属性。如果没有获取代码并检查是否所有函数调用都可以归类为纯。
import inspect, textwrap
def is_pure(f, _visited=None):
try:
return f.pure
except AttributeError:
pass
try:
code = inspect.getsource(f.__code__)
except AttributeError:
return False
code = textwrap.dedent(code)
node = compile(code, "<unknown>", "exec", ast.PyCF_ONLY_AST)
if _visited is None:
_visited = []
visitor = PureVisitor(_visited)
visitor.visit(node)
return visitor.pure
请注意,print(is_pure(lambda x: math.sin(x)))
不起作用,因为 inspect.getsource(f.__code__)
returns 逐行编码。因此 getsource
返回的源将包括 print
和 is_pure
调用,从而产生 False
。 除非这些函数被覆盖。
要验证它是否有效,请通过以下方式对其进行测试:
print(house_area) # Prints: True
列出当前模块中的所有函数:
import sys, types
for k in dir(sys.modules[__name__]):
v = globals()[k]
if isinstance(v, types.FunctionType):
print(k, is_pure(v))
visited
列表跟踪哪些功能已经过纯验证。这有助于规避与递归相关的问题。由于代码未执行,评估将递归访问 factorial
.
@pure
def factorial(n):
return 1 if n == 1 else n * factorial(n - 1)
请注意,您可能需要修改以下代码。选择另一种方式从函数名称中获取函数。
try:
callee = eval(name)
if not is_pure(callee, self.visited):
self.pure = False
except NameError:
self.pure = False