392 lines
		
	
	
		
			12 KiB
		
	
	
	
		
			Python
		
	
	
	
	
	
		
		
			
		
	
	
			392 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 yap_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) | ||
|  | 
 | ||
|  |     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) | ||
|  |         <... 'builtin_function_or_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: | ||
|  |             raise AttributeError(key) | ||
|  |         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]) | ||
|  | 
 |