Python Worst Practices

Steven D'Aprano steve+comp.lang.python at pearwood.info
Fri Feb 27 20:09:31 EST 2015


Travis Griggs wrote:

> If I were giving a talk at SPLASH (or some other suitable polyglot
> conference), I might do one called “Language Design Worst Practices”.
> 
> One of my first slides might be titled:
> 
> Abuse Common Tokens in Confusing Ways
> 
> * Make your language have a lot of keywords. Enough to make memorizing
> them ALL unlikely, requiring constant visits to your documentation 

Is 33 a lot?

py> import keyword
py> keyword.kwlist
['False', 'None', 'True', 'and', 'as', 'assert', 'break', 'class', 
'continue', 'def', 'del', 'elif', 'else', 'except', 'finally', 'for', 
'from', 'global', 'if', 'import', 'in', 'is', 'lambda', 'nonlocal', 
'not', 'or', 'pass', 'raise', 'return', 'try', 'while', 'with', 'yield']


> * Make sure said keywords are many of the obvious words programmers would
> use in their applications (map, object, bytes, dir, etc) 

Luckily, Python doesn't make that mistake of making built-ins keywords. That
would require making actual changes to the parser each time a new built-in
function was added, as well as breaking people's existing code.

Fortunately, Python has a much better system: a small set of keywords, very
few of which would make useful variable names ("else = 23"), and a much
larger set of regular names in a built-in namespace.


py> import builtins  # use __builtin__ in Python 2
py> sorted(vars(builtins).keys())
['ArithmeticError', 'AssertionError', ... 'type', 'vars', 'zip']


There's 147 of the built-ins in Python 3.3, although a few of those aren't
truly built-in, merely added at interpreter startup.

The ability to shadow built-ins is not a bug, it is a feature. It's an
amazingly powerful feature, and not particularly newbie-friendly, but
*many* things are not easy for newbies to master or avoid abusing.

- Code can override, or overload, built-ins, either at the level of 
  an entire module, or inside a particular function.

- Modules can offer functions which clash with a built-in name.
  E.g. reprlib.repr, math.pow.

- More importantly, modules can offer stable APIs with no fear that 
  the introduction of a new built-in function will require them to 
  change their function's name.

- Which is a special case of a more general benefit, the introduction 
  of a new built-in name does *not* break existing code that already 
  uses that name.


Newbies misuse this feature because they still have a wishful-thinking
approach to programming. One example of wishful-thinking is the common
newbie mistake of wondering why their loop variable never changes:

# Toss a coin until you get Tails.
x = random.random()
while x < 0.5:
    print "Heads"
print "Tails"

Isn't it obvious that I want x to get a new random number every time through
the loop? I wish the computer understood me so I didn't need to write all
the steps out.


Likewise:

int = 23
n = int("42")

Isn't it obvious that the second use of int has to be the built-in function?
I wish that the computer would understand from context which one I mean.

Other newbie stylistic mistakes which can increase the chance of shadowing
errors include:

* Too many overly generic variable names like "int" and "str".

* Insufficient use of functions and too much top-level code. When they
  shadow a built-in, they shadow it everywhere.

* Excessively large functions that do too much. By the time they reach 
  the end of their 300 line function, they have forgotten that they
  have already used "list" for a variable name.


However, even experienced developers can make this mistake too. Generally
speaking, it's trivially easy to recover from. Although if you're doing it
*regularly* that might be a hint of deeper problems, e.g. poor variable
naming skills, too much top-level code.

There's no harm in calling a local variable "id", if you don't use the
built-in id() inside that function. That's one of the reasons why functions
exist, so that the names you use inside a function are distinct from those
outside.



> * Design your syntax so that you can’t disambiguate them contextually
> between bind and reference 

Do you have an example of where Python cannot distinguish between a binding
operation and a reference?


> * Be sure to use it in a late bound language where no warnings will be
> provided about the mistake you’re making at authorship time, deferring the
> educational experience to sundry run times

Python raises a SyntaxError at compile time, not run time, if you try to
bind to a keyword:

py> global = 23
  File "<stdin>", line 1
    global = 23
           ^
SyntaxError: invalid syntax




-- 
Steven




More information about the Python-list mailing list