0

I am confused by the behavior of a python list. If someone can please shed light on why python is behaving this way I would greatly appreciate it.

list = [[]]*5
list[0].append(10)
list[1].append(20)
list.append(30)

I believe the output should be:

[ [10], [20], [], [], [], 30 ]

but I am getting an output of:

[ [10, 20], [10, 20], [10, 20], [10, 20], [10, 20], 30 ]

Would this be because the nested lists are just a copy of the initial list? If so what is there a way to do this so that it doesn't just make copies of the initial list?

Don Davis
  • 7
  • 2
  • First of all, never use something that can be called in the interpreter by ```dir``` as a variable. Open a new interpreter, call ```dir(list)```, then say ```list=1``` then call ```dir(list)``` again. – neutrino_logic Mar 14 '20 at 05:31
  • I know why. I've run into this in function declarations trying to make [] a default value for a parameter. What you create with the first line is a list of references to the same list. – Todd Mar 14 '20 at 05:36
  • Try what neutrino_logic suggests to get unique list instances in your list. `li = [[] for _ in range(8)]`. – Todd Mar 14 '20 at 05:39
  • What @neutrino_logic is saying is good advice. Stated another way: you're using `list` as a variable name... but Python already had `list` defined as a class. You're overriding it. It's probably benign, but not good style. You can use `dir()` in a fresh shell to list out all these names you shouldn't want to override. – Todd Mar 14 '20 at 06:21

2 Answers2

1

You want to generate your nested list like this:

foo = [[] for x in range(5)]

then if you say:

foo[0].append(10)
foo[1].append(20)

You will get the expected result:

[[10], [20], [], [], []]

[edit] Anytime you have nesting, Python defaults to shallow copies for the basic operations. Let's say you now wanted to make an independent copy of foo. You'd have to do this:

import copy
bar = copy.deepcopy(foo)

Without doing this, you'd have a problem like:

zed = foo
foo[2].append(40)
print(zed)
>>> [[10], [20], [40], [], []]

This is not a problem unless you have nested lists... but see other answer for other places it can crop up.

neutrino_logic
  • 1,289
  • 1
  • 6
  • 11
  • Can you explain briefly that her first line is not creating unique instances? @neutrino_logic – Todd Mar 14 '20 at 05:42
  • @Todd I gave it a shot... the whole 'everything is an object' concept is sometimes glossed over in introductory Python. Calling ```dir(int)``` is illuminating... – neutrino_logic Mar 14 '20 at 05:48
1

I see a good answer posted, but felt some explanation is in order here since this is a common gotcha for people new, and sometimes experienced, with Python. There are two ways off the top of my head where this can happen. One is in list declarations as you have, and the other is in function/method declarations.

The line li = [[]] * 8 creates a list of references to the same list. That's why when you append to one li[0].append(10), the list shows you eight lists with 10 as a member. The solution would be to use a list comprehension in the declaration. li = [[] for _ in range(8)].

Now for the next gotcha. Function or method declarations. Often we want default values for parameters like so:

>>> def foo(bar=[]):
...     bar.append('hello')
...     return bar
...     
>>> foo()
['hello']
>>> foo()
['hello', 'hello']
>>> foo()
['hello', 'hello', 'hello']
>>> 

The problem with using [] as a default value is the interpreter, when it parses the function declaration, creates only one list instance as the default value. So every time you call the function, that same list is getting passed in.

This goes for dictionaries as well. In fact, using any sort of mutable object instance as a parameter default value in function declarations could potentially cause confusion.

I think the key takeaway is * 8 applied to certain objects like lists creates references - not instances.

Todd
  • 4,669
  • 1
  • 22
  • 30