[Tutor] list as a first class citizen in python? [language comparisons!]

Danny Yoo dyoo@hkn.eecs.berkeley.edu
Wed, 26 Dec 2001 00:16:41 -0800 (PST)


[Warning; this message has quite a bit of Perl code in it.  It also
borders on silly language war stuff.  My apologies!]



On Wed, 26 Dec 2001, Karthik Gurumurthy wrote:

>     Because everything is a reference, and there's no way to dereference
>     that reference, it turns out that there is no trivial way to copy
>     a list!  This fails:
> 	x = [1,2]
> 	y = x
>     Because you don't get a new list there, just a copy to an
>     old one.

The wording is getting REALLY loose here.  There is no "copy" to talk
about: 'y' and 'x' are refering to the same list.  For example:

###
>>> x = [1, 2]
>>> y = x
>>> id(x)
135040592
>>> id(y)
135040592
###

id() is something of a low-level function, so it's not really important in
casual Python use.  id() tells us where in memory an object is located.  
In the example above, we can see that 'x' and 'y' are just names for the
list value '[1, 2]', because they have the same "id".


>     Suggested work-arounds include
> 	y = x[0:]
> 	y = x[:]
> 	y = x + []
> 	y = x * 1
>     This forces people to think about references, again.

Python doesn't automatically create new lists until we tell it to.  This
can be both an advantage and a disadvantage.  I consider it a design
decision, not a flaw as the word 'work-around' suggests... *grin*


I'm not quite sure what they mean about not being able to copy a list
"trivially" though.  The 'copy' module,

    http://www.python.org/doc/lib/module-copy.html

is a nice way of abstracting the shallow copying of a data structure:

###
>>> y = copy.copy(x)
>>> id(y)
135041912
>>> id(x)
135040592
###

and works uniformly on the standard Python data types.



>     So much for lists being first class citizens!

Again, I'm not quite sure what they mean here.  If they mean "first class"
as in:

http://mitpress.mit.edu/sicp/full-text/book/book-Z-H-12.html#%_idx_1218

then I personally don't see anything about Python lists that doesn't make
it "first class".



>  Compare
>     this with Perl's
> 	@x = (1,2);
> 	@y = @x;
>     Or even with references:
> 	$x = [1,2];
> 	$y = [ @$x ];
>     or
> 	@y = @$x;
> >>>>>>>>
> 
> what's wrong with working with a reference.
>
> can someone explain the argument here?


I don't think it's an argument; I think it's more of a language preference
thing.

Perl does make it nice to work with flat data structures, but as soon as
we get into nested data structures, Perl programmers have to worry about
the exact same details about copying that Python programmers think about.  
Here's an example in Perl that's similar to the situation the author was
pointing out:

### # Perl code
use Data::Dumper;
@x = ([1,2], [3, 4]);
@y = @x;
$x[0][0] = 42;
print Dumper(@x);
print Dumper(@y);
###

This shows that Perl programmers do need to deal with the same issues as
Python programmers.  There is no magical cure here, nor just one best way
to approach things.



One other advantage of having everything as a "reference" in Python is
that Python functions don't need to do anything special when taking in
many lists as parameters:

###
def myzip(l1, l2):
    results = []
    for i in range(len(l1)):
        results.append(l1[i])
        results.append(l2[i])
    return results
###

###
>>> myzip([1, 2, 3], [4, 5, 6])
[1, 4, 2, 5, 3, 6]
###


But to get the same thing to work in Perl, we have to think twice.  A
first attempt:

###
## test.pl.
sub buggy_myzip {
    my (@l1, @l2) = @_;
    my @results;
    for my $i (0..scalar(@l1)) {
        push(@results, $l1[$i]);
        push(@results, $l2[$i]);
    }
    return @results;
}

@l1 = (1, 2, 3);
@l2 = (4, 5, 6);
print join " ", buggy_myzip(@l1, @l2), "\n";
###


won't work the way a beginner might expect!  Take a look:

###
[dyoo@hkn dyoo]$ perl test.pl
1  2  3  4  5  6
###


Here's a fixed version:

###
sub myzip {
    my ($l1, $l2) = @_;
    my @results;
    for my $i (0..scalar(@$l1)) {
        push(@results, $l1->[$i]);
        push(@results, $l2->[$i]);
    }
    return @results;
}

@l1 = (1, 2, 3);
@l2 = (4, 5, 6);
print join " ", myzip(\@l1, \@l2), "\n";
###

And this works.  But to make this "simple" function work, a Perl
programmer does need to be aware of array references.  I'd better stop
before I offend the Python and Perl Gods any further.  *grin*

Hope this helps!