dawg
dawg

Reputation: 103884

Shortcut to turn a ONE element dict into ONE tuple in Python

There are dozens of questions on turning a Python dict with some number of elements into a list of tuples. I am looking for a shortcut to turn a one element dict into a tuple.

Ideally, it would:

  1. Be idiomatic and simple (ie, not a subclass of dict, not a function, etc).
  2. Throw an error if there are more that one element in the dict.
  3. Support multiple assignment by tuple unpacking.
  4. Not destroy the dict.

I am looking for a shortcut to do this (that is not destructive to the dict):

k,v=unpack_this({'unknown_key':'some value'})

* does not work here.

I have come up with these that work:

k,v=next(iter(di.items()))   # have to call 'iter' since 'dict_items' is not

Or:

k,v=(next(((k,v) for k,v in di.items())))

Or even:

k,v=next(zip(di.keys(), di.values()))

Finally, the best I can come up with:

k,v=list(di.items())[0]      # probably the best...

Which can be wrapped into a function if I want a length check:

def f(di):
    if (len(di)==1): return list(di.items())[0]
    raise ValueError(f'Too many items to unpack. Expected 2, got {len(di)*2}')

These methods seem super clumsy and none throw an error if there is more than one element.

Is there an idiomatic shortcut that I am missing?

Upvotes: 1

Views: 372

Answers (3)

dawg
dawg

Reputation: 103884

@chepner has the right approach with .popitem():

>>> d = {'a': 1}
>>> d.popitem()
('a', 1)        # d is now {}

This will return the pair associated with the last key added, with no error error if the dict has multiple keys. It will also destroy d.

You can keep d intact by using this idiom to create a new dict and pop off the last item from the new dict created with {**d}:

>>> {**d}.popitem()
('a', 1)
>>> d
{'a': 1}

As far as a 1 line unpackable tuple that throws an error if the dict has more than one item, you can use a Python conditional expression with an alternate that throws the error desired:

# success: k=='a' and v==1
>>> d={'a':1}
>>> k,v={**d}.popitem() if len(d)==1 else 'wrong number of values'


# len(d)!=1 - pass a string to k,v which is too long
>>> d={'a':1,'b':2}
>>> k,v={**d}.popitem() if len(d)==1 else 'wrong number of values'
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
ValueError: too many values to unpack (expected 2) 

If you object to different types being passed, you could do:

>>> k,v={**d}.popitem() if len(d)==1 else {}.fromkeys('too many keys').items()
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
ValueError: too many values to unpack (expected 2)

Upvotes: 0

chepner
chepner

Reputation: 531480

>>> d = {'a': 1}
>>> d.popitem()
('a', 1)

This will return the pair associated with the last key added, with no error error if the dict has multiple keys, but the same length check you've made in your function f can be used.

def f(di):
    if len(di) == 1:
        return d.popitem()
    raise ValueError(f'Dict has multiple keys')

Upvotes: 3

hpchavaz
hpchavaz

Reputation: 1388

Why not :

next(iter(d.items())) if len(d)==1 else (None,None)

Upvotes: 1

Related Questions