Let’s create dictionary dctD
entries with values being set()s:
dctD = {'key0':set(['value0'])} # static constant (key, value) pair dctD['key1'] = set(['value1']) # static constant (key, value) pair
Now the dictionary has to be updated with a new value which should extend its set() value if the at runtime unknown key value is already in the dictionary:
# v-- simulation of --v-- dynamic assignment varkey = 'key2' ; varval = 'value2' # ^-- with unknown --^-- values during runtime
The GOAL is to extend the set being value in dctD
by a value also in case the key does not yet exist. Because the set.add()
method does not return the with the new value updated set a tmpSet
identifier is required to achieve such goal. The code for extending existing or creating new set is then:
tmpSet = dctD.get(varkey, set()) tmpSet.add(varval) dctD[varkey] = tmpSet
With help of the walrus operator it is possible to cut down the code above to two lines of code as follows:
(tmpSet:= dctD.get(varkey, set())).add(varval) dctD[varkey] = tmpSet
And with help of a chainOfSet
class as a wrapper for the set() methods to one line (with improved readability):
dctD[varkey] = chainOfSet(dctD.get(varkey, set())).add(varval).get()
Now the question:
Does Python allow a simpler way to code the above one-line assignment without the need of writing a wrapper class?
Below the code of the chainOfSet
class and all of the code mentioned above:
class chainOfSet: """ Allows chaining (by dot syntax) else not chainable set() methods and addition/subtraction of other sets. Is doesn't support interaction of objects of this class itself as this is considered to be out of scope of the purpose for which this class was created. """ def __init__(s, sv=set()): s.sv = sv # --- def add(s, itm): s.sv.add(itm) return s def update(s, *itm): s.sv.update(itm) return s def remove(s, itm): # key error if not in set s.sv.remove(itm) return s def discard(s, itm): # remove if present, but no error if not s.sv.discard(itm) return s def pop(s): s.sv.pop() return s def clear(s): s.sv.clrear() return s # --- def intersection(s, p): s.sv = s.sv.intersection(p) return s def union(s, p): s.sv = s.sv.union(p) return s def __add__(s, itm): if isinstance(itm, set): s.sv = s.sv.union(itm) else: s.sv.update(itm) return s def difference(s,p): s.sv = s.sv.difference(p) return s def __sub__(s, itm): if isinstance(itm, set): s.sv = s.sv - itm else: s.sv.difference(set(itm)) return s def symmetric_difference(s,p): # equivalent to: union - intersection s.sv = s.sv.symmetric_difference(p) return s def copy(s): s.sv = s.sv.copy() return s # --- def len(s): return len(s.sv) def isdisjoint(s,p): return s.sv.isdisjoint(p) def issubset(s,p): return s.sv.issubset(p) def issuperset(s,p): return s.sv.issuperset(p) # --- def get(s): return s.sv #:class chainOfSet(set) # Let's create dictionary dctD entries with values being set()s: dctD = {'key0':set(['value0'])} # static constant (key, value) pair dctD['key1'] = set(['value1']) # static constant (key, value) pair # Now the dictionary has to be updated with a new value which should # extend its set() value if the at runtime unknown key value is already # in the dictionary: # v-- simulation of --v-- dynamic assignment varkey = 'key2' ; varval = 'value2' # ^-- with unknown --^-- values during runtime # The GOAL is to extend the set being value in dctD by a value also in # case the key does not yet exist. # Because set.add() method does not return a value a tmpSet identifier # is required to achieve the goal. The code for extending existing or # creating new set is then: tmpSet = dctD.get(varkey, set()) tmpSet.add(varval) dctD[varkey] = tmpSet # With use of the walrus operator it is possible to cut down the code # above to two lines of code as follows: (tmpSet:= dctD.get(varkey, set())).add(varval) dctD[varkey] = tmpSet # And with use of a 'class chainOfSet' as a wrapper for the set() # methods to one line (one-liner with improved readability): dctD[varkey] = chainOfSet(dctD.get(varkey, set())).add(varval).get() # Does Python allow a simpler way to code the above one-line assignment # without need of writing a wrapper class?
Notice that above provided wrapper class allows not only chaining, but also usage of the addition operator:
print((chainOfSet(set([1,2,3]))+{5,6}-{1}).intersection({1,2,5}).get())
printing as output
{2,5}
Update: evolution of code improvements in 4 steps:
All of the 4 steps produce the same result, so the code below runs without assertion error. Notice how it was possible to get the lines and code length in characters down using the different approaches:
# >>> My own first version: dctD = {} tmpVal = dctD.get('key1', set()) tmpVal.add('value1.1') dctD['key1'] = tmpVal tmpVal = dctD.get('key1', set()) tmpVal.add('value1.2') dctD['key1'] = tmpVal tmpVal = dctD.get('key2', set()) tmpVal.add('value2.1') dctD['key2'] = tmpVal # >>> My own second version: dctD0 = {} (tmpVal := dctD0.get('key1', set())).add('value1.1') dctD0['key1'] = tmpVal (tmpVal := dctD0.get('key1', set())).add('value1.2') dctD0['key1'] = tmpVal (tmpVal := dctD0.get('key2', set())).add('value2.1') dctD0['key2'] = tmpVal print(dctD0) # >>> Approach provided in answer by John Kugelman : dctD1 = {} dctD1.setdefault('key1', set()).add('value1.1') dctD1.setdefault('key1', set()).add('value1.2') dctD1.setdefault('key2', set()).add('value2.1') print(dctD1) # >>> Approach provided in answer by Tom McLean: from collections import defaultdict dctD2 = defaultdict(set) dctD2['key1'].add('value1.1') dctD2['key1'].add('value1.2') dctD2['key2'].add('value2.1') print(dctD2) # --- assert dctD == dctD0 == dctD1 == dctD2 == dctD2
Advertisement
Answer
From what I can understand, this can easily be done with a defaultdict
from collections import defaultdict dctD = defaultdict(set) varkey = "key2" varval = "value" dctD[varkey].add(varval) print(dctD["key2"]) # {'value'} print(dctD["key1"]) # set() print("key3" in dctD) # False