Difference between , and + when printing output

Viewed 1683

Quick and probably dumb question. When I need to join text and numerical values for output, I would typically use:

number = 4
print("Two plus two = " + str(number))

But sometimes I see:

number = 4
print("Two plus two =",number)

The second example does not require type casting and adds a leading space, but otherwise they do the same thing. Anyone know why there are two ways of doing the same thing? Which method is 'better'?

3 Answers

It is important to understand that in print("Two plus two = " + str(number)) the concatenation operation has nothing to do with print and happens before print is called.

Let's do some timings:

from timeit import Timer

def with_concat():
    print("Two plus two = " + str(4))

def no_concat():
    print("Two plus two =", 4)

print(min(Timer(with_concat).repeat(100, 100)))
print(min(Timer(no_concat).repeat(100, 100)))

Outputs

0.0006760049999998685
0.0013034899999999627

Counter-intuitively (see my comment to the question, string concatenation may be expensive) the example with concatenation is actually faster (by a factor of 2!) in a reproducible manner. But why?

let's inspect the byte code:

from dis import dis

def with_concat():
    print("Two plus two = " + str(4))

def no_concat():
    print("Two plus two =", 4)

dis(with_concat)

outputs

 0 LOAD_GLOBAL              0 (print)
 2 LOAD_CONST               1 ('Two plus two = ')
 4 LOAD_GLOBAL              1 (str)
 6 LOAD_CONST               2 (4)
 8 CALL_FUNCTION            1
10 BINARY_ADD
12 CALL_FUNCTION            1
14 POP_TOP
16 LOAD_CONST               0 (None)
18 RETURN_VALUE

While

dis(no_concat)

outputs

 0 LOAD_GLOBAL              0 (print)
 2 LOAD_CONST               1 ('Two plus two =')
 4 LOAD_CONST               2 (4)
 6 CALL_FUNCTION            2
 8 POP_TOP
10 LOAD_CONST               0 (None)
12 RETURN_VALUE

Judging by the byte code it also looks like no_concat should be faster (shorter, simpler code).

The delay must come from the C source code (in the case of CPython at least).

Let's look at the relevant lines:

static PyObject *
builtin_print(PyObject *self, PyObject *args, PyObject *kwds)
{
    .
    .
    .

    for (i = 0; i < PyTuple_Size(args); i++) {
        if (i > 0) {
            if (sep == NULL)
                err = PyFile_WriteString(" ", file);
            else
                err = PyFile_WriteObject(sep, file,
                                         Py_PRINT_RAW);
            if (err)
                return NULL;
        }
        err = PyFile_WriteObject(PyTuple_GetItem(args, i), file,
                                 Py_PRINT_RAW);
        if (err)
            return NULL;
    }

    .
    .
    .
}

To me it seems like the overhead of using print(*args) is due to the repetitive calls to PyTuple_GetItem(args, i), and that the benefit of using it over print(a + lot + of + concatenated + strings) is there only when the number of concatenated strings is large enough to make the concatenation the bottleneck (ie slower than the repetitive calls to PyTuple_GetItem(args, i)).

As @DeepSpace commented, using the + operator concatenating the strings, using two args just prints the strings one by one using the sep argument as a seperator (default of blank space). For further information you can check the Python 3.4 source code.

This is an old thread but I'll try to answer this in a simpler way as I once had the exact same doubt. I'm not sure if this is the correct approach and please correct me if I'm wrong.

So if you check the python print function documentation:

print(*objects, sep=' ', end='\n', file=sys.stdout, flush=False). Print objects to the text stream file, separated by sep and followed by end. sep, end, file, and flush, if present, must be given as keyword arguments.

All non-keyword arguments are converted to strings like str() does and written to the stream, separated by sep and followed by end.

For example let's check a sample code:

age = 21
name = "Swapneil"
str1 = "Name is: "+name"+" and age is: "+str(age)

print(str1)
print("Name is:",name,"and age is",age)

The output of both print statements is same here, but in the first print str1 is a single object as the strings are concatenated and it requires a type case where required. However, in the second print statement as all non-keyword arguments are "," separated. It will convert all the arguments into string separated by default value of sep argument and throw the same result.

The only difference here is str1 is a single object and in the second print there are multiple objects (The first argument of print function is *objects) it can take as many positional arguments as we give.

Related