From: kkumer on

I have to merge two dictionaries into one, and in
a "shallow" way: changing items should be possible
by operating either on two parents or on a
new dictionary. I am open to suggestions how
to do this (values are always numbers, BTW), but
I tried to do it by creating a dict-like class that just
forwards all calls to the two parent dicts, see below.

It works, but one important thing is missing. I
am not able to expand new dictionary with
double-star operator ** to use it as a
set of keyword arguments of a function.
I googled a bit, but was unable to find what
property must an object have to be correctly
treated by **.

I hope the following code is self-explanatory:

------------------------------------

import itertools

class hubDict(dict):
"""Merges two dictionaries, but not actually but just by forwarding."""

def __init__(self, da, db):
self.d1 = da
self.d2 = db

def __getitem__(self, name):
if self.d1.has_key(name):
return self.d1[name]
else:
return self.d2[name]

def __setitem__(self, name, value):
if self.d1.has_key(name):
self.d1[name] = value
else:
self.d2[name] = value

def __iter__(self):
return itertools.chain(self.d1.__iter__(), self.d2.__iter__())

def has_key(self, name):
if self.d1.has_key(name) or self.d2.has_key(name):
return True
else:
return False

def keys(self):
return self.d1.keys() + self.d2.keys()

def items(self):
return self.d1.items() + self.d2.items()

def iteritems(self):
return itertools.chain(self.d1.iteritems(), self.d2.iteritems())

def iterkeys(self):
return itertools.chain(self.d1.iterkeys(), self.d2.iterkeys())

def itervalues(self):
return itertools.chain(self.d1.itervalues(), self.d2.itervalues())

def copy(self):
print "Can't copy hubDict yet!!!"

def update(self, d):
for key in d:
self.__setitem__(key, d[key])

def popitem(self):
try:
return self.d1.popitem()
except KeyError:
return self.d2.popitem()

def __repr__(self):
return 'First: %s\nSecond: %s' % (
self.d1.__repr__(), self.d2.__repr__())


# Trying it now:

da = {'a':1}
db = {'b':2}
dh = hubDict(da, db)
def kwa(**kwargs): print kwargs

#OK
kwa(**da)

#not OK: prints empty dict
kwa(**dh)


import itertools

class hubDict(dict):
"""Merges two dictionaries, but not actually but just by forwarding."""

def __init__(self, da, db):
self.d1 = da
self.d2 = db

def __getitem__(self, name):
if self.d1.has_key(name):
return self.d1[name]
else:
return self.d2[name]

def __setitem__(self, name, value):
if self.d1.has_key(name):
self.d1[name] = value
else:
self.d2[name] = value

def __iter__(self):
return itertools.chain(self.d1.__iter__(), self.d2.__iter__())

def has_key(self, name):
if self.d1.has_key(name) or self.d2.has_key(name):
return True
else:
return False

def keys(self):
return self.d1.keys() + self.d2.keys()

def items(self):
return self.d1.items() + self.d2.items()

def iteritems(self):
return itertools.chain(self.d1.iteritems(), self.d2.iteritems())

def iterkeys(self):
return itertools.chain(self.d1.iterkeys(), self.d2.iterkeys())

def itervalues(self):
return itertools.chain(self.d1.itervalues(), self.d2.itervalues())

def copy(self):
print "Can't copy hubDict yet!!!"

def update(self, d):
for key in d:
self.__setitem__(key, d[key])

def popitem(self):
try:
return self.d1.popitem()
except KeyError:
return self.d2.popitem()

def __repr__(self):
return 'First: %s\nSecond: %s' % (
self.d1.__repr__(), self.d2.__repr__())

def __len__(self):
return self.d1.__len__() + self.d2.__len__()

# Trying it now:

da = {'a':1}
db = {'b':2}
dh = hubDict(da, db)
def kwa(**kwargs): print kwargs

#OK
kwa(**da)

#not OK: prints empty dict
kwa(**dh)

From: Thomas Jollans on
On 06/07/2010 10:17 PM, kkumer wrote:
> I have to merge two dictionaries into one, and in
> a "shallow" way: changing items should be possible
> by operating either on two parents or on a
> new dictionary. I am open to suggestions how
> to do this (values are always numbers, BTW), but
> I tried to do it by creating a dict-like class that just
> forwards all calls to the two parent dicts, see below.
>
> It works, but one important thing is missing. I
> am not able to expand new dictionary with
> double-star operator ** to use it as a
> set of keyword arguments of a function.
> I googled a bit, but was unable to find what
> property must an object have to be correctly
> treated by **.
>
My guess would be that this only works with dicts, and uses the internal
representation of the dict, not the python-defined methods.
I don't know what you want to do, but you might be better off creating a
special "mergeabledict" type for the parents, and then allow them to
connect, actually copying all items over. Though it might be best just
to use one single dict ;-)

> I hope the following code is self-explanatory:
>
> ------------------------------------
>
> import itertools
>
> class hubDict(dict):
> """Merges two dictionaries, but not actually but just by forwarding."""
>
> def __init__(self, da, db):
> self.d1 = da
> self.d2 = db
>
> def __getitem__(self, name):
> if self.d1.has_key(name):
> return self.d1[name]
> else:
> return self.d2[name]
>
> def __setitem__(self, name, value):
> if self.d1.has_key(name):
> self.d1[name] = value
> else:
> self.d2[name] = value
>
> def __iter__(self):
> return itertools.chain(self.d1.__iter__(), self.d2.__iter__())
>
> def has_key(self, name):
> if self.d1.has_key(name) or self.d2.has_key(name):
> return True
> else:
> return False
>
> def keys(self):
> return self.d1.keys() + self.d2.keys()
>
> def items(self):
> return self.d1.items() + self.d2.items()
>
> def iteritems(self):
> return itertools.chain(self.d1.iteritems(), self.d2.iteritems())
>
> def iterkeys(self):
> return itertools.chain(self.d1.iterkeys(), self.d2.iterkeys())
>
> def itervalues(self):
> return itertools.chain(self.d1.itervalues(), self.d2.itervalues())
>
> def copy(self):
> print "Can't copy hubDict yet!!!"
>
> def update(self, d):
> for key in d:
> self.__setitem__(key, d[key])
>
> def popitem(self):
> try:
> return self.d1.popitem()
> except KeyError:
> return self.d2.popitem()
>
> def __repr__(self):
> return 'First: %s\nSecond: %s' % (
> self.d1.__repr__(), self.d2.__repr__())
>
>
> # Trying it now:
>
> da = {'a':1}
> db = {'b':2}
> dh = hubDict(da, db)
> def kwa(**kwargs): print kwargs
>
> #OK
> kwa(**da)
>
> #not OK: prints empty dict
> kwa(**dh)
>
>
> import itertools
>
> class hubDict(dict):
> """Merges two dictionaries, but not actually but just by forwarding."""
>
> def __init__(self, da, db):
> self.d1 = da
> self.d2 = db
>
> def __getitem__(self, name):
> if self.d1.has_key(name):
> return self.d1[name]
> else:
> return self.d2[name]
>
> def __setitem__(self, name, value):
> if self.d1.has_key(name):
> self.d1[name] = value
> else:
> self.d2[name] = value
>
> def __iter__(self):
> return itertools.chain(self.d1.__iter__(), self.d2.__iter__())
>
> def has_key(self, name):
> if self.d1.has_key(name) or self.d2.has_key(name):
> return True
> else:
> return False
>
> def keys(self):
> return self.d1.keys() + self.d2.keys()
>
> def items(self):
> return self.d1.items() + self.d2.items()
>
> def iteritems(self):
> return itertools.chain(self.d1.iteritems(), self.d2.iteritems())
>
> def iterkeys(self):
> return itertools.chain(self.d1.iterkeys(), self.d2.iterkeys())
>
> def itervalues(self):
> return itertools.chain(self.d1.itervalues(), self.d2.itervalues())
>
> def copy(self):
> print "Can't copy hubDict yet!!!"
>
> def update(self, d):
> for key in d:
> self.__setitem__(key, d[key])
>
> def popitem(self):
> try:
> return self.d1.popitem()
> except KeyError:
> return self.d2.popitem()
>
> def __repr__(self):
> return 'First: %s\nSecond: %s' % (
> self.d1.__repr__(), self.d2.__repr__())
>
> def __len__(self):
> return self.d1.__len__() + self.d2.__len__()
>
> # Trying it now:
>
> da = {'a':1}
> db = {'b':2}
> dh = hubDict(da, db)
> def kwa(**kwargs): print kwargs
>
> #OK
> kwa(**da)
>
> #not OK: prints empty dict
> kwa(**dh)
>
>

From: Ian Kelly on
On Mon, Jun 7, 2010 at 2:17 PM, kkumer
<kkumer(a)best-search-engines-mail.com> wrote:
>
> I have to merge two dictionaries into one, and in
> a "shallow" way: changing items should be possible
> by operating either on two parents or on a
> new dictionary. I am open to suggestions how
> to do this (values are always numbers, BTW), but
> I tried to do it by creating a dict-like class that just
> forwards all calls to the two parent dicts, see below.
>
> It works, but one important thing is missing. I
> am not able to expand new dictionary with
> double-star operator ** to use it as a
> set of keyword arguments of a function.
> I googled a bit, but was unable to find what
> property must an object have to be correctly
> treated by **.

I don't think that what you want to do here is possible. It appears
to be hard-coded and not affected by subclassing, as evidenced by the
following snippet:

class NonDict(dict):
for attr in dict.__dict__:
if attr not in ('__init__', '__new__',):
locals()[attr] = None

d = NonDict({'a': 1})
def kwa(**kw): print kw
kwa(**d)

Prints:
{'a': 1}

Cheers,
Ian
From: Dave Angel on
kkumer wrote:
> I have to merge two dictionaries into one, and in
> a "shallow" way: changing items should be possible
> by operating either on two parents or on a
> new dictionary. I am open to suggestions how
> to do this (values are always numbers, BTW), but
> I tried to do it by creating a dict-like class that just
> forwards all calls to the two parent dicts, see below.
>
> It works, but one important thing is missing. I
> am not able to expand new dictionary with
> double-star operator ** to use it as a
> set of keyword arguments of a function.
> I googled a bit, but was unable to find what
> property must an object have to be correctly
> treated by **.
>
> I hope the following code is self-explanatory:
>
> ------------------------------------
>
> import itertools
>
> class hubDict(dict):
> """Merges two dictionaries, but not actually but just by forwarding."""
>
> def __init__(self, da, db):
> self.d1 = da
> self.d2 = db
>
> def __getitem__(self, name):
> if self.d1.has_key(name):
> return self.d1[name]
> else:
> return self.d2[name]
>
> def __setitem__(self, name, value):
> if self.d1.has_key(name):
> self.d1[name] = value
> else:
> self.d2[name] = value
>
> def __iter__(self):
> return itertools.chain(self.d1.__iter__(), self.d2.__iter__())
>
> def has_key(self, name):
> if self.d1.has_key(name) or self.d2.has_key(name):
> return True
> else:
> return False
>
> def keys(self):
> return self.d1.keys() + self.d2.keys()
>
> def items(self):
> return self.d1.items() + self.d2.items()
>
> def iteritems(self):
> return itertools.chain(self.d1.iteritems(), self.d2.iteritems())
>
> def iterkeys(self):
> return itertools.chain(self.d1.iterkeys(), self.d2.iterkeys())
>
> def itervalues(self):
> return itertools.chain(self.d1.itervalues(), self.d2.itervalues())
>
> def copy(self):
> print "Can't copy hubDict yet!!!"
>
> def update(self, d):
> for key in d:
> self.__setitem__(key, d[key])
>
> def popitem(self):
> try:
> return self.d1.popitem()
> except KeyError:
> return self.d2.popitem()
>
> def __repr__(self):
> return 'First: %s\nSecond: %s' % (
> self.d1.__repr__(), self.d2.__repr__())
>
>
> # Trying it now:
>
> da = {'a':1}
> db = {'b':2}
> dh = hubDict(da, db)
> def kwa(**kwargs): print kwargs
>
> #OK
> kwa(**da)
>
> #not OK: prints empty dict
> kwa(**dh)
>
>
> import itertools
>
> class hubDict(dict):
> """Merges two dictionaries, but not actually but just by forwarding."""
>
> def __init__(self, da, db):
> self.d1 = da
> self.d2 = db
>
> def __getitem__(self, name):
> if self.d1.has_key(name):
> return self.d1[name]
> else:
> return self.d2[name]
>
> def __setitem__(self, name, value):
> if self.d1.has_key(name):
> self.d1[name] = value
> else:
> self.d2[name] = value
>
> def __iter__(self):
> return itertools.chain(self.d1.__iter__(), self.d2.__iter__())
>
> def has_key(self, name):
> if self.d1.has_key(name) or self.d2.has_key(name):
> return True
> else:
> return False
>
> def keys(self):
> return self.d1.keys() + self.d2.keys()
>
> def items(self):
> return self.d1.items() + self.d2.items()
>
> def iteritems(self):
> return itertools.chain(self.d1.iteritems(), self.d2.iteritems())
>
> def iterkeys(self):
> return itertools.chain(self.d1.iterkeys(), self.d2.iterkeys())
>
> def itervalues(self):
> return itertools.chain(self.d1.itervalues(), self.d2.itervalues())
>
> def copy(self):
> print "Can't copy hubDict yet!!!"
>
> def update(self, d):
> for key in d:
> self.__setitem__(key, d[key])
>
> def popitem(self):
> try:
> return self.d1.popitem()
> except KeyError:
> return self.d2.popitem()
>
> def __repr__(self):
> return 'First: %s\nSecond: %s' % (
> self.d1.__repr__(), self.d2.__repr__())
>
> def __len__(self):
> return self.d1.__len__() + self.d2.__len__()
>
> # Trying it now:
>
> da = {'a':1}
> db = {'b':2}
> dh = hubDict(da, db)
> def kwa(**kwargs): print kwargs
>
> #OK
> kwa(**da)
>
> #not OK: prints empty dict
> kwa(**dh)
>
>
>
From the help on "Container types",

>>The UserDict <../library/userdict.html#module-UserDict> module
provides a DictMixin class to help create those methods from a base set
of __getitem__() <#object.__getitem__>, __setitem__()
<#object.__setitem__>, __delitem__() <#object.__delitem__>, and keys().
<<

and
>>It is recommended that both mappings and sequences implement the
__contains__() <#object.__contains__> method to allow efficient use of
the in operator; for mappings, in should be equivalent of has_key(); for
sequences, it should search through the values. It is further
recommended that both mappings and sequences implement the __iter__()
<#object.__iter__> method to allow efficient iteration through the
container; for mappings, __iter__() <#object.__iter__> should be the
same as iterkeys(); for sequences, it should iterate through the values.
<<


I'd guess that the ** operator calls one or more of these special
methods directly, rather than calling the equivalent regular methods.

DaveA

From: Peter Otten on
kkumer wrote:

>
> I have to merge two dictionaries into one, and in
> a "shallow" way: changing items should be possible
> by operating either on two parents or on a
> new dictionary. I am open to suggestions how
> to do this (values are always numbers, BTW), but
> I tried to do it by creating a dict-like class that just
> forwards all calls to the two parent dicts, see below.
>
> It works, but one important thing is missing. I
> am not able to expand new dictionary with
> double-star operator ** to use it as a
> set of keyword arguments of a function.
> I googled a bit, but was unable to find what
> property must an object have to be correctly
> treated by **.

The following experiment shows that you only need to implement a keys() and
__getitem__() method.

$ cat kw.py
class A(object):
def keys(self): return list("ab")
def __getitem__(self, key):
return 42

def f(**kw):
print(kw)

f(**A())
$ python kw.py
{'a': 42, 'b': 42}

However, if you have A inherit from dict...

$ cat kwd.py
class A(dict):
def keys(self): return list("ab")
def __getitem__(self, key):
return 42

def f(**kw):
print(kw)

f(**A())
$ python kwd.py
{}

it stops working -- probably a side-effect of some optimization.
So if you change your hubDict's base class from dict to object you should
get the desired behaviour.

Peter