views:

154

answers:

4

I have to make a rudimentary FSM in a class, and am writing it in Python. The assignment requires we read the transitions for the machine from a text file. So for example, a FSM with 3 states, each of which have 2 possible transitions, with possible inputs 'a' and 'b', wolud have a text file that looks like this:

2          # first line lists all final states
0 a 1
0 b 2
1 a 0
1 b 2
2 a 0
2 b 1

I am trying to come up with a more pythonic way to read a line at a time and convert the states to ints, while keeping the input vals as strings. Basically this is the idea:

self.finalStates = f.readline().strip("\n").split(" ")
for line in f:
  current_state, input_val, next_state = [int(x) for x in line.strip("\n").split(" ")]

Of course, when it tries to int("a") it throws a ValueError. I know I could use a traditional loop and just catch the ValueError but I was hoping to have a more Pythonic way of doing this.

+11  A: 

You should really only be trying to parse the tokens that you expect to be integers

for line in f:
    tokens = line.split(" ")
    current_state, input_val, next_state = int(tokens[0]), tokens[1], int(tokens[2])

Arguably more-readable:

for line in f:
    current_state, input_val, next_state = parseline(line)

def parseline(line):
    tokens = line.split(" ")
    return (int(tokens[0]), tokens[1], int(tokens[2]))
matt b
+5  A: 

This is something very functional, but I'm not sure if it's "pythonic"... And it may cause some people to scratch their heads. You should really have a "lazy" zip() to do it this way if you have a large number of values:

types = [int, str, int]
for line in f:
   current_state, input_val, next_state = multi_type(types, line)

def multi_type(ts,xs): return [t(x) for (t,x) in zip(ts, xs.strip().split())]

Also the arguments you use for strip and split can be omitted, because the defaults will work here.

Edit: reformatted - I wouldn't use it as one long line in real code.

viraptor
I don't think I'd take this approach if my lines contained only three values, but if they contained more? Definitely.
Robert Rossney
This is also a pretty nice solution if you need to change the "type" function for individual parameters often - i.e. switch parameter 1 from int() to str()
matt b
A: 
self.finalStates = [int(state) for state in f.readline().split()]

for line in f:
    words = line.split()
    current_state, input_val, next_state = int(words[0]), words[1], int(words[2])
    # now do something with values

Note that you can shorten line.strip("\n").split(" ") down to just line.split(). The default behavior of str.split() is to split on any white space, and it will return a set of words that have no leading or trailing white space of any sort.

If you are converting the states to int in the loop, I presume you want the finalStates to be int as well.

steveha
+1  A: 

You got excellent answers that match your problem well. However, in other cases, there may indeed be situations where you want to convert some fields to int if feasible (i.e. if they're all digits) and leave them as str otherwise (as the title of your question suggests) without knowing in advance which fields are ints and which ones are not.

The traditional Python approach is try/except...:

def maybeint(s):
  try: return int(s)
  except ValueError: return s

...which you need to wrap into a function as there's no way to do a try/except in an expression (e.g. in a list comprehension). So, you'd use it like:

several_fields = [maybeint(x) for x in line.split()]

However, it is possible to do this specific task inline, if you prefer:

several_fields = [(int(x) if x.isdigit() else x) for x in line.split()]

the if/else "ternary operator" looks a bit strange, but one can get used to it;-); and the isdigit method of a string gives True if the string is nonempty and only has digits.

To repeat, this is not what you should do in your specific case, where you know the specific int-str-int pattern of input types; but it might be appropriate in a more general situation where you don't have such precise information in advance!

Alex Martelli