Deleting an default argument inside function in Python - python

I have tried the following in the console:
>>> def f(l=[]):
... l.append(1)
... print(l)
... del l
...
>>> f()
[1]
>>> f()
[1, 1]
What I don't understand is how the interpreter is still able to find the same list l after the delete instruction.
From the documentation l=[] should be evaluated only once.

The variable is not the object. Each time the function is called, the local variable l is created and (if necessary) set to the default value.
The object [], which is the default value for l, is created when the function is defined, but the variable l is created each time the function runs.

To delete a element for the list,del l[:] should be used.If u use just l the list will remain itself.
def f(l=[]):
l.append(1)
print(l)
del l[:]
print(l)
>>> f()
[1] #element in the list
[] #list after deletion of the element
>>> f()
[1]
[]
>>> f()
[1]
[]

Related

Python Default Argument List -- Inconsistent behavior

I have a situation in which the value of the default argument in a function head is affected by an if-clause within the function body. I am using Python 3.7.3.
function definitions
We have two functions f and j. I understand the behavior of the first two function. I don't understand the second function's behavior.
def f(L=[]):
print('f, inside: ', L)
L.append(5)
return L
def j(L=[]):
print('j, before if:', L)
if L == []:
L = []
print('j, after if: ', L)
L.append(5)
return L
function behavior that I understand
Now we call the first function three times:
>>> print('f, return: ', f())
f, inside: []
f, return: [5]
>>> print('f, return: ', f())
f, inside: [5]
f, return: [5, 5]
>>> print('f, return: ', f())
f, inside: [5, 5]
f, return: [5, 5, 5]
The empty list [] is initialized on the first call of the function. When we append 5 to L then the one instance of the list in the memory is modified. Hence, on the second function call, this modified list is assigned to L. Sounds reasonable.
function behavior that I don't unterstand
Now, we call the third function (j) and get:
>>> print('j, return: ', j())
j, before if: []
j, after if: []
j, return: [5]
>>> print('j, return: ', j())
j, before if: []
j, after if: []
j, return: [5]
>>> print('j, return: ', j())
j, before if: []
j, after if: []
j, return: [5]
According to the output, L is an empty list in the beginning of each call of the function j. When I remove the if-clause, in the body of function j the function is equal to function f and yields the same output. Hence, the if-clause seems to have some side effect. Testing for len(L) == 0 instead of L == [] in j has the same effect.
related to
My question is related to:
Stackoverflow Question Python Default Arguments Evaluation
Stackoverflow Question "Least Astonishment" and the Mutable Default Argument and
the Python Tutorial Section 4.7.1. (Default Argument Values)
But the answers to these question and the tutorial answer only, what I already know.
Modify your print statements to also print id(L):
def j(L=[]):
print('j, before if:', L, id(L))
if L == []:
L = []
print('j, after if: ', L, id(L))
L.append(5)
return L
Now check your results:
>>> j()
j, before if: [] 2844163925576
j, after if: [] 2844163967688
[5]
Note the difference in the IDs. By the time you get to the portion of the function where you modify L, it no longer refers to the same object as the default argument. You've rebound L to a new list (with the L = [] in the body of the if statement), and as a result, you are never changing the default argument.
if all boils down to this:
if L == [5]:
L = []
Here you're not changing the value of the default argument, just binding locally to a new name.
You can do that instead:
if L == [5]:
L.clear()
or
if L == [5]:
L[:] = []
to clear the data of the parameter object.
def a():
print "assigning default value"
return []
def b(x=a()):
x.append(10)
print "inside b",x
return x
print b()
print b()
print b()
try running this. You'll see that default value is not assigned every time you run the function
OUTPUT
assigning default value
inside b [10]
[10]
inside b [10, 10]
[10, 10]
inside b [10, 10, 10]
[10, 10, 10]
only once it called the 'a' function to the default value. rest is very well explained above about the compilation of a method so not repeating the same

What is the difference between del a_list[:] and a_list = [] in a function?

This is just a question asking for the difference in the code.
I have several lists ie. a=[], b=[], c=[], d=[]
Say if I have a code that appends to each list, and I want to reset all these lists to its original empty state, I created a function:
def reset_list():
del a[:]
del b[:]
del c[:]
del d[:]
So whenever I call reset_list() in a code, it removes all the appended items and set all lists to []. However, the one below doesn't work:
def reset_list():
a = []
b = []
c = []
d = []
This might be a stupid question but I was wondering why the second one wouldn't work.
When you do del a[:] then it looks for the variable a (including outer contexts) and then performs del found_a[:] on it.
But when you use a = [] it creates a name a in the current context and assigns an empty list to it. When the function exits the variable a from the function is not "accessible" anymore (destroyed).
So in short the first works because you change the a from an outer context, the second does not work because you don't modify the a from the outer context, you just create a new a name and temporarily (for the duration of the function) assigns an empty list to it.
There's a difference between del a[:] and a = []
Note that these actually do something different which becomes apparent if you have additional references (aliases) to the original list. (as noted by #juanpa.arrivillaga in the comments)
del list[:] deletes all elements in the list but doesn't create a new list, so the aliases are updated as well:
>>> list_1 = [1,2,3]
>>> alias_1 = list_1
>>> del alist_1[:]
>>> list_1
[]
>>> alias_1
[]
However a = [] creates a new list and assigns that to a:
>>> list_2 = [1,2,3]
>>> alias_2 = list_2
>>> list_2 = []
>>> list_2
[]
>>> alias_2
[1, 2, 3]
If you want a more extensive discussion about names and references in Python I can highly recommend Ned Batchelders blog post on "Facts and myths about Python names and values".
A better solution?
In most cases where you have multiple variables that belong together I would use a class for them. Then instead of reset you could simply create a new instance and work on that:
class FourLists:
def __init__(self):
self.a = []
self.b = []
self.c = []
self.d = []
Then you can create a new instance and work with the attributes of that instance:
>>> state = FourLists()
>>> state.a
[]
>>> state.b.append(10)
>>> state.b.extend([1,2,3])
>>> state.b
[10, 1, 2, 3]
Then if you want to reset the state you could simply create a new instance:
>>> new_state = FourLists()
>>> new_state.b
[]
You need to declare a,b,c,d as global if you want python to use the globally defined 'versions' of your variables. Otherwise, as pointed out in other answers, it will simply declare new local-scope 'versions'.
a = [1,2,3]
b = [1,2,3]
c = [1,2,3]
d = [1,2,3]
def reset_list():
global a,b,c,d
a = []
b = []
c = []
d = []
print(a,b,c,d)
reset_list()
print(a,b,c,d)
Outputs:
[1, 2, 3] [1, 2, 3] [1, 2, 3] [1, 2, 3]
[] [] [] []
As pointed out by #juanpa.arrivillaga, there is a difference between del a[:] and a = []. See this answer.
The 1st method works because:
reset_list() simply deletes the contents of the four lists. It works on the lists that you define outside the function, provided they are named the same. If you had a different name, you'd get an error:
e = [1,2,3,4]
def reset_list():
del a[:] #different name for list
NameError: name 'e' is not defined
The function will only have an effect if you initialize the lists before the function call. This is because you are not returning the lists back after the function call ends:
a = [1,2,3,4] #initialize before function definition
def reset_list():
del a[:]
reset_list() #function call to modify a
print(a)
#[]
By itself the function does not return anything:
print(reset_list())
#None
The 2nd method doesn't work because:
the reset_list() function creates 4 empty lists that are not pointing to the lists that may have been defined outside the function. Whatever happens inside the function stays inside(also called scope) and ends there unless you return the lists back at the end of the function call. The lists will be modified and returned only when the function is called. Make sure that you specify the arguments in reset_list(a,..) in the function definition:
#function definition
def reset_list(a):
a = []
return a
#initialize list after function call
a = [1,2,3,4]
print("Before function call:{}".format(a))
new_a = reset_list(a)
print("After function call:{}".format(new_a))
#Output:
Before function call:[1, 2, 3, 4]
After function call:[]
As you've seen, you should always return from a function to make sure that your function "does some work" on the lists and returns the result in the end.
The second function (with a = [ ] and so on) initialises 4 new lists with a local scope (within the function). It is not the same as deleting the contents of the list.

Copy values from one list to another without altering the reference in python

In python objects such as lists are passed by reference. Assignment with the = operator assigns by reference. So this function:
def modify_list(A):
A = [1,2,3,4]
Takes a reference to list and labels it A, but then sets the local variable A to a new reference; the list passed by the calling scope is not modified.
test = []
modify_list(test)
print(test)
prints []
However I could do this:
def modify_list(A):
A += [1,2,3,4]
test = []
modify_list(test)
print(test)
Prints [1,2,3,4]
How can I assign a list passed by reference to contain the values of another list? What I am looking for is something functionally equivelant to the following, but simpler:
def modify_list(A):
list_values = [1,2,3,4]
for i in range(min(len(A), len(list_values))):
A[i] = list_values[i]
for i in range(len(list_values), len(A)):
del A[i]
for i in range(len(A), len(list_values)):
A += [list_values[i]]
And yes, I know that this is not a good way to do <whatever I want to do>, I am just asking out of curiosity not necessity.
You can do a slice assignment:
>>> def mod_list(A, new_A):
... A[:]=new_A
...
>>> liA=[1,2,3]
>>> new=[3,4,5,6,7]
>>> mod_list(liA, new)
>>> liA
[3, 4, 5, 6, 7]
The simplest solution is to use:
def modify_list(A):
A[::] = [1, 2, 3, 4]
To overwrite the contents of a list with another list (or an arbitrary iterable), you can use the slice-assignment syntax:
A = B = [1,2,3]
A[:] = [4,5,6,7]
print(A) # [4,5,6,7]
print(A is B) # True
Slice assignment is implemented on most of the mutable built-in types. The above assignment is essentially the same the following:
A.__setitem__(slice(None, None, None), [4,5,6,7])
So the same magic function (__setitem__) is called when a regular item assignment happens, only that the item index is now a slice object, which represents the item range to be overwritten. Based on this example you can even support slice assignment in your own types.

Modifying a list outside of recursive function in Python

There is a multidimensional list with not clear structure:
a=[[['123', '456'], ['789', '1011']], [['1213', '1415']], [['1617', '1819']]]
And there is a recursive function, which operates the list:
def get_The_Group_And_Rewrite_It(workingList):
if isinstance(workingList[0],str):
doSomething(workingList)
else:
for i in workingList:
get_The_Group_And_Rewrite_It(i)
Through time the get_The_Group_And_Rewrite_It() should get a list, for instance, ['123','456'], and as soon as it get it, doSomething function should rewrite it with ['abc'] in entire list.
The same with other lists of format[str,str,...]. In the end I should get something like
a=[[['abc'], ['abc']], [['abc']], [['abc']]]
I see it would be easy in C++, using *links, but how to do that in Python?
For this case, you can use slice assignment:
>>> a = [[['123', '456']]]
>>> x = a[0][0]
>>> x[:] = ['abc']
>>> a
[[['abc']]]
>>> def f(workingList):
... if isinstance(workingList[0],str):
... workingList[:] = ['abc']
... else:
... for i in workingList:
... f(i)
...
>>> a=[[['123', '456'], ['789', '1011']], [['1213', '1415']], [['1617', '1819']]]
>>> f(a)
>>> a
[[['abc'], ['abc']], [['abc']], [['abc']]]

Python list.append as an argument

Why does the following code give 'None'? How can I resolve this?
def f1(list1):
f2(list1.append(2))
def f2(list1):
print(list1)
f1([1])
What also doesn't work:
def f1(list1):
arg1 = list1.append(2)
f2(arg1)
In general, Python methods that mutate an object (such as list.append, list.extend, or list.sort) return None.
If you wish to print out the new list:
def f1(list1):
list1.append(2)
f2(list1)
It depends on what you want to do. If you want list1 to have changed after a call to f1, use
def f1(list1):
list1.append(2)
f2(list1)
See what happens:
>>> l = [1]
>>> f1(l) # Modifies l in-place!
[1, 2]
>>> l
[1, 2]
If you don't want list1 to be changed:
def f1(list1):
f2(list1 + [2])
Now see this:
>>> l = [1]
>>> f1(l) # Leaves l alone!
[1, 2]
>>> l
[1]

Categories