I have a function of for saving three lists so it takes those lists as arguments and then writes each of them line by line to a text file.
rotor1, rotor1_pos, rotor2, rotor2_pos, rotor3, rotor3_pos = [x for x in range(26)], 0, [x for x in range(26)], 0, [x for x in range(26)], 0
def save_rotors(rpos1=rotor1_pos,rpos2=rotor2_pos,rpos3=rotor3_pos,r1=rotor1[:],r2=rotor2[:],r3=rotor3[:]):
print(r1,r2,r3)
with open('rotors.txt.',mode='w') as f:
f.write(str(rpos1)+'\n')
for num in r1:
f.write(str(num)+'\n')
f.write(str(rpos2)+'\n')
for num in r2:
f.write(str(num)+'\n')
f.write(str(rpos3)+'\n')
for num in r3:
f.write(str(num)+'\n')
But when I run
print(rotor1,rotor2,rotor3)
save_rotors()
I find that my lists are correctly populated but inside of save_rotors I have empty lists? As shown above I immediately printing out the r1,r2,r3 arguments so nothing else is happening to the lists, they are only being passed as arguments. My function reads the correct values from the integer valued variables but not the list. My text file ends up looking like
0
[]
0
[]
0
[]
What is going on here?
You've used a Mutable Default Argument Which is defining the lists when the function is defined.
You should instead pass the arguments in by calling the function:
def save_rotors(pos1 ,rpos2, rpos3, r1, r2, r3):
...
save_rotors(rotor1_pos, rotor2_pos, rotor3_pos, rotor1, rotor2, rotor3)
EDIT:
After a discussion in chat, we discovered that there may be some non-visible characters in the original source file that were causing the interpreter some grief. Copying and Pasting the code as it appears in the question seems to work just fine. I will leave the original discussion of mutable default arguments here, however since I still feel it is relevant.
Related
I'm working on getting a better grasp of Python 3 fundamentals, specifically objects and modifying them in the context of a list (for now).
I created a simple class called MyThing() that just has a number, letter, and instance method for incrementing the number. My goal with this program was to create a list of 3 "MyThings", and manipulate the list in various ways. To start, I iterated through the list (obj_list_1) and incremented each number using each object's instance method. Easy enough.
What I'm trying to figure out how to do is perform the same operation in one line using the map function and lambda expressions (obj_list_2).
#!/usr/bin/env py
import copy
class MyThing:
def __init__(self, letter='A', number=0):
self.number = number
self.letter = letter
def __repr__(self) -> str:
return("(letter={}, number={})".format(self.letter, self.number))
def incr_number(self, incr=0):
self.number += incr
# Test program to try different ways of manipulating lists
def main():
obj1 = MyThing('A', 1)
obj2 = MyThing('B', 2)
obj3 = MyThing('C', 3)
obj_list_1 = [obj1, obj2, obj3]
obj_list_2 = copy.deepcopy(obj_list_1)
# Show the original list
print("Original List: {}".format(obj_list_1))
# output: [(letter=A, number=1), (letter=B, number=2), (letter=C, number=3)]
# Standard iterating over a list and incrementing each object's number.
for obj in obj_list_1:
obj.incr_number(1)
print("For loop over List, adding one to each number:\n{}".format(obj_list_1))
# output: [(letter=A, number=2), (letter=B, number=3), (letter=C, number=4)]
# Try using map function with lambda
obj_list_2 = list(map(lambda x: x.incr_number(1), obj_list_2))
print("Using maps with incr_number instance method:\n{}".format(obj_list_2))
# actual output: [None, None, None] <--- If I don't re-assign obj_list_2...it shows the proper sequence
# expected output: [(letter=A, number=2), (letter=B, number=3), (letter=C, number=4)]
if __name__ == "__main__":
main()
What I can't figure out is how to get map() to return the correct type, a list of "MyThing"s.
I understand that between Python 2 and Python 3, map changed to return an iterable instead of a list, so I made sure to cast the output. What I get is a list of 'None' objects.
What I noticed, though, is that if I don't re-assign obj_list_2, and instead just call list(map(lambda x: x.incr_number(1), obj_list_2)), then print obj_list_2 in the next line, the numbers get updated as I expect.
However, if I don't cast the map iterable and just do map(lambda x: x.incr_number(1), obj_list_2), the following print statement shows the list as having not been updated. I read in some documentation that the map function is lazy and doesn't operate until it's use by something...so this makes sense.
Is there a way that I can get the output of list(map(lambda x: x.incr_number(1), obj_list_2)) to actually return my list of objects?
Are there any other cool one-liner solutions for updating a list of objects with their instance methods that I'm not thinking of?
TL;DR: Just use the for-loop. There's no advantage to using a map in this case.
Firstly:
You're getting a list of Nones because the mapped function returns None. That is, MyThing.incr_number() doesn't return anything, so it returns None implicitly.
Fewer lines is not necessarily better. Two simple lines are often easier to read than one complex line.
Notice that you're not creating a new list in the for-loop, you're only modifying the elements of the existing list.
list(map(lambda)) is longer and harder to read than a list comprehension:
[x.incr_number(1) for x in obj_list_2]
vs
list(map(lambda x: x.incr_number(1), obj_list_2))
Now, take a look at Is it Pythonic to use list comprehensions for just side effects? The top answer says no, it creates a list that never gets used. So there's your answer: just use the for-loop instead.
This is because, your incr_number doesn't return anything. Change it to:
def incr_number(self, incr=0):
self.number += incr
return self
The loop is clearly better, but here's another way anyway. Your incr_number doesn't return anything, or rather returns the default None. Which is a false value, so if you simply append or x, then you do get the modified value instead of the None
Change
list(map(lambda x: x.incr_number(1), obj_list_2))
to this:
list(map(lambda x: x.incr_number(1) or x, obj_list_2))
I came across closures in python, and I've been tinkering around the subject.
Please Correct me if I'm wrong here, but what I understood for when to use closures (generally) is that it can be used as a replacement of small classes (q1) and to avoid the use of globals (q2).
Q1: [replacing classes]
Any instance created from the datafactory class will have it's own list of data, and hence every appending to that object's list will result in an incremental behavior. I understand the output from an OO POV.
class datafactory():
def __init__(self):
self.data = []
def __call__(self, val):
self.data.append(val)
_sum = sum(self.data)
return _sum
incrementwith = datafactory()
print(incrementwith(1))
print(incrementwith(1))
print(incrementwith(2))
OUTPUT:
1
2
4
I tried replacing this with a closure, it did the trick, but my understanding to why/how this is happening is a bit vague.
def data_factory():
data = []
def increment(val):
data.append(val)
_sum = sum(data)
return _sum
return increment
increment_with = data_factory()
print(increment_with(1))
print(increment_with(1))
print(increment_with(2))
OUTPUT:
1
2
4
What I'm getting is that the data_factory returns the function definition of the nested increment function with the data variable sent along as well, I would've understood the output if it was something like this:
1
1
2
But how exactly the data list persists with every call?
Shouldn't variables defined in a function die after the function finishes execution and get regenerated and cleared out with the next fn call?
Note: I know that this behavior exists normally in a function defined with default parameters like def func(val, l = []): where the list will not be cleared on every fn call, but rather be updated with a new element/append, which is also something that I do not fully understand.
I would really appreciate an academic explanation to what happens in both scenarios (OO and closures).
Q2: [replacing use of global]
Is there a way using closures to increment the following variable without using globals or a return statement ?
a = 0
print("Before:", a) # Before: 0
def inc(a):
a += 1
print("After:", a) # After: 0
Thank you for your time.
For the first question, I found after some digging that passing mutables as default parameters isn't really a good move to make:
https://florimond.dev/blog/articles/2018/08/python-mutable-defaults-are-the-source-of-all-evil/#:~:text=of%20this%20mess.-,The%20problem,or%20even%20a%20class%20instance.
Let me preface this by saying that I am fairly new to coding, so be gentle.
I have been writing the following:
def execute_move(player, house_choice, houses):
next_house=houses[house_choice]
chosen_house=houses[house_choice-1]
chosen_house_seeds=chosen_house[1]
for i in range(chosen_house_seeds):
if player=='P1': # skips the store of the opposite player
if next_house==houses[13]:
next_house_index=houses.index(next_house)
new_nhi=next_house_index+1
next_house=houses[new_nhi]
elif player=='P2':
if next_house==houses[6]:
next_house_index=houses.index(next_house)
new_nhi=next_house_index+1
next_house=houses[new_nhi]
[(next_house[0], (next_house[1]+1)) if x==next_house else x for x in houses]
next_house_index=houses.index(next_house)
new_nhi=next_house_index+1
next_house=houses[new_nhi]
[(chosen_house[0], (chosen_house[1]-chosen_house_seeds)) if x==chosen_house else x for x in houses]
return houses
My aim is to replace some of the tuples in the list 'houses', and then return the new list of tuples.
For some reason the var I assign the call to later on in the code only produces the original list when printed.
Im thinking that it may have something to do with the indentation of the 'if' statements or the indentation of the return statement.
Help much appreciated!
There is nothing intrinsically special about a return statement when compared to other Python statements.
As such, you should indent it as you would any other (i.e., by placing the return line inside the block that makes sense in your specific algorithm).
Any control structure (such as an if statement) expects the next line to be indented. The control structure ends when the code begins to go back to the original level
https://docs.python.org/3/reference/lexical_analysis.html#indentation
def function(input):
input = input * 2
if (input > 5):
input * 2
return input
input +1
return input
so in this case, if the input is < 2.5, it is returned doubled + 1. If it is >2.5 it is returned * 4
It is probably just a bad cut and paste, but the body of the function must be indented as well.
Your list comprehension statements, those that look like [(next_house[0], ...] are building lists but it's not assigned to anything so it is being discarded. Try setting result to a variable...
list_of_next_house_tuples = [(next_house[0], (next_house[1]+1)) if x==next_house else x for x in houses]
Then determine what you are going to do with this new list.
This is a part of my homework assignment and im close to the final answer but not quite yet. I need to write a function that writes the odd number between position 1 and 5 in a list.
I make something like that:
-in a file domain I write the condition for odd number:
def oddNumber(x):
"""
this instruction help us to write the odd numbers from the positions specificated
input: x-number
output:-True if the number is odd
-False otherwise
"""
if x % 2==1:
return True
else:
return False
-then the tests:
def testOdd_Number():
testOdd_Number=[0,1,2,3,4,5,6,7,8]
oddNumber(testOdd_Number,0,6)
assert (testOdd_Number==[1,3,5])
oddNumber(testOdd_Number,0,3)
assert (testOdd_Number==[3])
-and in the other file named userinterface I write this:
elif(cmd.startswith("odd from ", "")):
try:
cmd=cmd.replace("odd from ", "")
cmd=cmd.replace("to ", "")
i=int(cmd[:cmd.find(" ")])
j=int(cmd[cmd.find(" "):])
if (i>=len(NumberList) or i>j or j>=len(NumberList) or i<0 or j<0):
print("Invalid value(s).")
else:
for m in range(i-1,j):
if oddNumber(NumberList[m]):
print (NumberList[m])
except:
print("Error!")
-when I run the entire project(I have more requirements but the others one are good), and write odd from [pos] to [pos] it says me
Traceback (most recent call last):
File "C:\Users\Adina\My Documents\LiClipse Workspace\P1\userinterface.py", line 94, in <module>
run()
File "C:\Users\Adina\My Documents\LiClipse Workspace\P1\userinterface.py", line 77, in run
elif(cmd.startswith("odd from ", "")):
TypeError: slice indices must be integers or None or have an __index__ method
I've forgotten to say that I have a also a function main() where I print the requirements.Where am I wrong?
Python's string startswith method, described here:
https://docs.python.org/2/library/stdtypes.html
states that arguments are
some_string.startswith(prefix, beginning, end) #where beginning and end are optional integers
and You have provided prefix and empty string ( cmd.startswith("odd from ", "") )
Some things I noticed:
1) you can shorten your oddNumber function to
def oddNumber(x):
return x%2
2) in your tests, you rebind the functions name testOdd_Number to some list, then pass that around to your oddNumber function. is that the same function described above? Then it won't work, as this function expects a single integer to be passed.
Using the same name to refer to two different things is discouraged.
Actually, I have no idea what your testcode does or should do. Are you passing a list and expect oddNumber to modify it in place?
3) your custom command parser looks... odd, and fragile. Maybe invest in a real parser?
You should decouple command parsing and actual computation.
As brainovergrow pointed out, there is also your error, since .startswith does not accept a string as second argument.
Some general hints:
You can use list(range(9)) instead of hardcoding [0,1,2,3,4,5,6,7,8]
You can use filter to filter the odd numbers of a given list:>>> list(filter(oddNumber, range(9))) yields [1, 3, 5, 7].
You can also use list comprehensions: [x for x in range(9) if x%2] yields the same.
you might find any() and all() useful. Take a look at them.
Your naming scheme is neighter consistent nor pythonic. Read PEP8 for a style guide.
colours = [turtle.color("red"),turtle.color("blue"),turtle.color("yellow"),turtle.color("green")]
fred = colours[0],turtle.forward(100),turtle.left(90),colours[1],turtle.forward(100),turtle.left(90),colours[2],turtle.forward(100),turtle.left(90),colours[3],turtle.forward(100),turtle.left(90)
Attemping to make a square with 4 different colours from a list, type(colours[0]) returns class Nonetype. How can I access the colours from my list?
Your code:
colours = [turtle.color("red")]
Will run the function turtle.color("red"), and store the return value in the list.
This is exactly the same as doing:
colours = [None]
If you call colours[0] you get the return value, not the function. Python has no idea if the None ended up there through a function call, or if you just assigned it manually.
You only posted 2 lines of code, so I don't quite know what the context is here, but you may want to do something like:
colours = [lambda: turtle.color("red"), lambda: turtle.color("blue")]
What this does, is store a lamba (or 'anonymous function') in your list. This function is not executed. You will now get:
>>> colours[0]
<function <lambda> at 0x80089e710>
And you can execute this as many times as you want by appending parenthesis, like so: colours[0]()
This technique is known as 'currying' by the way.