Strange Behavior of Lists in Python

Strange behavior of lists in python

When you multiply a list, it copies a reference to the list, it doesn't create a copy of the list. As lists are mutable, when you change it, it is changed for all the references to it.

In ASCII-art terms:

a1 --- [list1, list2] --- list1 = [0, 0]
--- list2 = [0, 0]

a2 --- [list3, list3] --- list3 = [0, 0]

You can clearly see that changing list3 will affect both positions.

If you want to create variable-length lists without copying references, you should instead do something like this:

>>> a2 = [[0]*2 for _ in range(2)]
>>> a2[0][0] = 1
>>> a2
[[1, 0], [0, 0]]

Here we are using a list comprehension in order to create new elements each time, rather than copying references to old elements. In more complex situations, where you have existing mutable objects you want to repeat, you might want to use the copy module.

Note the [0]*2 operation is still OK, as ints in Python are immutable, and can't change, so it doesn't matter if you have references to the same object.

Strange behavior with python slicing

With

a[3:8:-1]

The start and stop positions of the slice aren't adjusted based on the step. With a negative step, you're having it go backwards from 3, but there are no elements with indices in the range 3 to 8 counting back from 3, so you get an empty list.

You need to set the start and stop accordingly:

a[8:3:-1]

Which will count back from 8 to 4.

Some strange behavior Python list and dict

That's a normal behaviour. Python uses references to store elements.
When you do r.append(t) python will store t in r. If you modify t later, t in r will be also modified because it's the same object.

If you want to make t independant from the value stored in r you have to copy it. Look at the copy module.

Strange behavior: list comprehension with dictionaries

You have the inner and outer loop of the double loop backwards. Try it with:

result = [d2['a'] for d1 in dics for d2 in d1['b']]

Actually, it's surprising that

[d2['a'] for d2 in d1['b'] for d1 in dics]

didn't result in a NameError because in the outer loop d1['b'] is undefined.

Strange behavior of list iteration using for loop in python

The problem is with the loop for weight in weights:

The loop is iterating over the items in the list, but the Python loop uses the position of the item in order to do that. When you remove an item with weights.remove(weight) the list shrinks while the position increases by 1 as it normally would. Basically, because you are removing items from weights while iterating over the list, it goes over every other item—hence the different list lengths as it shrinks (you can verify this by setting all the weights equal to 1; you'll get the same results as with 2)

An example of what's going on:

list = [1, 2, 3, 4, 5]
for item in list:
print(item)
list.remove(item)
print(list)

# --> 1
# --> [2, 3, 4, 5]
# --> 3
# --> [2, 4, 5]
# --> 5
# --> [2, 4]

^^ Notice how it only iterates every other item. Precisely what is happening with your weights.

An easy fix is to have your for loop iterate over a copy, while you remove from the original:

list = [1, 2, 3, 4, 5]
for item in list.copy():
print(item)
list.remove(item)
print(list)

# --> 1
# --> [2, 3, 4, 5]
# --> 2
# --> [3, 4, 5]
# --> 3
# --> [4, 5]
# --> 4
# --> [5]
# --> 5
# --> []

WOW! Look at that work so beautifully. For your cows that would look like this:

def greedy_cow_transport(cows,limit=10):
weights = []
for weight in cows.values():
weights.append(weight)
weights.sort(reverse=True)
cows_copy = cows.copy()
all_trips = []
while (len(weights) > 0):
avail_weight = limit
curr_trip = []
for weight in weights.copy():
if weight <= avail_weight:
for n, w in cows_copy.items(): # <--!!! THE CHANGE IS HERE
if weight == w:
curr_trip.append(n)
weights.remove(weight)
cows_copy.pop(n, None)
avail_weight -= w
break
all_trips.append(curr_trip)
return all_trips
cows = {'Lola': 2, 'Oreo': 2, 'Millie': 2, 'Betsy': 2, 'Moo Moo': 2, 'Milkshake': 2, 'Herman': 2, 'Florence': 2, 'Maggie': 2, 'Henrietta': 2}
print(greedy_cow_transport(cows))

# --> [['Oreo', 'Milkshake', 'Herman', 'Florence', 'Lola'], ['Maggie', 'Millie', 'Henrietta', 'Betsy', 'Moo Moo']]

And voila! Hope you enjoyed.

@ShadowRanger added that in older versions of Python, list did not have a copy method, and so an alternative to list.copy() is using an empty slice like so list[:].

Strange behavior observed when iterating over list in python

When you run the for loop, it takes the id of the provided object and iterates over it.

In the first code snippet, you are changing an element of the original object, so when the iterator reaches the second element it takes the updated value.

However, in the second case you are creating a new object with different values, but the object that you provided to the loop stays the same.

A way of checking this behaviour is to get the id of the variable before and after the modifications, and see how it does not change in the first case but it changes in the second case:

my_list = [1, 2, 3]
original_id = id(my_list)

# Check if the object identification changes after modifying one element
my_list[2] = 4
new_id = id(my_list)
if original_id == new_id:
print("1st case: The ID stays the same")
else:
print("1st case: The ID has changed")

# Check now what happens if you create a new list
my_list = [3, 2, 1]
new_id = id(my_list)
if original_id == new_id:
print("2nd case: The ID stays the same")
else:
print("2nd case: The ID has changed")

The obtained result is the following:

1st case: The ID stays the same
2nd case: The ID has changed

Weird behavior in python list concatenation

The reason behind this is that += and + calls two different methods of the class, __iadd__ method and __add__ method.

From an API perspective, iadd is supposed to be used for modifying mutable objects in place (returning the object which was mutated) whereas add should return a new instance of something. For immutable objects, both methods return a new instance, but iadd will put the new instance in the current namespace with the same name that the old instance had. This is why

i = 1
i += 1

seems to increment i. In reality, you get a new integer and assign it "on top of" i -- losing one reference to the old integer. In this case, i += 1 is exactly the same as i = i + 1. But, with most mutable objects, it's a different story:

As a concrete example:

a = [1, 2, 3]
b = a
b += [1, 2, 3]
print a #[1, 2, 3, 1, 2, 3]
print b #[1, 2, 3, 1, 2, 3]

compared to:

a = [1, 2, 3]
b = a
b = b + [1, 2, 3]
print a #[1, 2, 3]
print b #[1, 2, 3, 1, 2, 3]

notice how in the first example, since b and a reference the same object, when I use += on b, it actually changes b (and a sees that change too -- After all, it's referencing the same list). In the second case however, when I do b = b + [1, 2, 3], this takes the list that b is referencing and concatenates it with a new list [1, 2, 3]. It then stores the concatenated list in the current namespace as b -- With no regard for what b was the line before.

Strange behavior with nested list comprehension

Is sl defined elsewhere in your code? Perhaps as 5? As written, your first example should not run, and does not run for me in Python 3.6. The correct way to write it would be

[e for sl in [1,[2,3],4,5] for e in [sl]]

Note that here sl is defined before it is used.

Edit:

Python reads list comprehensions left to right. When it gets to for e in [sl], it evaluates the expression [sl] based on what it already knows, without reading the rest of the line. You list comprehension is then something like

[e for e in [5] for sl in [1,[2,3],4,5]]

As there are four sl in [[1,[2,3],4,5]], you get 5 four times in the resultant list.

When writing list comprehensions, it's natural to write them from smallest to biggest

e for e in x for x in y for y in z #wrong

but you should actually write them from right to left, so that the interpreter recognizes the identifiers that you use in the nested comprehensions

e for y in z for x in y for e in x

This is no different from regular for loops:

for e in x:
for x in y:
for y in z:
print(e)

is pretty obviously wrong, and list comprehensions are no different.



Related Topics



Leave a reply



Submit