def recordclass(typename, field_names, rename=False, defaults=None, module=None, verbose=False, source=True): """Returns a new subclass of array with named fields. >>> Point = recordarray('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() else: field_names = list(map(str, field_names)) typename = _intern(str(typename)) if rename: seen = set() for index, name in enumerate(field_names): if (not isidentifier(name) 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 type(name) != str: raise TypeError('Type names and field names must be strings') if not isidentifier(name): 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) if defaults is not None: defaults = tuple(defaults) if len(defaults) > len(field_names): raise TypeError('Got more default values than field names') field_defaults = dict( reversed(list(zip(reversed(field_names), reversed(defaults))))) else: field_defaults = {} field_names = tuple(map(_intern, field_names)) num_fields = len(field_names) arg_list = repr(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)) # Fill-in the class template class_definition = _class_template.format(typename=typename, field_names=field_names, field_defaults=field_defaults, num_fields=num_fields, arg_list=arg_list, repr_fmt=repr_fmt, field_defs=field_defs) # Execute the template string in a temporary namespace and support # tracing utilities by setting a value for frame.f_globals['__name__'] namespace = dict(_memoryslots_new=memoryslots.__new__, __name__='recordclass_' + typename) code = compile(class_definition, "", "exec") eval(code, namespace) result = namespace[typename] if defaults is not None: result.__new__.__defaults__ = defaults if source: 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 environments where # sys._getframe is not defined (Jython for example) or sys._getframe is not # defined for arguments greater than 0 (IronPython). if module is None: try: module = _sys._getframe(1).f_globals.get('__name__', '__main__') except (AttributeError, ValueError): pass if module is not None: result.__module__ = module return result
def new_datatype(typename, fields=0, bases=None, namespace=None, varsize=False, use_dict=False, use_weakref=False, hashable=True, sequence=False, readonly=False, gc=False, defaults=None, module=None, argsonly=False): annotations = {} fields_is_int = False if isinstance(fields, str): fields = fields.replace(',', ' ').split() elif isinstance(fields, int_type): fields_is_int = True else: msg = "new_datatype('Name', [(f0, t0), (f1, t1), ...]); each t must be a type" field_names = [] if isinstance(fields, dict): for fn in fields: t = fields[fn] t = _type_check(t, msg) annotations[fn] = t field_names.append(fn) else: for fn in fields: if type(fn) is tuple: n, t = fn t = _type_check(t, msg) annotations[n] = t field_names.append(n) else: field_names.append(fn) fields = field_names typename = _intern(str(typename)) if not fields_is_int and defaults is not None: n_fields = len(fields) defaults = tuple(defaults) n_defaults = len(defaults) if n_defaults > n_fields: raise TypeError('Got more default values than fields') else: defaults = None options = { 'dict':use_dict, 'weakref':use_weakref, 'hash':hashable, 'varsize':varsize, 'sequence':sequence, 'readonly':readonly, 'defaults':defaults, 'argsonly':argsonly, } if namespace is None: ns = {} else: ns = namespace if defaults: for i in range(-n_defaults, 0): fname = fields[i] val = defaults[i] ns[fname] = val ns['__options__'] = options ns['__fields__'] = fields if annotations: ns['__annotations__'] = annotations cls = datatype(typename, bases, ns) if gc: cls = enable_gc(cls) if module is None: try: module = _sys._getframe(1).f_globals.get('__name__', '__main__') except (AttributeError, ValueError): module = None if module is not None: cls.__module__ = module return cls
def recordclass(typename, fields, rename=False, defaults=None, readonly=False, hashable=False, gc=False, module=None): """Returns a new subclass of array with named fields. >>> Point = recordclass('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 >>> d.x = 33 # assign new value >>> 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) """ if readonly: baseclass = immutabletuple else: baseclass = mutabletuple # 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(fields, str): field_names = fields.replace(',', ' ').split() annotations = None else: msg = "recordclass('Name', [(f0, t0), (f1, t1), ...]); each t must be a type" annotations = {} field_names = [] for fn in fields: if type(fn) is tuple: n, t = fn n = str(n) if _type_check: t = _type_check(t, msg) annotations[n] = t field_names.append(n) else: field_names.append(str(fn)) typename = _intern(str(typename)) if rename: seen = set() for index, name in enumerate(field_names): if (not _isidentifier(name) 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 type(name) != str: raise TypeError('Type names and field names must be strings') if not _isidentifier(name): 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) if defaults is not None: defaults = tuple(defaults) if len(defaults) > len(field_names): raise TypeError('Got more default values than field names') field_defaults = dict( reversed(list(zip(reversed(field_names), reversed(defaults))))) else: field_defaults = {} field_names = tuple(map(_intern, field_names)) n_fields = len(field_names) arg_list = ', '.join(field_names) repr_fmt = ', '.join( _repr_template.format(name=name) for name in field_names) if readonly: new_func_template = """\ def __new__(_cls, {1}): 'Create new instance of {0}({1})' return _method_new(_cls, ({1})) """ _method_new = immutabletuple.__new__ else: new_func_template = """\ def __new__(_cls, {1}): 'Create new instance: {0}({1})' return _method_new(_cls, {1}) """ _method_new = mutabletuple.__new__ new_func_def = new_func_template.format(typename, arg_list) # Execute the template string in a temporary namespace and support # tracing utilities by setting a value for frame.f_globals['__name__'] namespace = dict(_method_new=_method_new) code = compile(new_func_def, "", "exec") eval(code, namespace) __new__ = namespace['__new__'] if defaults is not None: __new__.__defaults__ = defaults if annotations: __new__.__annotations__ = annotations def _make(_cls, iterable): ob = _method_new(_cls, *iterable) if len(ob) != n_fields: raise TypeError('Expected %s arguments, got %s' % (n_fields, len(ob))) return ob _make.__doc__ = 'Make a new %s object from a sequence or iterable' % typename if readonly: def _replace(_self, **kwds): result = _self._make((kwds.pop(name) for name in field_names)) if kwds: raise ValueError('Got unexpected field names: %r' % list(kwds)) return result else: def _replace(_self, **kwds): for name, val in kwds.items(): setattr(_self, name, val) return _self _replace.__doc__ = 'Return a new %s object replacing specified fields with new values' % typename def __repr__(self): 'Return a nicely formatted representation string' return self.__class__.__name__ + "(" + (repr_fmt % tuple(self)) + ")" def _asdict(self): 'Return a new OrderedDict which maps field names to their values.' return OrderedDict(zip(self.__fields__, self)) 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 def __reduce__(self): 'Reduce' return type(self), tuple(self) if not readonly and hashable: def __hash__(self): return hash(tuple(self)) __hash__.__qualname__ = typename + "." + "__hash__" for method in (__new__, _make, _replace, __repr__, _asdict, __getnewargs__, __reduce__, __getstate__): method.__qualname__ = typename + "." + method.__name__ _make = classmethod(_make) if readonly: _cache = _itemgeters else: _cache = _itemgetseters class_namespace = {} for index, name in enumerate(field_names): try: item_object = _cache[index] except KeyError: if readonly: item_object = mutabletuple_itemget(index) else: item_object = mutabletuple_itemgetset(index) #doc = 'Alias for field number ' + str(index) _cache[index] = item_object class_namespace[name] = item_object __options__ = {'hashable': hashable, 'gc': gc} if readonly: __options__['hashable'] = True if module is None: try: module = _sys._getframe(1).f_globals.get('__name__', '__main__') except (AttributeError, ValueError): pass class_namespace.update({ '__slots__': (), '__doc__': typename + '(' + arg_list + ')', '__fields__': field_names, '__new__': __new__, '_make': _make, '_replace': _replace, '__repr__': __repr__, '_asdict': _asdict, '__getnewargs__': __getnewargs__, '__getstate__': __getstate__, '__reduce__': __reduce__, '__dict__': property(_asdict), '__options__': __options__, '__module__': module, }) _result = recordclasstype(typename, (baseclass, ), class_namespace) if annotations: _result.__annotations__ = annotations return _result
def recordclass(typename, fields, rename=False, defaults=None, readonly=False, hashable=False, gc=False, module=None): """Returns a new subclass of array with named fields. >>> Point = recordclass('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 >>> d.x = 33 # assign new value >>> 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) """ if readonly: baseclass = memoryslotsreadonly else: baseclass = memoryslots # 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(fields, str): field_names = fields.replace(',', ' ').split() annotations = None else: msg = "recordclass('Name', [(f0, t0), (f1, t1), ...]); each t must be a type" annotations = {} field_names = [] for fn in fields: if type(fn) is tuple: n, t = fn n = str(n) if _type_check: t = _type_check(t, msg) annotations[n] = t field_names.append(n) else: field_names.append(str(fn)) typename = _intern(str(typename)) if rename: seen = set() for index, name in enumerate(field_names): if (not _isidentifier(name) 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 type(name) != str: raise TypeError('Type names and field names must be strings') if not _isidentifier(name): 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) if defaults is not None: defaults = tuple(defaults) if len(defaults) > len(field_names): raise TypeError('Got more default values than field names') field_defaults = dict(reversed(list(zip(reversed(field_names), reversed(defaults))))) else: field_defaults = {} field_names = tuple(map(_intern, field_names)) n_fields = len(field_names) arg_list = ', '.join(field_names) repr_fmt=', '.join(_repr_template.format(name=name) for name in field_names) if readonly: new_func_template = """\ def __new__(_cls, {1}): 'Create new instance of {0}({1})' return _method_new(_cls, ({1})) """ _method_new = memoryslotsreadonly.__new__ else: new_func_template = """\ def __new__(_cls, {1}): 'Create new instance: {0}({1})' return _method_new(_cls, {1}) """ _method_new = memoryslots.__new__ new_func_def = new_func_template.format(typename, arg_list) # Execute the template string in a temporary namespace and support # tracing utilities by setting a value for frame.f_globals['__name__'] namespace = dict(_method_new=_method_new) code = compile(new_func_def, "", "exec") eval(code, namespace) __new__ = namespace['__new__'] if defaults is not None: __new__.__defaults__ = defaults if annotations: __new__.__annotations__ = annotations def _make(_cls, iterable): ob = _method_new(_cls, *iterable) if len(ob) != n_fields: raise TypeError('Expected %s arguments, got %s' % (n_fields, len(ob))) return ob _make.__doc__ = 'Make a new %s object from a sequence or iterable' % typename if readonly: def _replace(_self, **kwds): result = _self._make((kwds.pop(name) for name in field_names)) if kwds: raise ValueError('Got unexpected field names: %r' % list(kwds)) return result else: def _replace(_self, **kwds): for name, val in kwds.items(): setattr(_self, name, val) return _self _replace.__doc__ = 'Return a new %s object replacing specified fields with new values' % typename def __repr__(self): 'Return a nicely formatted representation string' return self.__class__.__name__ + "(" + (repr_fmt % tuple(self)) + ")" def _asdict(self): 'Return a new OrderedDict which maps field names to their values.' return OrderedDict(zip(self.__attrs__, self)) 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 def __reduce__(self): 'Reduce' return type(self), tuple(self) if not readonly and hashable: def __hash__(self): return hash(tuple(self)) __hash__.__qualname__ = typename + "." + "__hash__" for method in (__new__, _make, _replace, __repr__, _asdict, __getnewargs__, __reduce__, __getstate__): method.__qualname__ = typename + "." + method.__name__ _make = classmethod(_make) if readonly: cache = _itemgeters else: cache = _itemgetseters class_namespace = {} for index, name in enumerate(field_names): try: item_object = cache[index] except KeyError: if readonly: item_object = itemget(index) else: item_object = itemgetset(index) #doc = 'Alias for field number ' + str(index) cache[index] = item_object class_namespace[name] = item_object __options__ = {'hashable':hashable, 'gc':gc} if readonly: __options__['hashable'] = True class_namespace.update({ '__slots__': (), '__doc__': typename+'('+arg_list+')', '__attrs__': field_names, '__new__': __new__, '_make': _make, '_replace': _replace, '__repr__': __repr__, '_asdict': _asdict, '__getnewargs__': __getnewargs__, '__getstate__': __getstate__, '__reduce__': __reduce__, '__dict__': property(_asdict), '__options__': __options__, }) _result = recordclasstype(typename, (baseclass,), class_namespace) # For pickling to work, the __module__ variable needs to be set to the frame # where the class is created. if module is None: try: module = _sys._getframe(1).f_globals.get('__name__', '__main__') except (AttributeError, ValueError): pass if module is not None: _result.__module__ = module if annotations: _result.__annotations__ = annotations return _result
def structclass(typename, fields, rename=False, defaults=None, readonly=False, usedict=False, gc=False, weakref=False, hashable=False, assequence=True, module=None): """Returns a new subclass of array with named fields. >>> Point = structclass('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 >>> d.x = 33 # assign new value >>> Point(**d) # convert from a dictionary Point(x=33, y=22) >>> p._replace(x=100) # _replace() is like str.replace() but targets named fields Point(x=100, y=22) """ if isinstance(fields, str): field_names = fields.replace(',', ' ').split() annotations = None else: msg = "recordclass('Name', [(f0, t0), (f1, t1), ...]); each t must be a type" annotations = {} field_names = [] for fn in fields: if type(fn) is tuple: n, t = fn n = str(n) if _type_check: t = _type_check(t, msg) annotations[n] = t field_names.append(n) else: field_names.append(str(fn)) typename = _intern(str(typename)) if rename: seen = set() for index, name in enumerate(field_names): if (not _isidentifier(name) 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 type(name) != str: raise TypeError('Type names and field names must be strings') if not _isidentifier(name): 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) if defaults is not None: defaults = tuple(defaults) if len(defaults) > len(field_names): raise TypeError('Got more default values than field names') field_names = tuple(map(_intern, field_names)) n_fields = len(field_names) arg_list = ', '.join(field_names) repr_fmt=', '.join(_repr_template.format(name=name) for name in field_names) if usedict: new_func_template = """\ def __new__(_cls, {1}, **kw): 'Create new instance of {0}({1})' return _baseclass.__new__(_cls, {1}, **kw) """ else: new_func_template = """\ def __new__(_cls, {1}): 'Create new instance of {0}({1})' return _baseclass.__new__(_cls, {1}) """ new_func_def = new_func_template.format(typename, arg_list) #print(new_func_def) namespace = dict(_baseclass=recordobject) code = compile(new_func_def, "", "exec") eval(code, namespace) __new__ = namespace['__new__'] if defaults is not None: __new__.__defaults__ = defaults #@classmethod def _make(_cls, iterable): ob = _cls(*iterable) if len(ob) != n_fields: raise TypeError('Expected %s arguments, got %s' % (n_fields, len(ob))) return ob _make.__doc__ = 'Make a new %s object from a sequence or iterable' % typename def _replace(_self, **kwds): for name, val in kwds.items(): setattr(_self, name, val) return _self _replace.__doc__ = 'Return a new %s object replacing specified fields with new values' % typename def __repr__(self): 'Return a nicely formatted representation string' args_text = repr_fmt % tuple(self) try: kw = self.__dict__ except AttributeError: kw = None if kw: kw_text = repr(kw) return self.__class__.__name__ + "(" + args_text + ", **" + kw_text + ")" else: return self.__class__.__name__ + "(" + args_text + ")" def _asdict(self): 'Return a new OrderedDict which maps field names to their values.' return OrderedDict(zip(self.__attrs__, self)) for method in (__new__, _make, _replace, __repr__, _asdict,): method.__qualname__ = typename + "." + method.__name__ _make = classmethod(_make) __options__ = {'readonly':readonly, 'usedict':usedict, 'gc':gc, 'weakref':weakref, 'hashable':hashable, 'assequence':assequence} class_namespace = { '__doc__': typename+'('+arg_list+')', '__attrs__': field_names, '__new__': __new__, '_make': _make, '_replace': _replace, '__repr__': __repr__, '_asdict': _asdict, '__options__': __options__, } _result = structclasstype(typename, (recordobject,), class_namespace) # For pickling to work, the __module__ variable needs to be set to the frame # where the class is created. if module is None: try: module = _sys._getframe(1).f_globals.get('__name__', '__main__') except (AttributeError, ValueError): pass if module is not None: _result.__module__ = module if annotations: _result.__annotations__ = annotations return _result
def structclass(typename, fields=None, use_dict=False, use_weakref=False, hashable=True, sequence=True, mapping=False, readonly=False, defaults=None, module=None, gc=False): from ._dataobject import _clsconfig, _enable_gc from ._dataobject import dataobject from .datatype import datatype annotations = {} if isinstance(fields, str): field_names = fields.replace(',', ' ').split() field_names = [fn.strip() for fn in field_names] else: msg = "make_dataclass('Name', [(f0, t0), (f1, t1), ...]); each t must be a type" field_names = [] if isinstance(fields, dict): for fn, tp in fields.items(): tp = _type_check(tp, msg) check_name(fn) fn = _intern(fn) annotations[fn] = tp field_names.append(fn) else: for fn in fields: if type(fn) is tuple: fn, tp = fn tp = _type_check(tp, msg) annotations[fn] = tp check_name(fn) fn = _intern(fn) field_names.append(fn) n_fields = len(field_names) typename = check_name(typename) if defaults is not None: n_fields = len(field_names) defaults = tuple(defaults) n_defaults = len(defaults) if n_defaults > n_fields: raise TypeError('Got more default values than fields') else: defaults = None def _make(_cls, iterable): ob = _cls(*iterable) if len(ob) != n_fields: raise TypeError('Expected %s arguments, got %s' % (n_fields, len(ob))) return ob _make.__doc__ = 'Make a new %s object from a sequence or iterable' % typename def _replace(_self, **kwds): for name, val in kwds.items(): setattr(_self, name, val) return _self _replace.__doc__ = 'Return a new %s object replacing specified fields with new values' % typename def _asdict(self): 'Return a new OrderedDict which maps field names to their values.' return OrderedDict(zip(self.__fields__, self)) for method in ( _make, _replace, _asdict, ): method.__qualname__ = typename + "." + method.__name__ _make = classmethod(_make) options = { 'readonly': readonly, 'defaults': defaults, 'argsonly': False, 'sequence': sequence, 'mapping': mapping, 'iterable': sequence, 'use_dict': use_dict, 'use_weakref': use_weakref, 'readonly': readonly, 'hashable': hashable, 'gc': gc, } ns = { '_make': _make, '_replace': _replace, '_asdict': _asdict, '__doc__': typename + '(' + ', '.join(field_names) + ')', '__module__': module } if defaults: for i in range(-n_defaults, 0): fname = field_names[i] val = defaults[i] ns[fname] = val if use_dict and '__dict__' not in field_names: field_names.append('__dict__') if use_weakref and '__weakref__' not in field_names: field_names.append('__weakref__') ns['__options__'] = options ns['__fields__'] = field_names if annotations: ns['__annotations__'] = annotations bases = (dataobject, ) if module is None: try: module = _sys._getframe(1).f_globals.get('__name__', '__main__') except (AttributeError, ValueError): pass ns['__module__'] = module cls = datatype(typename, bases, ns) if gc: _enable_gc(cls) return cls
def structclass(typename, fields, rename=False, defaults=None, readonly=False, usedict=False, gc=False, weakref=False, hashable=False, assequence=True, module=None): """Returns a new subclass of array with named fields. >>> Point = structclass('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 >>> d.x = 33 # assign new value >>> Point(**d) # convert from a dictionary Point(x=33, y=22) >>> p._replace(x=100) # _replace() is like str.replace() but targets named fields Point(x=100, y=22) """ if isinstance(fields, str): field_names = fields.replace(',', ' ').split() annotations = None else: msg = "recordclass('Name', [(f0, t0), (f1, t1), ...]); each t must be a type" annotations = {} field_names = [] for fn in fields: if type(fn) is tuple: n, t = fn n = str(n) if _type_check: t = _type_check(t, msg) annotations[n] = t field_names.append(n) else: field_names.append(str(fn)) typename = _intern(str(typename)) if rename: seen = set() for index, name in enumerate(field_names): if (not _isidentifier(name) 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 type(name) != str: raise TypeError('Type names and field names must be strings') if not _isidentifier(name): 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) if defaults is not None: defaults = tuple(defaults) if len(defaults) > len(field_names): raise TypeError('Got more default values than field names') field_names = tuple(map(_intern, field_names)) n_fields = len(field_names) arg_list = ', '.join(field_names) repr_fmt = ', '.join( _repr_template.format(name=name) for name in field_names) if usedict: new_func_template = """\ def __new__(_cls, {1}, **kw): 'Create new instance of {0}({1})' return _baseclass.__new__(_cls, {1}, **kw) """ else: new_func_template = """\ def __new__(_cls, {1}): 'Create new instance of {0}({1})' return _baseclass.__new__(_cls, {1}) """ new_func_def = new_func_template.format(typename, arg_list) #print(new_func_def) namespace = dict(_baseclass=recordobject) code = compile(new_func_def, "", "exec") eval(code, namespace) __new__ = namespace['__new__'] if defaults is not None: __new__.__defaults__ = defaults #@classmethod def _make(_cls, iterable): ob = _cls(*iterable) if len(ob) != n_fields: raise TypeError('Expected %s arguments, got %s' % (n_fields, len(ob))) return ob _make.__doc__ = 'Make a new %s object from a sequence or iterable' % typename def _replace(_self, **kwds): for name, val in kwds.items(): setattr(_self, name, val) return _self _replace.__doc__ = 'Return a new %s object replacing specified fields with new values' % typename def __repr__(self): 'Return a nicely formatted representation string' args_text = repr_fmt % tuple(self) try: kw = self.__dict__ except AttributeError: kw = None if kw: kw_text = repr(kw) return self.__class__.__name__ + "(" + args_text + ", **" + kw_text + ")" else: return self.__class__.__name__ + "(" + args_text + ")" def _asdict(self): 'Return a new OrderedDict which maps field names to their values.' return OrderedDict(zip(self.__attrs__, self)) for method in ( __new__, _make, _replace, __repr__, _asdict, ): method.__qualname__ = typename + "." + method.__name__ _make = classmethod(_make) __options__ = { 'readonly': readonly, 'usedict': usedict, 'gc': gc, 'weakref': weakref, 'hashable': hashable, 'assequence': assequence } class_namespace = { '__doc__': typename + '(' + arg_list + ')', '__attrs__': field_names, '__new__': __new__, '_make': _make, '_replace': _replace, '__repr__': __repr__, '_asdict': _asdict, '__options__': __options__, } _result = structclasstype(typename, (recordobject, ), class_namespace) # For pickling to work, the __module__ variable needs to be set to the frame # where the class is created. if module is None: try: module = _sys._getframe(1).f_globals.get('__name__', '__main__') except (AttributeError, ValueError): pass if module is not None: _result.__module__ = module if annotations: _result.__annotations__ = annotations return _result
def make_dataclass(typename, fields=None, bases=None, namespace=None, varsize=False, use_dict=False, use_weakref=False, hashable=True, sequence=False, mapping=False, iterable=False, readonly=False, defaults=None, module=None, argsonly=False, gc=False): from ._dataobject import _clsconfig, _enable_gc from ._dataobject import dataobject, datatuple from .datatype import datatype annotations = {} if isinstance(fields, str): fields = fields.replace(',', ' ').split() fields = [fn.strip() for fn in fields] else: msg = "make_dataclass('Name', [(f0, t0), (f1, t1), ...]); each t must be a type" field_names = [] if isinstance(fields, dict): for fn, tp in fields.items(): tp = _type_check(tp, msg) check_name(fn) fn = _intern(fn) annotations[fn] = tp field_names.append(fn) else: for fn in fields: if type(fn) is tuple: fn, tp = fn tp = _type_check(tp, msg) annotations[fn] = tp check_name(fn) fn = _intern(fn) field_names.append(fn) fields = field_names typename = check_name(typename) if defaults is not None: n_fields = len(fields) defaults = tuple(defaults) n_defaults = len(defaults) if n_defaults > n_fields: raise TypeError('Got more default values than fields') else: defaults = None options = { 'readonly': readonly, 'defaults': defaults, 'argsonly': argsonly, 'sequence': sequence, 'mapping': mapping, 'iterable': iterable, 'use_dict': use_dict, 'use_weakref': use_weakref, 'readonly': readonly, 'hashable': hashable, 'gc': gc, } if namespace is None: ns = {} else: ns = namespace.copy() if defaults: for i in range(-n_defaults, 0): fname = fields[i] val = defaults[i] ns[fname] = val if use_dict and '__dict__' not in fields: fields.append('__dict__') if use_weakref and '__weakref__' not in fields: fields.append('__weakref__') ns['__options__'] = options ns['__fields__'] = fields if annotations: ns['__annotations__'] = annotations if bases: base0 = bases[0] if varsize: if not isinstance(base0, datatuple): raise TypeError( "First base class should be subclass of datatuple") else: if not isinstance(base0, dataobject): raise TypeError( "First base class should be subclass of dataobject") else: if varsize: bases = (datatuple, ) else: bases = (dataobject, ) if varsize: _sequence = True else: _sequence = sequence if module is None: try: module = _sys._getframe(1).f_globals.get('__name__', '__main__') except (AttributeError, ValueError): pass ns['__module__'] = module cls = datatype(typename, bases, ns) if gc: _enable_gc(cls) return cls
def __new__(metatype, typename, bases, ns): from ._dataobject import _clsconfig, _dataobject_type_init, dataslotgetset options = ns.pop('__options__', {}) readonly = options.get('readonly', False) hashable = options.get('hashable', False) sequence = options.get('sequence', False) mapping = options.get('mapping', False) iterable = options.get('iterable', False) argsonly = options.get('argsonly', False) use_dict = options.get('use_dict', False) use_weakref = options.get('use_weakref', False) use_dict = False use_weakref = False if not bases: raise TypeError("The base class in not specified") if bases[0].__itemsize__: varsize = True else: varsize = False annotations = ns.get('__annotations__', {}) if '__fields__' in ns: fields = ns['__fields__'] else: fields = [name for name in annotations] has_fields = True if isinstance(fields, int_type): has_fields = False n_fields = fields sequence = True iterable = True fields = () else: fields = [_intern(check_name(fn)) for fn in fields] if varsize: sequence = True iterable = True if sequence or mapping: iterable = True if has_fields: if annotations: annotations = {fn:annotations[fn] for fn in fields if fn in annotations} if '__dict__' in fields: fields.remove('__dict__') if '__dict__' in annotations: del annotations['__dict__'] use_dict = True if '__weakref__' in fields: fields.remove('__weakref__') if '__weakref__' in annotations: del annotations['__weakref__'] use_weakref = True _fields, _defaults, _annotations = collect_info_from_bases(bases) defaults = {f:ns[f] for f in fields if f in ns} _matching_annotations_and_defaults(annotations, defaults) if fields: fields = [f for f in fields if f not in _fields] fields = _fields + fields fields = tuple(fields) n_fields = len(fields) _defaults.update(defaults) defaults = _defaults _annotations.update(annotations) annotations = _annotations if fields and (not argsonly or defaults) and '__new__' not in ns: __new__ = _make_new_function(typename, fields, defaults, annotations, varsize, use_dict) __new__.__qualname__ = typename + '.' + '__new__' ns['__new__'] = __new__ if has_fields: if readonly: if type(readonly) is type(True): readonly_fields = set(fields) else: readonly_fields = set(readonly) else: readonly_fields = set() for i, name in enumerate(fields): offset = dataslot_offset(i, n_fields, varsize) if name in readonly_fields: ns[name] = dataslotgetset(offset, True) else: ns[name] = dataslotgetset(offset) module = ns.get('__module__', None) if module is None: try: module = _sys._getframe(2).f_globals.get('__name__', '__main__') ns['__module'] = module except (AttributeError, ValueError): pass else: pass cls = type.__new__(metatype, typename, bases, ns) if has_fields: cls.__fields__ = fields if defaults: cls.__defaults__ = defaults if annotations: cls.__annotations__ = annotations _dataobject_type_init(cls) _clsconfig(cls, sequence=sequence, mapping=mapping, readonly=readonly, use_dict=use_dict, use_weakref=use_weakref, iterable=iterable, hashable=hashable) return cls