python3 list creation from class makes a global list rather than a series iterated ones - python-3.x

So here is the problem I am having. I am trying to iterate the makeAThing class, and then create a list for the iteration using the makeAList class. Instead of making seperate lists for each iteration of makeAThing, it is making one big global list and adding the different values to it. Is there something I am missing/don't know yet, or is this just how python behaves?
class ListMaker(object):
def __init__(self,bigList = []):
self.bigList = bigList
class makeAThing(object):
def __init__(self,name = 0, aList = []):
self.name = name
self.aList = aList
def makeAList(self):
self.aList = ListMaker()
k = []
x = 0
while x < 3:
k.append(makeAThing())
k[x].name = x
k[x].makeAList()
k[x].aList.bigList.append(x)
x += 1
for e in k:
print(e.name, e.aList.bigList)
output:
0 [0, 1, 2]
1 [0, 1, 2]
2 [0, 1, 2]
the output I am trying to achieve:
0 [0]
1 [1]
2 [2]
After which I want to be able to edit the individual lists and keep them assigned to their iterations

Your init functions are using mutable default arguments.
From the Python documentation:
Default parameter values are evaluated from left to right when the
function definition is executed. This means that the expression is
evaluated once, when the function is defined, and that the same
“pre-computed” value is used for each call. This is especially
important to understand when a default parameter is a mutable object,
such as a list or a dictionary: if the function modifies the object
(e.g. by appending an item to a list), the default value is in effect
modified. This is generally not what was intended. A way around this
is to use None as the default, and explicitly test for it in the body
of the function, e.g.:
def whats_on_the_telly(penguin=None):
if penguin is None:
penguin = []
penguin.append("property of the zoo")
return penguin
In your code, the default argument bigList = [] is evaluated once - when the function is defined - the empty list is created once. Every time the function is called, the same list is used - even though it is no longer empty.
The default argument aList = [] has the same problem, but you immediately overwrite self.aList with a call to makeAList, so it doesn't cause any problems.
To verify this with your code, try the following after your code executes:
print(k[0].aList.bigList is k[1].aList.bigList)
The objects are the same.
There are instances where this behavior can be useful (Memoization comes to mind - although there are other/better ways of doing that). Generally, avoid mutable default arguments. The empty string is fine (and frequently used) because strings are immutable. For lists, dictionaries and the sort, you'll have to add a bit of logic inside the function.

Related

How passing a container(list/dictionary) to a function would be different in python?

I have a data structure/container-python list/dictionary that I want to update based some computation. There are a few ways I have in mind:
1.
new_list=list() # initialised globally!
def func():
for i in range(5):
new_list.append(i) # updtaing here!
print('in function:', new_list)
pass
def main():
print('before:', new_list)
func()
print('after:',new_list)
if __name__ == '__main__':
main()
2.
def func(container):
for i in range(5):
container.append(i)
print('in function:', container)
pass
def main():
new_list=list()
print('before:', new_list)
func(new_list)
print('after:',new_list)
if __name__ == '__main__':
main()
3.
def func(container):
for i in range(5):
container.append(i)
print('in function:', container)
return container
def main():
new_list=list()
print('before:', new_list)
new_list = func(new_list)
print('after:',new_list)
if __name__ == '__main__':
main()
Could anyone explain what the difference between the 3 versions.? Logically all 3 of them work and even the results are same! but I am curious to know what the difference between these approaches and which is better?
Globals are evil. For this specific example, it might work. But if you would later decide to add a second list, you would have to rewrite your code or duplicate your functions. And it would be more complicated to write unit tests for the function.
I think there's nothing wrong with this approach in general.
This might be a matter of taste. Returning the object has no real purpose here, since the caller already has the object. And returning it might give the impression that the returned object is a different object. So personally, I wouldn't recommend this approach. I think this pattern is more often used in some other high-level object oriented programming languages as Java (or possibly C++), but I don't think it's very Pythonic.
PS: The pass statements do not have any effect. Why did you add these?
UPDATE: Extending a bit on your related question about how arguments are passed (by value or reference) and how that impacts the code:
In Python, all types are classes, and are passed by reference. When you assign a value to a local variable (e.g. the function argument), a new reference is made, but the caller still refers to the original object. However, when you modify the contents of the object, the caller "sees" that changes as well. Simply said, the difference is whether the statement includes an assignment operator (=) or not.
With integers, you would always create a new integer object using an assignment (e.g. x = 3, or even x += 3). Also strings are immutable, so you cannot modify a string in a function, but only create a new string (e.g. word = word.lower()).
If you modify a list using one of its class methods, such as list.append(), you update the original object. But if you create and assign a new list, the original list will not be changed. So, to clear a list in a function, you could use container.clear() but not container = []. I hope the following example clarifies this:
def add_numbers_to_container(container):
for i in range(5):
container.append(i)
def clear1(container):
container = []
# This creates a new list and assigns it to the local variable.
# The original list is not modified!
def clear2(container):
container.clear()
# This clears the list that was passed as argument.
def main():
new_list = []
print(new_list) # []
add_numbers_to_container(new_list)
print(new_list) # [0, 1, 2, 3, 4]
clear1(new_list)
print(new_list) # STILL [0, 1, 2, 3, 4] !
clear2(new_list)
print(new_list) # []
if __name__ == '__main__':
main()
ALSO NOTE: if you have a number of functions/methods that are processing the same data, it's a good practice to create a class for it. This has both benefits: you don't have to pass the list to each function, but you don't have to create global variables as well. So you could easily handle multiple lists with the same code. See the following example code.
Method 4:
class MyContainer:
def __init__(self):
self.container = []
# Here the container is initialized with an empty list.
def add_numbers(self, start, stop):
for i in range(start, stop):
self.container.append(i)
def clear(self):
# Both of the following lines are correct (only one is necessary):
self.container = []
self.container.clear()
def print(self):
print(self.container)
def main():
# You could even create multiple independent containers, and use the
# same functions for each object:
container1 = MyContainer()
container2 = MyContainer()
container1.print() # []
container2.print() # []
container1.add_numbers(0, 5)
container2.add_numbers(5, 8)
container1.print() # [0, 1, 2, 3, 4]
container2.print() # [5, 6, 7]
container1.clear()
container1.print() # []
if __name__ == '__main__':
main()
When you pass the object as a parameter (example 2) it should make a so-called by-reference passage so it doesn't copy the variable but just pass the pointer to that variable so it's basically the same as using a global variable, using the addictional return container (example 3) is redoundant for lists because they're passed as pointers. Instead for integers andother classes there is a big difference because the copy by value and copy by reference have differents effects. The first create a local copy and the changes won't affect the global variable while the second method would affect the global variable.

Recursion happens too many times and list is not iterable

I'm trying to make a secret santa programm. The input is in form of the list of names of people g. ["John", "Bob", "Alice"] and the list of emials ["John#gmail.com", "Bob#gmail.com", "Alice#outlook.com"]. I need to generate pairs of email adress and a random name which doesn't belong to the said email adress. For this I have written the function compare.
def compare(list_of_names, list_of_emails):
zipped_lists = zip(list_of_emails, list_of_names)
random.shuffle(list_of_emails)
zipped_shuffled_lists = zip(list_of_emails, list_of_names)
for pair in zipped_lists:
for shuffle_pair in zipped_shuffled_lists:
if shuffle_pair == pair:
return compare(list_of_names, list_of_emails)
return zipped_shuffled_lists
But instead of shuffling like it should it just creates a recursion. i still can't find out why. After a finite amount of time it should create two different lists that work. Also the shuffled_list_of_emails is not iterable, why?
EDIT:changed the code with shuffle because it works in place
zip is lazy!
I'm not sure why, but I'm too excited about this right now, so the answer might be a bit messy. Feel free to ask for clarification)
Let's step through your code:
def compare(list_of_names, list_of_emails):
# the `zip` object doesn't actually iterate over any of its arguments until you attempt to iterate over `zipped_lists`
zipped_lists = zip(list_of_emails, list_of_names)
# modify this IN-PLACE; but the `zip` object above has a pointer to this SAME list
random.shuffle(list_of_emails)
# since the very first `zip` object has `list_of_emails` as its argument, AND SO DOES THE ONE BELOW, they both point to the very same, SHUFFLED (!) list
zipped_shuffled_lists = zip(list_of_emails, list_of_names)
# now you're iterating over identical `zip` objects
for pair in zipped_lists:
for shuffle_pair in zipped_shuffled_lists:
# obviously, this is always true
if shuffle_pair == pair:
# say "hello" to infinite recursion, then!
return compare(list_of_names, list_of_emails)
return zipped_shuffled_lists
Let's recreate this in the Python interpreter!
>>> List = list(range(5))
>>> List
[0, 1, 2, 3, 4]
>>> zipped_1 = zip(List, range(5))
>>> import random
>>> random.shuffle(List)
>>> zipped_2 = zip(List, range(5))
>>> print(List)
[4, 2, 3, 0, 1]
>>> zipped_1, zipped_2 = list(zipped_1), list(zipped_2)
>>> zipped_1 == zipped_2
True
You see, two different zip objects applied to the same list at different times (before and after that list is modified in-place) produce the exact same result! Because zip doesn't do the zipping once you do zip(a, b), it will produce the zipped... uh, stuff... on-the-fly, while you're iterating over it!
So, to fix the issue, do not shuffle the original list, shuffle its copy:
list_of_emails_copy = list_of_emails.copy()
random.shuffle(list_of_emails_copy)
zipped_shuffled_lists = zip(list_of_emails_copy, list_of_names)
There's correct answer from #ForceBru already. But a will contribute a little.
You should avoid zip's lazy evaluation and unfold zips with, for example, list:
def compare(list_of_names, list_of_emails):
zipped_lists = list(zip(list_of_emails, list_of_names)) # eager evaluation instead of lazy
random.shuffle(list_of_emails) # shuffle lists
zipped_shuffled_lists = list(zip(list_of_emails, list_of_names)) # eager again
for pair in zipped_lists:
for shuffle_pair in zipped_shuffled_lists:
if shuffle_pair == pair:
return compare(list_of_names, list_of_emails)
return zipped_shuffled_lists
But I guess you need no recursion and can achieve your task easier:
def compare(list_of_names, list_of_emails):
zipped_lists = list(zip(list_of_emails, list_of_names))
random.shuffle(zipped_lists) # shuffle list of emails and names
result = []
shuffled_emails = [i[0] for i in zipped_lists]
for i, _ in enumerate(shuffled_emails):
result.append(zipped_lists[i-1][1]) # shift email relatively one position to the right
return list(zip(result, shuffled_emails))
This code links an name with an email of a previous name, which is randomly selected, and it guaranteed does not match.
There's no recursion, works fine for lists with two or more elements.

Variable scope python3

I have
def func1(var):
if var == 0:
return
else
var = var - 1
func1(var)
PROPOSAL = 1
def func2():
func1(PROPOSAL)
print(PROPOSAL)
In the recursive calls in func1, will the variable PROPOSAL be decremented, meaning the print statement will print 0?
Edit: I should've asked, why doesn't it do this?
No, the PROPOSAL global variable will not be decremented by your code. This isn't really because of scope, but because of how Python passes arguments.
When you call a function that takes an argument, the value of the argument you pass is bound to a parameter name, just like an assignment to a variable. If the value is mutable, an in-place modification through one name will be visible through the other name, but if the variable is immutable (as ints are in Python), you'll never see a change to one variable effect another.
Here's an example that shows functions and regular assignment working the same way:
x = 1
y = x # binds the y to the same value as x
y += 1 # modify y (which will rebind it, since integers are immutable)
print(x, y) # prints "1 2"
def func(z): # z is a local variable in func
z += 1
print(x, z)
func(x) # also prints "1 2", for exactly the same reasons as the code above
X = [1]
Y = X # again, binds Y to the same list as X
Y.append(2) # this time, we modify the list in place (without rebinding)
print(X, Y) # prints "[1, 2] [1, 2]", since both names still refer to the same list
def FUNC(Z):
Z.append(3):
print(X, Z)
FUNC(X) # prints "[1, 2, 3] [1, 2, 3]"
Of course, rebinding a variable that referred to a mutable value will also cause the change not to be reflected in other references to the original value. For instance, you replaced the append() calls in the second part of the code with Y = Y + [2] and Z = Z + [3], the original X list would not be changed, since those assignment statements rebind Y and Z rather than modifying the original values in place.
The "augmented assignment" operators like +=, -=, and *= are a bit tricky. They will first try to do an in-place modification if the value on the left side supports it (and many mutable types do). If the value doesn't support in-place modification of that kind (for instance, because it's an immutable object, or because the specific operator is not allowed), it will fall back on using the regular + operator to create a new value instead (if that fails too it will raise an exception).
func1(PROPOSAL) will return NONE and it won't affect the global PROPOSAL variable because you don't assign that return value to PROPOSAL.
func2() just calls func1() and then prints the PROPOSAL variable that wasn't changed it that scope just in func1(PROPOSAL)

How do you modify a variable that's a value in a dictionary when calling that variable by its key?

n = 3
d = {'x':n}
d['x'] += 1
print(n)
When I run it, I get
3
How do I make n = 4?
You can't do this, at least, not in any simple way.
The issue is very similar when you're just dealing with two variables bound to the same object. If you rebind one of them with an assignment, you will not see the new value through the other variable:
a = 3
b = a
a += 1 # binds a to a new integer, 4, since integers are immutable
print(b) # prints 3, not 4
One exception is if you are not binding a new value to the variable, but instead modifying a mutable object in-place. For instance, if instead of 1 you has a one-element list [1], you could replace the single value without creating a new list:
a = [3]
b = a
a[0] += 1 # doesn't rebind a, just mutates the list it points to
print(b[0]) # prints 4, since b still points to the same list as a
So, for your dictionary example you could take a similar approach and have n and your dictionary value be a list or other container object that you modify in-place.
Alternatively, you could store the variable name "n" in your dictionary and then rather than replacing it in your other code, you could use for a lookup in the globals dict:
n = 3
d = {"x": "n"} # note, the dictionary value is the string "n", not the variable n's value
globals()[d["x"]] += 1
print(n) # this actually does print 4, as you wanted
This is very awkward, of course, and only works when n is a global variable (you can't use the nominally equivalent call to locals in a function, as modifying the dictionary returned by locals doesn't change the local variables). I would not recommend this approach, but I wanted to show it can be done, if only badly.
You could use a class to contain the data values to enable additions. Basically you are creating a mutable object which acts as an integer.
It is a work around, but lets you accomplish what you want.
Note, that you probably need to override a few more Python operators to get full coverage:
class MyInt(object):
val = 0
def __init__(self,val):
self.val = val
def __iadd__(self,val):
self.val = self.val + val
def __repr__(self):
return repr(self.val)
n = MyInt(3)
print(n)
d = {'x':n}
d['x'] += 1
print(n)

Iteration to Recurssion

so myListToPyList(lst): takes lst, a MyList object and returns a Python list containing the same data
def myListToPyList(lst):
return myListToPyListRec(lst.head)
here's my helper function:
def myListToPyListRec(node):
if node == None:
return
else:
st1 = []
st1.append(node.data)
myListToPyListRec(node.next)
return st1
it's not working correctly.
Now here is my iterative solution that works correctly:
def myListToPyList(lst):
"""
Takes a list and returns a python list containing
the same data
param; lst
return; list
"""
st1 = []
curr = lst.head
while curr != None:
st1.append(curr.data)
curr = curr.next
return st1
Your current recursive code doesn't work because each time it gets called, it creates a new empty list, adds a single value to the list, then recurses (without passing the list along). This means that when the last item in the link list is being processed, the call stack will have N one-element Python lists (where N is the number of list nodes).
Instead, you should create the list just once, in your non-recursive wrapper function. Then pass it along through all of the recursion:
def myListToPyList(lst):
result_list = [] # create just one Python list object
myListToPyListRec(lst.head, result_list) # pass it to the recursive function
return result_list # return it after it has been filled
def myListToPyListRec(node, lst):
if node is not None # the base case is to do nothing (tested in reverse)
lst.append(node.data) # if node exists, append its data to lst
myListToPyListRec(node.next, lst) # then recurse on the next node
Because Python lists are mutable, we don't need to return anything in our recursive calls (None will be returned by default, but we ignore that). The list referred to by result_list in myListToPyList is the same object referred to by lst in each of the recursive calls to myListToPyListRec. As long as the recursive function mutates the object in place (e.g. with append) rather than rebinding it, they'll all see the same thing.
Note that recursion is going to be less eficient in Python than iteration, since function calls have more overhead than just updating a couple variables.
A while loop is equivalent to tail recursion, and vice versa. (One reason Python does not have automatic tail-call elimination is that the 'vice versa' part is rather easy.) The tail recursion requires that you add an accumulator parameter to be returned in the base case. Although I do not have a linked list for testing, I believe the following should work. (If not, this is close.) Python's default arguments make the helper either easier or unnecessary.
def myListToPyListRec(node, py_list=[]):
if node
py_list.append(node.data)
return myListToPyListRec(node.next, py_list)
else:
return py_list

Resources