简体   繁体   English

在python中识别纯函数

[英]Identifying pure functions in python

I have a decorator @pure that registers a function as pure, for example: 我有一个装饰器@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

Next, I want to identify a newly defined pure function 接下来,我想确定一个新定义的纯函数

def house_area(a,b,c):
    return rectangle_area(a,b) + triangle_area(a,b,c)

Obviously house_area is pure, since it only calls pure functions. 显然house_area是纯粹的,因为它只调用纯函数。

How can I discover all pure functions automatically (perhaps by using ast ) 如何自动发现所有纯函数(可能使用ast

Assuming operators are all pure, then essentially you only need to check all the functions calls. 假设运算符都是纯粹的,那么基本上你只需要检查所有函数调用。 This can indeed be done with the ast module. 这确实可以通过ast模块完成。

First I defined the pure decorator as: 首先,我将pure装饰器定义为:

def pure(f):
    f.pure = True
    return f

Adding an attribute telling that it's pure, allows skipping early or "forcing" a function to identify as pure. 添加一个告诉它是纯粹的属性,允许提前跳过或“强制”函数识别为纯。 This is useful if you'd need a function like math.sin to identify as pure. 如果您需要像math.sin这样的函数来识别为纯函数,那么这非常有用。 Additionally since you can't add attributes to builtin functions. 此外,因为您无法向内置函数添加属性。

@pure
def sin(x):
    return math.sin(x)

All in all. 总而言之。 Use the ast module to visit all the nodes. 使用ast模块访问所有节点。 Then for each Call node check whether the function being called is pure. 然后为每个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

Then check whether the function has the pure attribute. 然后检查该函数是否具有pure属性。 If not get code and check if all the functions calls can be classified as 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

Note that print(is_pure(lambda x: math.sin(x))) doesn't work since inspect.getsource(f.__code__) returns code on a line by line basis. 请注意, print(is_pure(lambda x: math.sin(x)))不起作用,因为inspect.getsource(f.__code__)返回代码。 So the source returned by getsource would include the print and is_pure call, thus yielding False . 所以getsource返回的源包括printis_pure调用,因此产生False Unless those functions are overridden. 除非这些功能被覆盖。


To verify that it works, test it by doing: 要验证它是否有效,请执行以下操作进行测试:

print(house_area) # Prints: True

To list through all the functions in the current module: 列出当前模块中的所有功能:

import sys, types

for k in dir(sys.modules[__name__]):
    v = globals()[k]
    if isinstance(v, types.FunctionType):
        print(k, is_pure(v))

The visited list keeps track of which functions have already been verified pure. visited列表跟踪哪些功能已经被验证为纯。 This help circumvent problems related to recursion. 这有助于规避与递归相关的问题。 Since the code isn't executed, the evaluation would recursively visit factorial . 由于代码未执行,评估将递归访问factorial

@pure
def factorial(n):
    return 1 if n == 1 else n * factorial(n - 1)

Note that you might need to revise the following code. 请注意,您可能需要修改以下代码。 Choosing another way to obtain a function from its name. 选择另一种从名称中获取函数的方法。

try:
    callee = eval(name)
    if not is_pure(callee, self.visited):
        self.pure = False
except NameError:
    self.pure = False

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM