12 ms·
Python Idioms [pdf]
- deleted 13y ago[deleted]
- csense 13y agoI disagree with promoting try / catch. Exceptions like ValueError can really happen almost anywhere, so it is usually better to sanitize your inputs. E.g. something like: try: something = myfunc(d['x']) except ValueError: something = None The programmer's intent is probably to only catch errors in the key lookup d['x'], but if there is some bug in the implementation of myfunc() or any of the functions called by myfunc() which causes a ValueError to unintentionally be raised, it will be caught by the except. For dictionary lookups specifically, get() is usually preferable: something = d.get('x') if something is not None: something = myfunc(something) Or if dictionary may potentially contain None values: if 'x' in d: something = myfunc(d['x'])
- middleclick 13y agoWhile not disagreeing with your case, there are some cases where I try to follow this rule because in most of those cases, I would expect that there is no exception and if there is, then catch it. try: open(FILE) except IOError: it failed
- eliteraspberrie 13y agoAlso note the second argument to the get method specifies the default value to return in case of a KeyError: something = d.get('x', default)
- drakeandrews 13y agoThat example would surely be better as: something = myfunc(d['x']) if 'x' in d else None
- prawks 13y agoDoesn't this go against the Pythonic notion of handling exceptions rather than "looking before you leap"?
- wiredfool 13y agoThat looks like perl.
- calroc 13y agoIn this form you perform the lookup twice: once to test 'x' in d and then again to actually get the value d['x']. Try clauses in Python are very inexpensive if they pass (don't raise an exception), so often the try..except version would be preferable. In any event don't optimize prematurely and use a profiler rather than guessing if performance is an issue. ;-)
- smnrchrds 13y agoNot to counter your point, but I would like to quote from PEP8 here: Additionally, for all try/except clauses, limit the try clause to the absolute minimum amount of code necessary. Again, this avoids masking bugs. Yes: try: value = collection[key] except KeyError: return key_not_found(key) else: return handle_value(value) No: try: # Too broad! return handle_value(collection[key]) except KeyError: # Will also catch KeyError raised by handle_value() return key_not_found(key)
- yetanotherphd 13y agoThese two comments together make a lot of sense. When your try block is a single lookup, you might as well use an if statement or get. However, when the 'absolute minimum' is nontrivial try/except is still a good option, e.g. try: name = employee['name'] first_name = name['first_name'] last_name = name['last_name'] except KeyError: print "Bad employee data" return
- GFK_of_xmaspast 13y agowhat's going to happen in your second example if somebody gives you a defaultdict instead of a regular dict for d.
- simon_weber 13y ago> 9. Create dict from keys and values using zip In 2.7+, I'd recommend a dictionary comprehension instead.
- aaren 13y agoIn this case, {k: v for k, v in zip(keys, values)} edit: As I mention below, this becomes useful when you want to do e.g. {f(k): g(v) for k, v in zip(keys, values)}
- epistasis 13y agoI'd disagree, as dict(zip(keys, value)) is more concise, doesn't introduce extra variables, and doesn't repeat itself, and explicitly names a dict rather than using a symbol.
- herge 13y agoRight, but the second you need to modify either the key or the value, you are better off with the dict comprehension. It's like map() vs [...].
- aaren 13y agoI like list comprehensions and to me dict comprehensions feel like a natural extension of this. It means that you can do things like {k.upper(): v ** 2 for k, v in zip(keys, values)} Or generally {f(k): g(v) for k, v in zip(keys, values)} I find this very extensible. In terms of conciseness, they both fit on a single line and I like the whitespace inside a dict comprehension.
- Walkman 13y ago"Simple is better than complex."
- Redoubts 13y agoI don't understand the truth table on slide 9 for " - vs None " and "__nonzero__ (2.x) " __bool__ (3.x) vs __nonzero__ (2.x) " __bool__ (3.x) "
- Arnor 13y agoI'm pretty sure the "-" vs "None" meant that there wasn't a truthy alternative to None. More accurate would have been "N/A" vs "None". Not clear on the __nonzero__/__bool__ stuff...
- falcolas 13y ago__nonzero__/__bool__ are the methods called on objects when evaluating truthiness. So, if you're implementing custom objects, those are what you would use, and return either True or False, based on their truthiness value. As an example, you might represent __bool__ on a database connection object to reflect whether there is a live connection, allowing you to: if not conn: conn = MySQLdb.Connect()
- mattbot5000 13y agoHe's referring to the special methods available to classes that allow implementation of the truth value of an object. In version 2.x of Python, this method is __nonzero__[1] and in version 3.x it's called __bool__[2] [1] http://docs.python.org/2/reference/datamodel.html#object.__nonzero__ http://docs.python.org/2/reference/datamodel.html#object.__n... [2] http://docs.python.org/3/reference/datamodel.html#object.__bool__ http://docs.python.org/3/reference/datamodel.html#object.__b...
- udev 13y agoOne of the handy things not mentioned: with open("x.txt") as f: data = f.read() # do something with data
- theophrastus 13y agoabsolutely! that one is quite high up on my python_idioms_to_import file
- hyperbovine 13y agoHis last slide could be written more idiomatically as ''.join('Thanks!')
- Walkman 13y agoAnd this would be more idiomatic: 'Thanks!' (It is faster also! :D)
- hyperbovine 13y agoAhh but that is a mere expression, not an idiom :-)
- thomasahle 13y agoAh yes, just like the set('abcd') idiom.
- Vaskivo 13y agoAs a huge Python fan, I'm ashamed to admit but I don't get the while True: break What's the problem? I supose the use case is while True: # do stuff if some_condition: break What is the alternative? 'while some_condition'? That means we must have the 'some_condition' variable outside of the loop. And if we have multiple exit points it may become a mess.
- herge 13y agoAlso an easy way to fake a do ... while loop.
- roywiggins 13y agoIf there is a simple condition I prefer "while condition" because it makes the code easier to read: I can tell (roughly) when the loop is supposed to exit without reading the contents of the loop and scanning for a break.
- dec0dedab0de 13y agoThat means we must have the 'some_condition' variable outside of the loop. I'm not sure what you mean by that, this works just fine. Though you would probably use range in the real world. i=0 while i !=5: i+=1 Instead of i=0 while True: i+=1 if i == 5: break
- klapinat0r 13y agoHe means the variable used in the condition to break (or start/not start), i.e. "i" in your example - which you have outside of your loop.
- dec0dedab0de 13y agoOK, that makes sense then. I was thinking "the condition" was the variable changing within the loop.
- wiredfool 13y ago
- crazygringo 13y agoInteresting philosophical points. To me personally, testing for 'truthy' and 'falsy' values, or relying on exceptions rather than checking values in advance, feels like sloppy and imprecise programming. A string being empty or not, or an array having items or not, or a boolean being true or false, are all qualitatively totally different things to me -- and just because Python can treat them the same, doesn't mean a programmer should take advantage of that fact. Sometimes it's possible to over-simplify things in a way that obfuscates instead of clarifying. When I read: if name and pets and owners I have no intuitive idea of what that means, of what's going on in the program. When I read if name != '' and len(pets) > 0 and owners != {} I understand it exactly. But by this point, I've come to understand that, for a lot of people, it seems to be the opposite. It seems to be more of a philosophical difference, not right/wrong.
- andrewfong 13y agoI think part of the reasoning behind the truthy / falsy mechanic is that it's more robust. If, for whatever reason, we did: name = None instead of name = '' Then the second conditional would fail, whereas the first would still be fine.
- riquito 13y agoIn this particular case, you want to write name is None since None is a unique distinct value edit: didn't notice at first that you wrote an assignment, not a comparison
- danudey 13y agoNote that 'is' in Python is kind of tricky. None is None, and any value==None is also None. 1 is 1, and 100 is 100; 256 is 256 but 257 is not 257, and "test" is not "test". The reason for this is that 'is' tests to see if both operands are the same object; it's not a test of equivalence on any level. There is only ever one instance of None, and you simply get or create references to it. Likewise, there is only one instance of integers up to 256 (presumably a performance optimization for small loops, indexes, etc.). Integers after 256 get separate instances each time, so 'is' fails after that point. A lot of newcomers to Python come across the 'is None' construct and make assumptions about what 'is' does; the worst part is that they're often correct, and the behaviour becomes difficult to track down when it changes.
- agentultra 13y agoAs an implementor of Hy (a homoiconic lisp frontend to Python) I've found certain Python idioms to be rather infuriating of late. In particular: >>> 0 == False True Which makes the idiom of testing truthiness quite annoying in parsing code such as: def is_digit_char(s): """ Return a parsed integer from 's'.""" try: return int(s) except (ValueError, TypeError): return None Which is harmless enough except that as a predicate it sucks because parsing "0" will return False in a context where I'd rather know whether I parsed an integer or not... which leads to non-idiomatic code. This is mainly because True/False are essentially aliases for 1/0 and as such won't identify so: >>> 0 is False False >>> 0 is 0 True So it's important to remember another Tim Peters-ism: Although practicality beats purity. As read in the Zen of Python it seems he's referring to special cases which this might be. As a shameless aside, you should see what we're working on in Hy. There will likely come a point where we'll be able to do kibit-style suggestions of idiomatic transformations to your Python code. Update: I ran into this while trying to write token parsers for a parser-combinator lib in Hy.
- icebraining 13y agoWell, it's not really Python's behaviour, just the behaviour of that type. You can always use your own type: class I(int): def __nonzero__(self): return True def is_digit_char(s): try: return I(s) except (ValueError, TypeError): return None >>> if is_digit_char('0'): print "True" True
- riquito 13y agoYour function is_digit_char(s) is peculiar. From the name I would expect it to return a boolean, instead it returns a number or None. To write it like that and have a problem with the falseness of 0 it means that you use it in a way to both use it as conditional expression and an integer value, e.g. digit = is_digit_char('0') if digit: # fail print(digit) else: print('not a digit') You should then check for his equality to None digit = is_digit_char('0') if digit is None: # pass print('not a number') else: print(digit) But I would argue that you were in search for troubles when you wrote an is_something() function that doesn't return a boolean. That is not idiomatic. p.s. Hy is too crazy :-)
- kerpal 13y agoThanks for this write up. I didn't know about enumerate. I never thought of swapping variables as in example 4 either. I noticed one small mistake in section 9: d[keys] = values[i] Should be: d[key] = values[i]
- aaren 13y agoFor a good few months I was doing for i in range(len(input_list)): v = input_list[i] ...do something with v and i... enumerate is much nicer! for i, v in enumerate(input_list): ...
- Walkman 13y agoFor point 10: '_' is often aliased as gettext to ease translation of string: from django.utils.translation import ugettext as _ translated_str = _('Something to translate') so using it will overwrite the alias. Instead, you can use '__' (double underscore) as ncoghlan suggests below his answer [1]. or you can use the 'unused_' prefix as Google Python Style Guide suggests [2] or you can change your code, so you don't need to use '_' as Alex Martelli suggests in his answer [3]. [1]: http://stackoverflow.com/a/5893946/720077 http://stackoverflow.com/a/5893946/720077 [2]: http://google-styleguide.googlecode.com/svn/trunk/pyguide.html?showone=Lint#Lint http://google-styleguide.googlecode.com/svn/trunk/pyguide.ht... [3]: http://stackoverflow.com/a/1739541/720077 http://stackoverflow.com/a/1739541/720077
- falcolas 13y agoI have personally never seen this before. I'd be wary to use it, since it breaks the "_ is a throwaway" idiom, as well as the REPL "_ is the results of the last expression" function. Aliasing it to "t" or "txl" seems like a saner way, if I'm honest.
- Walkman 13y agoyou never use gettext in REPL :) and it's common to use '_' as gettext. Less typing, more readable strings. If you don't believe me, you should believe Alex Martelli [1], a random Python developer [2] or just read Official Python documentation [3] which recommends assignign gettext.gettext to '_'... [1]: http://stackoverflow.com/a/2745537/720077 http://stackoverflow.com/a/2745537/720077 http://stackoverflow.com/a/1739541/720077 http://stackoverflow.com/a/1739541/720077 [2]: http://stackoverflow.com/a/5893946/720077 http://stackoverflow.com/a/5893946/720077 [3]: http://docs.python.org/2/library/gettext.html http://docs.python.org/2/library/gettext.html
- thruflo 13y agoThis has been established practise in Zope, Plone et al for many years. http://developer.plone.org/i18n/internationalisation.html#marking-translatable-strings-in-python http://developer.plone.org/i18n/internationalisation.html#ma...
- aaren 13y agoI would add generator expressions: (f(x) for x in list_of_inputs) Just like a list comprehension, but with (...) rather than [...] and with lazy evaluation. These are useful when you don't need to evaluate all of the inputs at once but still want to iterate over them at some point later on.
- runarberg 13y agoNot to mention how awesome they become when you feed them as a set of values to a function. values = (f(x) for x in list) g(*values)
- jfischer 13y agoAlex Martelli gave a nice talk called "Permission or Forgiveness" about the exception handling style recommended by OP: http://pyvideo.org/video/1338/permission-or-forgiveness-0 http://pyvideo.org/video/1338/permission-or-forgiveness-0. He has some nice insights into this issue. That being said, I think there are some situations where you want to check for problems up front (possibly in addition to exception handling). In particular, if you are parsing some data from outside the program, you may want to provide some context about what was wrong. KeyError is not very helpful to your users.
- drewblay 13y ago>pets = ['Dog', 'Cat', 'Hamster'] >for pet in pets: > print('A', pet, 'can be very cute!') This may be nit picking but I prefer output like this: print 'A %s can be very cute!' %(pet)
- rsfinn 13y agoPossibly because you haven't moved to Python 3?
- pekk 13y agoPython 3 doesn't dictate that style, just the parentheses
- macbony 13y agoI prefer print("A {0} can be very cute!".format(pet)) .format() is very versatile. d = {'first': 'Robert', 'last': 'Paulson'} print("His name was {first} {last}!".format(**d)) >> His name was Robert Paulson! class Person: def __init__(self, first, last): self.first, self.last = first, last p = Person('Robert', 'Paulson') print("His name is {0.first} {0.last}!".format(p)) >> His name was Robert Paulson! You also do not need to know what type is being passed in as the __str__ method is used for .format().
- mangeletti 13y agoSrsly, what Python programmer writes the code in the "Bad" examples therein? This list looks like it's from 2005 or something.
- deckiedan 13y agoLists like this aren't for experienced pythonistas, but more for new people to know what things to avoid, and what not to copy if they do come across it online (in an article from 2005...) :-)
- calroc 13y agoThis is a great, if somewhat basic list. My $0.02: Python is a tool for thinking clearly. That you can run your "thoughts" as written is a very nice bonus, of course. There are some really interesting things that Python allows: >>> d = {} >>> d[23], d[14], d['hmm'] = 'abc' >>> d {'hmm': 'c', 14: 'b', 23: 'a'}
- calroc 13y agoI couldn't resist. Moar funky-cool Python: >>> from string import ascii_letters >>> ors = ['|'] * (len(ascii_letters) * 2 - 1) >>> ors[::2] = ascii_letters >>> ''.join(ors) 'a|b|c|d|e|f|g|h|i|j|k|l|m|n|o|p|q|r|s|t|u|v|w|x|y|z|A|B|C|D|E|F|G|H|I|J|K|L|M|N|O|P|Q|R|S|T|U|V|W|X|Y|Z' (Obviously, in this example '|'.join(ascii_letters) would suffice, but if the objects weren't strings...)
- beambot 13y agoNot gonna lie... your snippet made me say "WTF?" Why not just do: from string import ascii_letters '|'.join( ascii_letters ) (I also like list slicing... but only when necessary.)
- calroc 13y agoYeah, I realized after posting it. (See edit.) The original code was using non-string objects. I basically come up with this to get the same behavior as str.join(). ;-)
- jerf 13y ago'|'.join(str(x) for x in y) # or '|'.join(map(str, y))
- calroc 13y agoThe original use for that code was to build a list of parsing objects to create a single parsing object that was the OR'ing of the basic ones. Aw, heck, code speaks louder than words: https://github.com/PhoenixBureau/PigeonComputer/blob/master/pigeon/cola/bubbles.py#L200 https://github.com/PhoenixBureau/PigeonComputer/blob/master/...
- jackmaney 13y agoAlthough many of them boil down to preferences and philosophical points of view, I find these kinds of idioms useful. Whenever I write code in a new language, I want to "write as a native" so that I can maximize the effect that the language has on how I think about programming. For Python in particular, Jeff Knupp's "Writing Idiomatic Python"[1] (not free, but not expensive, either) goes into detail on a lot of the concepts in the OP's slides. (I'm not affiliated with Jeff in any way, just a satisfied customer.) [1] http://www.jeffknupp.com/writing-idiomatic-python-ebook http://www.jeffknupp.com/writing-idiomatic-python-ebook
- tbirdz 13y agoWhat level would you say the idioms in the book are at? I have already been programming in python for a little while, and I wouldn't want to pay for something which I already know. It would be nice if there were more sample idioms on that site so you could have a better idea of what the rest of the book was like.
- e12e 13y agoGiven that the author is willing to give away copies to those that can't afford the book, I'm sure he'll consider a preview/promise refund if you send him an email? (See bottom of page). Note: Also not affiliated in any way. [edit: given: http://www.goodreads.com/book/show/17354838-writing-idiomatic-python-2-7-3 http://www.goodreads.com/book/show/17354838-writing-idiomati... it doesn't look like it's of all that much value to an experienced python developer. I'd love to hear some other comments, though.]
- jackmaney 13y agoIt's of more use to a new Python developer than an old hand, but it does touch on some relatively unknown language features (eg for loops having an else clause). Honestly, though, it's only $10, so it's not a massive investment.
- mrfusion 13y agoI'd love to see a writeup like this for Perl or Javascript. Has anyone come across one? How about Java?
- rafekett 13y agoi agree with all but #2. this seems to embrace conciseness as simplicity or understandability. it forgets the more cardinal value that explicit is better than implicit.
- myle 13y agoString concatenation is pretty fast nowadays with the + operator.
- elbear 13y agoDo you have a source? Also, I would argue that concatenation with + is less readable than string formatting.
- NigelTufnel 13y agoI think that concatenation with + is okay when you're concatenating two strings. And CPython can optimize x = x + y or x += y calls. http://docs.python.org/2/library/stdtypes.html http://docs.python.org/2/library/stdtypes.html
- jzwinck 13y agoThe very first one, "Make a script both importable and executable," needs some caveats. It's useful sometimes, but people often use it in places where it is not a great idea. Here's why: 1) If you are in the mindset of "I want a single file which is both a library and a program," how will you name it? Files to import must end with ".py" and follow C-style name rules, so cannot start with a number cannot contain hyphens. This leads many people to break conventions when naming their programs, because on Unix, hyphens are conventional in program names but underscores are not (count the examples of each in /usr/bin on any system). And naming your program something like "2to3" is impractical if you want it to be importable also. 2) It is unclear where to install files which are both programs and libraries. Programs usually go in some sort of "bin" directory (again, on Unix systems), but libraries do not. Where do you put a file which is both? 3) Sometimes the __name__ == '__main__' trick is used to include unit tests within a module. That's not bad, but consider using Python's excellent doctest feature instead, which often serves the same need but in a richer way.
- analog31 13y agoI use the __name__ == '__main__' thing for unit testing. I don't know if this is a generally applicable technique, but a lot of my modules interact with hardware or physical measurements, so I have to "see" the results in order to believe that the units are working. Often, what I'm looking for is problems with what's actually being measured, and the effect of changing operating conditions, not just my own copious programming bugs. For this reason, my unit tests can be pretty elaborate, with GUI, graphs, and other stuff. The unit test also functions as a "demo" of the module.
- jzwinck 13y agoAbsolutely--that's a great example of a library module that may also be usefully executed. I had something similar today: a module that sends email. It's useful to be able to run it (by explicit "python foo.py", not chmod +x) and see a sample email in my inbox. Unfortunately, for every good use of this trick, there seem to be two poor ones. Oh well. Most of the things in TFA are more generally applicable.
- gabipurcaru 13y agoAnother one that I find useful -- using `map` instead of list comprehensions when you want to apply a function to every element. So instead of: [str(x) for x in array] Do this: map(str, array)
- malkia 13y agoOn page 20, there is slight mistake - count is used in the second ("NOT SO GOOD") example but "i" is printed.
- deleted 13y ago[deleted]
- juanuys 13y agoOn page 20, didn't he/she mean "print(count, name)" in the BAD example? On page 18, the Java comment: it depends. E.g. see [1] [1] http://stackoverflow.com/questions/299068/how-slow-are-java-exceptions http://stackoverflow.com/questions/299068/how-slow-are-java-...