Scope of a function

hello,
I understand the classic example when a list gets changed, and the other variable that points to it also “changes”.
But why does it happen also when change occurs inside a function? Shouldn’t a function have a separate scope that prevents this?

# classic example
x = ['a','b','c']
y = x
x.append('D')
print(y) # ['a', 'b', 'c', 'D']
# shouldn't a function have a separate scope?
def myfunc(mlist):
    mlist.append('D')

x = ['a','b','c']
myfunc(x) 
print(x) # ['a', 'b', 'c', 'D']

There is one list object [‘a’, ‘b’, ‘c’] , that’s referred to by three different names: arr1 , arr2, and mlist. Calling myfunc() doesn’t copy the list object, just makes the local name refer to the same object.

2 Likes

right.
But wouldn’t it be better if calling a function would in fact create a copy of the list object?
(That’s why I posted it in the “Ideas” category. Also adjusting a code little bit.)

Wouldn’t you want to retain the option of having the function work on the original object? Then you have the choice of having it do that, or you can have the function create a copy of the original and work on the copy instead. It’s much more flexible as it is now.

1 Like

This is also about mutability.

  • function can’t modify immutable object outside it’s local scope.
  • function can modify mutable object in-place outside of it’s local scope
  • function can’t reassign mutable object outside of it’s local scope
# can't modify immutable object out of function scope

>>> i = 5
>>> def func(num):
...     num += 1
...
>>> func(i)
>>> i
5

# can modify mutable object in place

>>> l = list('abc')
>>> def func(list_):
...     list_[0] = 1
...
>>> func(l)
>>> l
[1, 'b', 'c']

# can't reassign

>>> l = list('abc')
>>> def func(list_):
...     list_ = [0]
...
>>> func(l)
>>> l
['a', 'b', 'c']
3 Likes

It is best to avoid posting code as pictures or screen captures. Instead, post it as formatted text, so that we can copy, paste and execute it for testing.

Please see Python Discourse Quick Start for useful information about this forum. In particular, take a look at How do I post code snippets, error messages etc? on that page for advice on how to properly format code and output for posting.

If you want that, you can do so easily with the list.copy() method:

>>> a = [1, 2, 3]
>>> b = a.copy()
>>> assert a == b and a is not b

a == b verifies that the content is the same, a is not b that they are different objects. You can do that

  • at call by myfunc(x.copy()) if the calling code wants to preserve x
  • or within myfunc() by mlist = mlist.copy() if you want myfunc() to behave as a ‘pure’ function (no side effects)

But (and one of the reasons this isn’t the default behaviour), that copy() takes time. Not noticeably so for our toy examples, but imagine a million-element list. That performance impact, and the ability to have ‘impure functions’ with side effects as @Quercus mentioned, make it preferable that the developer explicitly copies the data.

(For comparison between languages Evaluation strategy - Wikipedia)

Variables in Python don’t “point to” objects (i.e., you don’t have to write any code to “follow” the pointer, and you can’t “dereference a null pointer” - that’s where NameError and UnboundLocalError come from - and you can’t “dereference an invalid pointer” because they can’t be invalid in the first place). Instead, they name objects.

The function’s name for the object that was passed is a separate name for that object, even if it’s spelled the same way. However, it’s still naming the same object.

Assignment in Python doesn’t copy values. = is an assignment. So is passing an argument to a function. So are:

  • “augmented” assignments like +=
  • the walrus operator (:=), which works as an operator in an expression but still binds a name
  • setting the iteration variable for a for or while loop, generator expression or dict/set/list comprehension
  • any use of the as keyword: an explicitly renamed import, giving a name to an exception caught in an except block, or giving a name to a resource used in a with block
  • other uses of import
  • creating a function with def
  • creating a class with class

No; this denies you the option not to copy, which is essential in many cases (for example, built-in list methods like .append). Python doesn’t have to support separate pass-by-value and pass-by-reference schemes, while a programmer who needs a copy writes code that makes a copy, rather than relying on a rule that says a copy is made in certain circumstances. This is in accordance with the Zen: “implicit is better than explicit”, and “simple is better than complex”.

thanks for all responses,
just a follow-up,
I’m pasting here some useful links.

It seems these beginner errors are well documented in official Frequently Asked Questions :slight_smile: Programming FAQ — Python 3.11.5 documentation

namely:
Why did changing list ‘y’ also change list ‘x’?
also:
Why am I getting an UnboundLocalError when the variable has a value?

And also found this old moin-wiki:
Common Problems Beginners Have

Kind regards,
M.

1 Like