Skip to content

Instantly share code, notes, and snippets.

@urigoren
Last active August 9, 2017 20:03
Show Gist options
  • Save urigoren/6ceda834174657d5396bd047addaddba to your computer and use it in GitHub Desktop.
Save urigoren/6ceda834174657d5396bd047addaddba to your computer and use it in GitHub Desktop.
This module tests whether a function is pure
import ast, inspect, textwrap
whitelist = {'math', 'itertools', 'collections', 'functools', 'operator',
'json', 'pickle', 'string', 'types', 'statistics', 'fractions', 'decimal'}
def pure(f):
"""pure decorator"""
f.pure = True
return f
def impure(f):
"""impure decorator"""
f.pure = False
return f
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
def is_pure(f, _visited=None):
"""Returns True if f is declared pure or if it calls only pure functions"""
try:
return f.pure
except AttributeError:
pass
try:
module = inspect.getmodule(f).__name__
except AttributeError:
module = ''
if module in whitelist:
return True
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
def immutable_args():
"""Forces only immutable types at runtime"""
def decorator(function):
signature= inspect.signature(function)
def wrapper(*args, **kwargs):
bound_args= signature.bind(*args, **kwargs)
for variable, value in bound_args.arguments.items():
if not immutable_value(value):
raise SyntaxError(variable + " is immutable, {v} is illegal".format(v=value))
return function(*args, **kwargs)
return wrapper
return decorator
def immutable_value(x):
"""checks if x is immutable"""
return isinstance(x, tuple) or isinstance(x, int) or isinstance(x, float) or isinstance(x, str)
if __name__ == "__main__":
import math, datetime
@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)
assert is_pure(house_area) == True
assert is_pure(math.sin) == True
assert is_pure(datetime.date.today) == False
assert is_pure(map) == False
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment