list mutability

John Machin sjmachin at lexicon.net
Mon Feb 18 20:40:33 CET 2008


On Feb 19, 5:09 am, gigs <g... at hi.t-com.hr> wrote:
> hi im having this code
>
> l = [1, 3, 5, 'D', 1, 2, 3, 4, 5, 6, 7, 'A', 'S', 9, 8, 7, 6, 5, 4, 3, 2, 1, 0, 'A']
>
> why i need to copy x list? can someone explain me. If i dont copy it i get this
> result:
>  >>> took_num_range(l)
> [[1, 2, 3, 4, 5, 6, 7], [6, 5, 4, 3, 2, 1, 0], [6, 5, 4, 3, 2, 1, 0], [6, 5, 4,
> 3, 2, 1, 0], [6, 5, 4, 3, 2, 1, 0]]
>
> but if i copy it i get result as im looking for
>  >>> took_num_range(l)
> [[1, 2, 3, 4, 5, 6, 7], [9, 8, 7, 6, 5, 4, 3], [8, 7, 6, 5, 4, 3, 2], [7, 6, 5,
> 4, 3, 2, 1], [6, 5, 4, 3, 2, 1, 0]]
>  >>>
>
> def took_num_range(l):
>         j = []
>         x = []
>         for i in l:
>                 if type(i) is int and len(x) == 7:
>                         j.append(x)
>                         x = x[:]  # im mean here
>                         x.pop(0)

j.append(x) saves a reference to the same list that x refers to. So
when you later mutate that one list, all references in j refer to the
mutated list.

Instead of
    x = x[:]
    x.pop(0)
which copies the contents twice, you can do
    x = x[1:]

In a more complicated example, it would be better to take a copy at
each point:

j.append(x[:])

... then you are sure you have a photo of x and it doesn't matter how/
when you mutate x later.

>                 if type(i) is int and len(x) < 7:

This is a big trap for the casual reader (len(x) may have changed
since the previous "if" statement) ... see below.

>                         x.append(i)
>                 if type(i) is not int and len(x) == 7:
>                         j.append(x)
>                         x = []
>                 if type(i) is not int and len(x) != 7:
>                         x = []

What do you want to do if your input ends with 7 integers and no
letter, e.g.
[1,2,3,'A',1,2,3,4,5,6,7]
?

>         return j

Some suggestions:
1. use meaningful names
2. don't have hard-wired numbers like 7
3. use "else" and "elif" as appropriate to avoid repeating conditions
unnecessarily.

Here's a suggested replacement for your code, tested to the extent
shown:

C:\junk>type tooknumrange.py
def took_num_range(seq, size, grab_end=False):
    result = []
    queue = []
    for item in seq:
        if isinstance(item, int):
            if len(queue) == size:
                result.append(queue)
                queue = queue[1:]
            queue.append(item)
        else:
            if len(queue) == size:
                result.append(queue)
            queue = []
    if grab_end and len(queue) == size:
        result.append(queue)
    return result

test1 = [
    1, 3, 5, 'D',
    1, 2, 3, 4, 5, 6, 7, 'A',
    'S',
    9, 8, 7, 6, 5, 4, 3, 2, 1, 0, 'A',
    ]
test1r = [
    [1, 2, 3, 4, 5, 6, 7],
    [9, 8, 7, 6, 5, 4, 3],
    [8, 7, 6, 5, 4, 3, 2],
    [7, 6, 5, 4, 3, 2, 1],
    [6, 5, 4, 3, 2, 1, 0],
    ]
test2 = [1, 2, 3, 4, 5]
test2r = [[1, 2, 3, 4, 5]] ### or is it [] ???
tests = [
    (test1, test1r, 7, False),
    (test2, [],     5, False),
    (test2, test2r, 5, True),
    ]

failed = 0
for tno, (tseq, tresult, tsize, tgrab) in enumerate(tests):
    aresult = took_num_range(tseq, tsize, grab_end=tgrab)
    if aresult != tresult:
        failed += 1
        print 'Test %d failed' % (tno+1)
        print 'Expected:', tresult
        print 'Actual  :', aresult
print 'Failed %d test(s) out of %d' % (failed, len(tests))


C:\junk>tooknumrange.py
Failed 0 test(s) out of 3

HTH,
John



More information about the Python-list mailing list