

Improving your (Python) code with modern idioms - rbanffy
http://python3porting.com/improving.html

======
strictfp
Why change the excellent printf-style string formatting syntax to the vastly
inferior slf4j-style? If I want simple concatenation, i use concatenation. If
I want really complex formatting I write explicit code to do it. Printf style
hits a sweet spot: it formats common datatypes to common presentation formats.
Python 3 adds a formatting mini-language which looks more convoluted for
simple tasks and unreadable for complex ones. Why change the syntax?

~~~
jbarham
I'm in the same boat. It's an old joke that Java tried to kill off printf
style formatting but had to bring it back because _it's a really great idea_ :

1980: C

printf("%10.2f", x);

1988: C++

cout << setw(10) << setprecision(2) << showpoint << x;

1996: Java

java.text.NumberFormat formatter = java.text.NumberFormat.getNumberInstance();
formatter.setMinimumFractionDigits(2); formatter.setMaximumFractionDigits(2);
String s = formatter.format(x); for (int i = s.length(); i < 10; i++)
System.out.print(' '); System.out.print(s);

2004: Java

System.out.printf("%10.2f", x);

2008: Scala and Groovy

printf("%10.2f", x)

(from <http://www.horstmann.com>)

To complete the circle I'd add:

2009: Go

fmt.Printf("%10.2f", x)

~~~
ondrasej
I wouldn't argue against using a formatting string in general, but the printf
style order-based formatting strings are really bad once you need to localize
your app and have to support languages with different word order. In this
sense, .NET or Python3-like formatting strings with position-based formatting
is orders of magnitude better and I was surprised that Sun decided to use %*
instead.

Take a simple example in the lines of "I've seen {0} {1} {2}".format("John",
"eat", "an apple") ...and try localizing this message into e.g. German.

I have a feeling that printf-style is one of the reasons, why the texts in
localized versions of some programs are as bad as they are.

~~~
Erwin
glibc sprintf supports that as something like %2$s %1$s which Python could
potentially support.

I don't buy the internationalization argument however. You have to explicitly
select strings for i18n anyway and run them through something that retrieves
the data from a catalogue based on the current language. It's not like you
will override str.__mod__ to start i18n.

And if you want to do it right, there are more complex rules that need a
special formatting language. For example if you want to display {count}
error/errors, then the text of error/errors will vary based on language not
just based on count 0/1/more but even more wildly based on language. E.g. some
have a special noun declension for 2. For example in Polish, that might be
when there are 5 errors: <http://blogs.transparent.com/polish/cardinal-
numbers/>

~~~
andrewaylett
Python supports `"%(name)s"`, which means you're not dependent on order at all
-- in my opinion, much nicer than indexing into the parameter list:

    
    
      >>> "%(name)s has %(count)d %(thing)s" % {'thing': 'bananas', 'count': 10, 'name': "Phil"}
      'Phil has 10 bananas'

~~~
andolanra
The new-style formatting allows that as well:

    
    
        >>> "{name} has {count} {thing}".format(
              thing='bananas', count=10, name='Phil')
        'Phil has 10 bananas'
    

It also allows for more complex expressions inside of format specifiers, so if
you need to grab data out of an object, you can do something like:

    
    
        >>> from collections import namedtuple
        >>> Sentence = namedtuple('Sentence', ['name', 'thing', 'count'])
        >>> s = Sentence(name='Phil', thing='bananas', count=10)
        >>> '{dat[0]} has {dat.count} {dat.thing}'.format(dat=s)
        'Phil has 10 bananas'
    

which is verbose and contrived here, but could be immensely useful in cases
like

    
    
        >>> '... {numeral[5]} {item.plural}'.format(
              numeral=fr_numerals, item=animal)
        '... cinq animaux'

~~~
286c8cb04bda
How do you handle it when you need to convert, e.g., "American woman" to
"femme américaine"?

~~~
andolanra
You use something else. It's still just a format string, not a whole
localization solution. Named arguments means it might be easier to hack
together something like

    
    
        templates = {'en': '{adj} {noun}', 'fr': '{noun} {adj}'}
        print(templates['fr'].format(noun='fromage', adj='délicieux')
    

but that's still a hack, and doesn't even come close to addressing cases like
Chinese's "{adj} {counter_word[noun]} {noun}" or gender concord or any of the
myriad other things you come across in practice.

Edit: used 'positional' instead of 'named'.

------
raymondh
This article contains excellent recommendations except for one (the numbers.py
module is nearly useless).

~~~
mattdeboard
Raymond, what's your take on the ``redirect_stdout`` class not inheriting? I
thought with new-style classes inheriting from ``object`` was the standard.

~~~
wladimir
Python 3 removes old-style classes and thus removes the need to explicitly
inherit from object.

(Also it appears it's just an example to illustrate the use of context
managers, not of the perfect Python program. You'd never want to use the code
in practice, if only because it relies on changing global state and thus will
mess up when multiple threads use it at once)

------
0ca0
The bit on the yield is a little thin. The other day I noticed that generators
have a `send` and `throw` method which provide interesting ways for
controlling flow.

<http://www.python.org/dev/peps/pep-0342/>

------
RyanMcGreal
Before you decide to go with advanced string formatting, beware the encoding
bug: <http://bugs.python.org/issue7300>

------
cageface
The Python vs Ruby thing is tired by now but reading this reminds me again of
how many problems Ruby managed to solve with just one construct (blocks).

Python was my first "scripting" language but modern idiomatic Python is almost
unrecognizable compared to the Python I started out with in 1997.

The fact that the Ruby I was writing in 2002 looks almost modern is a
testament to Matz' foresight.

~~~
dalke
Here's code I wrote in early 1998, and posted to comp.lang.python:
<http://lwn.net/1998/1105/a/crosscopy.html> .

The code still looks stylistically modern, except for its use of getopt, but
that's a library issue, not a syntax one. It's by far from "almost
unrecognizable."

Can you therefore elaborate with an example from that era?

~~~
cageface
Python from that era didn't have generators, new classes, list comprehensions,
annotations etc. If you're not using any of these in your code now you're not
writing idiomatic modern Python.

But the point of my post wasn't to bash Python but rather to note that Ruby
got an unusually large number of thing right on the first try.

~~~
Styck
Or that it hasn't fixed the warts it has. I use Ruby rarely enough to really,
really miss explicit imports. I guess it's not a problem if you use it all the
time and magically know what's in the global namespace at any given time but
it's a pain in the ass when you have to touch a legacy rails app twice a year.

------
joejohnson
_There is however no benefit in replacing a mylist.sort() with mylist =
sorted(mylist), in fact it will use more memory._

Isn't that kind of a deal-breaker?

~~~
sophacles
It depends on you case.

If you are doing:

    
    
       slist = list(mylist) #create new copy of the list
       slist.sort() #sort the list
       use_sorted_list(slist)
    

The use of sorted is more more convenient, and really doesn't use more memory
(since the memory from that statement you quoted is for the list copy).

More usefully: sorted will take anything that is iterable, allowing you to
write simpler code, rather than testing for various types and dealing with
them in type specific ways.

