2

Is it an pythonic way to re-assign variables with values of different types?

I have a string of the form k1:v1,k2:v2,k3:v3, etc which comes from the command line. It is straightforward to parse it:

kvs=args.kvs
if kvs is not None:
    kvs = [(kv[0], kv[1]) for kv in [kv_str.split(':') for kv_str in kvs.split(',')]]   

Since I came to python from strictly-typed languages the approach does not look really clean to me since it is re-assigned with a different type.

Would it be considered a pythonic solution of parsing the string?

6
  • 3
    Yes, it is usually fine on a case-by-case basis. I would consider this OK. This is really an opinion-based question though. Commented Apr 29, 2020 at 21:32
  • 3
    FWIW you can simplify: kvs = [tuple(kv.split(':')) for kv in kvs.split(',')] Commented Apr 29, 2020 at 21:34
  • 2
    None of the v values can contain a :, right? If they can, use split(':', 1). Commented Apr 29, 2020 at 21:35
  • @chepner very useful note, thx Commented Apr 29, 2020 at 21:36
  • 1
    If you are using argparse, you might consider using a custom type to unpack the string during parsing, rather than after. p.add_argument('--kvs', type=lambda kvs: [tuple(kv.split(':', 1)) for kv in kvs.split(",")]). Commented Apr 29, 2020 at 21:42

2 Answers 2

5

Short Answer:

That is fine a way to do it. Although you can just do if kvs: since empty collection types are considered falsey in Python (if that is appropriate for your case of course).

Long Answer:

This is really just opinion in Python world unfortunately. When coming from strictly-typed languages to Python you will see people do things that are discouraged or prohibited in a stricter language.

That being said I think it is always safer to avoid mutation whenever possible and to create a new variable if the type changes. In large codebases where args are passed around with no type checking, this approach can save you a lot of time debugging in the future.

Another way you may see this written is something like this that assigns the list object to a new variable.

kvs = "k1:v1,k2:v2,k3:v3"

kv_pairs = [
    tuple(kv_str.split(':'))
    for kv_str in kvs.split(',')
] if kvs else []

EDIT: fixed the code sample I posted

Sign up to request clarification or add additional context in comments.

4 Comments

"you can just do if kvs: though" - I definitely wouldn't do that, in case you want to pass an empty string: --kvs '' -> kvs = [] instead of kvs = None. If --kvs is not provided, then kvs = None.
Empty string/array/dict/set and None all are falsey. Try it out. Set kvs to None or some different empty data structures to and it will result in kv_pairs == []. If you wanted to do type checking before the comprehension was run for some reason, it would be better to explicitly raise a TypeError.
I mean OP is talking about command line argument handling, and those are always strings. As well OP's code leaves kvs == None if it's already None. I just updated my answer to show what I mean. No flag -> kvs = None, with flag and empty string -> kvs = []
Oh I see. Thank you for clarifying!
2

It's not bad, but you might find it cleaner to parse it during argument parsing. For example in argparse, you can use a custom type:

import argparse

def kvs_string(kvs):
    if not kvs:
        return []  # Since str.split will return "['']"
    return [tuple(kv.split(':')) for kv in kvs.split(",")]

parser = argparse.ArgumentParser()
parser.add_argument('--kvs', type=kvs_string)

for args in [], ['--kvs', ''], ['--kvs', 'k1:v1,k2:v2,k3:v3']:
    print(parser.parse_args(args))

Output:

Namespace(kvs=None)
Namespace(kvs=[])
Namespace(kvs=[('k1', 'v1'), ('k2', 'v2'), ('k3', 'v3')])

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.