SD / Lib /site-packages /boltons /namedutils.py
DrFetWartz's picture
Upload folder using huggingface_hub
ffaa9fc
# -*- coding: utf-8 -*-
# Copyright (c) 2013, Mahmoud Hashemi
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions are
# met:
#
# * Redistributions of source code must retain the above copyright
# notice, this list of conditions and the following disclaimer.
#
# * Redistributions in binary form must reproduce the above
# copyright notice, this list of conditions and the following
# disclaimer in the documentation and/or other materials provided
# with the distribution.
#
# * The names of the contributors may not be used to endorse or
# promote products derived from this software without specific
# prior written permission.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
"""\
The ``namedutils`` module defines two lightweight container types:
:class:`namedtuple` and :class:`namedlist`. Both are subtypes of built-in
sequence types, which are very fast and efficient. They simply add
named attribute accessors for specific indexes within themselves.
The :class:`namedtuple` is identical to the built-in
:class:`collections.namedtuple`, with a couple of enhancements,
including a ``__repr__`` more suitable to inheritance.
The :class:`namedlist` is the mutable counterpart to the
:class:`namedtuple`, and is much faster and lighter-weight than
full-blown :class:`object`. Consider this if you're implementing nodes
in a tree, graph, or other mutable data structure. If you want an even
skinnier approach, you'll probably have to look to C.
"""
from __future__ import print_function
import sys as _sys
try:
from collections import OrderedDict
except ImportError:
# backwards compatibility (2.6 has no OrderedDict)
OrderedDict = dict
from keyword import iskeyword as _iskeyword
from operator import itemgetter as _itemgetter
try:
basestring
def exec_(code, global_env):
exec("exec code in global_env")
except NameError:
basestring = (str, bytes) # Python 3 compat
def exec_(code, global_env):
exec(code, global_env)
__all__ = ['namedlist', 'namedtuple']
# Tiny templates
_repr_tmpl = '{name}=%r'
_imm_field_tmpl = '''\
{name} = _property(_itemgetter({index:d}), doc='Alias for field {index:d}')
'''
_m_field_tmpl = '''\
{name} = _property(_itemgetter({index:d}), _itemsetter({index:d}), doc='Alias for field {index:d}')
'''
#################################################################
### namedtuple
#################################################################
_namedtuple_tmpl = '''\
class {typename}(tuple):
'{typename}({arg_list})'
__slots__ = ()
_fields = {field_names!r}
def __new__(_cls, {arg_list}): # TODO: tweak sig to make more extensible
'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 __repr__(self):
'Return a nicely formatted representation string'
tmpl = self.__class__.__name__ + '({repr_fmt})'
return tmpl % self
def _asdict(self):
'Return a new OrderedDict which maps field names to their values'
return OrderedDict(zip(self._fields, self))
def _replace(_self, **kwds):
'Return a new {typename} object replacing field(s) with new values'
result = _self._make(map(kwds.pop, {field_names!r}, _self))
if kwds:
raise ValueError('Got unexpected field names: %r' % kwds.keys())
return result
def __getnewargs__(self):
'Return self as a plain tuple. Used by copy and pickle.'
return tuple(self)
__dict__ = _property(_asdict)
def __getstate__(self):
'Exclude the OrderedDict from pickling' # wat
pass
{field_defs}
'''
def namedtuple(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 pos 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 accessible 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, basestring):
field_names = field_names.replace(',', ' ').split()
field_names = [str(x) for x in field_names]
if rename:
seen = set()
for index, name in enumerate(field_names):
if (not all(c.isalnum() or c == '_' for c in name)
or _iskeyword(name)
or not name
or name[0].isdigit()
or name.startswith('_')
or name in seen):
field_names[index] = '_%d' % index
seen.add(name)
for name in [typename] + field_names:
if not all(c.isalnum() or c == '_' for c in name):
raise ValueError('Type names and field names can only contain '
'alphanumeric characters and underscores: %r'
% name)
if _iskeyword(name):
raise ValueError('Type names and field names cannot be a '
'keyword: %r' % name)
if name[0].isdigit():
raise ValueError('Type names and field names cannot start with '
'a number: %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)
# Fill-in the class template
fmt_kw = {'typename': typename}
fmt_kw['field_names'] = tuple(field_names)
fmt_kw['num_fields'] = len(field_names)
fmt_kw['arg_list'] = repr(tuple(field_names)).replace("'", "")[1:-1]
fmt_kw['repr_fmt'] = ', '.join(_repr_tmpl.format(name=name)
for name in field_names)
fmt_kw['field_defs'] = '\n'.join(_imm_field_tmpl.format(index=index, name=name)
for index, name in enumerate(field_names))
class_definition = _namedtuple_tmpl.format(**fmt_kw)
if verbose:
print(class_definition)
# Execute the template string in a temporary namespace and support
# tracing utilities by setting a value for frame.f_globals['__name__']
namespace = dict(_itemgetter=_itemgetter,
__name__='namedtuple_%s' % typename,
OrderedDict=OrderedDict,
_property=property,
_tuple=tuple)
try:
exec_(class_definition, namespace)
except SyntaxError as e:
raise SyntaxError(e.message + ':\n' + class_definition)
result = namespace[typename]
# For pickling to work, the __module__ variable needs to be set to the frame
# where the named tuple is created. Bypass this step in environments where
# sys._getframe is not defined (Jython for example) or sys._getframe is not
# defined for arguments greater than 0 (IronPython).
try:
frame = _sys._getframe(1)
result.__module__ = frame.f_globals.get('__name__', '__main__')
except (AttributeError, ValueError):
pass
return result
#################################################################
### namedlist
#################################################################
_namedlist_tmpl = '''\
class {typename}(list):
'{typename}({arg_list})'
__slots__ = ()
_fields = {field_names!r}
def __new__(_cls, {arg_list}): # TODO: tweak sig to make more extensible
'Create new instance of {typename}({arg_list})'
return _list.__new__(_cls, ({arg_list}))
def __init__(self, {arg_list}): # tuple didn't need this but list does
return _list.__init__(self, ({arg_list}))
@classmethod
def _make(cls, iterable, new=_list, len=len):
'Make a new {typename} object from a sequence or iterable'
# why did this function exist? why not just star the
# iterable like below?
result = cls(*iterable)
if len(result) != {num_fields:d}:
raise TypeError('Expected {num_fields:d} arguments,'
' got %d' % len(result))
return result
def __repr__(self):
'Return a nicely formatted representation string'
tmpl = self.__class__.__name__ + '({repr_fmt})'
return tmpl % tuple(self)
def _asdict(self):
'Return a new OrderedDict which maps field names to their values'
return OrderedDict(zip(self._fields, self))
def _replace(_self, **kwds):
'Return a new {typename} object replacing field(s) with new values'
result = _self._make(map(kwds.pop, {field_names!r}, _self))
if kwds:
raise ValueError('Got unexpected field names: %r' % kwds.keys())
return result
def __getnewargs__(self):
'Return self as a plain list. Used by copy and pickle.'
return tuple(self)
__dict__ = _property(_asdict)
def __getstate__(self):
'Exclude the OrderedDict from pickling' # wat
pass
{field_defs}
'''
def namedlist(typename, field_names, verbose=False, rename=False):
"""Returns a new subclass of list with named fields.
>>> Point = namedlist('Point', ['x', 'y'])
>>> Point.__doc__ # docstring for the new class
'Point(x, y)'
>>> p = Point(11, y=22) # instantiate with pos args or keywords
>>> p[0] + p[1] # indexable like a plain list
33
>>> x, y = p # unpack like a regular list
>>> x, y
(11, 22)
>>> p.x + p.y # fields also accessible 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, basestring):
field_names = field_names.replace(',', ' ').split()
field_names = [str(x) for x in field_names]
if rename:
seen = set()
for index, name in enumerate(field_names):
if (not all(c.isalnum() or c == '_' for c in name)
or _iskeyword(name)
or not name
or name[0].isdigit()
or name.startswith('_')
or name in seen):
field_names[index] = '_%d' % index
seen.add(name)
for name in [typename] + field_names:
if not all(c.isalnum() or c == '_' for c in name):
raise ValueError('Type names and field names can only contain '
'alphanumeric characters and underscores: %r'
% name)
if _iskeyword(name):
raise ValueError('Type names and field names cannot be a '
'keyword: %r' % name)
if name[0].isdigit():
raise ValueError('Type names and field names cannot start with '
'a number: %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)
# Fill-in the class template
fmt_kw = {'typename': typename}
fmt_kw['field_names'] = tuple(field_names)
fmt_kw['num_fields'] = len(field_names)
fmt_kw['arg_list'] = repr(tuple(field_names)).replace("'", "")[1:-1]
fmt_kw['repr_fmt'] = ', '.join(_repr_tmpl.format(name=name)
for name in field_names)
fmt_kw['field_defs'] = '\n'.join(_m_field_tmpl.format(index=index, name=name)
for index, name in enumerate(field_names))
class_definition = _namedlist_tmpl.format(**fmt_kw)
if verbose:
print(class_definition)
def _itemsetter(key):
def _itemsetter(obj, value):
obj[key] = value
return _itemsetter
# Execute the template string in a temporary namespace and support
# tracing utilities by setting a value for frame.f_globals['__name__']
namespace = dict(_itemgetter=_itemgetter,
_itemsetter=_itemsetter,
__name__='namedlist_%s' % typename,
OrderedDict=OrderedDict,
_property=property,
_list=list)
try:
exec_(class_definition, namespace)
except SyntaxError as e:
raise SyntaxError(e.message + ':\n' + class_definition)
result = namespace[typename]
# For pickling to work, the __module__ variable needs to be set to
# the frame where the named list is created. Bypass this step in
# environments where sys._getframe is not defined (Jython for
# example) or sys._getframe is not defined for arguments greater
# than 0 (IronPython).
try:
frame = _sys._getframe(1)
result.__module__ = frame.f_globals.get('__name__', '__main__')
except (AttributeError, ValueError):
pass
return result