3

So I'm writing a game in Python, and a problem I'm trying to solve requires that I turn a 2D list (that is, a list of lists) into a 1D list. I've seen several ways to do this, but I don't know if any of them create copies of any objects held within or just new references. To be honest, the Python standard library confuses me in that it's not always obvious whether objects/sequences are copied or just filled with references.

These are the assumptions about my particular situation;

  • We can assume that my 2D list, if represented as a matrix, is perfectly rectangular.
  • It'd be nice if I could have a solution for which the above assumption isn't true, but it's not required at the moment.
  • No need to worry about 3D+ lists.

Thanks!

JesseTG
  • 2,025
  • 1
  • 24
  • 48
  • 1
    "To be honest, the Python standard library confuses me in that it's not always obvious whether objects/sequences are copied or just filled with references." The distinction does not make a lot of sense. However, assignment does not copy in Python. Generally you only get copies when you ask for them; however, "copying" something usually entails setting a bunch of references anyway. For example, an `int` in a Python program is an object that contains a lot more data than just the numeric value; but even something like `x = int(y)` (where `y` is already an int) will not re-create that object. – Karl Knechtel Sep 06 '22 at 03:37

4 Answers4

7

While I can't speak for every way you might have seen, in general no copies will be made due to Python's object semantics.

>>> a = [[1,2.3,'3', object()], [None, [2,3], 4j]]
>>> b = [v for row in a for v in row]
>>> a
[[1, 2.3, '3', <object object at 0x1002af090>], [None, [2, 3], 4j]]
>>> b
[1, 2.3, '3', <object object at 0x1002af090>, None, [2, 3], 4j]
>>> [id(obj) for row in a for obj in row]
[4298185464, 4298195480, 4299558664, 4297781392, 4296523616, 4299692656, 4297773296]
>>> [id(obj) for obj in b]
[4298185464, 4298195480, 4299558664, 4297781392, 4296523616, 4299692656, 4297773296]

That v doesn't mean "some new object equal to v", it simply means v, the object itself.

DSM
  • 342,061
  • 65
  • 592
  • 494
  • I had to wrap my head around the syntax there for a moment, as I'd expected it to be something like `[v for v in row for row in a]` at first. Once I realized the nested nature of the logic, (that the're just nested for-loops,) the syntax started to click. Would not have figured that one out on my own. Thanks! – Mark G. May 31 '13 at 06:59
1

Regarding to your question about copies/references, Python operates with mutable/immutable variables in different ways: for immutable variables (int,float,string,tuples etc) you will get copies, for mutable (everything else) - "references".

Regarding flattening of python list: look at itertools module. You can do something like this:

>>> from itertools import chain
>>> chain(*[[1,2,3], [4,5,6], [7,8,9]])
<itertools.chain object at 0x104ff5110>
>>> list(_)
[1, 2, 3, 4, 5, 6, 7, 8, 9]
Alexey Kachayev
  • 6,106
  • 27
  • 24
  • 1
    What's with that asterisk on Line 2? – JesseTG Aug 26 '12 at 19:31
  • 1
    no copies are created whatever objects are in 2D list. You can check the identity with `is` operator. – jfs Aug 26 '12 at 19:31
  • 1
    @JesseTG: that's argument unpacking (see [here](http://docs.python.org/tutorial/controlflow.html#unpacking-argument-lists)). Basically, `f(*(2,3)) == f(2,3)`. – DSM Aug 26 '12 at 19:38
  • also `chain.from_iterable([[1,2,3], [4,5,6], [7,8,9]])` – jfs Aug 27 '12 at 09:13
1

Probably this piece from the python documentation can help you understand what python does:

Nobody “owns” an object; however, you can own a reference to an object. An object’s reference count is now defined as the number of owned references to it. The owner of a reference is responsible for calling Py_DECREF() when the reference is no longer needed. Ownership of a reference can be transferred. There are three ways to dispose of an owned reference: pass it on, store it, or call Py_DECREF(). Forgetting to dispose of an owned reference creates a memory leak.

Now, this refers to using python objects from C code, but, since the interpreter does exactly this, you can understand how objects and references are managed in python.

"No one owns an object", so when you put an item in a list, this list does not copy the item, it simply owns a reference. In practice what's happen is that it copies a pointer and increments the reference count(and maybe also decrement the other object that was replaced).

From the python point of view, you cannot access objects, but only references to objects. Anything you can put in a variable is actually a reference to an object. Thus [1,2].append(3) will not copy 3, but simply the reference.

If you want to copy an object, then you should use the copy module Or, for some types, call the class with the object as argument(e.g. int(1234) creates a copy of "1234", even though python stores in a small array the integers from -5 to 256 and small strings, so doing int(42) does not copy "42")

Bakuriu
  • 98,325
  • 22
  • 197
  • 231
  • the description is CPython specific. But the answer to the OP is the same for all Python implementations (Pypy, Jython, IronPython): no copies are created. – jfs Aug 29 '12 at 22:30
  • Well, he did not mention about using some specific python implementation, so I assumed CPython. – Bakuriu Aug 30 '12 at 06:46
0

You can do something like:

arr2d = [[1,2,3],[4,5,6],[7,8,9]]
arr1d = []
for x in arr2d:
   arr1d.extend(x)

This does not create copies, change the 1d array, it won't reflect. For future referecne, if you want to make sure that it is not copied, import the copy module, and use copy.deepcopy(array) instead of just array, and you will be safe.

SexyBeast
  • 7,913
  • 28
  • 108
  • 196