Suppose I had a class 'A' and nested within 'A' was class 'B' and both objects were for use publicly...
class A(object):
class B(object):
def __init__(self):
self._b = dict()
self.a = A()
def __setitem__(self, key, value):
# set self._b
self.a[key] = value # recursion ??
def __init__(self):
self._a = dict()
self.b = A.B()
def __setitem__(self, key, value):
# set self._a
self.b[key] = value # recursion ??
Now when doing...
d = A()
d['sap'] = 'nin'
A.__setitem__ will call B.__setitem__, which will call A.__setitem__ and so on...
If this is the desired interface, what would be the best way to deal with this recursive behaviour?
for more context, I've posted on codereview here...
https://codereview.stackexchange.com/questions/85842/a-dictionary-that-allows-multiple-keys-for-one-value
I've tried using another nested class to pass to other internal functions so that they can determine whether it was called from the user, or internally from other functions...
class A(object):
class _Container(object):
def __init__(self, _object):
self.object = _object
class B(object):
def __init__(self, d, **kwargs):
if is instance(d, A._Container):
self.a = d.object
else:
self.a = A(A._Container(self))
def __init__(self, d, **kwargs):
if isinstance(d, A._Container):
self.b = d.object
else:
self.b = A.B(A._Container(self))
This works for init, but I'm not sure I'd like to do that with setitem and all other methods.
I've also tried delegating all or most of the work to class 'A' so essentially class 'B''s methods act as hooks...
class A(object):
class B(object):
def __setitem__(self, key, value):
# some functionality
self.a[key] = value
def __setitem__(self, key, value):
self._a[key] = value
self._b[key] = value
If it helps, here is the code I'm working on atm.
# -*- coding: utf-8 -*-
class mkdict(object):
""" A dictionary that allows multiple keys for one value """
class _Container(object):
""" This is used to wrap an object to avoid infinite
recursion when calling my own methods from the inside.
If a method sees this container, it assumes it has been
called from the inside and not the user.
"""
def __init__(self, _object):
self.object = _object
class dict(object):
""" Interface for mkdict.dict for dict-like behaviour """
def __init__(self, d={}, **kwargs):
""" Using an mkdict._Container to avoid infinite
recursion when allowing:
>>> d = mkdict({'what': 'ever'})
>>> d = mkdict.dict({'what': 'ever'})
"""
if isinstance(d, mkdict._Container):
self.mkdict = d.object
else:
self.mkdict = mkdict(mkdict._Container(self))
self.update(d, **kwargs)
def __str__(self):
return str(self.mkdict._dict)
def __repr__(self):
return str(self)
def __len__(self):
return len(self.mkdict._dict)
def __setitem__(self, key, value):
""" Desired behaviour:
>>> d = mkdict()
>>>
>>> d['what', 'ever'] = 'testing'
>>> d
{'what': 'testing', 'ever': 'testing'}
>>> d.dict
{('what', 'ever'): 'testing'}
>>> d['what'] is d['ever']
True
>>>
>>> d.dict['what'] = 'new value'
>>> d
{'what': 'new value', 'ever': 'testing'}
>>> d.dict
{'what': 'new value', 'ever': 'testing'}
>>> d['what'] is d['ever']
False
"""
if key not in self and key in self.mkdict:
self.mkdict._key_already_set(key)
self.mkdict[key] = value
def __getitem__(self, key):
return self.mkdict._dict[key]
def __contains__(self, key):
return key in self.mkdict._dict
def __delitem__(self, key):
if key not in self:
raise KeyError(key)
if isinstance(key, tuple):
key = key[0]
del self.mkdict[key]
def clear(self):
self.mkdict.clear()
def update(self, d, **kwargs):
if isinstance(d, mkdict.dict):
d = d.mkdict._dict
elif isinstance(d, mkdict):
d = d._dict
d.update(kwargs):
for k, v in d.items():
self[k] = v
class _FullKeyPtr(object):
""" Desired behaviour:
full_key_ptr1 = _FullKeyPtr()
mkdict._key_map -> {'key1', full_key_ptr1,
'key2', full_key_ptr1}
>>> d = mkdict()
>>> d['what', 'ever'] = 'testing'
>>> d._key_map
>>>
>>> # d._key_map:
>>> # {'what': full_key_ptr1, 'ever': full_key_ptr1}
>>> d._key_map
>>> {'what': ('what', 'ever'), 'ever': ('what', 'ever')}
>>>
>>> d['what']
>>> 'testing'
>>>
>>> # full_key = _key_map['ever'].full_key
>>> # i.e. full_key = ('what', 'ever')
>>> # _dict[full_key] = 'test'
>>> d['ever'] = 'test'
>>>
>>>
>>> d['what']
>>> 'test'
"""
def __init__(self, full_key):
self.full_key = full_key
def __str__(self):
return str(self.full_key)
def __repr__(self):
return str(self)
def __init__(self, d={}, **kwargs):
self._dict = dict()
self._key_map = dict()
self._dict_backup = None
self._key_map_backup = None
if isinstance(d, mkdict._Container):
self.dict = d.object
else:
self.dict = mkdict.dict(mkdict._Container(self))
self.update(d, **kwargs)
def __str__(self):
return str(dict(self.items()))
def __repr__(self):
return str(self)
def __len__(self):
return len(self._key_map)
def __iter__(self):
return iter(self.keys())
def __getitem__(self, key):
full_key = self.full_key(key)
return self.dict[full_key]
def __setitem__(self, key, value):
""" Desired behaviour:
>>> d = mkdict()
>>> d['what', 'ever'] = 'testing'
>>>
>>> d
{'what': 'testing', 'ever': 'testing'}
>>>
>>> d.dict
{('what', 'ever'): 'testing'}
>>> d['what'] is d['ever']
True
>>>
>>> d['what'] = 'new value'
>>> d
{'what': 'new value', 'ever': 'new value'}
>>>
>>> d.dict
{('what', 'ever'): 'new value'}
>>> d['what'] is d['ever']
True
"""
if key in self:
key = self.full_key(key)
if key not in self._dict:
if isinstance(key, tuple):
full_key_ptr = self._FullKeyPtr(key)
for k in key:
if k in self:
self._key_already_set(k)
self._key_map[k] = full_key_ptr
else:
self._key_map[key] = self._FullKeyPtr(key)
self._dict[key] = value
def __delitem__(self, key):
full_key = self.full_key(key)
if isinstance(full_key, tuple):
for k in full_key:
del self._key_map[k]
else:
del self._key_map[full_key]
del self._dict[full_key]
def __contains__(self, key):
return key in self._key_map
def items(self):
return [(k, self[k]) for k, v in self._key_map.items()]
def iteritems(self):
return iter(self.items())
def update(self, d={}, **kwargs):
if isinstance(d, mkdict.dict):
d = d.mkdict._dict
elif isinstance(d, mkdict):
d = d._dict
d.update(kwargs)
for k, v in d.items():
self[k] = v
def clear(self):
self._dict.clear()
self._key_map.clear()
def keys(self):
return self._key_map.keys()
def full_key(self, key):
return self._key_map[key].full_key
def has_key(self, key):
return key in self
def append(self, key, otherkey):
pass
def remove(self, key):
full_key = self.full_key(key)
if not isinstance(full_key, tuple):
del self._dict[full_key]
del self._key_map[full_key]
return
new_full_key = list(full_key)
new_full_key.remove(key)
if len(new_full_key) == 1:
new_full_key = new_full_key[0]
else:
new_full_key = tuple(new_full_key)
self._dict[new_full_key] = self.dict[full_key]
del self._dict[full_key]
self._key_map[key].full_key = new_full_key
del self._key_map[key]
def aliases(self, key):
full_key = self.full_key(key)
if isinstance(full_key, tuple):
aliases = list(full_key)
aliases.remove(key)
return aliases
return list()
def backup(self):
pass
def revert(self):
pass
def _key_already_set(self, key):
self.remove(key)
Desired behaviour for above code:
>>> d = mkdict()
>>>
>>> d['-p', '--port'] = 1234
>>> d
{'-p': 1234, '--port': 1234}
>>> d.dict
{('-p', '--port'): 1234}
>>>
>>> d['-p'] = 5678
>>> d
{'-p': 5678, '--port': 5678}
>>> d['--port'] is d['-p']
True
>>> d.aliases('-p')
['--port']
>>>
>>> d.dict['-p'] = 1234
>>> d
{'-p': 1234, '--port': 5678}
>>> d.dict
{'-p': 1234, '--port': 5678}
>>>
>>> d['-p'] is d['--port']
False
I've come up with an alternative solution, which is not so elegant (it's a hack), but should work as you've said. It overrides getitem and setitem, and for tuple like key representation, the minimal_get function is added.
from operator import itemgetter
from itertools import groupby
import logging
class mydict(dict):
def __init__(self, **kwargs):
super(mydict, self).__init__(**kwargs)
# self.d = {}
def __getitem__(self, item):
if isinstance(item, tuple):
d = {}
for an_item in item:
d[an_item] = self.__getitem__(an_item)
return d
else:
return super(mydict, self).__getitem__(item)
def __setitem__(self, keys, value, _depth=0):
if isinstance(keys, tuple) and _depth == 0:
if isinstance(value, tuple):
if len(keys) == len(value):
pass
else:
value = len(keys) * (value,)
else:
value = len(keys) * (value,)
for an_item in zip(keys, value):
self.__setitem__(an_item[0], an_item[1], _depth=1)
else:
super(mydict, self).__setitem__(keys, value)
def minimal_get(self):
x = {}
for item in groupby(sorted(self.items(), key=itemgetter(1)), key=itemgetter(1)):
keys = []
try:
while item[1]:
keys.append(item[1].next()[0])
except StopIteration:
logging.info("StopIteration")
x[tuple(keys)] = item[0]
return x
dic = mydict()
dic["one"] = 1
dic["seven"] = 2
print "dic is", dic
dic["two", "three", "four"] = [1, 2, 3]
print "dic.minimal_get() is ", dic.minimal_get()
print "dic is", dic
dic["two", "ten"] = "lol"
print "dic[\"one\", \"two\"] is ", dic["one", "two"]
print "dic[\"three\"] is", dic["three"]
print "dic.minimal_get() is ", dic.minimal_get()
dic[("x", "z",), "h"] = (1, 2, 3)
print "dic is", dic
print "dic.minimal_get() is ", dic.minimal_get()
Result is,
Related
Simple question; any ideas how to write this more concisely? I have a class with these (already initialised) parameters that I would like to have the option for to update.
def update_parameters(self, a=None, g=None, n=None, d=None, s=None, K=None, A=None, L=None):
# If the argument is not none, change the parameter value of the class
if a:
self.a = a
if g:
self.g = g
if n:
self.n = n
if d:
self.d = d
if s:
self.s = s
if K:
self.K = K
if A:
self.A = A
if L:
self.L = L
You could just use **args instead of explicitly defining all of those parameters. You could then use setattr() to update any valid ones.
For example:
class Test():
def __init__(self):
self.a = None
self.g = None
self.n = None
def update_parameters(self, **args):
for arg, value in args.items():
if hasattr(self, arg) and value is not None:
setattr(self, arg, value)
def __str__(self):
return f"{self.a}, {self.g}, {self.n}"
t = Test()
t.update_parameters(a=21, z='rubbish', n=100)
t.update_parameters(a=42, n=None)
print(t)
This shows a and n being updated and z being ignored:
42, None, 100
Obviously you need to extend it to all of your parameters.
FYI:
setattr(x, 'foobar', 123) is equivalent to x.foobar = 123
You can use **kwargs and force the users to only pass keyword argument. Then it's easy to iterate over them and use setattr():
class A:
def update_parameters(self, **kwargs):
for k, v in kwargs.items():
if v is not None:
setattr(self, k, v)
but this has a problem, Any not-none values are going to be added to the instance's dictionary. So maybe you can add another check to see if the k is valid.
class A:
def update_parameters(self, **kwargs):
for k, v in kwargs.items():
if k in valid_attrs and v is not None:
setattr(self, k, v)
Or even use the instance's __dict__:
def update_parameters(self, **kwargs):
for k, v in kwargs.items():
if k in self.__dict__ and v is not None:
setattr(self, k, v)
Here is the complete code:
class A:
def __init__(self, a, b, c):
self.a = a
self.b = b
self.c = c
def update_parameters(self, **kwargs):
for k, v in kwargs.items():
if k in self.__dict__ and v is not None:
setattr(self, k, v)
def __repr__(self):
return f"A(a={self.a}, b={self.b}, c={self.c})"
obj = A(10, 20, 30)
print(obj)
obj.update_parameters(b=40, c=50, d=60)
print(obj)
output:
A(a=10, b=20, c=30)
A(a=10, b=40, c=50)
This ignores the d=60 value. If you want you can raise exception.
How about making a seperate method that checks for None?
def UpdateIfNotNone(To, From):
if From is not None:
To = From
Then the original method can be like
def update_parameters(self, a=None, g=None, ...):
worklist = [(self.a,a), (self.g, g) ...]
for task in worklist:
UpdateIfNotNone(task[0], task[1])
def check():
dict_choice_a = {(a, b) : value, (b, a) : value} #(a, b) and (b, a) refer to the same value but repeted
dict_choice_b = {tuple(sorted((a, b)) : value} #not repetitive but unreadable
dict_choice_a[(a, b)] = new_value #need to do twice to change value but more readable than dict_choice_b
dict_choice_a[(b, a)] = new_value
#value of both keys are always the same
I want to create a dictionary that has tuple keys referred to its values, that keys need to be exchangeable as (a, b) = (b, a) and they only refer to the same value.
Here's the question is: what is the best way to make the element of tulpe of keys exchangeable but also refer to the same value.
Moreover, string should be also work in the solution.
Per the comments, you can put a and b into a frozenset, which is unordered:
dict_choice = {frozenset((a, b)): value}
If you need this to be automatic, you could create your own MutableMapping:
class MyDict(MutableMapping):
def __init__(self, arg=None):
self._map = {}
if arg is not None:
self.update(arg)
def __getitem__(self, key):
return self._map[frozenset(key)]
def __setitem__(self, key, value):
self._map[frozenset(key)] = value
def __delitem__(self, key):
del self._map[frozenset(key)]
def __iter__(self):
return iter(self._map)
def __len__(self):
return len(self._map)
In use:
>>> d = MyDict([((1, 2), 'hello'), ((3, 4), 'world')])
>>> d[(2, 1)]
'hello'
However note that this could have unexpected behaviour with other kinds of keys:
>>> d['hello'] = 'world'
>>> d['hole']
'world'
>>> d[1] = 2
Traceback (most recent call last):
File "python", line 1, in <module>
File "python", line 14, in __setitem__
TypeError: 'int' object is not iterable
Using #jonrsharpe solution, I created an alternative for the unexpected behavior with other kinds of keys, considered that only tuples will be used in an unordered way:
class MyDict(MutableMapping):
def __init__(self, arg=None):
self._map = {}
if arg is not None:
self.update(arg)
def __getitem__(self, key):
if isinstance(key, tuple):
return self._map[frozenset(key)]
return self._map[key]
def __setitem__(self, key, value):
if isinstance(key, tuple):
self._map[frozenset(key)] = value
else:
self._map[key] = value
def __delitem__(self, key):
if isinstance(key, tuple):
del self._map[frozenset(key)]
else:
del self.map[key]
def __iter__(self):
return iter(self._map)
def __len__(self):
return len(self._map)
def __str__(self):
return str(self._map)
With the following class
from collections import defaultdict, OrderedDict
class NestedOrderedDict(OrderedDict):
def __missing__(self,k):
val = self[k] = NestedOrderedDict()
return self[k]
I tried to make ordered dictionary of dictionary of list
orddodol = NestedOrderedDict()
orddodol["foo"]["a"].append(1)
orddodol["foo"]["b"].append(1)
orddodol["foo"]["b"].append(12)
orddodol["bar"]["a"].append(2)
orddodol["bar"]["a"].append(3)
But it failed giving
AttributeError: 'NestedOrderedDict' object has no attribute 'append'
What's the right way to do it?
You have to initialize the second level dictionary values as lists:
from collections import defaultdict, OrderedDict
class NestedOrderedDict(OrderedDict):
def __missing__(self,k):
val = self[k] = NestedOrderedDict()
return self[k]
orddodol = NestedOrderedDict()
orddodol["foo"]["a"] = [] # these
orddodol["foo"]["b"] = [] # three
orddodol["bar"]["a"] = [] # lines
orddodol["foo"]["a"].append(1)
orddodol["foo"]["b"].append(1)
orddodol["foo"]["b"].append(12)
orddodol["bar"]["a"].append(2)
orddodol["bar"]["a"].append(3)
You almost did it right :) If you want to create ordered dictionary of dictionary of list, then you'd better use defaultdict(list) for that purpose:
from collections import defaultdict, OrderedDict
class NestedOrderedDict(OrderedDict):
def __missing__(self, k):
>>>> self[k] = defaultdict(list)
return self[k]
orddodol = NestedOrderedDict()
orddodol["foo"]["a"].append(1)
orddodol["foo"]["b"].append(1)
orddodol["foo"]["b"].append(12)
orddodol["bar"]["a"].append(2)
orddodol["bar"]["a"].append(3)
from collections import defaultdict, OrderedDict
class NestedOrderedDict(OrderedDict):
def __missing__(self,k):
val = self[k] = NestedOrderedDict()
return self[k]
orddodol = NestedOrderedDict()
orddodol["foo"]["a"] = []
orddodol["foo"]["b"] = []
orddodol["bar"]["a"] = []
orddodol["foo"]["a"].append(1)
orddodol["foo"]["b"].append(1)
orddodol["foo"]["b"].append(12)
orddodol["bar"]["a"].append(2)
orddodol["bar"]["a"].append(3)
or you can implement a defaultdict with ordered like this post Can I do an ordered, default dict in Python
from collections import OrderedDict, Callable
class DefaultOrderedDict(OrderedDict):
# Source: https://stackoverflow.com/a/6190500/562769
def __init__(self, default_factory=None, *a, **kw):
if (default_factory is not None and
not isinstance(default_factory, Callable)):
raise TypeError('first argument must be callable')
OrderedDict.__init__(self, *a, **kw)
self.default_factory = default_factory
def __getitem__(self, key):
try:
return OrderedDict.__getitem__(self, key)
except KeyError:
return self.__missing__(key)
def __missing__(self, key):
if self.default_factory is None:
raise KeyError(key)
self[key] = value = self.default_factory()
return value
def __reduce__(self):
if self.default_factory is None:
args = tuple()
else:
args = self.default_factory,
return type(self), args, None, None, self.items()
def copy(self):
return self.__copy__()
def __copy__(self):
return type(self)(self.default_factory, self)
def __deepcopy__(self, memo):
import copy
return type(self)(self.default_factory,
copy.deepcopy(self.items()))
def __repr__(self):
return 'OrderedDefaultDict(%s, %s)' % (self.default_factory,
OrderedDict.__repr__(self))
Looking for a solution to detect value change when I do : class_instance.list.append(value).
I wrote a little example to illustrate my problem.
class Foo(object):
def __setattr__(self, key, value):
print('set -> ', key, value)
self.__dict__[key] = value
if __name__ == '__main__':
f = Foo()
#set/change detected
f.bar = ['foo']
# change not detected
f.bar.append('bar')
#change detected
f.bar = ['foo', 'bar']
Thank you for your help.
With help of #harobed, I found this solution http://code.activestate.com/recipes/306864-list-and-dictionary-observer/, credits goes to Bernhard Mulder for the observer class.
Here is a working sample of what I want to achieve
class Foo(object):
def __init__(self):
self._dirty = False
def __setattr__(self, key, value):
if key != '_dirty':
if isinstance(value, list):
self.__dict__[key] = list_observer(value, self.observer(self))
else:
self.__dict__[key] = value
self._make_dirty()
def _make_dirty(self):
self._dirty = True
print('is dirty')
def _not_dirty(self):
self._dirty = False
print('is no more dirty')
class observer(object):
"""
If a call to a method is made, this class prints the name of the method
and all arguments.
"""
def __init__(self, instance):
self.instance = instance
def p(self, *args):
print self.attr, args
self.instance._make_dirty()
def __getattr__(self, attr):
self.attr = attr
return self.p
class list_observer(list):
"""
Send all changes to an observer.
"""
def __init__(self, value, observer):
list.__init__(self, value)
self.set_observer(observer)
def set_observer(self, observer):
"""
All changes to this list will trigger calls to observer methods.
"""
self.observer = observer
def __setitem__(self, key, value):
"""
Intercept the l[key]=value operations.
Also covers slice assignment.
"""
try:
oldvalue = self.__getitem__(key)
except KeyError:
list.__setitem__(self, key, value)
self.observer.list_create(self, key)
else:
list.__setitem__(self, key, value)
self.observer.list_set(self, key, oldvalue)
def __delitem__(self, key):
oldvalue = list.__getitem__(self, key)
list.__delitem__(self, key)
self.observer.list_del(self, key, oldvalue)
def __setslice__(self, i, j, sequence):
oldvalue = list.__getslice__(self, i, j)
self.observer.list_setslice(self, i, j, sequence, oldvalue)
list.__setslice__(self, i, j, sequence)
def __delslice__(self, i, j):
oldvalue = list.__getitem__(self, slice(i, j))
list.__delslice__(self, i, j)
self.observer.list_delslice(self, i, oldvalue)
def append(self, value):
list.append(self, value)
self.observer.list_append(self)
def pop(self):
oldvalue = list.pop(self)
self.observer.list_pop(self, oldvalue)
def extend(self, newvalue):
list.extend(self, newvalue)
self.observer.list_extend(self, newvalue)
def insert(self, i, element):
list.insert(self, i, element)
self.observer.list_insert(self, i, element)
def remove(self, element):
index = list.index(self, element)
list.remove(self, element)
self.observer.list_remove(self, index, element)
def reverse(self):
list.reverse(self)
self.observer.list_reverse(self)
def sort(self, cmpfunc=None):
oldlist = self[:]
list.sort(self, cmpfunc)
self.observer.list_sort(self, oldlist)
if __name__ == '__main__':
f = Foo()
#change detected, f is dirty
f.bar = ['foo']
f._not_dirty()
#change detected, f is dirty again
f.bar.append('bar')
EDIT
Is better to Rely on isinstance(a, list) to detect type according to this post -> Differences between isinstance() and type() in python
I was instructed to use more pythonish way of setter and getters #property. So we have something like this:
from UserDict import DictMixin
class A(dict):
def __init__(self, a, b):
self.a = a
self.b = b
#property
def a(self):
return self._a
#a.setter
def a(self, value):
self._a = value
def __getitem__(self, key):
return getattr(self, key)
def __setitem__(self, key, value):
setattr(self, key, value)
def keys(self):
return [k for k in self.__dict__.keys() if not k.startswith('_')]
def do_whatever(self):
pass
a = A(1,2)
print a.keys()
output is ['b'] and at first I wasn't expecting that, but it actually makes sense.
Question is how to get all properties names but not names of methods. Any ideas?
Properties are implemented as descriptors, and so belong to the class, not the instance:
>>> class Question(object):
... #property
... def answer(self):
... return 7 * 6
...
>>> q = Question()
>>> q.answer
42
>>> q.__dict__
{}
>>> for key, value in Question.__dict__.iteritems():
... if isinstance(value, property):
... print key
...
answer