awf / awfutils Goto Github PK
View Code? Open in Web Editor NEWAWF's Utilities
License: MIT License
AWF's Utilities
License: MIT License
Line 168 in 7359acb
Extremely impressive. @awfutils.typecheck
is the first practical attempt I've seen at performing static type-checking at runtime. Take all my thunderous clapping! ๐ ๐
The current approach is outrageously useful, but appears to currently only support isinstance()
-able classes rather than PEP-compliant type hints: e.g.,
# I suspect this fails hard, but am lazy and thus did not test.
@typecheck
def foo(x : List[int], y : int):
z : List[int] = x * y
w : float = z[0] * 3.2
return w
foo([3, 2, 1], 1.3)
Is that right? If so, that's still impressive tech for a several hundred-line decorator. Still, it'd be even more outrageously useful if we could generalize your @typecheck
decorator to support arbitrary PEP-compliant type hints. Can we? Yes, we can.
That's right. We're talkin' @beartype, because of course we are. Implementing full-blown type-checking for all PEP standards past and present probably isn't somewhere you want to willingly go. Thankfully, @beartype already went there for you. ๐ฅณ
The core issue appears to be the current usage of the isinstance()
builtin in the TypeCheckVisitor.visit_FunctionDef()
method. Specifically, this AST transform:
node_assert = ast.Assert(
test=ast.Call(
ast.Name("isinstance", ctx=ast.Load()),
[
ast.Name(
node.target.id, ctx=ast.Load()
), # Convert ctx from Store to Load
node.annotation,
],
[],
),
msg=ast.Constant(value=f"{node.target.id} not a {annot_str}", kind=None),
)
I'm fairly certain (...but technically uncertain, because lazy and thus untested) that replacing the above with the below should generalize @typecheck
to support PEP-compliant type hints:
class TypeCheckVisitor(ast.NodeTransformer):
def visit_Module(self, node: Module) -> Module:
'''
Add a new abstract syntax tree (AST) child node to the passed AST module
parent node encapsulating the module currently being loaded, importing
the :func:`beartype.abby.die_if_unbearable` runtime type-checker for
subsequent use by the other visitor methods defined by this class.
Parameters
----------
node : Module
AST module parent node to be transformed.
Returns
----------
Module
That same AST module parent node.
'''
# 0-based index of the first safe position of the list of all AST child
# nodes of this AST module parent node to insert an import statement
# importing our beartype decorator, initialized to the erroneous index
# "-1" to enable detection of empty modules (i.e., modules whose AST
# module nodes containing *NO* child nodes) below.
import_beartype_index = -1
# AST child node of this AST module parent node immediately preceding
# the AST import child node to be added below, defaulting to this AST
# module parent node to ensure that the _copy_node_code_metadata()
# function below *ALWAYS* copies from a valid AST node for sanity.
module_child: AST = node
# Efficiently find this index. Since, this iteration is guaranteed to
# exhibit worst-case O(1) time complexity despite superficially
# appearing to perform a linear search of all n child nodes of this
# module parent node and thus exhibit worst-case O(n) time complexity.
#
# For the 0-based index and value of each direct AST child node of this
# AST module parent node...
for import_beartype_index, module_child in enumerate(node.body):
# If this child node signifies either...
if (
# A module docstring...
#
# If that module defines a docstring, that docstring *MUST* be
# the first expression of that module. That docstring *MUST* be
# explicitly found and iterated past to ensure that the import
# statement added below appears *AFTER* rather than *BEFORE* any
# docstring. (The latter would destroy the semantics of that
# docstring by reducing that docstring to an ignorable string.)
(
isinstance(module_child, Expr) and
isinstance(module_child.value, Str)
) or
# A future import (i.e., import of the form
# "from __future__ ...") *OR*...
#
# If that module performs one or more future imports, these
# imports *MUST* necessarily be the first non-docstring
# statement of that module and thus appear *BEFORE* all import
# statements that are actually imports -- including the import
# statement added below.
(
isinstance(module_child, ImportFrom) and
module_child.module == '__future__'
)
):
# Then continue past this child node to the next child node.
continue
# If the 0-based index of the first safe position of the list of all AST
# child nodes of this AST module parent node to insert an import
# statement importing our beartype decorator is *NOT* the erroneous
# index to which this index was initialized above, this module contains
# one or more child nodes and is thus non-empty. In this case...
if import_beartype_index != -1:
# AST import child node importing our private
# beartype._decor.decorcore.beartype_object_nonfatal() decorator for
# subsequent use by the other visitor methods defined by this class.
import_beartype = ImportFrom(
module='beartype.abby',
names=[alias('die_if_unbearable')],
)
# Copy all source code metadata from the AST child node of this AST
# module parent node immediately preceding this AST import child
# node onto this AST import child node.
_copy_node_code_metadata(
node_src=node, node_trg=import_beartype)
# Insert this AST import child node at this safe position of the
# list of all AST child nodes of this AST module parent node.
node.body.insert(import_beartype_index, import_beartype)
# Else, this module is empty. In this case, silently reduce to a noop.
# Since this edge case is *EXTREMELY* uncommon, avoid optimizing for
# this edge case (here or elsewhere).
# Recursively transform *ALL* AST child nodes of this AST module node.
self.generic_visit(node)
# Return this AST module node as is.
return node
...
def visit_AnnAssign(self, node):
# An assignment with a type annotation.
# node.target : single node and can be a Name, a Attribute or a Subscript.
# node.annotation : annotation, such as a Str or Name node.
# node.value : single optional node.
# node.simple : True for a Name node in target that do not appear between
# parentheses and are hence pure names and not expressions.
if not node.simple:
return node
assert isinstance(node.target, ast.Name) # Should be guaranteed by node.simple
node_typecheck = ast.Call(
ast.Name('die_if_unbearable', ctx=ast.Load()),
[
ast.Name(
node.target.id, ctx=ast.Load()
), # Convert ctx from Store to Load
node.annotation,
],
[],
)
node_typecheck = ast.copy_location(node_typecheck, node)
ast.fix_missing_locations(node_typecheck)
return [node, node_typecheck]
The visit_Module()
implementation is copied almost verbatim from a similar AST transform in the @beartype
codebase itself. So, possibly working?
Regardless of where you choose to take this awesomeness, this has been a considerable inspiration. @beartype
will probably end up assimilating this into itself, because everyone over at beartype/beartype#105 really wants this to happen.
In short, you're amazing.
A declarative, efficient, and flexible JavaScript library for building user interfaces.
๐ Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. ๐๐๐
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google โค๏ธ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.