Benjamin Elfner
Benjamin Elfner

Reputation: 13

What is the best way to cast variables when unpacking iterables?

In my code I am reading lines from a file. Each line contains space delimited values.

foo True 8 9.2
bar False 17 -3.1

After reading the file and splitting the text by line. I will end up with a list.

lines = ['foo True 8 9.2', 'bar False 17 -3.1']

I then iterate through the lines and unpack the variables using .split():

for line in lines:
    string, boolean, integer, floating_point = line.split()

I then cast each variable to its proper type:

boolean = bool(boolean)
integer = int(integer)
floating_point = float(floating_point)

My question is, is there a more Pythonic way to cast the variables and/or use less lines?

Upvotes: 1

Views: 533

Answers (3)

martineau
martineau

Reputation: 123463

Here's another way to use ast.literal_eval():

import ast


def convert(s):
    """ Determine value of (Python) literal given in string `s`. """
    try:
        return ast.literal_eval(s)
    except ValueError:
        return s


filename = 'data_file.txt'
with open(filename) as file:
    for line in file:
        string, boolean, integer, floating_point = (convert(item) for item in line.split())
        print(string, boolean, integer, floating_point)

Output:

foo True 8 9.2
bar False 17 -3.1

Upvotes: 1

Tadhg McDonald-Jensen
Tadhg McDonald-Jensen

Reputation: 21453

when logic gets messy usually the solution is to abstract that logic to another function, and if the function represents an iterable then you would write a generator, for instance you could do this:

def mapTypes(values, types):
    return (cast(v) for cast, v in zip(types, values))

def mapRows(file, rowTypes):
    for line in file:
        yield mapTypes(line.split(), rowTypes)


def str2bool(string):
    if string == "True":
        return True
    elif string == "False":
        return False
    else:
        raise ValueError("string must be True or False, got {!r}".format(string))


lines = ['foo True 8 9.2', 'bar False 17 -3.1']
for row in mapRows(lines, [str, str2bool, int,float]):
    string, boolean, integer, floating_point = row
    print(string, boolean, integer, floating_point)

Upvotes: 0

jupiterbjy
jupiterbjy

Reputation: 3503

Use ast.literal_eval. Unlike eval this only determines literals.

from ast import literal_eval


lines = ['foo True 8 9.2', 'bar False 17 -3.1']

for line in lines:
    for word in line.split():
        try:
            eval_ed = literal_eval(word)
        except ValueError:
            eval_ed = word  # it's a string, EAFP at it's finest
        print(type(eval_ed), eval_ed)

Output:

<class 'str'> foo
<class 'bool'> True
<class 'int'> 8
<class 'float'> 9.2
<class 'str'> bar
<class 'bool'> False
<class 'int'> 17
<class 'float'> -3.1

Upvotes: 0

Related Questions