Default function values and variables in memory - python-3.x

I came across the following code:
def f(x,l=[]):
for i in range(x):
l.append(i*i)
print(l)
f(2)
f(3,[3,2,1])
f(3)
which returns:
[0, 1]
[3, 2, 1, 0, 1, 4]
[0, 1, 0, 1, 4]
my question is this:
Why, in the third function call f(3), is l not overwritten with l=[] in def (f(x,l=[]) and then return [0,1,4]? What is going on here??
When I do this instead, things work as expected..:
l=[]
[l.append(i*i) for i in range(2)]
print(l)
l=[1, 2, 3]
[l.append(i*i) for i in range(3)]
print(l)
l=[]
[l.append(i*i) for i in range(3)]
print(l)
which prints:
[0, 1]
[1, 2, 3, 0, 1, 4]
[0, 1, 4]
I know the answer has to do with the way python stores data in memory, but I can't wrap my head around this one, even after exploring with id(l) (which prints out the same ID of l for both f(2) and f(3) but a different ID for f(3, [3,2,1]).
Thank you for any insight!

The correct way of doing this is to have a local variable lst inside the function which takes care of the operation you are doing inside the function, rather than operating on a variable you are passing like so.
def f(x,lst=None):
lst = lst or []
for i in range(x):
lst.append(i*i)
print(lst)
f(2)
f(3,[3,2,1])
f(3)
Which gives you output as
[0, 1]
[3, 2, 1, 0, 1, 4]
[0, 1, 4]
The reason the previous approach doesn't work is because when a mutable value as list or dictionary is detected in a default value for an argument, they are evaluated only once at function definition time, which means that modifying the default value of the argument will affect all subsequent calls of the function.
But in my changed function, I am making a new local variable to operate on the list, which is a copy of the argument passed, so we don't get this issue here.

Related

Difference between [range(5)] and list(range(5))

I'm trying to make a list through a range() using 2 different methods,
When I run it in python terminal, it return False
list(range(5))==[range(5)]
>>> list(range(5))
[0, 1, 2, 3, 4]
>>> [range(5)]
[range(0, 5)]
I expect the output of [range(5)] = [0, 1, 2, 3, 4],
but it was [range(0, 5)]
list() is a function call, it takes an iterator as input and converts it into a list.
[] simply wraps whatever we put inside the bracket with a list.
So counter to your example, list(32) will throw an error but [32] will make a list with 32 as its element.

parsing infinite list into one list

I have this task building a code using recursion. The task is taking a list who can have an infinite amount of lists inside it and making it one list.
This is so far what I have:
def flat_list(array):
new_array =[]
for i in range(0,len(array)):
if len(str(array[i])) > 1:
flat_list(array[i:i+1])
else:
new_array += array[i:len(str(array))-1]
return new_array
These are the tests it needs to pass:
assert flat_list([1, 2, 3]) == [1, 2, 3]
assert flat_list([1, [2, 2, 2], 4]) == [1, 2, 2, 2, 4]
assert flat_list([[[2]], [4, [5, 6, [6], 6, 6, 6], 7]]) == [2, 4, 5, 6, 6, 6, 6, 6, 7]
assert flat_list([-1, [1, [-2], 1], -1]) == [-1, 1, -2, 1, -1]
Mine returns this:
flat_list([1, [2, 2, 2], 4])
my result: [1,[2,2,2],4]
right answer: [1,2,2,2,4]
I think my problem is with creating a new local variable of the new_array at each entry, How can I return one list with no other lists inside it?
This task is without using numpy, but if you can also show me how it can be done with numpy it will really educate me. :)
Thank you for answering
Try this:
def flatten(S):
if S == []:
return S
if isinstance(S[0], list):
return flatten(S[0]) + flatten(S[1:])
return S[:1] + flatten(S[1:])
How it works:
1. The list is passed as an argument to a recursive function to flatten the list.
2. In the function, if the list is empty, the list is returned.
3. Otherwise the function is recursively called with the sublists as the parameters until the entire list is flattened.
I suggest you the following suggestion: it iterates over the list and if the encountered item is a list, then it recursively flattens it before appending it to the resulting flattened list:
def flat_list(aList):
result = []
for i in aList:
if isinstance(i, list):
result += flat_list(i)
else:
result.append(i)
return result

How to write a function where the original list of integers is changed without using return?

Let us say we have a list of integers:
list = [6, 4, 1, 4, 4, 4, 4, 4, 2, 1]
I now wrote a function which returns another list with all the integers from the list above without repeats.
def no_repeats(s):
new_list = []
for number in s:
if new_list.count(number) < 1:
new_list.append(number)
return(new_list)
The new_list returns [6, 4, 1, 2] which is good! My question is how I would now write two similar functions:
A function clean(s) which does not return a new list like the function above, but changes the original list by deleting all the numbers that repeat. Thus, the result has to be the same and the function must not include "return" or create a new list. It must only clean the original list.
A function double(s) which, again, changes the original list (does not return a new list!) but this time, by doubling every number in the original list. Thus, double(list) should change the original list above to:
[6, 6, 4, 4, 1, 1, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 2, 2, 1, 1]
Thank you for all the help!
Removing duplicates inplace without preserving the order:
def no_repeats(L):
L[:] = set(L)
There are several variations possible (preserve order, support non-hashable items, support item that do not define total ordering) e.g., to preserve order:
from collections import OrderedDict
def no_repeats(L):
L[:] = OrderedDict.fromkeys(L)
To double each element's value inplace:
def double(L):
for i in range(len(L)):
L[i] *= 2
To duplicate each element:
def duplicate_elements(L):
L[:] = [x for x in L for _ in range(2)]
>>> def clean(s):
... s[:] = [s[i] for i in range(len(s)) if s[i] not in s[:i]]
...
>>> st = [1, 2, 3, 2, 1]
>>> clean(st)
>>> st
[1, 2, 3]
>>> def double(s):
... s[:] = [s[i//3] for i in range(3*len(s)) if i % 3]
...
>>> st = [1, 2, 3, 2, 1]
>>> double(st)
>>> st
[1, 1, 2, 2, 3, 3, 2, 2, 1, 1]
neither is particularly efficient nor pythonic, yet do address the OP question
def double(s):
... s[:] = [s[i//2] for i in range(2*len(s))]
will also do the trick, with a little less obsfucation

How does call by reference in Python work?

Please explain when calling function first time the code follows call by reference. But when calling function second time it does not follow the same.
##functions
num = [0,1,2,3,4]
def first(num1):
num1.append([5, 6]);
print(num1)
def second(num1):
num1 = num1 + [7, 8];
print(num1)
print(num)
first(num)
print(num)
second(num)
print(num)
In first the argument is passed by reference and modified inside the function using that reference.
In second the name num1 is used but also assigned a new value. The num1 is bound to a new value and this does not change the original passed in.
In first method you are appending a list to num1 that basically points to num at that point, So basically it refers to the same location while in second method you are adding a list to num1 which create a new location.
You can understand better by using the function id() which basically gives a unique integer value for every object.
##functions
num = [0,1,2,3,4]
def first(num1):
print(id(num1)) # obj reference
num1.append([5, 6]);
print(id(num1))
print(num1)
def second(num1):
print(id(num1))
num1 = num1 + [7, 8];
print(id(num1))
print(num1)
print(num)
first(num)
print(num)
second(num)
print(num)
If you run the above code, you will find that the object reference is same in first method before and after operation, while in second method it is different.
OUTPUT:
[0, 1, 2, 3, 4]
140360682379784
140360682379784
[0, 1, 2, 3, 4, [5, 6]]
[0, 1, 2, 3, 4, [5, 6]]
140360682379784
140360672905160
[0, 1, 2, 3, 4, [5, 6], 7, 8]
[0, 1, 2, 3, 4, [5, 6]]
And also this article would help you understand better about the concept of call by value and call by reference in python.
Is Python call-by-value or call-by-reference? Neither
You can try the code here: Code

Need help dealing with the checker here

Okay, so I have this function that I need to create and I think the code checker is somehow flawed and I tried manage it but my code still seems to fail
def reversecomp(L):
""" assumes L is a list of lists whose elements are ints
Mutates L such that it reverses its elements and also
reverses the order of the int elements in every element of L.
It does not return anything.
"""
if L == []:
return L
elif type(L) == int:
return L
else:
return reversecomp(L[1:]) + [reversecomp(L[0])]
def run_code(L):
return reversecomp(L)
print(L)
The question states that you need to mutate L. Your code must work when you do this:
L = [[0, 1, 2], [1, 2, 3], [3, 2, 1], [10, -10, 100]]
reversecomp(L)
print(L)
Test: run_code([[0, 1, 2], [1, 2, 3]])
Your output:
[[3, 2, 1], [2, 1, 0]]
Correct output:
[[3, 2, 1], [2, 1, 0]]
None
The spec says "It does not return anything"; your program does.
L is a list of lists of ints
Okay, so why are you checking type(L) == int when type(L) == list is always true, per the specification?
Mutates L
You're not mutating L at all; you're returning a new list. Mutating L means doing something like L[...] = xxx.
It does not return anything.
You shouldn't be using the return keyword at all in reversecomp.

Resources