7

I'm trying to create a function where the given value (passed as a string) is checked to see if the number of digits is either 4 or 6, and that it is a number.

My first impulse was to go with this code:

def number(x):
    if (len(x) == (4 or 6)) and x.isdigit():
        print "True"
    else:
        print "False"

This code above only passes the first test below...I don't understand why it passes this but none of the other tests:

number("1234")

Only when I separate out the len() functions will it work properly.

def number(x):
    if (len(x) == 4 or len(x) == 6) and x.isdigit():
        print "True"
    else:
        print "False"


## Checks
number("1234")
number("123456")
number("abcd")
number("abcdef")
number("1")
number("a")

The above code passes all tests.

So my questions are:

  1. What's going on here?
  2. Any way to write cleaner code for this?

Thank for the help!

** Not a duplicate question because although this question has the same underlying concepts regarding boolean operators, the problem itself is different due to the usage of len(), isdigit(), and the additional question of how best to improve it (someone commented the usage of return). Definitely adds another perspective to the other question though.

jhub1
  • 611
  • 3
  • 7
  • 19

6 Answers6

12

It helps to examine the logic of this line:

if (len(x) == (4 or 6)):

The (4 or 6) clause contains a logical or short circuit. The value 4 is true, so it is evaluated and returned to the == relational comparison.

The way that or works is that its lefthand side is evaluated for Boolean truth, and its value is returned if true. If the lefthand side is not Boolean true, then the righthand side is evaluated and its value is returned.

Because the lefthand side of the 4 or ... is true in a Boolean sense, the righthand side is never evaluated. Python doesn't even look past 4 or. If the left-hand value were a false value (such as 0), then the right hand side of the or would be evaluated.

To see this in action, try print 4 or 6. The output will be 4.

So since the 4 is a hard-coded true value, your comparison is semantically the same as if (len(x) == 4) -- that is, since 4 is true, 6 is never evaluated.

What I suppose you really want to know is if len(x) is either 4 or 6. You could put that to code in a couple of ways:

if(len(x) == 4 or len(x) == 6 ...

if(len(x) in (4,6) ...
DavidO
  • 13,812
  • 3
  • 38
  • 66
  • Strangely enough in my case this is behaving differently: Here both are taken into acount > `if file.endswith(".log") or file.endswith(".blg"):` Here only the left side is taken into account> `if file.endswith(".log" or ".blg"):` – Matthieu Ducorps Feb 07 '19 at 10:47
7

You can use the in operator like so:

def number(x):
    if len(x) in (4, 6) and x.isdigit():
    print "True"
else:
    print "False"

where in checks for containment in a given container. Note that 4 or 6 on their own evaluate to something undesirable, which is why your first code segment fails. You can check it out on the python shell:

>>> 4 or 6
4
ifma
  • 3,673
  • 4
  • 26
  • 38
3

You probably wanted to write

if (len(x) in (4, 6)) and x.isdigit():

Instead of

if (len(x) == (4 or 6)) and x.isdigit(): 
jonas_toth
  • 872
  • 5
  • 8
3

Short answer: len(x) in [4, 6] or len(x) == 4 or len(x) == 6.

"or" is a boolean, or logical choice. (4 or 6) is guaranteed to resolve to a non-zero (true) value. In this case, it resolves to 4, so your test case passes.

Charles Merriam
  • 19,908
  • 6
  • 73
  • 83
  • It clicked in my head after reading your response - thanks. For anyone else reading this - the critical mistake I did was not thinking of OR as boolean. – jhub1 Jul 20 '16 at 17:14
1

I'll go ahead and answer

Any way to write cleaner code for this?

Yes. Return the boolean value, rather than printing a string.

def number(x): 
    return len(x) in {4, 6} and x.isdigit()

print(number("1234")) # True

Then, it is simple do use your method in a if-statement without string comparison.

OneCricketeer
  • 179,855
  • 19
  • 132
  • 245
  • Thanks. To expand off of this - if you wanted to time this both these functions, how would you do it? I tried [ Python -m timeit "import simplified; simplified.number() ] in the console...but it doesn't work. simplified.py is the name of the file that this function resides in. – jhub1 Jul 20 '16 at 18:56
  • Not sure. Never used `timeit`. I want to say this is quicker, though, because there is no if-else conditional – OneCricketeer Jul 20 '16 at 19:33
0

The trouble is in your or statement.

Any value greater than one will evaluate to True when you put it in a conditional. So (4 or 6) will always resolve to true.

You could use the in statement above or you could just use two =='s:

if (len(x) == 4 or len(x) == 6) and x.isdigit()

It's a bit wordier, I find it easier to read.

Athena
  • 3,200
  • 3
  • 27
  • 35