local and global variable usage in Python - python

I am testing a reverse function. My code is as follow:
def deep_reverse(L):
new_list = []
for element in L:
new_list += [element[::-1]]
L = new_list[::-1]
L = [[1, 2], [3, 4], [5, 6, 7]]
deep_reverse(L)
print(L)
I expect my output L can be like [[7, 6, 5], [4, 3], [2, 1]], but it only works inside my function.
I wasn't able to change it in the global variable. How can I make it happen? Thank you

L is just a local variable, and assigning a new object to it won't change the original list; all it does is point the name L in the function to another object. See Ned Batchelder's excellent explanation about Python names.
You could replace all elements in the list L references by assigning to the [:] identity slice instead:
L[:] = new_list[::-1]
However, mutable sequences like lists have a list.reverse() method, so you can just reverse the list in-place with that:
L.reverse()
Do the same for each sublist:
def deep_reverse(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.
"""
for element in L:
element.reverse()
L.reverse()

Related

I want to know list parmeters in python function like a[:]

pset = []
def powe(a):
powehelp(a,0)
def powehelp(a, ind):
if len(a) == ind:
pset.append(a)
return
powehelp(a[:], ind+1)
a.pop(ind)
powehelp(a[:],ind)
powe([1,2,3])
print(pset)
This code creates its subset, and in this code I want to know why I can't use powehelp(a, ind+1) instead of powehelp(a[:], ind+1)?
I know that a[:] means get all the values ​​of list a.
When using a, the result is [[], [], [], []].
Your powehelp function uses pop, which means it alters the list it is given.
If you pass a into it, it is a that gets altered, and evidently ends up empty.
a[:] creates a copy of a. If you pass a[:] into powehelp, your original list a is unaffected.
The given a is a list in itself, and so appending a list to a list would make a nested list.
a = [1, 2, 3]
b = []
b.append(a)
b
[[1, 2, 3]]
When you pset.append(a) you insert the list a into the endtail of the list pset so you result is a nested list. If I understand your requirement correctly, you're looking to concatenate your lists, as in:
c = [4, 5, 6]
a + c
[1, 2, 3, 4, 5, 6]

List of list, converting all strings to int, Python 3

I am trying to convert all elements of the small lists in the big list to integers, so it should look like this:
current list:
list = [['1','2','3'],['8','6','8'],['2','9','3'],['2','5','7'],['5','4','1'],['0','8','7']]
for e in list:
for i in e:
i = int(i)
new list:
list = [[1,2,3],[8,6,8],[2,9,3],[2,5,7],[5,4,1],[0,8,7]]
Could anyone tell me why doesn't this work and show me a method that does work? Thanks!
You can use a nested list comprehension:
converted = [[int(num) for num in sub] for sub in lst]
I also renamed list to lst, because list is the name of the list type and not recommended to use for variable names.
for e in range(len(List)):
for p in range(len(List[e])):
List[e][p] = int(List[e][p])
Or, you could create a new list:
New = [list(map(int, sublist)) for sublist in List]
Nested list comprehension is the best solution, but you can also consider map with lambda function:
lista = [['1','2','3'],['8','6','8'],['2','9','3'],['2','5','7'],['5','4','1'],['0','8','7']]
new_list = map(lambda line: [int(x) for x in line],lista)
# Line is your small list.
# With int(x) you are casting every element of your small list to an integer
# [[1, 2, 3], [8, 6, 8], [2, 9, 3], [2, 5, 7], [5, 4, 1], [0, 8, 7]]
In short, you are not mutating lst:
for e in lst:
for i in e:
# do stuff with i
is the equivalent of
for e in lst:
for n in range(len(e)):
i = e[n] # i and e[n] are assigned to the identical object
# do stuff with i
Now, whether the "stuff" you are doing to i is reflected in the original data, depends on whether it is a mutation of the object, e.g.
i.attr = 'value' # mutation of the object is reflected both in i and e[n]
However, string types (str, bytes, unicode) and int are immutable in Python and variable assignment is not a mutation, but a rebinding operation.
i = int(i)
# i is now assigned to a new different object
# e[n] is still assigned to the original string
So, you can make your code work:
for e in lst:
for n in range(len(e)):
e[n] = int(e[n])
or use a shorter comprehension notation:
new_lst = [[int(x) for x in sub] for sub in lst]
Note, however, that the former mutates the existing list object lst, while the latter creates a new object new_lst leaving the original unchanged. Which one you choose will depend on the needs of your program.

Function which removes the first item in a list (Python)

I am trying to write a function which removes the first item in a Python list. This is what I've tried. Why doesn't remove_first_wrong change l when I call the function on it? And why does the list slicing approach work when I do it in the main function?
def remove_first_wrong(lst):
lst = lst[1:]
def remove_first_right(lst):
lst.pop(0)
if __name__ == '__main__':
l = [1, 2, 3, 4, 5]
remove_first_wrong(l)
print(l)
l_2 = [1, 2, 3, 4, 5]
remove_first_right(l_2)
print(l_2)
# Why does this work and remove_first_wrong doesn't?
l_3 = [1, 2, 3, 4, 5]
l_3 = l_3[1:]
print(l_3)
Slicing a list returns a new list object, which is a copy of the original list indices you indicated in the slice. You then rebound lst (a local name in the function) to reference that new list instead. The old list is never altered in that process.
list.pop() on the other hand, operates on the list object itself. It doesn't matter what reference you used to reach the list.
You'd see the same thing without functions:
>>> a = [1, 2]
>>> b = a[:] # slice with all the elements, produces a *copy*
>>> b
[1, 2]
>>> a.pop() # remove an element from a won't change b
2
>>> b
[1, 2]
>>> a
[1]
Using [:] is one of two ways of making a shallow copy of a list, see How to clone or copy a list?
You may want to read or watch Ned Batchelder's Names and Values presestation, to further help understand how Python names and objects work.
Inside the function remove_first_wrong the = sign reassigns the name lst to the object on the right. Which is a brand new object, created by slicing operation lst[1:]. Thus, the object lst assigned to is local to that function (and it actually will disappear on return).
That is what Martijn means by "You then rebound lst (a local name in the function) to reference that new list instead."
On contrary, lst.pop(0) is a call to the given object -- it operates on the object.
For example, this will work right too:
def remove_first_right2(lst):
x = lst # x is assigned to the same object as lst
x.pop(0) # pop the item from the object
Alternately, you can use del keyword:
def remove_first_element(lst):
del lst[0]
return lst

Are there efficiency differences in extend vs. adding vs. appending in Python?

I refer to list operations:
L = myList + otherList
L = myList.append([5])
L = myList.extend(otherList)
I am curious if there are efficiency differences among these operations.
These are totally different operations.
They have different purposes, so efficiency wouldn't matter. append is used to append a single value to a list, extend is used for multiple values, and the addition is for when you don't want to modify the original list, but to have another list with the extra values added on.
>>> lst = [1, 2, 3]
>>> lst2 = [5, 6]
>>> lst.append(4) # appending
>>> lst
[1, 2, 3, 4]
>>> lst.extend(lst2) # extending
>>> lst
[1, 2, 3, 4, 5, 6]
>>> lst + lst2 # addition
[1, 2, 3, 4, 5, 6, 5, 6]
Also note that list.append and list.extend operate in-place, so assigning the result to a variable will make that variable hold the value None.
Your example here is sort of misleading in the case of append.
>>> l1 = [1,2,3,4]
>>> l1.append([5])
>>> l1
[1, 2, 3, 4, [5]]
Append takes a single item and appends it to the end of the existing list. By passing in an iterable to append, you're adding another list (in this case) within a list.
extend takes an iterable and essentially calls append for each item in the iterable`, adding the items onto the end of the existing list.
The mylist + otherlist is the only interesting case here, as the result of using the + operator creates a new list, using more memory.
Timing them answers your question about efficiency in regards of speed:
import timeit
def first():
mylist + otherlist
def second():
mylist.append(otherlist)
def third():
mylist.extend(otherlist)
for test in (first, second, third):
mylist = [1, 2, 3, 4]
otherlist = [5]
print "%s: %f" % (test, timeit.timeit(test, number=1000000))
On my machine the result was:
<function first at 0x10ff3ba28>: 0.320835
<function second at 0x10ff3baa0>: 0.275077
<function third at 0x10ff3bb18>: 0.284508
Showing that the first example was clearly slowest.

reverse method mutating input

For an assignment we were asked to create a function that would reverse all the elements in an arbitrarily nested list. So inputs to the function should return something like this:
>>> seq = [1,[2,[3]]]
>>> print arb_reverse(seq)
[[[3],2],1]
>>> seq = [9,[[],[0,1,[[],[2,[[],3]]]],[],[[[4],5]]]]
>>> print arb_reverse(seq)
[[[[5,[4]]],[],[[[[3,[]],2],[]],1,0],[]],9]
I came up with a recursive solution which works well:
def arb_reverse(seq):
result = []
for element in reversed(seq):
if not is_list(element):
result.append(element)
else:
result.append(arb_reverse(element))
return result
But for a bit of a personal challenge I wanted to create a solution without the use of recursion. One version of this attempt resulted in some curious behavior which I am not understanding. For clarification, I was NOT expecting this version to work properly but the resulting input mutation does not make sense. Here is the iterative version in question:
def arb_reverse(seq):
elements = list(seq) #so input is not mutated, also tried seq[:] just to be thorough
result = []
while elements:
item = elements.pop()
if isinstance(item, list):
item.reverse() #this operation seems to be the culprit
elements += item
else:
result.append(item)
return result
This returns a flattened semi-reversed list (somewhat expected), but the interesting part is what it does to the input (not expected)...
>>> a = [1, [2, [3]]]
>>> arb_reverse(a)
[2, 3, 1]
>>> a
[1, [[3], 2]]
>>> p = [1, [2, 3, [4, [5, 6]]]]
>>> print arb_reverse(p)
[2, 3, 4, 5, 6, 1]
>>> print p
[1, [[[6, 5], 4], 3, 2]]
I was under the impression that by passing the values contained in the input to a variable using list() or input[:] as i did with elements, that I would avoid mutating the input. However, a few print statements later revealed that the reverse method had a hand in mutating the original list. Why is that?
The list() call is making a new list with shallow-copied lists from the original.
Try this (stolen from here):
from copy import deepcopy
listB = deepcopy(listA)
Try running the following code through this tool http://people.csail.mit.edu/pgbovine/python/tutor.html
o1 = [1, 2, 3]
o2 = [4, 5, 6]
l1 = [o1, o2]
l2 = list(l1)
l2[0].reverse()
print l2
print l1
Specifically look at what happens when l2[0].reverse() is called.
You'll see that when you call list() to create a copy of the list, the lists still reference the same objects.

Categories

Resources