Why does it work?

2019-06-28 00:39发布

问题:

So I'm learning Python and working through a list of program ideas. Of course I wrote the obligatory FizzBuzz, which worked, but was basically if elif else blablabla. I googled it to see if there are other ways and found this dank one-liner:

for i in range(1,101): 
    print("Fizz" * (i % 3 == 0) + "Buzz" * (i % 5 == 0) or i)

There are no ifs, no elifs, nothing. I googled "string concatenation" and found info on the * symbol, but don't understand how it's working in this case. Can somebody explain?

回答1:

There are no ifs, no elifs, nothing.

Sure there are! Just disguised. Looking for string concatenation (i.e. +) won't help you, because * is repetition. Specifically, string * n gives you a string that is n copies of string in a row. Further, a boolean value can be implicitly converted to an integer: True becomes 1 and False becomes 0. So

"Fizz" * (i % 3 == 0)

means "One Fizz if i % 3 == 0, none if not." Same with Buzz.

Finally, that or i at the end means that if you got the empty string because both parts came up empty, then you get i instead. or really means "the value of the left hand side unless the left hand side is a false value, in which case return the value of the right hand side."

This trick gets used elsewhere, too. Python doesn't have a direct equivalent to C's ?: operator, but you can get close to one with a two-element tuple and an index operation because of the bool-to-integer conversion I mentioned above. So C's

a? b: c

which means "b if a is true, otherwise c" becomes this in Python:

(c, b)[a]


回答2:

Break it out and you'll understand it.

def does_it_fizz(num):
    return num % 3 == 0

def does_it_buzz(num):
    return num % 5 == 0

for num in range(1, 101):
    print("Fizz" * does_it_fizz(num) + "Buzz" * does_it_buzz(num) or num)

String multiplication repeats the string, so 'a' * n is aaaaa...n times...a. if i % 3 != 0, then does_it_fizz(i) returns 0. "any string" * 0 == "". If the number should neither Fizz nor Buzz, you get print("" or num). The empty string is Falsey, but num is always Truthy, so it prints num



回答3:

You are going to print the string "Buzz" either once if (i % 5 == 0) is True or return i:

In [5]: "foo" * 2
Out[5]: 'foofoo'

In [6]: "foo" * 3
Out[6]: 'foofoofoo'

In [7]: i = 5
In [8]: "foo" *  (i % 5 == 0) or i
Out[9]: 'foo'  
In [9]: "foo" *  (i % 5 == 1) or i
Out[22]: 5

The same logic applies to "Fizz" sometimes (i % 3 == 0) will be True so we see it once, when it is False we don't see it.

When you use the * operator on a string it will repeat the string n times, in this case it will once at most as either string is only printed based on the result of the boolean test.

You can see in ipython exactly what happens with True and False:

In [26]: "foo" * True
Out[26]: 'foo'

In [27]: "foo" * False
Out[27]: ''

Basically True * "foo" is equivalent to 1 * "foo" "foo" * False is equivalent to 0 * "foo"

Bool is a subclass of int so the code is taking advantage of that fact, you sometimes see similar logic used with indexing a list based on a test although not recommended:

In [31]: d = ["bar","foo"]   
In [32]: d[3<2] # False so we get 0 the first element
Out[32]: 'bar'   
In [33]: d[3>2] # True so we get 1 the second element
Out[33]: 'foo'


回答4:

When i == 3, the (i % 3 == 0) will be True.

Any string * True will return the string. It helps to think of True as the integer 1 in this case (as anything multiplied by 1 is the original thing that was multiplied).

When eg. i == 1, the (i % 3 == 0) will be False (as 1 % 3 == 1).

So string * False == empty string

Take the fizz and the buzz strings that were returned above and concatenate them using the + operator.

Now in the print statement, if the result of that concatenation is an empty string, the or operator will use the value of i instead.



回答5:

Here is a breakdown of what is happening:

for i in range(1,101):
    if (i % 3 == 0):
        print "Fizz"
    if (i % 5 == 0):
        print "Buzz"
    if (i % 5 != 0 and (i % 3 != 0)):
        print i

My ide is set to Python 2.7 btw



回答6:

If you consider just the conditional:

(i % 3 == 0)

this generates a boolean which states whether that particular value of i modulo 3 is equal to 0. This is the case for a multiple of 3, or 0 (0, 3, 6, etc). So this is how you would know to print "Fizz" (or "Buzz", given the other conditional).

The cool thing about strings in Python is that you can conditionally print a string. For instance, fire up an interpreter and type this:

'foo' * True
'foo' * False

This should result in the following output:

'foo'
''

So basically, for a given value of i, you're doing this:

print("Fizz" * (0 % 3 == 0) + "Buzz" * (0 % 5 == 0) or i) -> print("Fizz" * True + "Buzz" * True or i) -> printf('Fizz'+'Buzz')
print("Fizz" * (1 % 3 == 0) + "Buzz" * (1 % 5 == 0) or i) -> print("Fizz" * False + "Buzz" * False or i) -> printf(1)
print("Fizz" * (2 % 3 == 0) + "Buzz" * (2 % 5 == 0) or i) -> print("Fizz" * False + "Buzz" * False or i) -> printf(2)
print("Fizz" * (3 % 3 == 0) + "Buzz" * (3 % 5 == 0) or i) -> print("Fizz" * True + "Buzz" * False or i) -> printf("Fizz")
....

This is how the "dank" one liner works.