This is by design. A function accesses variables in its more general lexical scope (in this case, the module's top-level ) and can modify them at will. There is no copy being made. Even this is why you should not use changeable data as default parameters of functions:
>>> def foo(x=[]):
... x.append('bar')
... print x
...
>>> foo()
['bar']
>>> foo()
['bar', 'bar']
>>> foo()
['bar', 'bar', 'bar']
If you need within your f
function to have a list identical to the one defined at the time of the call, which you can modify at will without interfering with the original list, the only way out is to make a copy yourself. And your way of copying a list is ok.
P.S. By reading your question, I realize that this is independent of the function accessing a variable from outside or not:
x = [1, 2]
def f(y):
y[1] = "novo valor"
return y
f(x)
def g():
x[1] = "novo valor"
return x
g()
Since the parameter is changeable, changes made thereto will persist after the function has terminated.
Update: If you are looking for a generic way of copying function arguments to prevent them from being accidentally changed, one way is to create a decorator that deepcopy of all your arguments:
>>> from copy import deepcopy
>>> def sem_efeitos_colaterais(f):
... def ret(*args, **kwargs):
... args = [deepcopy(x) for x in args]
... kwargs = { deepcopy(k):deepcopy(v) for k,v in kwargs.items() }
... return f(*args, **kwargs)
... return ret
...
>>> @sem_efeitos_colaterais
... def foo(x):
... x.append(3)
... return x
...
>>> x = [1,2]
>>> foo(x)
[1, 2, 3]
>>> x
[1, 2]
Note that this only guarantees against changes in parameters, not against cases where the function accesses variables in its more general lexical scope (eg, the
g
function in the previous example). And, of course, it's good to point out that making copies of everything has a negative impact on performance ...