13

I have a question about function calls in Python. Say I want to write a function called superLongFunc(expr). The function is super long and really hard to debug. I want to split the function into smaller helper functions for better readability, to something like smallFunc1(expr),smallFunc2(expr), etc.

My question is, does this affect the performance of the code at all? How exactly does calling functions work in Python? Does Python pass the variables to a function by reference? Or does it make a copy of the variable before feeding it to the function?

I know it's a pretty nooby question but it's been bugging me for a while. Thanks in advance!

Fred Foo
  • 355,277
  • 75
  • 744
  • 836
turtlesoup
  • 3,188
  • 10
  • 36
  • 50
  • 1
    There are empirical ways of figuring this out as well. The `timeit` and `dis` module come to mind. Sometimes it's useful to be able to test things out for yourself. – Joel Cornett Jul 20 '12 at 20:06

1 Answers1

16

Python uses a system sometimes called call-by-object. Nothing is copied when you pass arguments to a function. The names of the function arguments are locally bound within the function body, to the same objects provided in the function call.

This is different from what most people think of as "call by value", because it doesn't copy the objects. But it's also different from "call by reference" because the reference is to the object --- a new name is bound, but to the same object. This means that you can mutate the passed-in object, but rebinding the name inside the function has no effect outside the function. A simple example of the difference:

>>> def func(x):
...     x[0] = 2 # Mutating the object affects the object outside the function
>>> myList = [1]
>>> func(myList)
>>> myList # myList has changed
[2]
>>> def func(x):
...     x = 2 # rebinding name has no effect outside the function
>>> myList = [1]
>>> func(myList)
>>> myList # myList is unaffected
[1]

My simple way of thinking about this is that assignment to a bare name --- that is, statements of the form name = value --- is completely different from everything else in Python. The only way to operate on names and not on values is to do name = value. (There are nitpicky exceptions to this, like mucking around with globals() and so on, but these are dangerous territory anyway.) In particular name = value is different from obj.prop = value, obj[0] = value, obj += value, and other similar things that look like assignment but actually operate on objects and not on names.

That said, function calls in Python have a certain amount of overhead just in themselves (for setting up the execution frame, etc.). If a function is called many times, this overhead can cause a noticeable performance impact. So splitting one function into many could still have a performance impact, since each additional function call will add some overhead.

BrenBarn
  • 242,874
  • 37
  • 412
  • 384
  • 3
    What you are describing is just call-by-value, where the values are references to objects. – Marcin Jul 20 '12 at 19:44
  • Yes, that is another way of describing call by object. – BrenBarn Jul 20 '12 at 19:50
  • It's also pretty much the same as call by reference, at least as implemented in C++, with the difference that many built-in types are immutable in Python :) – Fred Foo Jul 20 '12 at 19:53
  • In the second example you gave above do you mean the reference is the name? I don't see why func(myList) didn't do anything to myList =[ – turtlesoup Jul 20 '12 at 19:54
  • 2
    The difference between the two examples is that the first one operates on the *object* that `x` points to, but the second operates on the *name* `x` itself (by assigning it to a new object). That's why the second one has no effect. – BrenBarn Jul 20 '12 at 19:56
  • 1
    @larsmans C++ call-by-reference is the usual type of call-by-reference, which is to say that assignment in a function will affect the value of the variable in the calling context. In fact, here's the evidence: http://ideone.com/rW3Hl You can't do that in python (unless you actually pass the `locals()` dict to the function). – Marcin Jul 20 '12 at 19:59
  • @Marcin: But you can modify a value when you pass a mutable object to a Python function and call a mutating method. Actually there are two differences in Python, `=` always rebinds when given a variable on the LHS and there exist immutable types that still support `x += y` etc. through a rewrite to `x = x + y`. But I still call that call-by-reference. – Fred Foo Jul 20 '12 at 20:04
  • @BrenBarn How does Python know whether you're trying to operate on the object or on the name? Because when I tried to change the func() to x +=1 and pass myList into it I got an error – turtlesoup Jul 20 '12 at 20:05
  • 1
    @Jimster: when you try `x += 1`, the method `__iadd__` (in-place add) is looked up on the object. In the case of a `list`, that method is found and called. It then fails because adding an integer to a list is not allowed. (It would work for `x += [1]`.) If `__iadd__` is not found, the expression is "rewritten" to `x = x + 1` so `__add__` is called instead. `x = x + 1` rebinds the name. – Fred Foo Jul 20 '12 at 20:07
  • I added a little note to my answer. If you want more info about this, searching stackoverflow for various terms like "Python name value assignment" will turn up plenty of information. – BrenBarn Jul 20 '12 at 20:13
  • @larsmans I see, but that still doesn't answer my doubt. In the second example, the value of myList didn't change because Python saw the type of x didn't match right? – turtlesoup Jul 20 '12 at 20:14
  • 4
    It has nothing to do with types. Assignments of the form `name = value` rebind names. Everything else operates on values. – BrenBarn Jul 20 '12 at 20:14
  • 1
    @larsmans You can call that call-by-reference if you like, but then you're usng the term to mean something completely different from what most other people mean when they use the term. – Marcin Jul 20 '12 at 20:33
  • @Marcin: then maybe I've got my definitions wrong, and I should refresh my PL theory. – Fred Foo Jul 20 '12 at 20:41