Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Safely unpacking results of str.split [duplicate]

I've often been frustrated by the lack of flexibility in Python's iterable unpacking. Take the following example:

a, b = "This is a string".split(" ", 1)

Works fine. a contains "This" and b contains "is a string", just as expected. Now let's try this:

a, b = "Thisisastring".split(" ", 1)

Now, we get a ValueError:

ValueError: not enough values to unpack (expected 2, got 1)

Not ideal, when the desired result was "Thisisastring" in a, and None or, better yet, "" in b.

There are a number of hacks to get around this. The most elegant I've seen is this:

a, *b = mystr.split(" ", 1)
b = b[0] if b else ""

Not pretty, and very confusing to Python newcomers.

So what's the most Pythonic way to do this? Store the return value in a variable and use an if block? The *varname hack? Something else?

like image 590
squirl Avatar asked Jun 17 '17 20:06

squirl


3 Answers

This looks perfect for str.partition:

>>> a, _, b = "This is a string".partition(" ")
>>> a
'This'
>>> b
'is a string'
>>> a, _, b = "Thisisastring".partition(" ")
>>> a
'Thisisastring'
>>> b
''
>>>
like image 55
Ned Batchelder Avatar answered Oct 05 '22 23:10

Ned Batchelder


How about adding the default(s) at the end and throwing away the unused ones?

>>> a, b, *_ = "This is a string".split(" ", 1) + ['']
>>> a, b
('This', 'is a string')

>>> a, b, *_ = "Thisisastring".split(" ", 1) + ['']
>>> a, b
('Thisisastring', '')

>>> a, b, c, *_ = "Thisisastring".split(" ", 2) + [''] * 2
>>> a, b, c
('Thisisastring', '', '')

Similar (works in Python 2 as well):

>>> a, b, c = ("Thisisastring".split(" ", 2) + [''] * 2)[:3]
>>> a, b, c
('Thisisastring', '', '')
like image 20
Stefan Pochmann Avatar answered Oct 05 '22 23:10

Stefan Pochmann


The *varname hack seems very pythonic to me:

  • Similar to how function parameters are handled

  • Lets you use a one-liner or if block or nothing to correct type of the element if desired

You could also try something like the following if you don't find that clear enough for new users

def default(default, tuple_value):
    return tuple(map(lambda x: x if x is not None else default, tuple_value))

Then you can do something like

a, *b = default("", s.split(...))

Then you should be able to depend on b[0] being a string. I fully admit that the definition of default is obscure, but if you like the effect, you can refine until it meets your aesthetic. In general this is all about what feels right for your style.

like image 45
Sam Hartman Avatar answered Oct 06 '22 00:10

Sam Hartman