Replace a lot more of empty sets and unite calls.

This commit is contained in:
Dave Halter
2017-09-25 23:08:59 +02:00
parent 921d1008f2
commit 174eff5875
14 changed files with 72 additions and 66 deletions

View File

@@ -35,6 +35,9 @@ class ContextSet(object):
def __bool__(self): def __bool__(self):
return bool(self._set) return bool(self._set)
def __len__(self):
return len(self._set)
def __repr__(self): def __repr__(self):
return '%s(%s)' % (self.__class__.__name__, ', '.join(str(s) for s in self._set)) return '%s(%s)' % (self.__class__.__name__, ', '.join(str(s) for s in self._set))

View File

@@ -433,7 +433,7 @@ class Evaluator(object):
node = () node = ()
if trailer_op == '[': if trailer_op == '[':
return ContextSet(iterable.py__getitem__(self, context, types, trailer)) return iterable.py__getitem__(self, context, types, trailer)
else: else:
context_set = ContextSet() context_set = ContextSet()
for typ in types: for typ in types:

View File

@@ -223,7 +223,10 @@ def _execute_types_in_stmt(module_context, stmt):
contain is executed. (Used as type information). contain is executed. (Used as type information).
""" """
definitions = module_context.eval_node(stmt) definitions = module_context.eval_node(stmt)
return unite(_execute_array_values(module_context.evaluator, d) for d in definitions) return ContextSet.from_sets(
_execute_array_values(module_context.evaluator, d)
for d in definitions
)
def _execute_array_values(evaluator, array): def _execute_array_values(evaluator, array):
@@ -234,7 +237,10 @@ def _execute_array_values(evaluator, array):
if isinstance(array, SequenceLiteralContext): if isinstance(array, SequenceLiteralContext):
values = [] values = []
for lazy_context in array.py__iter__(): for lazy_context in array.py__iter__():
objects = unite(_execute_array_values(evaluator, typ) for typ in lazy_context.infer()) objects = ContextSet.from_sets(
_execute_array_values(evaluator, typ)
for typ in lazy_context.infer()
)
values.append(context.LazyKnownContexts(objects)) values.append(context.LazyKnownContexts(objects))
return set([FakeSequence(evaluator, array.array_type, values)]) return set([FakeSequence(evaluator, array.array_type, values)])
else: else:

View File

@@ -24,7 +24,8 @@ from jedi.evaluate.cache import evaluator_function_cache
from jedi.evaluate import imports from jedi.evaluate import imports
from jedi.evaluate.param import TreeArguments, create_default_params from jedi.evaluate.param import TreeArguments, create_default_params
from jedi.evaluate.helpers import is_stdlib_path from jedi.evaluate.helpers import is_stdlib_path
from jedi.evaluate.utils import to_list, unite from jedi.evaluate.utils import to_list
from jedi.common import ContextSet
from jedi.parser_utils import get_parent_scope from jedi.parser_utils import get_parent_scope
@@ -50,7 +51,7 @@ class MergedExecutedParams(object):
self._executed_params = executed_params self._executed_params = executed_params
def infer(self): def infer(self):
return unite(p.infer() for p in self._executed_params) return ContextSet.from_sets(p.infer() for p in self._executed_params)
@debug.increase_indent @debug.increase_indent
@@ -103,7 +104,7 @@ def search_params(evaluator, execution_context, funcdef):
evaluator.dynamic_params_depth -= 1 evaluator.dynamic_params_depth -= 1
@evaluator_function_cache(default=[]) @evaluator_function_cache(default=None)
@to_list @to_list
def _search_function_executions(evaluator, module_context, funcdef): def _search_function_executions(evaluator, module_context, funcdef):
""" """

View File

@@ -6,9 +6,9 @@ from abc import abstractmethod
from parso.tree import search_ancestor from parso.tree import search_ancestor
from jedi.evaluate import flow_analysis from jedi.evaluate import flow_analysis
from jedi.evaluate.utils import to_list, unite
from jedi.common import ContextSet from jedi.common import ContextSet
from jedi.parser_utils import get_parent_scope from jedi.parser_utils import get_parent_scope
from jedi.evaluate.utils import to_list
class AbstractNameDefinition(object): class AbstractNameDefinition(object):
@@ -36,10 +36,10 @@ class AbstractNameDefinition(object):
return '<%s: %s@%s>' % (self.__class__.__name__, self.string_name, self.start_pos) return '<%s: %s@%s>' % (self.__class__.__name__, self.string_name, self.start_pos)
def execute(self, arguments): def execute(self, arguments):
return unite(context.execute(arguments) for context in self.infer()) return self.infer().execute(arguments)
def execute_evaluated(self, *args, **kwargs): def execute_evaluated(self, *args, **kwargs):
return unite(context.execute_evaluated(*args, **kwargs) for context in self.infer()) return self.infer().execute_evaluated(*args, **kwargs)
@property @property
def api_type(self): def api_type(self):

View File

@@ -233,10 +233,7 @@ def _name_to_types(evaluator, context, tree_name):
# TODO check for types that are not classes and add it to # TODO check for types that are not classes and add it to
# the static analysis report. # the static analysis report.
exceptions = context.eval_node(tree_name.get_previous_sibling().get_previous_sibling()) exceptions = context.eval_node(tree_name.get_previous_sibling().get_previous_sibling())
types = unite( types = exceptions.execute_evaluated()
evaluator.execute(t, param.ValuesArguments([]))
for t in exceptions
)
else: else:
raise ValueError("Should not happen.") raise ValueError("Should not happen.")
return types return types
@@ -274,8 +271,7 @@ def _apply_decorators(evaluator, context, node):
debug.warning('decorator not found: %s on %s', dec, node) debug.warning('decorator not found: %s on %s', dec, node)
return initial return initial
values = unite(dec_value.execute(param.ValuesArguments([values])) values = dec_values.execute(param.ValuesArguments([values]))
for dec_value in dec_values)
if not len(values): if not len(values):
debug.warning('not possible to resolve wrappers found %s', node) debug.warning('not possible to resolve wrappers found %s', node)
return initial return initial

View File

@@ -24,7 +24,6 @@ from parso import python_bytes_to_unicode
from jedi._compatibility import find_module, unicode, ImplicitNSInfo from jedi._compatibility import find_module, unicode, ImplicitNSInfo
from jedi import debug from jedi import debug
from jedi import settings from jedi import settings
from jedi.evaluate.utils import unite
from jedi.evaluate import sys_path from jedi.evaluate import sys_path
from jedi.evaluate import helpers from jedi.evaluate import helpers
from jedi.evaluate import compiled from jedi.evaluate import compiled
@@ -67,13 +66,11 @@ def infer_import(context, tree_name, is_goto=False):
return NO_CONTEXTS return NO_CONTEXTS
if from_import_name is not None: if from_import_name is not None:
types = unite( types = types.py__getattribute__(
t.py__getattribute__( from_import_name,
from_import_name, name_context=context,
name_context=context, is_goto=is_goto,
is_goto=is_goto, analysis_errors=False
analysis_errors=False
) for t in types
) )
if not types: if not types:

View File

@@ -1,7 +1,6 @@
from abc import abstractproperty from abc import abstractproperty
from jedi._compatibility import is_py3 from jedi._compatibility import is_py3
from jedi.evaluate.utils import unite
from jedi import debug from jedi import debug
from jedi.evaluate import compiled from jedi.evaluate import compiled
from jedi.evaluate import filters from jedi.evaluate import filters
@@ -11,7 +10,7 @@ from jedi.evaluate.param import AbstractArguments, AnonymousArguments
from jedi.cache import memoize_method from jedi.cache import memoize_method
from jedi.evaluate import representation as er from jedi.evaluate import representation as er
from jedi.evaluate import iterable from jedi.evaluate import iterable
from jedi.common import ContextSet, iterator_to_context_set from jedi.common import ContextSet, iterator_to_context_set, NO_CONTEXTS
from jedi.parser_utils import get_parent_scope from jedi.parser_utils import get_parent_scope
@@ -59,7 +58,7 @@ class AbstractInstanceContext(Context):
raise AttributeError raise AttributeError
def execute(arguments): def execute(arguments):
return unite(name.execute(arguments) for name in names) return ContextSet.from_sets(name.execute(arguments) for name in names)
return execute return execute
@@ -81,7 +80,7 @@ class AbstractInstanceContext(Context):
return [] return []
def execute_function_slots(self, names, *evaluated_args): def execute_function_slots(self, names, *evaluated_args):
return unite( return ContextSet.from_sets(
name.execute_evaluated(*evaluated_args) name.execute_evaluated(*evaluated_args)
for name in names for name in names
) )
@@ -97,7 +96,7 @@ class AbstractInstanceContext(Context):
none_obj = compiled.create(self.evaluator, None) none_obj = compiled.create(self.evaluator, None)
return self.execute_function_slots(names, none_obj, obj) return self.execute_function_slots(names, none_obj, obj)
else: else:
return set([self]) return ContextSet(self)
def get_filters(self, search_global=None, until_position=None, def get_filters(self, search_global=None, until_position=None,
origin_scope=None, include_self_names=True): origin_scope=None, include_self_names=True):
@@ -123,7 +122,7 @@ class AbstractInstanceContext(Context):
names = self.get_function_slot_names('__getitem__') names = self.get_function_slot_names('__getitem__')
except KeyError: except KeyError:
debug.warning('No __getitem__, cannot access the array.') debug.warning('No __getitem__, cannot access the array.')
return set() return NO_CONTEXTS
else: else:
index_obj = compiled.create(self.evaluator, index) index_obj = compiled.create(self.evaluator, index)
return self.execute_function_slots(names, index_obj) return self.execute_function_slots(names, index_obj)

View File

@@ -144,7 +144,7 @@ class GeneratorMixin(object):
@register_builtin_method('__next__', python_version_match=3) @register_builtin_method('__next__', python_version_match=3)
def py__next__(self): def py__next__(self):
# TODO add TypeError if params are given. # TODO add TypeError if params are given.
return unite(lazy_context.infer() for lazy_context in self.py__iter__()) return ContextSet.from_sets(lazy_context.infer() for lazy_context in self.py__iter__())
def get_filters(self, search_global, until_position=None, origin_scope=None): def get_filters(self, search_global, until_position=None, origin_scope=None):
gen_obj = compiled.get_special_object(self.evaluator, 'GENERATOR_OBJECT') gen_obj = compiled.get_special_object(self.evaluator, 'GENERATOR_OBJECT')
@@ -297,7 +297,8 @@ class ArrayMixin(object):
return self.evaluator.BUILTINS return self.evaluator.BUILTINS
def dict_values(self): def dict_values(self):
return unite(self._defining_context.eval_node(v) for k, v in self._items()) return ContextSet.from_sets(self._defining_context.eval_node(v)
for k, v in self._items())
class ListComprehension(ArrayMixin, Comprehension): class ListComprehension(ArrayMixin, Comprehension):
@@ -335,7 +336,7 @@ class DictComprehension(ArrayMixin, Comprehension):
return self.dict_values() return self.dict_values()
def dict_values(self): def dict_values(self):
return unite(values for keys, values in self._iterate()) return ContextSet.from_sets(values for keys, values in self._iterate())
@register_builtin_method('values') @register_builtin_method('values')
def _imitate_values(self): def _imitate_values(self):
@@ -414,7 +415,7 @@ class SequenceLiteralContext(ArrayMixin, AbstractSequence):
def _values(self): def _values(self):
"""Returns a list of a list of node.""" """Returns a list of a list of node."""
if self.array_type == 'dict': if self.array_type == 'dict':
return unite(v for k, v in self._items()) return ContextSet.from_sets(v for k, v in self._items())
else: else:
return self._items() return self._items()
@@ -532,7 +533,7 @@ class FakeDict(_FakeArray):
return self._dct[index].infer() return self._dct[index].infer()
def dict_values(self): def dict_values(self):
return unite(lazy_context.infer() for lazy_context in self._dct.values()) return ContextSet.from_sets(lazy_context.infer() for lazy_context in self._dct.values())
def _items(self): def _items(self):
raise DeprecationWarning raise DeprecationWarning
@@ -555,7 +556,7 @@ class MergedArray(_FakeArray):
yield lazy_context yield lazy_context
def py__getitem__(self, index): def py__getitem__(self, index):
return unite(lazy_context.infer() for lazy_context in self.py__iter__()) return ContextSet.from_sets(lazy_context.infer() for lazy_context in self.py__iter__())
def _items(self): def _items(self):
for array in self._arrays: for array in self._arrays:
@@ -633,7 +634,7 @@ def py__iter__types(evaluator, types, contextualized_node=None):
Calls `py__iter__`, but ignores the ordering in the end and just returns Calls `py__iter__`, but ignores the ordering in the end and just returns
all types that it contains. all types that it contains.
""" """
return unite( return ContextSet.from_sets(
lazy_context.infer() lazy_context.infer()
for lazy_context in py__iter__(evaluator, types, contextualized_node) for lazy_context in py__iter__(evaluator, types, contextualized_node)
) )
@@ -740,7 +741,7 @@ def _check_array_additions(context, sequence):
is_list = sequence.name.string_name == 'list' is_list = sequence.name.string_name == 'list'
search_names = (['append', 'extend', 'insert'] if is_list else ['add', 'update']) search_names = (['append', 'extend', 'insert'] if is_list else ['add', 'update'])
added_types = NO_CONTEXTS() added_types = NO_CONTEXTS
for add_name in search_names: for add_name in search_names:
try: try:
possible_names = module_context.tree_node.get_used_names()[add_name] possible_names = module_context.tree_node.get_used_names()[add_name]

View File

@@ -10,6 +10,7 @@ from jedi.evaluate import context
from jedi.evaluate import docstrings from jedi.evaluate import docstrings
from jedi.evaluate import pep0484 from jedi.evaluate import pep0484
from jedi.evaluate.filters import ParamName from jedi.evaluate.filters import ParamName
from jedi.common import NO_CONTEXTS
def add_argument_issue(parent_context, error_name, lazy_context, message): def add_argument_issue(parent_context, error_name, lazy_context, message):
@@ -51,7 +52,7 @@ class AbstractArguments():
debug.warning('TypeError: %s expected at least %s arguments, got %s', debug.warning('TypeError: %s expected at least %s arguments, got %s',
name, len(parameters), i) name, len(parameters), i)
raise ValueError raise ValueError
values = set() if argument is None else argument.infer() values = NO_CONTEXTS if argument is None else argument.infer()
if not values and not optional: if not values and not optional:
# For the stdlib we always want values. If we don't get them, # For the stdlib we always want values. If we don't get them,

View File

@@ -25,11 +25,10 @@ import re
from parso import ParserSyntaxError from parso import ParserSyntaxError
from parso.python import tree from parso.python import tree
from jedi.evaluate.utils import unite
from jedi.evaluate.cache import evaluator_method_cache from jedi.evaluate.cache import evaluator_method_cache
from jedi.evaluate import compiled from jedi.evaluate import compiled
from jedi.evaluate.context import LazyTreeContext from jedi.evaluate.context import LazyTreeContext
from jedi.common import NO_CONTEXTS from jedi.common import NO_CONTEXTS, ContextSet
from jedi import debug from jedi import debug
from jedi import _compatibility from jedi import _compatibility
from jedi import parser_utils from jedi import parser_utils
@@ -146,7 +145,7 @@ def py__getitem__(context, typ, node):
if type_name in ("Union", '_Union'): if type_name in ("Union", '_Union'):
# In Python 3.6 it's still called typing.Union but it's an instance # In Python 3.6 it's still called typing.Union but it's an instance
# called _Union. # called _Union.
return unite(context.eval_node(node) for node in nodes) return ContextSet.from_sets(context.eval_node(node) for node in nodes)
if type_name in ("Optional", '_Optional'): if type_name in ("Optional", '_Optional'):
# Here we have the same issue like in Union. Therefore we also need to # Here we have the same issue like in Union. Therefore we also need to
# check for the instance typing._Optional (Python 3.6). # check for the instance typing._Optional (Python 3.6).

View File

@@ -7,7 +7,7 @@ from jedi._compatibility import unicode
from jedi import debug from jedi import debug
from jedi.evaluate.compiled import CompiledObject, create, builtin_from_name from jedi.evaluate.compiled import CompiledObject, create, builtin_from_name
from jedi.evaluate import analysis from jedi.evaluate import analysis
from jedi.common import ContextSet from jedi.common import ContextSet, NO_CONTEXTS
# Maps Python syntax to the operator module. # Maps Python syntax to the operator module.
COMPARISON_OPERATORS = { COMPARISON_OPERATORS = {
@@ -162,7 +162,7 @@ def _element_calculate(evaluator, context, left, operator, right):
else: else:
return ContextSet(create(evaluator, result)) return ContextSet(create(evaluator, result))
elif operator == 'in': elif operator == 'in':
return set() return NO_CONTEXTS
def check(obj): def check(obj):
"""Checks if a Jedi object is either a float or an int.""" """Checks if a Jedi object is either a float or an int."""

View File

@@ -29,6 +29,7 @@ therefore the quality might not always be maximal.
from contextlib import contextmanager from contextlib import contextmanager
from jedi import debug from jedi import debug
from jedi.common import NO_CONTEXTS
recursion_limit = 15 recursion_limit = 15
@@ -71,7 +72,7 @@ def execution_allowed(evaluator, node):
pushed_nodes.pop() pushed_nodes.pop()
def execution_recursion_decorator(default=set()): def execution_recursion_decorator(default=NO_CONTEXTS):
def decorator(func): def decorator(func):
def wrapper(execution, **kwargs): def wrapper(execution, **kwargs):
detector = execution.evaluator.execution_recursion_detector detector = execution.evaluator.execution_recursion_detector

View File

@@ -24,6 +24,7 @@ from jedi.evaluate import precedence
from jedi.evaluate import param from jedi.evaluate import param
from jedi.evaluate import analysis from jedi.evaluate import analysis
from jedi.evaluate.context import LazyTreeContext, ContextualizedNode from jedi.evaluate.context import LazyTreeContext, ContextualizedNode
from jedi.common import NO_CONTEXTS, ContextSet
# Now this is all part of fake tuples in Jedi. However super doesn't work on # Now this is all part of fake tuples in Jedi. However super doesn't work on
# __init__ and __new__ doesn't work at all. So adding this to nametuples is # __init__ and __new__ doesn't work at all. So adding this to nametuples is
@@ -77,7 +78,7 @@ def _follow_param(evaluator, arguments, index):
try: try:
key, lazy_context = list(arguments.unpack())[index] key, lazy_context = list(arguments.unpack())[index]
except IndexError: except IndexError:
return set() return NO_CONTEXTS
else: else:
return lazy_context.infer() return lazy_context.infer()
@@ -109,7 +110,7 @@ def argument_clinic(string, want_obj=False, want_context=False, want_arguments=F
try: try:
lst = list(arguments.eval_argument_clinic(clinic_args)) lst = list(arguments.eval_argument_clinic(clinic_args))
except ValueError: except ValueError:
return set() return NO_CONTEXTS
else: else:
kwargs = {} kwargs = {}
if want_context: if want_context:
@@ -137,15 +138,16 @@ def builtins_next(evaluator, iterators, defaults):
else: else:
name = '__next__' name = '__next__'
types = set() context_set = NO_CONTEXTS
for iterator in iterators: for iterator in iterators:
if isinstance(iterator, AbstractInstanceContext): if isinstance(iterator, AbstractInstanceContext):
for filter in iterator.get_filters(include_self_names=True): context_set = ContextSet.from_sets(
for n in filter.get(name): n.infer()
for context in n.infer(): for filter in iterator.get_filters(include_self_names=True)
types |= context.execute_evaluated() for n in filter.get(name)
if types: ).execute_evaluated()
return types if context_set:
return context_set
return defaults return defaults
@@ -159,16 +161,16 @@ def builtins_getattr(evaluator, objects, names, defaults=None):
else: else:
debug.warning('getattr called without str') debug.warning('getattr called without str')
continue continue
return set() return NO_CONTEXTS
@argument_clinic('object[, bases, dict], /') @argument_clinic('object[, bases, dict], /')
def builtins_type(evaluator, objects, bases, dicts): def builtins_type(evaluator, objects, bases, dicts):
if bases or dicts: if bases or dicts:
# It's a type creation... maybe someday... # It's a type creation... maybe someday...
return set() return NO_CONTEXTS
else: else:
return set([o.py__class__() for o in objects]) return ContextSet.from_iterable(o.py__class__() for o in objects)
class SuperInstance(AbstractInstanceContext): class SuperInstance(AbstractInstanceContext):
@@ -185,7 +187,7 @@ def builtins_super(evaluator, types, objects, context):
AnonymousInstanceFunctionExecution)): AnonymousInstanceFunctionExecution)):
su = context.instance.py__class__().py__bases__() su = context.instance.py__class__().py__bases__()
return unite(context.execute_evaluated() for context in su[0].infer()) return unite(context.execute_evaluated() for context in su[0].infer())
return set() return NO_CONTEXTS
@argument_clinic('sequence, /', want_obj=True, want_arguments=True) @argument_clinic('sequence, /', want_obj=True, want_arguments=True)
@@ -207,12 +209,12 @@ def builtins_reversed(evaluator, sequences, obj, arguments):
# just returned the result directly. # just returned the result directly.
seq = iterable.FakeSequence(evaluator, 'list', rev) seq = iterable.FakeSequence(evaluator, 'list', rev)
arguments = param.ValuesArguments([[seq]]) arguments = param.ValuesArguments([[seq]])
return set([CompiledInstance(evaluator, evaluator.BUILTINS, obj, arguments)]) return ContextSet(CompiledInstance(evaluator, evaluator.BUILTINS, obj, arguments))
@argument_clinic('obj, type, /', want_arguments=True) @argument_clinic('obj, type, /', want_arguments=True)
def builtins_isinstance(evaluator, objects, types, arguments): def builtins_isinstance(evaluator, objects, types, arguments):
bool_results = set([]) bool_results = set()
for o in objects: for o in objects:
try: try:
mro_func = o.py__class__().py__mro__ mro_func = o.py__class__().py__mro__
@@ -220,7 +222,7 @@ def builtins_isinstance(evaluator, objects, types, arguments):
# This is temporary. Everything should have a class attribute in # This is temporary. Everything should have a class attribute in
# Python?! Maybe we'll leave it here, because some numpy objects or # Python?! Maybe we'll leave it here, because some numpy objects or
# whatever might not. # whatever might not.
return set([compiled.create(True), compiled.create(False)]) return ContextSet(compiled.create(True), compiled.create(False))
mro = mro_func() mro = mro_func()
@@ -244,7 +246,7 @@ def builtins_isinstance(evaluator, objects, types, arguments):
'not %s.' % cls_or_tup 'not %s.' % cls_or_tup
analysis.add(lazy_context._context, 'type-error-isinstance', node, message) analysis.add(lazy_context._context, 'type-error-isinstance', node, message)
return set(compiled.create(evaluator, x) for x in bool_results) return ContextSet.from_iterable(compiled.create(evaluator, x) for x in bool_results)
def collections_namedtuple(evaluator, obj, arguments): def collections_namedtuple(evaluator, obj, arguments):
@@ -259,7 +261,7 @@ def collections_namedtuple(evaluator, obj, arguments):
""" """
# Namedtuples are not supported on Python 2.6 # Namedtuples are not supported on Python 2.6
if not hasattr(collections, '_class_template'): if not hasattr(collections, '_class_template'):
return set() return NO_CONTEXTS
# Process arguments # Process arguments
# TODO here we only use one of the types, we should use all. # TODO here we only use one of the types, we should use all.
@@ -274,7 +276,7 @@ def collections_namedtuple(evaluator, obj, arguments):
for v in lazy_context.infer() if hasattr(v, 'obj') for v in lazy_context.infer() if hasattr(v, 'obj')
] ]
else: else:
return set() return NO_CONTEXTS
base = collections._class_template base = collections._class_template
base += _NAMEDTUPLE_INIT base += _NAMEDTUPLE_INIT
@@ -293,7 +295,7 @@ def collections_namedtuple(evaluator, obj, arguments):
module = evaluator.grammar.parse(source) module = evaluator.grammar.parse(source)
generated_class = next(module.iter_classdefs()) generated_class = next(module.iter_classdefs())
parent_context = er.ModuleContext(evaluator, module, '') parent_context = er.ModuleContext(evaluator, module, '')
return set([er.ClassContext(evaluator, generated_class, parent_context)]) return ContextSet(er.ClassContext(evaluator, generated_class, parent_context))
@argument_clinic('first, /') @argument_clinic('first, /')
@@ -314,8 +316,8 @@ _implemented = {
'deepcopy': _return_first_param, 'deepcopy': _return_first_param,
}, },
'json': { 'json': {
'load': lambda *args: set(), 'load': lambda *args: NO_CONTEXTS,
'loads': lambda *args: set(), 'loads': lambda *args: NO_CONTEXTS,
}, },
'collections': { 'collections': {
'namedtuple': collections_namedtuple, 'namedtuple': collections_namedtuple,