Created
October 7, 2018 01:10
-
-
Save InzamamRahaman/a02f83e08f52384005829a7160a3b5a0 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# Adapted from https://hg.python.org/cpython/file/b14308524cff/Lib/collections/__init__.py#l232 | |
from keyword import iskeyword as _iskeyword | |
import sys as _sys | |
_class_template = """\ | |
from builtins import property as _property, tuple as _tuple | |
from operator import itemgetter as _itemgetter | |
from collections import OrderedDict | |
class {typename}(tuple): | |
'{typename}({arg_list})' | |
__slots__ = () | |
_fields = {field_names!r} | |
def __new__(_cls, {arg_list}): | |
'Create new instance of {typename}({arg_list})' | |
return _tuple.__new__(_cls, ({arg_list})) | |
@classmethod | |
def _make(cls, iterable, new=tuple.__new__, len=len): | |
'Make a new {typename} object from a sequence or iterable' | |
result = new(cls, iterable) | |
if len(result) != {num_fields:d}: | |
raise TypeError('Expected {num_fields:d} arguments, got %d' % len(result)) | |
return result | |
def _replace(_self, **kwds): | |
'Return a new {typename} object replacing specified fields with new values' | |
result = _self._make(map(kwds.pop, {field_names!r}, _self)) | |
if kwds: | |
raise ValueError('Got unexpected field names: %r' % list(kwds)) | |
return result | |
def __repr__(self): | |
'Return a nicely formatted representation string' | |
return self.__class__.__name__ + '({repr_fmt})' % self | |
@property | |
def __dict__(self): | |
'A new OrderedDict mapping field names to their values' | |
return OrderedDict(zip(self._fields, self)) | |
def _asdict(self): | |
'''Return a new OrderedDict which maps field names to their values. | |
This method is obsolete. Use vars(nt) or nt.__dict__ instead. | |
''' | |
return self.__dict__ | |
def __getnewargs__(self): | |
'Return self as a plain tuple. Used by copy and pickle.' | |
return tuple(self) | |
def __getstate__(self): | |
'Exclude the OrderedDict from pickling' | |
return None | |
{field_defs} | |
{operations} | |
""" | |
_operations = [('__add__', '+'), ('__sub__','-'), | |
('__mul__', '*'), ('__div__', '/')] | |
_repr_template = '{name}=%r' | |
_field_template = '''\ | |
{name} = _property(_itemgetter({index:d}), doc='Alias for field number {index:d}') | |
''' | |
_operation_template = '''\ | |
def {op_name}(self, other): | |
arr = [] | |
for i in range({n}): | |
arr.append(self[i]{op}other[i]) | |
return {typename}(*arr) | |
''' | |
def namedtuple2(typename, field_names, verbose=False, rename=False): | |
"""Returns a new subclass of tuple with named fields. | |
>>> Point = namedtuple('Point', ['x', 'y']) | |
>>> Point.__doc__ # docstring for the new class | |
'Point(x, y)' | |
>>> p = Point(11, y=22) # instantiate with positional args or keywords | |
>>> p[0] + p[1] # indexable like a plain tuple | |
33 | |
>>> x, y = p # unpack like a regular tuple | |
>>> x, y | |
(11, 22) | |
>>> p.x + p.y # fields also accessable by name | |
33 | |
>>> d = p._asdict() # convert to a dictionary | |
>>> d['x'] | |
11 | |
>>> Point(**d) # convert from a dictionary | |
Point(x=11, y=22) | |
>>> p._replace(x=100) # _replace() is like str.replace() but targets named fields | |
Point(x=100, y=22) | |
""" | |
# Validate the field names. At the user's option, either generate an error | |
# message or automatically replace the field name with a valid name. | |
if isinstance(field_names, str): | |
field_names = field_names.replace(',', ' ').split() | |
field_names = list(map(str, field_names)) | |
if rename: | |
seen = set() | |
for index, name in enumerate(field_names): | |
if (not name.isidentifier() | |
or _iskeyword(name) | |
or name.startswith('_') | |
or name in seen): | |
field_names[index] = '_%d' % index | |
seen.add(name) | |
for name in [typename] + field_names: | |
if not name.isidentifier(): | |
raise ValueError('Type names and field names must be valid ' | |
'identifiers: %r' % name) | |
if _iskeyword(name): | |
raise ValueError('Type names and field names cannot be a ' | |
'keyword: %r' % name) | |
seen = set() | |
for name in field_names: | |
if name.startswith('_') and not rename: | |
raise ValueError('Field names cannot start with an underscore: ' | |
'%r' % name) | |
if name in seen: | |
raise ValueError('Encountered duplicate field name: %r' % name) | |
seen.add(name) | |
operations = [] | |
n = len(field_names) | |
for op_name, op in _operations: | |
s = _operation_template.format(n=n, | |
op_name=op_name,op=op,typename=typename) | |
operations.append(s) | |
operations = '\n'.join(operations) | |
#print(operations) | |
# Fill-in the class template | |
class_definition = _class_template.format( | |
typename = typename, | |
field_names = tuple(field_names), | |
num_fields = len(field_names), | |
arg_list = repr(tuple(field_names)).replace("'", "")[1:-1], | |
repr_fmt = ', '.join(_repr_template.format(name=name) | |
for name in field_names), | |
field_defs = '\n'.join(_field_template.format(index=index, name=name) | |
for index, name in enumerate(field_names)), | |
operations=operations | |
) | |
# Execute the template string in a temporary namespace and support | |
# tracing utilities by setting a value for frame.f_globals['__name__'] | |
namespace = dict(__name__='namedtuple_%s' % typename) | |
exec(class_definition, namespace) | |
result = namespace[typename] | |
result._source = class_definition | |
if verbose: | |
print(result._source) | |
# For pickling to work, the __module__ variable needs to be set to the frame | |
# where the named tuple is created. Bypass this step in enviroments where | |
# sys._getframe is not defined (Jython for example) or sys._getframe is not | |
# defined for arguments greater than 0 (IronPython). | |
try: | |
result.__module__ = _sys._getframe(1).f_globals.get('__name__', '__main__') | |
except (AttributeError, ValueError): | |
pass | |
return result | |
Point = namedtuple2('Point', ['x', 'y']) | |
print(Point(2, 3) + Point(4, 5)) |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment