Skip to content

Commit a954bc7

Browse files
committed
Add type-specific nodes_of_class
nodes_of_class is a very flexible method, which is great for use in client code (e.g. Pylint). However, that flexibility requires a great deal of runtime type checking: def nodes_of_class(self, klass, skip_klass=None): if isinstance(self, klass): yield self if skip_klass is None: for child_node in self.get_children(): for matching in child_node.nodes_of_class(klass, skip_klass): yield matching return for child_node in self.get_children(): if isinstance(child_node, skip_klass): continue for matching in child_node.nodes_of_class(klass, skip_klass): yield matching First, the node has to check its own type to see whether it's of the desired class. Then the skip_klass flag has to be checked to see whether anything needs to be skipped. If so, the type of every yielded node has to be check to see if it should be skipped. This is fine for calling code whose arguments can't be known in advance ("Give me all the Assign and ClassDef nodes, but skip all the BinOps, YieldFroms, and Globals."), but in Astroid itself, every call to this function can be known in advance. There's no need to do any type checking if all the nodes know how to respond to certain requests. Take get_assign_nodes for example. The Assign nodes know that they should yield themselves and then yield their Assign children. Other nodes know in advance that they aren't Assign nodes, so they don't need to check their own type, just immediately yield their Assign children. Overly specific functions like get_yield_nodes_skip_lambdas certainly aren't very elegant, but the tradeoff is to take advantage of knowing how the library code works to improve speed.
1 parent bee7dc5 commit a954bc7

File tree

2 files changed

+63
-7
lines changed

2 files changed

+63
-7
lines changed

astroid/node_classes.py

Lines changed: 59 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -220,6 +220,7 @@ class NodeNG(object):
220220
221221
:type: bool
222222
"""
223+
is_lambda = False
223224
# Attributes below are set by the builder module or by raw factories
224225
lineno = None
225226
"""The line that this node appears on in the source code.
@@ -641,6 +642,30 @@ def nodes_of_class(self, klass, skip_klass=None):
641642
for matching in child_node.nodes_of_class(klass, skip_klass):
642643
yield matching
643644

645+
def _get_assign_nodes(self):
646+
for child_node in self.get_children():
647+
for matching in child_node._get_assign_nodes():
648+
yield matching
649+
650+
def _get_name_nodes(self):
651+
for child_node in self.get_children():
652+
for matching in child_node._get_name_nodes():
653+
yield matching
654+
655+
def _get_return_nodes_skip_functions(self):
656+
for child_node in self.get_children():
657+
if child_node.is_function:
658+
continue
659+
for matching in child_node._get_return_nodes_skip_functions():
660+
yield matching
661+
662+
def _get_yield_nodes_skip_lambdas(self):
663+
for child_node in self.get_children():
664+
if child_node.is_lambda:
665+
continue
666+
for matching in child_node._get_yield_nodes_skip_lambdas():
667+
yield matching
668+
644669
def _infer_name(self, frame, name):
645670
# overridden for ImportFrom, Import, Global, TryExcept and Arguments
646671
return None
@@ -1267,6 +1292,13 @@ def __init__(self, name=None, lineno=None, col_offset=None, parent=None):
12671292
def get_children(self):
12681293
yield from ()
12691294

1295+
def _get_name_nodes(self):
1296+
yield self
1297+
1298+
for child_node in self.get_children():
1299+
for matching in child_node._get_name_nodes():
1300+
yield matching
1301+
12701302

12711303
class Arguments(mixins.AssignTypeMixin, NodeNG):
12721304
"""Class representing an :class:`ast.arguments` node.
@@ -1702,6 +1734,13 @@ def get_children(self):
17021734

17031735
yield self.value
17041736

1737+
def _get_assign_nodes(self):
1738+
yield self
1739+
1740+
for child_node in self.get_children():
1741+
for matching in child_node._get_assign_nodes():
1742+
yield matching
1743+
17051744

17061745
class AnnAssign(mixins.AssignTypeMixin, Statement):
17071746
"""Class representing an :class:`ast.AnnAssign` node.
@@ -2792,7 +2831,7 @@ def catch(self, exceptions): # pylint: disable=redefined-outer-name
27922831
"""
27932832
if self.type is None or exceptions is None:
27942833
return True
2795-
for node in self.type.nodes_of_class(Name):
2834+
for node in self.type._get_name_nodes():
27962835
if node.name in exceptions:
27972836
return True
27982837
return False
@@ -3582,7 +3621,7 @@ def raises_not_implemented(self):
35823621
"""
35833622
if not self.exc:
35843623
return False
3585-
for name in self.exc.nodes_of_class(Name):
3624+
for name in self.exc._get_name_nodes():
35863625
if name.name == 'NotImplementedError':
35873626
return True
35883627
return False
@@ -3621,6 +3660,15 @@ def get_children(self):
36213660
if self.value is not None:
36223661
yield self.value
36233662

3663+
def _get_return_nodes_skip_functions(self):
3664+
yield self
3665+
3666+
for child_node in self.get_children():
3667+
if child_node.is_function:
3668+
continue
3669+
for matching in child_node._get_return_nodes_skip_functions():
3670+
yield matching
3671+
36243672

36253673
class Set(_BaseContainer):
36263674
"""Class representing an :class:`ast.Set` node.
@@ -4261,6 +4309,15 @@ def get_children(self):
42614309
if self.value is not None:
42624310
yield self.value
42634311

4312+
def _get_yield_nodes_skip_lambdas(self):
4313+
yield self
4314+
4315+
for child_node in self.get_children():
4316+
if child_node.is_function_or_lambda:
4317+
continue
4318+
for matching in child_node._get_yield_nodes_skip_lambdas():
4319+
yield matching
4320+
42644321

42654322
class YieldFrom(Yield):
42664323
"""Class representing an :class:`ast.YieldFrom` node."""

astroid/scoped_nodes.py

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1037,6 +1037,7 @@ class Lambda(mixins.FilterStmtsMixin, LocalsDictNodeNG):
10371037
_astroid_fields = ('args', 'body',)
10381038
_other_other_fields = ('locals',)
10391039
name = '<lambda>'
1040+
is_lambda = True
10401041

10411042
# function's type, 'function' | 'method' | 'staticmethod' | 'classmethod'
10421043
@property
@@ -1313,7 +1314,7 @@ def extra_decorators(self):
13131314
return []
13141315

13151316
decorators = []
1316-
for assign in frame.nodes_of_class(node_classes.Assign):
1317+
for assign in frame._get_assign_nodes():
13171318
if (isinstance(assign.value, node_classes.Call)
13181319
and isinstance(assign.value.func, node_classes.Name)):
13191320
for assign_node in assign.targets:
@@ -1530,9 +1531,7 @@ def is_generator(self):
15301531
:returns: True is this is a generator function, False otherwise.
15311532
:rtype: bool
15321533
"""
1533-
yield_nodes = (node_classes.Yield, node_classes.YieldFrom)
1534-
return next(self.nodes_of_class(yield_nodes,
1535-
skip_klass=(FunctionDef, Lambda)), False)
1534+
return next(self._get_yield_nodes_skip_lambdas(), False)
15361535

15371536
def infer_call_result(self, caller=None, context=None):
15381537
"""Infer what the function returns when called.
@@ -1563,7 +1562,7 @@ def infer_call_result(self, caller=None, context=None):
15631562
c._metaclass = metaclass
15641563
yield c
15651564
return
1566-
returns = self.nodes_of_class(node_classes.Return, skip_klass=FunctionDef)
1565+
returns = self._get_return_nodes_skip_functions()
15671566
for returnnode in returns:
15681567
if returnnode.value is None:
15691568
yield node_classes.Const(None)

0 commit comments

Comments
 (0)