Learned about
stringA = stringB[:] tonight from a textbook #python

@skimlines

I'm curious what the book said the use case for that was 😀

[:] makes a #copy of the entire string - or other #sequence. But strings in Python are #immutable; if you just do `stringA = stringB`, you'll get exactly the same behaviour - if someone else "modifies" B, they're actually creating a new object, and stringA will still refer to the original.

Copying a substring with [x:y] (optionally omitting one of them) is common and useful. But the whole thing... ?

@cazabon interestingly, the book said that if you use stringA = stringB, and then someone edits stringA, then this will also edit stringB because stringA and stringB refer to the same data. However, using [:] will create an actual copy that's indepedent of the copied source.
@skimlines @cazabon for strings this is rubbish. They’re immutable and aggressively merged. For lists and other sequences it is useful. But keep in mind that copying things is an expense.
@cs @cazabon ah... you're right, the textbook said [:] about lists.

@skimlines @cs

It's fine for #lists - but it's not the most "#Pythonic" way to do this, at least for lists. Like some other built-in objects, lists have a method `.copy()` which does what it says - returns a #copy of the #object. That's more obvious than using `...[:]` when reading the code, so that tends to be the preferred way in Python.

Unfortunately, #tuples don't have .copy(). They do support [:]. 🤷

[...]

@skimlines @cs

Keep in mind these are all "shallow" copies. It's a new list, but the objects in the list aren't copied, they're just additional references to the values in the original list. If those values are mutable objects - like dicts or lists - then someone changing one of those will affect the value seen in the copy.

e.g. in next post.

@skimlines @cs

>>> l1 = [1, 2, 3, {"a": 16}, 4]
>>> l2 = l1.copy()
>>> l2
[1, 2, 3, {'a': 16}, 4]

l2 is a copy of l1. Contents appear the same.

>>> l1[3]["a"] = 37
>>> l2
[1, 2, 3, {'a': 37}, 4]

The dict in the list is a mutable object. When we used l1 to access it, and then modified that dict, the change shows up in l2, because the dict it contains is the same object as in l1.

>>> l1[3] = {"a": 59}
>>> l1
[1, 2, 3, {'a': 59}, 4]
>>> l2
[1, 2, 3, {'a': 37}, 4]

[...]

@skimlines @cs

But replacing the dict object with a different one (above) in l1 does *not* affect l2, because its 4th element is still the original dict object, and that replacement doesn't affect l2 because l2 is a different list object.

It's like copying a list of pointers in C.

If you need to copy the contents of containers like lists and dicts and ensure you get your own unique objects, the copy module's deepcopy() will do that for you.

Have fun!

@cazabon @skimlines very belated followup post here: the purpose of .copy() is to make a new (shallow) copy of something so that the copy or the original can be modified independently (which one depends on your use case).

Tuples are immutable, and .copy() has no utility. Symmetry is nice, but:

t1 = 1, 2, 3
t2 = t1

is just as functional as some .copy()