You cannot select more than 25 topics
			Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
		
		
		
		
		
			
		
			
				
	
	
		
			380 lines
		
	
	
		
			12 KiB
		
	
	
	
		
			Python
		
	
			
		
		
	
	
			380 lines
		
	
	
		
			12 KiB
		
	
	
	
		
			Python
		
	
# encoding: utf-8
 | 
						|
"""A dict subclass that supports attribute style access.
 | 
						|
 | 
						|
Authors:
 | 
						|
 | 
						|
* Fernando Perez (original)
 | 
						|
* Brian Granger (refactoring to a dict subclass)
 | 
						|
"""
 | 
						|
 | 
						|
#-----------------------------------------------------------------------------
 | 
						|
#  Copyright (C) 2008-2011  The IPython Development Team
 | 
						|
#
 | 
						|
#  Distributed under the terms of the BSD License.  The full license is in
 | 
						|
#  the file COPYING, distributed as part of this software.
 | 
						|
#-----------------------------------------------------------------------------
 | 
						|
 | 
						|
#-----------------------------------------------------------------------------
 | 
						|
# Imports
 | 
						|
#-----------------------------------------------------------------------------
 | 
						|
 | 
						|
__all__ = ['Struct']
 | 
						|
 | 
						|
#-----------------------------------------------------------------------------
 | 
						|
# Code
 | 
						|
#-----------------------------------------------------------------------------
 | 
						|
 | 
						|
 | 
						|
class Struct(dict):
 | 
						|
    """A dict subclass with attribute style access.
 | 
						|
 | 
						|
    This dict subclass has a a few extra features:
 | 
						|
 | 
						|
    * Attribute style access.
 | 
						|
    * Protection of class members (like keys, items) when using attribute
 | 
						|
      style access.
 | 
						|
    * The ability to restrict assignment to only existing keys.
 | 
						|
    * Intelligent merging.
 | 
						|
    * Overloaded operators.
 | 
						|
    """
 | 
						|
    _allownew = True
 | 
						|
    def __init__(self, *args, **kw):
 | 
						|
        """Initialize with a dictionary, another Struct, or data.
 | 
						|
 | 
						|
        Parameters
 | 
						|
        ----------
 | 
						|
        *args : dict, Struct
 | 
						|
            Initialize with one dict or Struct
 | 
						|
        **kw : dict
 | 
						|
            Initialize with key, value pairs.
 | 
						|
 | 
						|
        Examples
 | 
						|
        --------
 | 
						|
        >>> s = Struct(a=10,b=30)
 | 
						|
        >>> s.a
 | 
						|
        10
 | 
						|
        >>> s.b
 | 
						|
        30
 | 
						|
        >>> s2 = Struct(s,c=30)
 | 
						|
        >>> sorted(s2.keys())
 | 
						|
        ['a', 'b', 'c']
 | 
						|
        """
 | 
						|
        object.__setattr__(self, '_allownew', True)
 | 
						|
        dict.__init__(self, *args, **kw)
 | 
						|
 | 
						|
    def __setitem__(self, key, value):
 | 
						|
        """Set an item with check for allownew.
 | 
						|
 | 
						|
        Examples
 | 
						|
        --------
 | 
						|
        >>> s = Struct()
 | 
						|
        >>> s['a'] = 10
 | 
						|
        >>> s.allow_new_attr(False)
 | 
						|
        >>> s['a'] = 10
 | 
						|
        >>> s['a']
 | 
						|
        10
 | 
						|
        >>> try:
 | 
						|
        ...     s['b'] = 20
 | 
						|
        ... except KeyError:
 | 
						|
        ...     print('this is not allowed')
 | 
						|
        ...
 | 
						|
        this is not allowed
 | 
						|
        """
 | 
						|
        if not self._allownew and key not in self:
 | 
						|
            raise KeyError(
 | 
						|
                "can't create new attribute %s when allow_new_attr(False)" % key)
 | 
						|
        dict.__setitem__(self, key, value)
 | 
						|
 | 
						|
    def __setattr__(self, key, value):
 | 
						|
        """Set an attr with protection of class members.
 | 
						|
 | 
						|
        This calls :meth:`self.__setitem__` but convert :exc:`KeyError` to
 | 
						|
        :exc:`AttributeError`.
 | 
						|
 | 
						|
        Examples
 | 
						|
        --------
 | 
						|
        >>> s = Struct()
 | 
						|
        >>> s.a = 10
 | 
						|
        >>> s.a
 | 
						|
        10
 | 
						|
        >>> try:
 | 
						|
        ...     s.get = 10
 | 
						|
        ... except AttributeError:
 | 
						|
        ...     print("you can't set a class member")
 | 
						|
        ...
 | 
						|
        you can't set a class member
 | 
						|
        """
 | 
						|
        # If key is an str it might be a class member or instance var
 | 
						|
        if isinstance(key, str):
 | 
						|
            # I can't simply call hasattr here because it calls getattr, which
 | 
						|
            # calls self.__getattr__, which returns True for keys in
 | 
						|
            # self._data.  But I only want keys in the class and in
 | 
						|
            # self.__dict__
 | 
						|
            if key in self.__dict__ or hasattr(Struct, key):
 | 
						|
                raise AttributeError(
 | 
						|
                    'attr %s is a protected member of class Struct.' % key
 | 
						|
                )
 | 
						|
        try:
 | 
						|
            self.__setitem__(key, value)
 | 
						|
        except KeyError as e:
 | 
						|
            raise AttributeError(e) from e
 | 
						|
 | 
						|
    def __getattr__(self, key):
 | 
						|
        """Get an attr by calling :meth:`dict.__getitem__`.
 | 
						|
 | 
						|
        Like :meth:`__setattr__`, this method converts :exc:`KeyError` to
 | 
						|
        :exc:`AttributeError`.
 | 
						|
 | 
						|
        Examples
 | 
						|
        --------
 | 
						|
        >>> s = Struct(a=10)
 | 
						|
        >>> s.a
 | 
						|
        10
 | 
						|
        >>> type(s.get)
 | 
						|
        <...method'>
 | 
						|
        >>> try:
 | 
						|
        ...     s.b
 | 
						|
        ... except AttributeError:
 | 
						|
        ...     print("I don't have that key")
 | 
						|
        ...
 | 
						|
        I don't have that key
 | 
						|
        """
 | 
						|
        try:
 | 
						|
            result = self[key]
 | 
						|
        except KeyError as e:
 | 
						|
            raise AttributeError(key) from e
 | 
						|
        else:
 | 
						|
            return result
 | 
						|
 | 
						|
    def __iadd__(self, other):
 | 
						|
        """s += s2 is a shorthand for s.merge(s2).
 | 
						|
 | 
						|
        Examples
 | 
						|
        --------
 | 
						|
        >>> s = Struct(a=10,b=30)
 | 
						|
        >>> s2 = Struct(a=20,c=40)
 | 
						|
        >>> s += s2
 | 
						|
        >>> sorted(s.keys())
 | 
						|
        ['a', 'b', 'c']
 | 
						|
        """
 | 
						|
        self.merge(other)
 | 
						|
        return self
 | 
						|
 | 
						|
    def __add__(self,other):
 | 
						|
        """s + s2 -> New Struct made from s.merge(s2).
 | 
						|
 | 
						|
        Examples
 | 
						|
        --------
 | 
						|
        >>> s1 = Struct(a=10,b=30)
 | 
						|
        >>> s2 = Struct(a=20,c=40)
 | 
						|
        >>> s = s1 + s2
 | 
						|
        >>> sorted(s.keys())
 | 
						|
        ['a', 'b', 'c']
 | 
						|
        """
 | 
						|
        sout = self.copy()
 | 
						|
        sout.merge(other)
 | 
						|
        return sout
 | 
						|
 | 
						|
    def __sub__(self,other):
 | 
						|
        """s1 - s2 -> remove keys in s2 from s1.
 | 
						|
 | 
						|
        Examples
 | 
						|
        --------
 | 
						|
        >>> s1 = Struct(a=10,b=30)
 | 
						|
        >>> s2 = Struct(a=40)
 | 
						|
        >>> s = s1 - s2
 | 
						|
        >>> s
 | 
						|
        {'b': 30}
 | 
						|
        """
 | 
						|
        sout = self.copy()
 | 
						|
        sout -= other
 | 
						|
        return sout
 | 
						|
 | 
						|
    def __isub__(self,other):
 | 
						|
        """Inplace remove keys from self that are in other.
 | 
						|
 | 
						|
        Examples
 | 
						|
        --------
 | 
						|
        >>> s1 = Struct(a=10,b=30)
 | 
						|
        >>> s2 = Struct(a=40)
 | 
						|
        >>> s1 -= s2
 | 
						|
        >>> s1
 | 
						|
        {'b': 30}
 | 
						|
        """
 | 
						|
        for k in other.keys():
 | 
						|
            if k in self:
 | 
						|
                del self[k]
 | 
						|
        return self
 | 
						|
 | 
						|
    def __dict_invert(self, data):
 | 
						|
        """Helper function for merge.
 | 
						|
 | 
						|
        Takes a dictionary whose values are lists and returns a dict with
 | 
						|
        the elements of each list as keys and the original keys as values.
 | 
						|
        """
 | 
						|
        outdict = {}
 | 
						|
        for k,lst in data.items():
 | 
						|
            if isinstance(lst, str):
 | 
						|
                lst = lst.split()
 | 
						|
            for entry in lst:
 | 
						|
                outdict[entry] = k
 | 
						|
        return outdict
 | 
						|
 | 
						|
    def dict(self):
 | 
						|
        return self
 | 
						|
 | 
						|
    def copy(self):
 | 
						|
        """Return a copy as a Struct.
 | 
						|
 | 
						|
        Examples
 | 
						|
        --------
 | 
						|
        >>> s = Struct(a=10,b=30)
 | 
						|
        >>> s2 = s.copy()
 | 
						|
        >>> type(s2) is Struct
 | 
						|
        True
 | 
						|
        """
 | 
						|
        return Struct(dict.copy(self))
 | 
						|
 | 
						|
    def hasattr(self, key):
 | 
						|
        """hasattr function available as a method.
 | 
						|
 | 
						|
        Implemented like has_key.
 | 
						|
 | 
						|
        Examples
 | 
						|
        --------
 | 
						|
        >>> s = Struct(a=10)
 | 
						|
        >>> s.hasattr('a')
 | 
						|
        True
 | 
						|
        >>> s.hasattr('b')
 | 
						|
        False
 | 
						|
        >>> s.hasattr('get')
 | 
						|
        False
 | 
						|
        """
 | 
						|
        return key in self
 | 
						|
 | 
						|
    def allow_new_attr(self, allow = True):
 | 
						|
        """Set whether new attributes can be created in this Struct.
 | 
						|
 | 
						|
        This can be used to catch typos by verifying that the attribute user
 | 
						|
        tries to change already exists in this Struct.
 | 
						|
        """
 | 
						|
        object.__setattr__(self, '_allownew', allow)
 | 
						|
 | 
						|
    def merge(self, __loc_data__=None, __conflict_solve=None, **kw):
 | 
						|
        """Merge two Structs with customizable conflict resolution.
 | 
						|
 | 
						|
        This is similar to :meth:`update`, but much more flexible. First, a
 | 
						|
        dict is made from data+key=value pairs. When merging this dict with
 | 
						|
        the Struct S, the optional dictionary 'conflict' is used to decide
 | 
						|
        what to do.
 | 
						|
 | 
						|
        If conflict is not given, the default behavior is to preserve any keys
 | 
						|
        with their current value (the opposite of the :meth:`update` method's
 | 
						|
        behavior).
 | 
						|
 | 
						|
        Parameters
 | 
						|
        ----------
 | 
						|
        __loc_data__ : dict, Struct
 | 
						|
            The data to merge into self
 | 
						|
        __conflict_solve : dict
 | 
						|
            The conflict policy dict.  The keys are binary functions used to
 | 
						|
            resolve the conflict and the values are lists of strings naming
 | 
						|
            the keys the conflict resolution function applies to.  Instead of
 | 
						|
            a list of strings a space separated string can be used, like
 | 
						|
            'a b c'.
 | 
						|
        **kw : dict
 | 
						|
            Additional key, value pairs to merge in
 | 
						|
 | 
						|
        Notes
 | 
						|
        -----
 | 
						|
        The `__conflict_solve` dict is a dictionary of binary functions which will be used to
 | 
						|
        solve key conflicts.  Here is an example::
 | 
						|
 | 
						|
            __conflict_solve = dict(
 | 
						|
                func1=['a','b','c'],
 | 
						|
                func2=['d','e']
 | 
						|
            )
 | 
						|
 | 
						|
        In this case, the function :func:`func1` will be used to resolve
 | 
						|
        keys 'a', 'b' and 'c' and the function :func:`func2` will be used for
 | 
						|
        keys 'd' and 'e'.  This could also be written as::
 | 
						|
 | 
						|
            __conflict_solve = dict(func1='a b c',func2='d e')
 | 
						|
 | 
						|
        These functions will be called for each key they apply to with the
 | 
						|
        form::
 | 
						|
 | 
						|
            func1(self['a'], other['a'])
 | 
						|
 | 
						|
        The return value is used as the final merged value.
 | 
						|
 | 
						|
        As a convenience, merge() provides five (the most commonly needed)
 | 
						|
        pre-defined policies: preserve, update, add, add_flip and add_s. The
 | 
						|
        easiest explanation is their implementation::
 | 
						|
 | 
						|
            preserve = lambda old,new: old
 | 
						|
            update   = lambda old,new: new
 | 
						|
            add      = lambda old,new: old + new
 | 
						|
            add_flip = lambda old,new: new + old  # note change of order!
 | 
						|
            add_s    = lambda old,new: old + ' ' + new  # only for str!
 | 
						|
 | 
						|
        You can use those four words (as strings) as keys instead
 | 
						|
        of defining them as functions, and the merge method will substitute
 | 
						|
        the appropriate functions for you.
 | 
						|
 | 
						|
        For more complicated conflict resolution policies, you still need to
 | 
						|
        construct your own functions.
 | 
						|
 | 
						|
        Examples
 | 
						|
        --------
 | 
						|
        This show the default policy:
 | 
						|
 | 
						|
        >>> s = Struct(a=10,b=30)
 | 
						|
        >>> s2 = Struct(a=20,c=40)
 | 
						|
        >>> s.merge(s2)
 | 
						|
        >>> sorted(s.items())
 | 
						|
        [('a', 10), ('b', 30), ('c', 40)]
 | 
						|
 | 
						|
        Now, show how to specify a conflict dict:
 | 
						|
 | 
						|
        >>> s = Struct(a=10,b=30)
 | 
						|
        >>> s2 = Struct(a=20,b=40)
 | 
						|
        >>> conflict = {'update':'a','add':'b'}
 | 
						|
        >>> s.merge(s2,conflict)
 | 
						|
        >>> sorted(s.items())
 | 
						|
        [('a', 20), ('b', 70)]
 | 
						|
        """
 | 
						|
 | 
						|
        data_dict = dict(__loc_data__,**kw)
 | 
						|
 | 
						|
        # policies for conflict resolution: two argument functions which return
 | 
						|
        # the value that will go in the new struct
 | 
						|
        preserve = lambda old,new: old
 | 
						|
        update   = lambda old,new: new
 | 
						|
        add      = lambda old,new: old + new
 | 
						|
        add_flip = lambda old,new: new + old  # note change of order!
 | 
						|
        add_s    = lambda old,new: old + ' ' + new
 | 
						|
 | 
						|
        # default policy is to keep current keys when there's a conflict
 | 
						|
        conflict_solve = dict.fromkeys(self, preserve)
 | 
						|
 | 
						|
        # the conflict_solve dictionary is given by the user 'inverted': we
 | 
						|
        # need a name-function mapping, it comes as a function -> names
 | 
						|
        # dict. Make a local copy (b/c we'll make changes), replace user
 | 
						|
        # strings for the three builtin policies and invert it.
 | 
						|
        if __conflict_solve:
 | 
						|
            inv_conflict_solve_user = __conflict_solve.copy()
 | 
						|
            for name, func in [('preserve',preserve), ('update',update),
 | 
						|
                               ('add',add), ('add_flip',add_flip),
 | 
						|
                               ('add_s',add_s)]:
 | 
						|
                if name in inv_conflict_solve_user.keys():
 | 
						|
                    inv_conflict_solve_user[func] = inv_conflict_solve_user[name]
 | 
						|
                    del inv_conflict_solve_user[name]
 | 
						|
            conflict_solve.update(self.__dict_invert(inv_conflict_solve_user))
 | 
						|
        for key in data_dict:
 | 
						|
            if key not in self:
 | 
						|
                self[key] = data_dict[key]
 | 
						|
            else:
 | 
						|
                self[key] = conflict_solve[key](self[key],data_dict[key])
 | 
						|
 |