More random python observations from a perl programmer

Tom Christiansen tchrist at
Thu Aug 19 17:22:49 CEST 1999

Per your collective requests, here is one of the documents
with my collective observations from learning Python.

This article was posted to the Perl newsgroup, but I have duplicated
the posting here without crossposting to avoid flame wars.


    When Python talks about tuples, lists, and dictionaries, 
    Perl people should think of lists, arrays, and hashes
    respectively.  Except that tuples in python are 1st-class
    citizens.  (Unclear whether this is good; see below.)

    Python's print() function is more like the Perl debugger's
    "x" command; that is, it's recursive and pretty-printed.

GOTCHA: (medium)
    The whole mutability versus immutability thing causes many 
    confusions.  For example:
	t = ([1,2],3)
	t[0][0] = "fred"	# legal
	t[1]    = "fred"	# ILLEGAL
    I don't understand why tuples weren't just lists that weren't
    somehow marked "read-only" or "constant".   That way you
    could do the same with dictionaries or any other data types.

GOTCHA: (low)
    You can't use "in" on dicts.  Instead, you must use 
	d = { "fred":"wilma", "barney":"betty" }
	if d.has_key("fred"):	# legal
	if "fred" in d:	        # ILLEGAL
    I don't understand why this was done.

    Both "and" and "or" return their last evaluated value
    in both Python and Perl.

GOTCHA: (low)
    I don't understand why we have so many C operators, such as "&" and
    "|" and "~", but we don't have "&&" or "||" or "!"  We also have "<<"
    and ">>".  It's bizarre that the relational and "?:" are missing.
    Python uses so much from C to make people see what they expect to see,
    but this is missing.  Why?

GOTCHA: (high)
    Local variables are never declared.  They just *happen*.  If a
    variable is assigned to, then it belongs to that "scope".  If it's
    only looked at, it may be from the current global scope.

GOTCHA: (medium)
    If you have a function that expects two arguments, such as:
	def fn(x,y):
	    return x + y
    and you have a tuple of the two elements:
	t = (1,2)
    You can't just call that function:
	print fn(t)
    Or you get a "TypeError: not enough arguments; expected 2, got 1"
    error.  You have to use this apply() function to get around this
	print apply(fn,t)

    The number zero, the empty string, and the special value None
    are all false.  Non-zero numbers and non-empty strings are
    all true.  This is all like Perl.  But here an empty tuple () 
    is false, as is an empty list [] or an empty dictionary {}.
    In Perl, the last two are true if you write them that way, because
    they're references.  

GOTCHA: (medium) 
    Assignment is a statement, not an operator.  That means that
    people are constantly writing loops that aren't really testing
    the exit condition, as in:
	while 1:
	    line = os.readline()
	    if not line: break
    Because they can't write:
	while (line = os.readline()):	# ILLEGAL
    I feel that this hides the real test condition in a place
    it doesn't belong.

GOTCHA: (low) 
    There are no compound assignment operators, like +=.

    There is no "test at the bottom" loop as in C's do{}while.

    There is no "three part for" loop from C.

GOTCHA: (low)
    There are no loop labels, and therefore "break" and "continue" are
    only through the next level.  This encourages the proliferation of
    spurious boolean condition variables.  It was annoying when Pascal
    made you do the same thing.  There is no "goto", which is how C
    works around it.  As with many things in Python, here you force the
    user to be tangled up with exceptions just to do very simple things.
    That's not as clear a solution.

    Both Python and Perl are 0-based for indexing.

    Python uses [] to create a new empty list, which you
	then reference with [] subscripts.
    Perl uses [] to create a new empty array, which you
	then reference with [] subscripts.
    Python uses {} to create a new empty dictionary, which you
	then reference with [] subscripts.
    Perl uses {} to create a new empty hash, which you
	then reference with {} subscripts.

GOTCHA: (high)
    I had hoped that having *only* reference semantics would spare the beginner
    from having to keep in mind the difference between a reference and its
    referent as we have in C or Perl.  Alas, it is not to be.
	 x = [1,2,3]
	 y = x
	 x[1] = "fred"
	 print y

    You can "foreach" across multiple items.
	a = [ [1,2], [3,4], [5,6] ]
	for (i,j) in a:
	    print i+j

    Python's "longs" are actually built-in, arbitrary precision 
    integers (i.e. BigInts)

    Named parameters are built into the language.  So are 
    default paramters and variadic ones, although order matters
    of course.  It's quite elaborate.

    Namespaces seem to be *the* thing in this language.  You can always
    get a listing of anything's names/attributes, even all the built-ins
    (which you can't do in perl) or all the names in the local scope
    (which you also can't do in perl).  

    A class method call gets no class name as its implicit extra
    argument the way an instance method would.  

    In Python, there is no difference between single and double quotes:
    both interpolate C-style escapes like \t, and neither 
    interpolates variables.  In Perl, single quotes do neither escapes
    nor variables, but double quotes do both.

GOTCHA: (low)
    Single and double quoted strings can't cross line boundaries.
    You need triple quotes for that!

    Python does a depth-first recursive search on class ancestors
    looking for resolution of a method call.  So does Perl.

    Python also does an inheritance search for data members that
    are missing.  This is probably cool, however.  There is no
    big difference between a functional and a data member.

    There are no private variables in a module or a class.
    They can always be fully-qualified and accessed from
    without.  Perl can do this with file-scoped lexicals,
    but still can't do so with data members (modulo obscure
    tricks such as using a non-hash for an object).

GOTCHA: (medium)
    Perl's hex() function is the opposite of Python's.  Perl's hex
    function converts 22 into 37.  Python's hex function converts 37 into
    0x22.  Oh my!  Like hex, Python has oct() completely backwards from
    Perl.  Python's oct(44) returns 054, but Perl's oct(44) return 36.

    Constructors and destructors (__init__ and __del__) aren't 
    automagically called in parent classes in Python.  Neither
    in Perl.

    Because there are no variable markers in Python, there are no 
    variable interpolation in strings.  That makes something like
	print "this $x is $y here\n" 
    become in python
	print "this %d is %d here\n" % (x, y)
    But since "stringification" is handy, Python usurps backticks
    for that purpose:
	print "this is", `x`, "here"
    Is going to call the x object's stringification method (x.__str__)
    to get a print value.  In perl:
	print "this is $x here"
    also does so, although the stringification method is oddly named
    in Perl.
	use overload '""' => ....

GOTCHA: (medium)
    Things that return lists can't be used where a tuple is expected.
    A function that returns a list must be coerced into a tuple to 
    use this, though.
	def fn: return [1,2]
	print "this %d is %d here\n" % fn()  # ILLEGAL
	print "this %d is %d here\n" % tuple(fn())
    The illegal part points out that this is an 
	TypeError: illegal argument type for built-in operation
    Which isn't very helpful.  

GOTCHA: (high)
    Python has no manpages!  The horror!!!!!!!!!!!!!!!!!!!!!!

GOTCHA: (low)
    Often Python's error messages leave something to be desired.
    I don't know whether 

GOTCHA: (medium)
    All ranges are up to but *not including* that point.  So range(3)
    is the list [0,1,2].  This is also true in slices, which is the
    real gotcha part.  A slide t[2:5] does not include t[5] in it.

    Negative subscripts count back from the right.  Same as in Perl.

    Slices can omit either the starting point or the ending point.
	t[2:5]		# elts 2 through 4 (but not 5!)
	t[:5]		# elts up to 4 (but not 5!)
	t[:-1]		# all but the last element (up to but not to -1)

    Python doesn't convert between strings and numbers unless you tell
    it to.  And if you don't tell it to, you get an exception.  Therefore
    you have no idea what these 
	x = y + z
	x = y - z
	x = y * z
    are really doing.  The first would concat strings or lists, the last would
    repeat them.  Personally, I don't care for this, because I always
    wonder what subtracting one string or list from another would be.

    Python has a reduce() built-in that works somewhat like map().

    Python's filter() [same-ish as Perl's grep()] and its map() operators
    can operate on items from separate sequences in parallel.

GOTCHA: (high)
    This is a big surprise: strings are not atomic (although they are
    immutable).  They are instead sequences of characters.  This comes
    up in strange places.  For example:	
	>>> for c in ("weird", "bits"):
	...      print c
	>>> for c in ("weird"):
	...      print c
    The second case autosplit the characters!  Here's another:
	>>> print map(None, "stuff")
	['s', 't', 'u', 'f', 'f']
	>>> print map(None, "stuff", "here")
	[('s', 'h'), ('t', 'e'), ('u', 'r'), ('f', 'e'), ('f', None)]
    (Python's map None is like Perl's map $_.) 

    This also means that instead of substr() in Perl, you slice a string
    as in 
	s = "string"
	print s[2:4]
    Yes, that's all you got.  Strange, eh?  See below on ranges.

GOTCHA: (high)
    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.  Suggested work-arounds include
	y = x[0:]
	y = x[:]
	y = x + []
	y = x * 1
    This forces people to think about references, again.    
    So much for lists being first class citizens!  Compare 
    this with Perl's
	@x = (1,2);
	@y = @x;
    Or even with references:
	$x = [1,2];
	$y = [ @$x ]; 
	@y = @$x;

GOTCHA: (medium)
    Slices in Python must be contiguous ranges of a sequence.
    In Perl, there's no such restriction.  

GOTCHA: (medium)
    You can't slice dictionaries at all in Python.  In Perl, it's
    easy to slice a hash, and just as sensible.

GOTCHA: (high)
    As we saw with lists, because everything is a reference, and there's
    no way to dereference that reference, this means that again there
    is also no built-in, intuitive way to copy a dictionary.  Instead, 
    the suggested work-around is to write a loop:
	new = {}
	for key in old.keys:
	    new[key] = old[key]
    But this is guaranteed slower, because it's not at the C level.
    It also shows that dictionaries aren't first class citizens in python
    as they are in Perl:
	%old = ( "fred" => "wilma", "barney" => "betty" );
	%new = %old;
    or even with references:
	$old = { "fred" => "wilma", "barney" => "betty" };
	$new = { %$old };
	%new = %$old;

GOTCHA: (high)
    Lists don't autoallocate the way dictionaries do.  So while this works
    on two dictionaries:
	new = {}
	for key in old.keys:
	    new[key] = old[key]
    This fails on two lists:
	new = []
	for i in old:
	    new[i] = old[i]
    Because Python refuses to grow the list as it did the dictionary.  Perl
    will grow both arrays and hashes just fine.

GOTCHA: (medium)
    There's no way to set up a permitted exports list.  The caller may have
    anything they ask for.  

    DBM files seem (?) to automatically know about nested datatypes.   

GOTCHA: (medium)
    Importing a variable only gives you a local copy.  In Perl, it makes
    two names for the same object.

GOTCHA: (low)
    Because you can't declare a local variable, you can't create variables
    local to something other than a module (ie. file), class, or function.
    There are no loop-local variables; they would persist through the end
    of the function.  You can't have a scope you can't name (well, almost;
    see lambdas)

GOTCHA: (high)
    Scopes don't nest in Python, but they they do in Pascal, Perl, or C.
    This is supposedly "simpler", but it's very suprising in many ways:
	x = 1			# global
	def fn1(): 
	    x = 10		# implicit local
	    def fn2():
		print x		# whose?
    The answer is that it prints 1, because fn2 has no x in its local
    scope, so gets the global.  The suggested work-around is
	    def fn2(x=x):
    using default parameters.

GOTCHA: (medium)
    You can't cut and paste these examples because of the issue
    of white space significance. :-(

GOTCHA: (low)
    List objects have built-in methods, like 
    But string objects don't.  You have to import
    from the string module to get them, and then they're
    functions, not methods.

GOTCHA: (low)
    You have insert and append methods for lists, but only
    a del function.  Why is it a function not a method, and
    why isn't it spelled out?  Apprently the fashionable wayto do this is
	a[2:3] = []
    Which of course, deletes only one element.  ARG.

    Where Python says "elif", Perl says "elsif".

    Neither Perl nor Python supports a case or switch statement, requiring
    multiway if's or a lookup of a function dispatch table.

    Where Python says "break" and "continue", Perl says "last" and "next".

    Where Perl says "continue", Python uses "else" (for a loop block).

    Constrast Python:
	import os
	data = os.popen('grep %s %s' % (patstr, srcfile)).read()
    with Perl:
	$data = `grep $patstr $srcfile`;

GOTCHA: (high)
    There doesn't seem to be away other than using low-level hand-rolling
    of posix functions to supply things like os.popen and os.system
    a list of shell-proof arguments.  Looks like it always goes through the
    shell.  This has security ramifications.  

GOTCHA: (high)
    Because you can't use readline() to get a number, people seem to enjoy
    calling eval() just to get a string turned into a number:
	import sys
	str = sys.stdin.readline()
	num = eval(x)
    This is scary.  Even scarier is the propensity for calling input(),
    which auto-eval()s its input to make sure it's the right "type".
    (Funny that a soi-disant "typeless language" should be so danged
    picky about this.)  That means you see people write:
	num = input("Pick a number? ")
    But the person can supply as an answer 0x23 or 2+9 or pow(2,4)
    or os.system("rm -rf *").  I am stunned.

GOTCHA: (medium)
    The expression 3/4 is 0, which is false.  In Perl, 3/4 is 0.75,
    which is what you'd expect.  You need to force the floatness.
    Sometimes Python is just too tied to C, other time not enough.
    This is one of the former.

GOTCHA: (low)
    Regexes default to emacs style, not egrep style!  Gads!  This is
    surprising to anyone except an emacs weenie.  Sigh.  And just *try*
    to read the manpage on the differences based on passed in arguments
    establishing the style.  There aren't any manpages!  Have a nice day.

GOTCHA: (low)
    An out-of-bounds list reference raises an "IndexError: list index out
    of range" exception, but not if you use a slice to get at it!
	t = range(5)
	print t[2:17]
      [2, 3, 4]

    Relationals stack:
	x < y < z
	x < y and y < z

    There's no distinction between the mechanism used for operator
    overloading and tying, as there is in Perl.  Both use special
    method names.  

GOTCHA: (high)
    Python's lambda's aren't really lambdas, because they are only 
    expressions, not full functions, and because they cannot see
    their enclosing scope.  

    What Perl calls eval(), Python calls exec().  
    What Perl calls exec(), Python calls os.execv().  

GOTCHA: (low)
    Python's eval() only works on expressions, not full code blocks full
    of statements.  You need exec() for the whole thing.

GOTCHA: (medium)
    This is a tuple of two elements
    But this is a tuple of one element:
    Whereas this is merely the expression 1:
    Yes, the trailing comma counts.

GOTCHA: (low)
    Normally, a print statement in python adds a newline.  If you
    don't want one, you need a trailing comma!

    Python uses a pow() function for Perl's ** operator.
    Wait, no, they just added "**" later, with the same
    semantics as in Perl, except that something like 2**3**4
    in Perl gives you an answer, and in python, an exception.

GOTCHA: (low)
    Python has a round() function, but it seems to ignore the 
    IEEE rules of rounding.  However, its sprintf operator doesn't:
	>>> print "int of %f is %.0f" % (1.5, 1.5) 
	int of 1.500000 is 2
	>>> print round(1.5,0)
	>>> print "int of %f is %.0f" % (2.5, 2.5)
	int of 2.500000 is 2
	>>> print round(2.5,0)
    And I'd jolly well like to know why I wasn't allowed to use
	print "int of %f is %.0f" % (2.5) * 2
    or if needed, 
	print "int of %f is %.0f" % ( (2.5) * 2 )

GOTCHA: (medium)
    Anything that python doesn't like, it raises an exception about.
    There is no fail-soft.  Even non-exception issues raise exceptions.
    It's pervasive.  K&P curse languages that force people who want
    to open() a file to wrap everything in exception code, saying that
    "failing to open a file is hardly exceptional".  

GOTCHA: (medium)
    You can't just interchange tuples and lists in python the way
    you can lists and arrays in Perl.  This is an error:
	import sys  # otherwise, no argv for you, buddy
	print "First arg %s and second %s" % sys.argv[1:3]
    because you need
	print "First arg %s and second %s" % tuple(sys.argv[1:3])

GOTCHA: (low)
    I can't figure out how to write a class destructor for at exit
    handling the way I can with Perl's END{}

GOTCHA: (medium)
    Python has no 2nd GC pass at thread shutdown time to find lost
    objects and destruct them.  This is a problem on embedded systems,
    and breaks correctness issues.

    Like Perl, Python requires access to members through a self reference.
    This gets rid of C++'s icky hidden scope.  This is good.  

    I believe that Python checks the prototype signature on methods
    as well as on functions.  I wonder whether Tim Bunce's ideas run-time
    evaluation of prototypes for Perl might be able to do this.

GOTCHA: (low)
    sort and reverse are in-place.  This leads to verbose code, such as:
	old = [1,2,3]
	new = old
    Likewise for sort.  

GOTCHA: (low)
    You have to compiled the definition for a  function before you may
    compile the call it.  This is ok
	def fn(): 
	    print "hi"
    But putting the fn() call first:
	def fn(): 
	    print "hi"
    Produces the ever helpful: 
	NameError: fn
    error message.  So much for autoloading.  Then again, this would
    save me from the darned legnth() bug in Perl, that is, switching
    characters.  Maybe a "no autoload" pragma for perl?  I always though
    "use strict subs" shoud ldo this.  But another python mystery is
    why it's ok to compile code to methods that you don't know whether
    will be legal, but not functions.  
	ob = some_fn()
    That will compile, even though I used an extra 't'.  It will die
    on run-time, just as in Perl.  But the fn() case at the start 
    of this looked like a compile-time death.
    ADDENDA: It turns out that def() happens *AT RUN TIME* in python.
    That's the root of this problem.  In Perl, a sub{} is a compile-time

GOTCHA: (low)
    When you need to make an empty copy of the same type, you write
	x = y[:0]
    So much for being typeless. Sigh.

    In theory, the Python rexec() module should be like Perl's Safe
    module.  I haven't tested to see whether they chroot the namespace,
    or whether the evil method name leakages occur through the box.

    Any function of class can have a significant string called a "doc
    string" that can be accessed as whatever.__doc__

    What is and what is not thread safe?

    Python uses None in many places where Perl uses undef().
    But not all.  You can't over-subscript an array and get
    away with it.

    I know it's weird, but it does make it easier to write poetry in perl.    :-)
                    --Larry Wall in <7865 at jpl-devvax.JPL.NASA.GOV>

More information about the Python-list mailing list