Skip to content

Instantly share code, notes, and snippets.

@gvx
Created December 11, 2014 23:28
Show Gist options
  • Star 2 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save gvx/308d9bd332b5fd30e42c to your computer and use it in GitHub Desktop.
Save gvx/308d9bd332b5fd30e42c to your computer and use it in GitHub Desktop.
An alternative namedtuple
from collections import OrderedDict
from inspect import Parameter, signature
from itertools import chain, starmap
from operator import itemgetter
__all__ = ['namedtuple']
dict_property = property(lambda self: OrderedDict(zip(self._fields, self)),
doc='dictionary for instance variables (if defined)')
def assign(sig, self):
bound = sig.bind(*self)
for param in sig.parameters.values():
if param.name not in bound.arguments \
and param.default is not param.empty:
bound.arguments[param.name] = param.default
return ', '.join(starmap('{}={!r}'.format, bound.arguments.items()))
def namedtuple(init):
sig = signature(init)
name = init.__name__
for param in sig.parameters.values():
if param.kind != param.POSITIONAL_OR_KEYWORD:
raise ValueError('Named tuple can only have regular arguments'
'that can be either positional or keyword')
def __new__(_cls, *args, **kwargs):
init(*args, **kwargs)
return tuple.__new__(_cls, sig.bind(*args, **kwargs).args)
new_params = chain([Parameter('_cls', Parameter.POSITIONAL_OR_KEYWORD)],
sig.parameters.values())
__new__.__signature__ = sig.replace(parameters=new_params)
def _make(cls, iterable):
'Make a new {name} object from a sequence or iterable'
result = tuple.__new__(cls, iterable)
if len(result) != len(sig.parameters):
raise TypeError('Expected {expected} arguments,'
'got {actual}'.format(expected=len(sig.parameters),
actual=len(result)))
return result
_make.__doc__ = _make.__doc__.format(name=name)
def _replace(_self, **kwds):
'Return a new {name} object replacing specified fields with new values'
result = _self._make(map(kwds.pop, sig.parameters.keys(), _self))
if kwds:
raise ValueError('Got unexpected'
'field names: {}'.format(', '.join(kwds)))
return result
_replace.__doc__ = _replace.__doc__.format(name=name)
def repr(self):
'x.__repr__() <==> repr(x)'
return '{name}({args})'.format(name=name, args=assign(sig, self))
@classmethod
def _method(cls, func):
'Assign a new method for the namedtuple class'
setattr(cls, func.__name__, func)
return func
@classmethod
def _property(cls, func):
'Assign a new property for the namedtuple class'
setattr(cls, func.__name__, property(func))
return func
dct = dict(_make=classmethod(_make),
_fields=list(sig.parameters.keys()),
_replace=_replace,
_method=_method,
_property=_property,
__slots__=(),
__doc__=init.__doc__ or name + str(sig),
__new__=__new__,
__repr__=repr,
__getnewargs__=tuple,
__getstate__=lambda self: None,
__dict__=dict_property)
for i, pname in enumerate(sig.parameters.keys()):
dct[pname] = property(itemgetter(i),
doc='Alias for field number {index:d}'
.format(index=i))
return type(name, (tuple,), dct)
from named import namedtuple
@namedtuple
def Point(x, y):
'A fancy, fancy, 2d point'
@Point._property
def hypot(self):
'p.hypot -> √(p.x² + p.y²)'
return (self.x ** 2 + self.y ** 2) ** 0.5
@Point._method
def __str__(self):
return 'Point: x=%6.3f y=%6.3f hypot=%6.3f' % (self.x,
self.y,
self.hypot)
@namedtuple
def EmployeeRecord(name, age, title, department, paygrade=1):
assert isinstance(paygrade, int) and paygrade >= 0, \
'paygrade should be an integer >= 0, but is: {!r}'.format(paygrade)
print(Point(3, y=4))
print(EmployeeRecord('Jim', 10, 'Something', 'sales'))
try:
print(EmployeeRecord(1, 2, 3, 4, -5))
except AssertionError as e:
print(e)
# also try these:
# help(Point.x)
# help(Point)
# help(EmployeeRecord)
print(vars(Point(3, 4)))
print(vars(EmployeeRecord(age=10, title='Something', department='sales',
name='Jim', paygrade=3)))
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment