Python Worst Practices

Chris Angelico rosuav at gmail.com
Tue Mar 3 18:12:19 EST 2015


On Wed, Mar 4, 2015 at 9:46 AM, Christian Gollwitzer <auriocus at gmx.de> wrote:
> I can agree with the argument that operator precedence can make
> problems; e.g. this
>
>         cout<<a==b;
>
> does not output the truth value of a==b, but instead outputs a and
> compares the stream to b (which will usually fail to compile, but still).
>
> But the argument that << is a left-shift and nothing else is silly. <<
> for bitshift is nothing more intuitive than % for modulus (where in math
> does this symbol occur?) or [] for indexing. We just got used to it, and
> to me << as an arrow for putting someting into a stream seems pretty
> obvious.

I don't mind the idea of << meaning something other than left-shift.
The problem comes usually when you try to use a binary operator in
place of a variadic function, and suddenly you need a pile of hacks to
get around operator overloading.

If you use << to add a flag to a flag-set (where the "flag-set" might
be an integer that retains bit-flags, or a set that retains strings,
or whatever), that would make reasonable sense. You take one thing on
the left, one thing on the right, and produce a result. (Or you'd use
<<= for that, which still looks fine. Then it does an assignment
instead of producing a result.)

If you create a Path object that responds to binary division with a
new Path that combines the left and right sides, that also makes very
good sense. The slash means "next path component" rather than
"divide", and it still is binary - there's no logical difference
between these:

Path("/etc") / "network" / "interfaces"
(Path("/etc") / "network") / "interfaces"

The problems come from needing more than two components at each step,
like with string formatting. You could write it like this:

"Hello, %s from %s!" % name % location

but then it'd be really hard to track down errors - the modulo
operator would have to handle the first percent sign and leave any
others unchanged. Plus there'd need to be some weird and funky magic
to mark the "current interpolation position" in order to cope with %%
becoming %, and the possibility that the person's name contains a
percent sign. No, string interpolation needs more than two arguments.
So either you have to mandatorially package the args up into an
iterable:

"Hello, %s from %s!" % (name, location)

which is very easy to forget (just look at what happens when you use
the Python DB API 2.0 and use "cur.execute(sql, single_argument)" -
you have to package that up into a one-tuple), or you have a special
case for non-tuple arguments, which is what Python has done. This is
great, except in the situation where you want to accept *any arbitrary
object* as your argument, eg for %r; you have to package that one up,
otherwise a tuple will behave very oddly. Hence, hacks on hacks to get
around the limitations of binary operators.

>> Operator overloading in each case here is "cute", not optimally practical.
>
> Maybe just sub-optimal? With today's C++ one could use a variadic
> template and still have type-safe compile-time bound output formatting.
> This hasn't been possible in the original iostream library back then.

I'm not sure how that would work, but the main question is: How is it
advantageous over a simple call? Actually, here's a simple way to do
it: Make the stream object callable.

cout("Hello, world!\n");

You can take as many args as you want, precedence and associativity
won't bite you, and it still reads reasonably well. The operator
method has to prove that it's better than that.

ChrisA



More information about the Python-list mailing list