Python: copying a list the right way
henry.precheur.org
henry.precheur.org
While the article explains some concepts well, [:] is no less pythonic than list(), though the latter might be more readable for people completely new to the language. In fact, it would be helpful to become very comfortable with python slices early on, because they allow you to easily manipulate any sequence, not just lists. For example, you can reverse a string using slices in one line:
reversed_string = orig_string[::-1]
Staying away from slices, you're leaving that power behind.a = range(6) a[::2] = range(10,13) -> generates a with [10,1,11,3,12,5]
I saw a very elegant implementation of Eratosthenes sieve based on that trick
from numpy import bool_, nonzero, ones
def primes_upto(limit):
is_prime = ones(limit, dtype=bool_)
for n in xrange(2, int(limit**0.5) + 1):
if is_prime[n]:
is_prime[n*n::n] = 0
return nonzero(is_prime)[0][2:]cryptic is anything that can stay in the head 'ram' of a normal programmer. stuff that typically needs to be written down to make sense.
i went through the above code with pencil and paper and it made sense. now i can look at the code and it makes sense. the person who originally wrote the code had to go through those steps too (not necessarily on paper, but loading the process into head ram). same with regex.
no big deal. i know about slices, and if i didn't that's what mentors are for---oh, that symbol? search for python slices! or a python book. at least python doesn't has less than a handful nonsearchable crypticness.
the comment linking to the eratosthenes sieving explanation was helpful.
1. Mark all integers that greater than 1 as primes. 2. Take the smallest prime that is not already considered and cross out all its multiples. 3. Repeat 2nd step for the next prime.
The above Python code that returns all primes number less than a given limit uses two optimization:
1. Repeat 2nd step upto sqrt(limit), not upto limit. 2. Start crossing out at the square of the prime, not at twice of the prime.
The only “intuitive” interface is the nipple. After that, it’s all learned. http://news.ycombinator.com/item?id=409288 (It might be not true literally but the quote is useful as a general idea that people have different backgrounds; an “intuitive” thing for one person is a cryptic for another. Intuitiveness changes with experience.)
/^1?$|^(11+?)\1+$/See my answer http://news.ycombinator.com/item?id=479514
Every single instance of "reversed_string = orig_string[::-1]" should be preceded with a comment saying "reverse the string", because it's not immediately obvious what [::-1] does. You can learn what it does, but it's not obvious, the same way a cryptic regex isn't obvious. So why not write reversed_string = orig_string.reverse()? (Or reverse( orig_string ) if that's more Pythonic.)
The purpose of comments should not be teaching the reader the language, that just makes the code noisy.
Basic language features don't need comments, difficult to read lines do.
Regexes shouldn't, IMO, be removed or avoided in Perl - they're a crucial part of the language, and one of the reasons Perl is so good for what it's good for.
Same goes for slices in python. Just like how if you're using Perl, you need to become familiar with regular expressions, if you're using python, you need to become familiar with slicing, because it's a crucial part of the language, and you'll probably be writing a lot of "un-pythonic" code in python without them, as well as being utterly unable to read someones code who is familiar with them.
As far as palish's point about explicit and implicit
listb = lista[:]
is explicit, once you're familiar with the basic features of python, assuming you consider slicing a "basic feature" - I certainly do.reverse() does an in-place reversal of the original list and does not return the result.
This works too, and IS more readable. But this doesn't work for a string for example.
I didn't stay to replace ALL slices with list(). Of course this wont work all the time. For instance [:] has nice properties like conserving the sequence type:
>>> (1, 2, 3)[:] (1, 2, 3)
>>> [1, 2, 3][:] [1, 2, 3]
This is not possible if you call list(), or tuple() directly.
Slice is a feature that requires more "cleverness" from the programmer and more brain power from the guy reading the code.
To cite Dijkstra: "[The competent programmer] avoids clever tricks like the plague."
If list() make your code clearer, use it. The goal is to write a working program with clear and readable code, not to show off with you mastery of slices.
Also [:] returns a reference to the existing tuple instead of a copy:
>>> a = (1, 2, 3)
>>> id(a)
3081276268L
>>> id(a[:])
3081276268L`copy.copy()` makes a shallow copy of both a ordinary list and numpy arrays.
use deepcopy or list().
I don't even use [] or {} to create empties anymore. I much prefer explicit esp since there is proliferation of container types and it's silly, inconsistant, and confusing that dicts and lists have syntactic exceptions.
newlist = list()
newdict = dict()
newdict = defaultdict(str)
etc.And to anuraggoel. not using [:] is not avoiding slices. just as not using string += "ext" (esp in loop) is not avoiding strings.
'not not variable' is similarly faster than bool(variable).
(not that I've run into this often in Python.)
Whoopee fucking doo, really.
It would have been worth mentioning the 'copy' module. 'copy.copy' for shallow copies of any object, 'copy.deepcopy' for recursive copies.
Of course, even if true, that may not matter.
See, I'm all about the weasel words today.
def copy(x):
"""Shallow copy operation on arbitrary Python objects.
See the module's __doc__ string for more info.
"""
cls = type(x)
copier = _copy_dispatch.get(cls)
if copier:
return copier(x)
# more after this point but it's not relevant for lists
So calling copy.copy on a list over using list() will check for the type, look up the type in a dictionary after which it will proceed as if you called list() yourself.>>> a = [1, [2, 3]] >>> b = list(a) # or a[:], they are identical >>> a[1].append(4) >>> a.append(5) >>> a [1, [2, 3, 4], 5] >>> b [1, [2, 3, 4]]
If you had used copy.deepcopy, b would now be [1, [2, 3]] as intended.
of course, what with classes i very rarely have deep lists anyway. in fact, copying lists is seldom an issue i run into (though i always run a few tests at the prompt to make sure my understanding is right because mutability bugs can be hard to track down later).
That doesn't follow.
Beginners need to learn list slicing to get anywhere with Python, and by the time they've got through [1], [0:10], [2:], [:5], [:-1], [0:10:2] and so on then [:] is just another use in the same pattern.
Followup: The tutorial didn't seem to mention what [x:y:z] does, so I checked:
>>> range(15)
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14]
>>> range(15)[0:10]
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
>>> range(15)[0:10:2]
[0, 2, 4, 6, 8]
>>> range(15)[0:10:3]
[0, 3, 6, 9]
So the third argument is the number of elements to skip (minus one) between each item in the new sequence. Neat.These are "extended slices" and the third argument is the step. This link also explores deletion and the __getitem__ method:
One can also now pass slice objects to the __getitem__ methods of the built-in sequences:
>>> range(10).__getitem__(slice(0, 5, 2))
[0, 2, 4]
Or use slice objects directly in subscripts:
>>> range(10)[slice(0, 5, 2)]
[0, 2, 4]
them = things[:]
looks different from
them = (list) things;
which in C-like languages usually does not make a copy of things.
Using slices may avoid fallacies like "I know that int(x) is something like a cast of x to int, so list(x) is like a cast of x to list, which does nothing when x is a list".
I guess that this allows me to type help(list) and figure it out, but I would have done that anyway to identify an operator.
Clearly the answer is a .clone() or .copy() method...
if you want Python to smell like Java or Ruby. :->
Seems to disagree with you.
What I'm saying is, there's no copy or clone or dup method built into Python's base object, as there is in Ruby. And Python programmers are not instructed to define a clone method for a class, as in Java. A __copy__ or __deepcopy__, okay, but not copy or deepcopy.
Hence, in Python you just don't see foo.clone() or foo.dup() or foo.copy() or foo.deepcopy(). Instead, you see copy.copy(foo) or copy.deepcopy(foo). Pythonic code often looks different than Ruby or Java, looks more stand-alone function-y than instance method-y.
I honestly have never seen someone do foo[:] just to copy a list, this is all news to me.