Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Asterisk symbol in Python

I was researching some different ways to approach the classic FizzBuzz problem, and stumbled across this:

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

Are the asterisks shorthand for an if statement? If so, is this notation specific to print?

Thanks in advance.

like image 408
Patrick Avatar asked Dec 26 '22 10:12

Patrick


1 Answers

The asterisk in Python is actually just the standard multiplication operator *. It maps to the __mul__ method of the object it's operated on, and thus can be overloaded to have custom meanings. This has nothing to do whatsoever with if or print.

In the case of strings (str and unicode) it has been overloaded/overridden to mean repetition of strings such that "foo" * 5 evaluates to "foofoofoofoofoo".

>>> 'foo' * 5  # and the other way around 5 * "foo" also works
'foofoofoofoofoo'

and "Fizz" * (i % 3 == 0) is simply a "smart" shorthand for:

"Fizz" if i % 3 == 0 else ""

This is because the expression i % 3 == 0 evaluates to a boolean, and booleans are a subtype of integers in Python, so that True == 1 and False == 0, so that if you "multiply" a string with a boolean, you'll either get the same string out or the empty string.

Note: I would also like to note that in my experience/understanding, this type of programming style is not encouraged in Python—it makes the code less readable (to both newcomers as well as oldtimers) as well as not any faster (and in fact, likely, slower; see http://pastebin.com/Q92j8qga for a quick benchmark (but interestingly, not in PyPy: http://pastebin.com/sJtZ6uDm)).


And * also works on instances of list and tuple:

>>> [1, 2, 3] * 3
[1, 2, 3, 1, 2, 3, 1, 2, 3]

>>> (1, 2, 3) * 3
(1, 2, 3, 1, 2, 3, 1, 2, 3)

You can also define your own * operators for your types using what amounts to operator overloading in Python:

class Foo(object):
    def __mul__(self, other):
        return "called %r with %r" % (self, other)

print Foo() * "hello"  # same as Foo().__mul__("hello")

outputs:

called <__main__.Foo object at 0x10426f090> with 'hello'

The case of * being mapped to __mul__ also holds for "primitive" types such as int, float and others, so 3 * 4 is equivalent to (3).__mul__(4) (and same about other operators). In fact, you can even subclass int and provide a custom behavior for *:

class MyTrickyInt(int):
    def __mul__(self, other):
        return int.__mul__(self, other) - 1
    def __add__(self, other):
        return int.__add__(self, other) * -1

print MyTrickInt(3) * 4  # prints 11
print MyTrickyInt(3) + 2  # prints -5

...but please don't do that :) (in fact, it doesn't hurt to stay clean of subclassing concrete types altogether!)

like image 190
Erik Kaplun Avatar answered Jan 04 '23 14:01

Erik Kaplun