##// END OF EJS Templates
Tidy up error raising in magic decorators....
Tidy up error raising in magic decorators. Closes gh-1778

File last commit:

r5390:c82649ea
r7047:6fd55e8d
Show More
ipstruct.py
395 lines | 11.6 KiB | text/x-python | PythonLexer
Brian Granger
Full refactor of ipstruct.Struct....
r2078 # encoding: utf-8
"""A dict subclass that supports attribute style access.
Bernardo B. Marques
remove all trailling spaces
r4872 Authors:
Brian Granger
Full refactor of ipstruct.Struct....
r2078
* Fernando Perez (original)
* Brian Granger (refactoring to a dict subclass)
Fernando Perez
Remove svn-style $Id marks from docstrings and Release imports....
r1853 """
fperez
Reorganized the directory for ipython/ to have its own dir, which is a bit...
r0
Brian Granger
Full refactor of ipstruct.Struct....
r2078 #-----------------------------------------------------------------------------
Matthias BUSSONNIER
update copyright to 2011/20xx-2011...
r5390 # Copyright (C) 2008-2011 The IPython Development Team
fperez
Reorganized the directory for ipython/ to have its own dir, which is a bit...
r0 #
# Distributed under the terms of the BSD License. The full license is in
# the file COPYING, distributed as part of this software.
Brian Granger
Full refactor of ipstruct.Struct....
r2078 #-----------------------------------------------------------------------------
fperez
Reorganized the directory for ipython/ to have its own dir, which is a bit...
r0
Brian Granger
Full refactor of ipstruct.Struct....
r2078 #-----------------------------------------------------------------------------
# Imports
#-----------------------------------------------------------------------------
fperez
Reorganized the directory for ipython/ to have its own dir, which is a bit...
r0
Brian Granger
Work to address the review comments on Fernando's branch....
r2498 from IPython.utils.data import list2dict2
fperez
Reorganized the directory for ipython/ to have its own dir, which is a bit...
r0
Brian Granger
Full refactor of ipstruct.Struct....
r2078 __all__ = ['Struct']
#-----------------------------------------------------------------------------
# Code
#-----------------------------------------------------------------------------
class Struct(dict):
"""A dict subclass with attribute style access.
This dict subclass has a a few extra features:
fperez
Reorganized the directory for ipython/ to have its own dir, which is a bit...
r0
Brian Granger
Full refactor of ipstruct.Struct....
r2078 * 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.
"""
Brian Granger
Improvement to how config is handled in Components....
r2184 _allownew = True
Brian Granger
Full refactor of ipstruct.Struct....
r2078 def __init__(self, *args, **kw):
Brian Granger
First draft of refactored ipstruct.py.
r2077 """Initialize with a dictionary, another Struct, or data.
fperez
Reorganized the directory for ipython/ to have its own dir, which is a bit...
r0
Brian Granger
First draft of refactored ipstruct.py.
r2077 Parameters
----------
Brian Granger
Full refactor of ipstruct.Struct....
r2078 args : dict, Struct
Initialize with one dict or Struct
Brian Granger
First draft of refactored ipstruct.py.
r2077 kw : dict
Initialize with key, value pairs.
Examples
--------
Brian Granger
Full refactor of ipstruct.Struct....
r2078
>>> s = Struct(a=10,b=30)
>>> s.a
10
>>> s.b
30
>>> s2 = Struct(s,c=30)
>>> s2.keys()
['a', 'c', 'b']
fperez
Reorganized the directory for ipython/ to have its own dir, which is a bit...
r0 """
Brian Granger
First draft of refactored ipstruct.py.
r2077 object.__setattr__(self, '_allownew', True)
Brian Granger
Full refactor of ipstruct.Struct....
r2078 dict.__init__(self, *args, **kw)
fperez
Reorganized the directory for ipython/ to have its own dir, which is a bit...
r0
Brian Granger
First draft of refactored ipstruct.py.
r2077 def __setitem__(self, key, value):
Brian Granger
Full refactor of ipstruct.Struct....
r2078 """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'
Bernardo B. Marques
remove all trailling spaces
r4872 ...
Brian Granger
Full refactor of ipstruct.Struct....
r2078 this is not allowed
"""
if not self._allownew and not self.has_key(key):
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.
Bernardo B. Marques
remove all trailling spaces
r4872 This calls :meth:`self.__setitem__` but convert :exc:`KeyError` to
Brian Granger
Full refactor of ipstruct.Struct....
r2078 :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"
Bernardo B. Marques
remove all trailling spaces
r4872 ...
Brian Granger
Full refactor of ipstruct.Struct....
r2078 you can't set a class member
"""
# If key is an str it might be a class member or instance var
Brian Granger
First draft of refactored ipstruct.py.
r2077 if isinstance(key, str):
# I can't simply call hasattr here because it calls getattr, which
Bernardo B. Marques
remove all trailling spaces
r4872 # calls self.__getattr__, which returns True for keys in
Brian Granger
First draft of refactored ipstruct.py.
r2077 # self._data. But I only want keys in the class and in
# self.__dict__
if key in self.__dict__ or hasattr(Struct, key):
Brian Granger
Full refactor of ipstruct.Struct....
r2078 raise AttributeError(
'attr %s is a protected member of class Struct.' % key
Brian Granger
First draft of refactored ipstruct.py.
r2077 )
Brian Granger
Full refactor of ipstruct.Struct....
r2078 try:
self.__setitem__(key, value)
except KeyError, e:
raise AttributeError(e)
Brian Granger
First draft of refactored ipstruct.py.
r2077
def __getattr__(self, key):
Brian Granger
Full refactor of ipstruct.Struct....
r2078 """Get an attr by calling :meth:`dict.__getitem__`.
Bernardo B. Marques
remove all trailling spaces
r4872 Like :meth:`__setattr__`, this method converts :exc:`KeyError` to
Brian Granger
Full refactor of ipstruct.Struct....
r2078 :exc:`AttributeError`.
Examples
--------
>>> s = Struct(a=10)
>>> s.a
10
>>> type(s.get)
Thomas Kluyver
Various fixes to tests in IPython.utils.
r4891 <... 'builtin_function_or_method'>
Brian Granger
Full refactor of ipstruct.Struct....
r2078 >>> try:
... s.b
... except AttributeError:
... print "I don't have that key"
Bernardo B. Marques
remove all trailling spaces
r4872 ...
Brian Granger
Full refactor of ipstruct.Struct....
r2078 I don't have that key
"""
Brian Granger
First draft of refactored ipstruct.py.
r2077 try:
Brian Granger
Full refactor of ipstruct.Struct....
r2078 result = self[key]
Brian Granger
First draft of refactored ipstruct.py.
r2077 except KeyError:
raise AttributeError(key)
else:
return result
def __iadd__(self, other):
Brian Granger
Full refactor of ipstruct.Struct....
r2078 """s += s2 is a shorthand for s.merge(s2).
Bernardo B. Marques
remove all trailling spaces
r4872
Brian Granger
Full refactor of ipstruct.Struct....
r2078 Examples
--------
Bernardo B. Marques
remove all trailling spaces
r4872
Brian Granger
Full refactor of ipstruct.Struct....
r2078 >>> s = Struct(a=10,b=30)
>>> s2 = Struct(a=20,c=40)
>>> s += s2
>>> s
{'a': 10, 'c': 40, 'b': 30}
"""
fperez
Reorganized the directory for ipython/ to have its own dir, which is a bit...
r0 self.merge(other)
return self
def __add__(self,other):
Brian Granger
Full refactor of ipstruct.Struct....
r2078 """s + s2 -> New Struct made from s.merge(s2).
Bernardo B. Marques
remove all trailling spaces
r4872
Brian Granger
Full refactor of ipstruct.Struct....
r2078 Examples
--------
Bernardo B. Marques
remove all trailling spaces
r4872
Brian Granger
Full refactor of ipstruct.Struct....
r2078 >>> s1 = Struct(a=10,b=30)
>>> s2 = Struct(a=20,c=40)
>>> s = s1 + s2
>>> s
{'a': 10, 'c': 40, 'b': 30}
"""
sout = self.copy()
sout.merge(other)
return sout
fperez
Reorganized the directory for ipython/ to have its own dir, which is a bit...
r0
def __sub__(self,other):
Brian Granger
Full refactor of ipstruct.Struct....
r2078 """s1 - s2 -> remove keys in s2 from s1.
Bernardo B. Marques
remove all trailling spaces
r4872
Brian Granger
Full refactor of ipstruct.Struct....
r2078 Examples
--------
Bernardo B. Marques
remove all trailling spaces
r4872
Brian Granger
Full refactor of ipstruct.Struct....
r2078 >>> s1 = Struct(a=10,b=30)
>>> s2 = Struct(a=40)
>>> s = s1 - s2
>>> s
{'b': 30}
"""
sout = self.copy()
sout -= other
return sout
fperez
Reorganized the directory for ipython/ to have its own dir, which is a bit...
r0
def __isub__(self,other):
Brian Granger
Full refactor of ipstruct.Struct....
r2078 """Inplace remove keys from self that are in other.
Bernardo B. Marques
remove all trailling spaces
r4872
Brian Granger
Full refactor of ipstruct.Struct....
r2078 Examples
--------
Bernardo B. Marques
remove all trailling spaces
r4872
Brian Granger
Full refactor of ipstruct.Struct....
r2078 >>> s1 = Struct(a=10,b=30)
>>> s2 = Struct(a=40)
>>> s1 -= s2
>>> s1
{'b': 30}
"""
fperez
Reorganized the directory for ipython/ to have its own dir, which is a bit...
r0 for k in other.keys():
if self.has_key(k):
Brian Granger
Full refactor of ipstruct.Struct....
r2078 del self[k]
return self
fperez
Reorganized the directory for ipython/ to have its own dir, which is a bit...
r0
Brian Granger
First draft of refactored ipstruct.py.
r2077 def __dict_invert(self, data):
Bernardo B. Marques
remove all trailling spaces
r4872 """Helper function for merge.
Brian Granger
Full refactor of ipstruct.Struct....
r2078
Bernardo B. Marques
remove all trailling spaces
r4872 Takes a dictionary whose values are lists and returns a dict with
Brian Granger
First draft of refactored ipstruct.py.
r2077 the elements of each list as keys and the original keys as values.
"""
fperez
Reorganized the directory for ipython/ to have its own dir, which is a bit...
r0 outdict = {}
Brian Granger
First draft of refactored ipstruct.py.
r2077 for k,lst in data.items():
if isinstance(lst, str):
fperez
Reorganized the directory for ipython/ to have its own dir, which is a bit...
r0 lst = lst.split()
for entry in lst:
outdict[entry] = k
return outdict
Brian Granger
Full refactor of ipstruct.Struct....
r2078
fperez
Reorganized the directory for ipython/ to have its own dir, which is a bit...
r0 def dict(self):
Brian Granger
Full refactor of ipstruct.Struct....
r2078 return self
def copy(self):
"""Return a copy as a Struct.
Bernardo B. Marques
remove all trailling spaces
r4872
Brian Granger
Full refactor of ipstruct.Struct....
r2078 Examples
--------
Bernardo B. Marques
remove all trailling spaces
r4872
Brian Granger
Full refactor of ipstruct.Struct....
r2078 >>> s = Struct(a=10,b=30)
>>> s2 = s.copy()
>>> s2
{'a': 10, 'b': 30}
>>> type(s2).__name__
'Struct'
"""
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
Brian Granger
First draft of refactored ipstruct.py.
r2077 """
Brian Granger
Full refactor of ipstruct.Struct....
r2078 return self.has_key(key)
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)
Brian Granger
First draft of refactored ipstruct.py.
r2077 def merge(self, __loc_data__=None, __conflict_solve=None, **kw):
Brian Granger
Full refactor of ipstruct.Struct....
r2078 """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.
fperez
Reorganized the directory for ipython/ to have its own dir, which is a bit...
r0 If conflict is not given, the default behavior is to preserve any keys
Brian Granger
Full refactor of ipstruct.Struct....
r2078 with their current value (the opposite of the :meth:`update` method's
fperez
Reorganized the directory for ipython/ to have its own dir, which is a bit...
r0 behavior).
Brian Granger
Full refactor of ipstruct.Struct....
r2078
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::
Bernardo B. Marques
remove all trailling spaces
r4872
Brian Granger
Full refactor of ipstruct.Struct....
r2078 __conflict_solve = dict(
func1=['a','b','c'],
func2=['d','e']
)
Bernardo B. Marques
remove all trailling spaces
r4872
Brian Granger
Full refactor of ipstruct.Struct....
r2078 In this case, the function :func:`func1` will be used to resolve
Bernardo B. Marques
remove all trailling spaces
r4872 keys 'a', 'b' and 'c' and the function :func:`func2` will be used for
Brian Granger
Full refactor of ipstruct.Struct....
r2078 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.
fperez
Reorganized the directory for ipython/ to have its own dir, which is a bit...
r0 As a convenience, merge() provides five (the most commonly needed)
pre-defined policies: preserve, update, add, add_flip and add_s. The
Brian Granger
Full refactor of ipstruct.Struct....
r2078 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
fperez
Reorganized the directory for ipython/ to have its own dir, which is a bit...
r0 of defining them as functions, and the merge method will substitute
Brian Granger
Full refactor of ipstruct.Struct....
r2078 the appropriate functions for you.
fperez
Reorganized the directory for ipython/ to have its own dir, which is a bit...
r0 For more complicated conflict resolution policies, you still need to
Brian Granger
Full refactor of ipstruct.Struct....
r2078 construct your own functions.
Examples
--------
This show the default policy:
Bernardo B. Marques
remove all trailling spaces
r4872
Brian Granger
Full refactor of ipstruct.Struct....
r2078 >>> s = Struct(a=10,b=30)
>>> s2 = Struct(a=20,c=40)
>>> s.merge(s2)
>>> s
{'a': 10, 'c': 40, 'b': 30}
Bernardo B. Marques
remove all trailling spaces
r4872
Brian Granger
Full refactor of ipstruct.Struct....
r2078 Now, show how to specify a conflict dict:
Bernardo B. Marques
remove all trailling spaces
r4872
Brian Granger
Full refactor of ipstruct.Struct....
r2078 >>> s = Struct(a=10,b=30)
>>> s2 = Struct(a=20,b=40)
>>> conflict = {'update':'a','add':'b'}
>>> s.merge(s2,conflict)
>>> s
{'a': 20, 'b': 70}
"""
data_dict = dict(__loc_data__,**kw)
fperez
Reorganized the directory for ipython/ to have its own dir, which is a bit...
r0 # 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
Brian Granger
Full refactor of ipstruct.Struct....
r2078
fperez
Reorganized the directory for ipython/ to have its own dir, which is a bit...
r0 # default policy is to keep current keys when there's a conflict
Brian Granger
First draft of refactored ipstruct.py.
r2077 conflict_solve = list2dict2(self.keys(), default = preserve)
Brian Granger
Full refactor of ipstruct.Struct....
r2078
fperez
Reorganized the directory for ipython/ to have its own dir, which is a bit...
r0 # 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),
Fernando Perez
Updated ipstruct to fix doctest failures.
r1280 ('add',add), ('add_flip',add_flip),
('add_s',add_s)]:
fperez
Reorganized the directory for ipython/ to have its own dir, which is a bit...
r0 if name in inv_conflict_solve_user.keys():
inv_conflict_solve_user[func] = inv_conflict_solve_user[name]
del inv_conflict_solve_user[name]
Brian Granger
First draft of refactored ipstruct.py.
r2077 conflict_solve.update(self.__dict_invert(inv_conflict_solve_user))
fperez
Unicode fixes (utf-8 used by default if ascii is not enough). This should fix some reported crashes....
r5 for key in data_dict:
fperez
Reorganized the directory for ipython/ to have its own dir, which is a bit...
r0 if key not in self:
self[key] = data_dict[key]
else:
self[key] = conflict_solve[key](self[key],data_dict[key])