9

When line 2 in the text file has 'nope' then it will ignore the line and continue the next one. Is there another way to write this without using try and except? Can I use if else statement to do this?

Example of text file:

0 1 
0 2 nope
1 3 
2 5 nope

Code:

e = open('e.txt')
alist = []
for line in e:
    start = int(line.split()[0])
    target = int(line.split()[1])
    try:
        if line.split()[2] == 'nope':
            continue
    except IndexError:
        alist.append([start, target])
mcarton
  • 27,633
  • 5
  • 85
  • 95
Hal
  • 372
  • 4
  • 13
  • 1
    What are you trying to achieve here? Two possibilities spring to mind: 1. test for the length of the split string, 2 test for the presence of 'nope at the end of/in the string. – Paula Thomas May 20 '18 at 07:41
  • 1
    I don't know if it is common practice in Python, but otherwise you shouldn't use exception to control the flow of you program in case of expected things happening: here you know "nope" could not exist, hence split and count how many elements you have, then decide to check the third or not. And also, I'd use a variable instead of splitting the line each time you need it: do it once, `splitted = line.split()`, then use this… – ShinTakezou May 20 '18 at 07:51

5 Answers5

6

Yes, you can use str.endswith() method to check the trailing of the lines.

with  open('e.txt') as f:
    for line in f:
        if not line.endswith(('nope', 'nope\n')):
            start, target = line.split()
            alist.append([int(start), int(target)])

Note that when you use a with statement to open a file you don't need to close the file explicitly the file will be closed automatically at the end of the block.

Another but more optimized approach to solve this is to use a list comprehension in order to refuse appending to the list at each iteration and benefit from its performance compare to a regular loop.

with open('e.txt') as f:
    alist = [tuple(int(n) for i in line.split()) for line in f if not line.endswith(('nope', 'nope\n'))]

Note that still, since your code is exception prone because of converting strings to integer and splitting the lines, etc. It's better to use a try-except in order to prevent your code from possible exceptions and handle them properly.

with  open('e.txt') as f:
    for line in f:
        if not line.endswith(('nope', 'nope\n')):
            try:
                start, target = line.split()
            except ValueError:
                # the line.split() returns more or less than two items
                pass # or do smth else
            try:
                alist.append([int(start), int(target)])
            except ValueError:
                # invalid literal for int() with base 10
                pass # or do smth else

Another and yet Pythonic approach is to use csv module for reading the file. In that case you don't need to split the lines and/or use str.endswith().

import csv
with open("e.txt") as f:
    reader = csv.reader(f, delimiter=' ')
    alist = [(int(i), int(j)) for i, j, *rest in reader if not rest[0]]
    # rest[0] can be either an empty string or the word 'nope' if it's
    # an empty string we want the numbers. 
Mazdak
  • 105,000
  • 18
  • 159
  • 188
3
with open('e.txt', 'r') as f:
    alist = []
    for line in f:
        words = line.split()
        if len(words) > 2 and words[2] == 'nope':
            continue
        else:
            alist.append([int(words[0]), int(words[1])])
Osman Mamun
  • 2,864
  • 1
  • 16
  • 22
  • 1
    And the OP appends integers, not strings (I missed it at first even in my answer) – ShinTakezou May 20 '18 at 08:34
  • 1
    Come on, `all([len(words) > 2, words[2] == 'nope']` is not only a really obscure way to write `len(words) > 2 and words[2] == 'nope'`, it also completely misses the point as the evaluation is not lazy here and your code will still raise the same exception. – Frax May 20 '18 at 16:09
  • @Frax I had only len(words) > 2 bcoz I thought that's should be enough. Then, Aran-Fey mentioned that the second words must be 'nope'. I understand your concern. – Osman Mamun May 20 '18 at 16:14
  • @mamun I'm not sure you understand seriousness of the issue: to evaluate `all([len(words) > 2, words[2] == 'nope']` you need to evaluate `words[2] == 'nope'` first, so if it's out of bounds you will trigger an exception before actually calling `all()`. (note: the issue is already fixed) – Frax May 20 '18 at 16:22
  • Awesome! I have had problem with 'and or' before, so I used 'all or any' for any mutually exclusive condition. Now, for this case it makes sense to not use 'any or all' – Osman Mamun May 20 '18 at 16:25
  • @Frax If I remember well, `all` stops evaluating at the first false condition; so if it evaluates `words[2] == 'nope'`, it must be because `len(words)>2` was true — hence, it is ok. Unless I remember wrongly. About “obscurity”, it depends on your background. – ShinTakezou May 22 '18 at 16:52
  • `all` is never called. To call a function, you need to first evaluate the arguments. You try to evaluate the argument, you get the exception on `words[2]`. – Frax May 22 '18 at 17:10
  • @ShinTakezou, forgot to call you, I answered your comment. OTOH, I see that I wrote exactly the same thing 3 comments above, so I'm not sure it is going to help. So to reiterate: evaluation of `all([len(words) > 2, words[2] == 'nope']` is roughly equivalent to evaluating `tmp = [, words[2] == 'nope']; all(tmp)`, which is in turn equivalent to `tmp1, tmp2 = len(words) > 2, words[2] == 'nope'; tmp3 = [tmp1, tmp2]; all(tmp3)`. I hope it's clear that this code must fail. – Frax May 22 '18 at 18:03
  • @Frax Seen. I thought it actually deferred the evaluation — missed the *not lazy* clue above in your comment. – ShinTakezou May 22 '18 at 20:36
  • 1
    @mamun the last line, `alist.append([int(words[0]), int(words[1])])` – ShinTakezou May 22 '18 at 21:49
1

Can I use if else statement to do this?

You should use if-else statements, not exceptions, to control the flow in case of ordinary “events” which you expect. This is common “rule” in many languages, I think Python doesn't raise an exception here, Python is an exception here, but hopefully not in cases like this.

Following your code but without calling line.split() each time, removing the try-except and using a proper condition in the if:

alist = []
with open('e.txt') as e:
    for line in e:
        splitted = line.split()
        if len(splitted) > 2 and splitted[2] == 'nope':
            continue
        else:
            alist.append([int(splitted[0]), int(splitted[1])])

And of course you can negate the condition and avoid the continue:

if len(splitted) <= 2 or splitted[2] != 'nope':
    alist.append([int(splitted[0]), int(splitted[1])])

Which shows (another) weakness in case when you have less than 2 elements. Here you could use try: the exception in this case tells you that the input format is wrong (because you expect at least 2 elements, it seems), so you have to reject the input and warn the user. Also, you could intercept ValueError if those 2 elements aren't integers.

Moreover, if your input is allowed to contain extra spaces, you can use something like splitted[2].strip().


Readings on SO/SE about the try-except matter.

ShinTakezou
  • 9,432
  • 1
  • 29
  • 39
  • In Python, throwing exceptions for normal situations, and catching exceptions rather than preventing them, is more idiomatic than in other languages. For instance, an iterator's `next` method throws an exception to indicate when all elements have been visited. – Sneftel May 20 '18 at 14:17
  • See, for example, https://stackoverflow.com/questions/11360858/what-is-the-eafp-principle-in-python . – Sneftel May 20 '18 at 14:19
  • 1
    @Sneftel Indeed, the general rule allows and even encourages to use exceptions, so this remark in the answer is wrong. However, I don't think this rule applies to this specific case. Here you have straighforward condition that is more readable (and shorter) to check directly. – Frax May 20 '18 at 16:13
  • @Sneftel I read something eons ago, which likely is what triggered the doubt [in this comment](https://stackoverflow.com/questions/50432668/other-options-instead-of-using-try-except/50432859?noredirect=1#comment87879706_50432668). I disagree here is good, and likely elsewhere, unless the `except` is anyway an exception (i.e., it occurs less than, say, half the time). If this probable efficiency loss doesn't matter—since efficiency doesn't always matter—it still looks more a matter of taste and (maybe) readability - maintainability than anything else. I don't think the *try* adds anything here. – ShinTakezou May 22 '18 at 16:50
1

If nope can be not only at the end of the line you can use this

with open('e.txt') as e:
    alist = [line.split() for line in e if 'nope' not in line]

print(alist)
Druta Ruslan
  • 7,171
  • 2
  • 28
  • 38
0
alist = []
with open('e.txt') as fin:
    for line in fin:
        rest_line, nope = line.strip().rsplit(' ', 1)
        if nope != 'nope':
            alist.append([int(rest_line), int(nope)])
Greg Eremeev
  • 1,760
  • 5
  • 23
  • 33