Python Forum
Thread Rating:
  • 0 Vote(s) - 0 Average
  • 1
  • 2
  • 3
  • 4
  • 5
[[' ']*3]*3 buggy behavior?
#1
Running 3.9.x in Jupyter Notebook
I used a shortcut to create 3x3 array, why doesn't it act correctly?
brd=[[' ']*3]*3
b=[[' ',' ',' '],[' ',' ',' '],[' ',' ',' ']]
print(b==brd)  #if these are equivalent, why doesn't the *3 method update correctly?
brd[0][0]='X'
b[0][0]='X'
print(brd)
print(b)
For those not running the code, line 3 prints True but the result of updating a single entry is not correct using the *3 method, here are the outputs:
Output:
[['X', ' ', ' '], ['X', ' ', ' '], ['X', ' ', ' ']] [['X', ' ', ' '], [' ', ' ', ' '], [' ', ' ', ' ']]
thanks
Reply
#2
You should really try searching the forum for your question. At the time I write this the last post prior to yours in General Programming Help (here) contained the answer to your question.

https://python-forum.io/thread-34507.html

This is not buggy behavior, it is completely expected behavior.
brd=[[' ']*3]*3
# Does this
string = ' '
inner = [string , string , string ]
brd = [inner , inner , inner ]
print(brd)
brd[0][0] = 'X'
print(brd)
Output:
[[' ', ' ', ' '], [' ', ' ', ' '], [' ', ' ', ' ']] [['X', ' ', ' '], ['X', ' ', ' '], ['X', ' ', ' ']]
Essentially you have one str (' '). You put three of these strings in the inner list, and then put three of the inner list in brd. And when I say "put three of these inner lists in brd" I mean the same list, not a copy of the list. This is why setting brd[0][0] = 'X' appears to change all 3 inner lists. It doesn't though. It only changes one list but brd contains that one list in three different places.

If you want three different lists in brd, you need to create three lists. This can be done using a for loop or a list comprehension (a compact way of writing a for loop).
Using a for loop
brd=[]
for _ in range(3):
    brd.append([' ']*3)
Each iteration appends a newly created list to brd. This can be shortened using to a one liner.
brd = [[' ']*3 for _ in range(3)]
brd[0][0] = 'X'
print(brd)
Larz60+ likes this post
Reply
#3
thanks for the reply.
I do not normally keep up on all the forum posts but I do try to search for a relevant post. Unfortunately I couldn't even figure out how to find relevant posts.
Your answer makes sense; I'm still fairly new to Python but I do remember learning (I'm in the middle of re-learning) about actual values vs pointers (using similar references in older languages), although the subtleties of my instance will require a closer inspection. I guess == statements aren't always at straight-forward as they seem.
Reply
#4
the inner list [[]*3] produces a list of three lists. But then this list is duplicated three times. However, in python, it's really a list of references that is being multiplied, so the reference is duplicated, but each reference still points to the same underlying list.

solve:
A = [[' ' for _ in range(3)] for _ in range(3)]
B = [[' ',' ',' '],[' ',' ',' '],[' ',' ',' ']]
print(A == B)
A[0][0] = 'X'
B[0][0] = 'X'
print(A)
print(B)
Output:
True [['X', ' ', ' '], [' ', ' ', ' '], [' ', ' ', ' ']] [['X', ' ', ' '], [' ', ' ', ' '], [' ', ' ', ' ']]
Reply
#5
(Aug-18-2021, 03:35 PM)naughtyCat Wrote: the inner list [[]*3] produces a list of three lists. But then this list is duplicated three times. However, in python, it's really a list of references that is being multiplied, so the reference is duplicated, but each reference still points to the same underlying list.

solve:
A = [[' ' for _ in range(3)] for _ in range(3)]
B = [[' ',' ',' '],[' ',' ',' '],[' ',' ',' ']]
print(A == B)
A[0][0] = 'X'
B[0][0] = 'X'
print(A)
print(B)
Output:
True [['X', ' ', ' '], [' ', ' ', ' '], [' ', ' ', ' ']] [['X', ' ', ' '], [' ', ' ', ' '], [' ', ' ', ' ']]

Thanks for the reply naughtyCat. I'm down to trying to understand when an assignment creates a new object vs references an existing object. Gotta do more reading on this subject. Perhaps immutable objects always get referenced and static things like a string get copied.
naughtyCat likes this post
Reply
#6
Quote:I'm down to trying to understand when an assignment creates a new object vs references an existing object. Gotta do more reading on this subject. Perhaps immutable objects always get referenced and static things like a string get copied.
Immutable things, what you call static, are never copied. There is no reason to make copies of an immutable object because it's value cannot be changed. Not only are immutable objects never copied, Python tries to reuse immutable objects. This code reuses 'Hello World!' for x and y but I managed to trick it with z. Notice that the ID for x and y shows they reference the same str object, but z is a different str.
x = 'Hello World!'
y = 'Hello' + ' ' + 'World!'
z = ''.join(['H', 'e', 'l', 'l', 'o', ' ', 'W', 'o', 'r', 'l', 'd', '!'])
print(x, y, z, id(x), id(y), id(z))
Output:
Hello World! Hello World! Hello World! 2103065666736 2103065666736 2103069140528
Python does an even better job reusing int objects.
x = 1
y = 1 * 1
z = int(input('Type 1 '))
print(x, y, z, id(x), id(y), id(z))
Output:
1 1 1 2816730622256 2816730622256 2816730622256
All three variables reference the same int object. Python can be less clever than when reusing strings. Python has a cache of ints. This program finds the range of cached ints for my Python (3.9.0) is -5 to 256.
cached = False
for x in range(-10000, 10000):
    y = int(x * 2 / 2)
    if (x is y) != cached:
        cached = not cached
        print(x, cached)
Output:
-5 True 257 False
"referenced" is not a concept in Python. Whenever your program creates an object, even numbers are objects in Python, the program returns a handle, or reference, to the object. This is your program's only view into the object. The handle is the address of the Python object in memory, but that is really an implementation detail and does not affect programs. When you add "1 + 2" you are actually calling a method of the int class and passing it two int objects. The method calls methods of the objects to get their value, adds the values together, and creates a new int object (or reuses a cached int object) to return the value. I guess you could say everything in Python is referenced and there is no such thing as "pass by value".

"when (does) an assignment creates a new object". Assignment never creates a new object. Assignment creates (or reassigns) a VARIABLE to reference the object. Object/instance creation occurs on the right hand side of the "=". Your question should be which operations create new objects and which don't.

Any operation involving immutable objects that produces a different value creates a new object. Adding two ints creates a new int. Concatenating two strs creates a new str. The answer is more complicated for mutable objects. Appending something to a list does not create a new list, but slicing a list does, even though the slice contains objects from the original list. Copy creates a new list, but unless you do a deep copy the new list contains the same objects as the source list. The only hard and fast rule for mutable objects is "read the docs" which should tell you if the operation is "in place" or if it returns a new object.
Reply
#7
(Sep-07-2021, 04:26 PM)deanhystad Wrote:
Quote:I'm down to trying to understand when an assignment creates a new object vs references an existing object. Gotta do more reading on this subject. Perhaps immutable objects always get referenced and static things like a string get copied.
Immutable things, what you call static, are never copied. There is no reason to make copies of an immutable object because it's value cannot be changed. Not only are immutable objects never copied, Python tries to reuse immutable objects. This code reuses 'Hello World!' for x and y but I managed to trick it with z. Notice that the ID for x and y shows they reference the same str object, but z is a different str.
x = 'Hello World!'
y = 'Hello' + ' ' + 'World!'
z = ''.join(['H', 'e', 'l', 'l', 'o', ' ', 'W', 'o', 'r', 'l', 'd', '!'])
print(x, y, z, id(x), id(y), id(z))
Output:
Hello World! Hello World! Hello World! 2103065666736 2103065666736 2103069140528
Python does an even better job reusing int objects.
x = 1
y = 1 * 1
z = int(input('Type 1 '))
print(x, y, z, id(x), id(y), id(z))
Output:
1 1 1 2816730622256 2816730622256 2816730622256
All three variables reference the same int object. Python can be less clever than when reusing strings. Python has a cache of ints. This program finds the range of cached ints for my Python (3.9.0) is -5 to 256.
cached = False
for x in range(-10000, 10000):
    y = int(x * 2 / 2)
    if (x is y) != cached:
        cached = not cached
        print(x, cached)
Output:
-5 True 257 False
"referenced" is not a concept in Python. Whenever your program creates an object, even numbers are objects in Python, the program returns a handle, or reference, to the object. This is your program's only view into the object. The handle is the address of the Python object in memory, but that is really an implementation detail and does not affect programs. When you add "1 + 2" you are actually calling a method of the int class and passing it two int objects. The method calls methods of the objects to get their value, adds the values together, and creates a new int object (or reuses a cached int object) to return the value. I guess you could say everything in Python is referenced and there is no such thing as "pass by value".

"when (does) an assignment creates a new object". Assignment never creates a new object. Assignment creates (or reassigns) a VARIABLE to reference the object. Object/instance creation occurs on the right hand side of the "=". Your question should be which operations create new objects and which don't.

Any operation involving immutable objects that produces a different value creates a new object. Adding two ints creates a new int. Concatenating two strs creates a new str. The answer is more complicated for mutable objects. Appending something to a list does not create a new list, but slicing a list does, even though the slice contains objects from the original list. Copy creates a new list, but unless you do a deep copy the new list contains the same objects as the source list. The only hard and fast rule for mutable objects is "read the docs" which should tell you if the operation is "in place" or if it returns a new object.

Thanks for the detailed reply deanhystad! Gotta shake those old concepts, object-oriented programming is still new to me.
(btw, 'static' was a typo, I meant 'mutable')
Reply


Forum Jump:

User Panel Messages

Announcements
Announcement #1 8/1/2020
Announcement #2 8/2/2020
Announcement #3 8/6/2020