9 ms·
The Elements of Python Style
- mpdehaan2 11y agoAt first read this is a pretty reasonable doc for some things to consider. I also recommend watching the linked "PEP8" YouTube presentation from PyCON: https://www.youtube.com/watch?v=wf-BqAjZb8M https://www.youtube.com/watch?v=wf-BqAjZb8M From past projects, I got way more adherance-to-the-letter type patches versus "make this code pythonic and elegant" type patches, and the latter IMHO is more of the point of where things should go. It's a bit of a long/slow talk, so if you find yourself getting bored, switch it up to 2x speed on youtube and I think many of you will like it. My personal PEP8 ignore list was: -pep8 -r --ignore=E501,E221,W291,W391,E302,E251,E203,W293,E231,E303,E201,E225,E261,E241 lib/ bin/ Which is quite a lot of it :) Codebases are easier to understand when everybody's consistent within the codebase, but things need to be idiomatic well beyond PEP8 and it's easy to have a forrest vs trees scenario.
- teddyh 11y ago> With tuple unpacking, using _ as a throwaway label is also OK. I see this sometimes, but the problem is that this breaks internationalization using gettext, commonly imported under the name _: from gettext import gettext as _ (For those who’ve never seen it, “_” is then used as a function which wraps strings, like _("Book"); translation tools can then parse the source code, deduce that “Book” is a term which needs translating, and at runtime the “_” function can return the translated term instead of the original string when a different language is needed. The name “_” for this functionality predates Python and is a common feature across most programming language using the gettext method of internationalization. It is therefore unfortunate that some Python users have taken “_” to be a kind of throwaway variable.)
- Spiritus 11y agoI thought it was mostly imported as __ (double underscore).
- teddyh 11y agoNo, I’ve never seen that. The normal single-underscore name is used in the Python documentation: https://docs.python.org/3.6/library/gettext.html https://docs.python.org/3.6/library/gettext.html
- Spiritus 11y agoYou're right, I was probably thinking of some other library/framework/language.
- deleted 11y ago[deleted]
- scardine 11y agoThis is true, but any decent IDE (I use PyCharm by Jetbrains) will hint you are overrinding a global variable. I like to use the underscore as a throwaway because it is explicit - it is far more common for me to override other common throwaway variable names like "i", "j" or "x" that are also often used as loop counters.
- oinksoft 11y agoDo those editors use the project's builtin namespace? gettext.install sets __builtins__._ to the translation function so that individual modules don't have to import it.
- batat 11y agoYep, there's an option "Collect run-time types information for code insight" (not sure if enabled by default). JetBrains IDEs are smart as hell.
- deathanatos 11y agoI abhor this practice, frankly. It doesn't take that much more work to type gettext("The translating string"), or even t("string"). The purpose of "blank variable" is better served by _ than translations are. And gettext the tool can pull out the terms requiring translation using any identifier; you don't have to use "_", and I believe "gettext" is supported automatically. Also, depending on what you're doing, a simple function might not be enough; the _("foo") stuff was intended to simply use the locale of the local machine, which is almost certainly wrong in certain environments, such as server-side programming. (Where we need to bind in things like the user's preferences, or the Accept-Language header on the current request to the call to the translation function, so _("foo") is really something like _("foo", request), though with a bit of magic we can get back to a _("foo") syntax, though again, I prefer the explicitness of gettext("foo") or translate("foo").)
- amyjess 11y ago> The biggest offender here is the bare except: pass clause. Never use these. Suppressing exceptions is simply dangerous. This doesn't apply if you're using exceptions as control flow, which is idiomatic and recommended Python behavior. Let's say you want to make sure some file doesn't exist before you start doing something. Specifically, you want to wipe it out if it exists, and you don't care if it doesn't exist. You do: if os.path.exists(temp_file): os.remove(temp_file) This isn't idiomatic Python behavior. Instead, you want to use exceptions: try: os.remove(temp_file) except OSError: pass Mind you, an actual bare "except:" (without specifying an exception type) is still bad, because that'll catch things you don't want being caught, like a SystemExit or KeyboardInterrupt, but it's perfectly acceptable to silently pass in this case.
- teddyh 11y agoNote that FileNotFoundError is preferred in modern Python instead of OSError. In case of old Python, one should narrow the catching to cover only the wanted case: import os, errno try: os.remove(temp_file) except OSError as e: if e.errno != errno.ENOENT: raise
- revelation 11y agoI can see why this is the preferred idiom.
- jbrownbridge 11y agoI never silently pass on exceptions any more as I've been bitten by way too many edge cases. You could imagine the case where your example is run by a different user to the owner of temp_file and fails to remove it due to permission issues. In my opinion you should at least log some sort of message here.
- to3m 11y agoThis still doesn't solve the problem, though, because you still have the race condition that the exception case is supposed to be fixing. There's probably no good solution to this in this case.
- entee 11y agoI've always been curious about docstring style. I've usually gone with numpy-style docstrings: https://github.com/numpy/numpy/blob/master/doc/HOWTO_DOCUMENT.rst.txt https://github.com/numpy/numpy/blob/master/doc/HOWTO_DOCUMEN... They tend to be easier to read than reST, and happen to be used by the packages I use most (numpy, scipy). They are also easily worked into sphynx/napoleon. 2 questions: 1.) Why use reST instead? 2.) Why isn't there a python-wide standard or best practices as is found for Scala/Java/others?
- wcarss 11y agoFrom [1], the "Docstring Standard" section of your link, "Our docstring standard uses re-structured text (reST) syntax". It seems like they use reST? 1 - https://github.com/numpy/numpy/blob/master/doc/HOWTO_DOCUMENT.rst.txt#docstring-standard https://github.com/numpy/numpy/blob/master/doc/HOWTO_DOCUMEN...
- yeukhon 11y agoThat's referring to the syntax in the docstring format they developed is only supported by reST, render by Sphinx's integration with reST. For exmaple, .. image:: filename You don't get that in Markdown.
- Spiritus 11y agoI use Sphinx style because it gives me better auto-completion via Jedi[1][2]. Otherwise I really like the look of Google style docstrings[3]. There's some ongoing work[4][5] to get support for that in Jedi as well. [1] https://github.com/davidhalter/jedi https://github.com/davidhalter/jedi [2] https://github.com/davidhalter/jedi-vim https://github.com/davidhalter/jedi-vim [3] http://sphinxcontrib-napoleon.readthedocs.org http://sphinxcontrib-napoleon.readthedocs.org [4] https://github.com/davidhalter/jedi/issues/504 https://github.com/davidhalter/jedi/issues/504 [5] https://github.com/davidhalter/jedi/pull/617 https://github.com/davidhalter/jedi/pull/617
- Walkman 11y agoThis guy got a bunch of things wrong: > encode = lambda x: x.encode("utf-8", "ignore") Why not rather call it s (short for str)? > With tuple unpacking, using _ as a throwaway label is also OK. Use __ (double underscore) instead so it won't clash with the gettext convention: from django.utils.translation import ugettext as _ > always use args and kwargs for variable argument lists No, no, no, no, no! Why not clarify what are those accepted arguments? What the following code is doing? def shuffle(*args): # do something It shuffles SOMETHING. And this? def shuffle(*cards): # do something It shuffles CARDS. Big difference IMO. > Use parens (...) for fluent APIs I don't like that. > Use implicit continuations in function calls PEP8 suggest: "The preferred place to break around a binary operator is after the operator, not before it." Following this logic, this should be: return set((key.lower(), val.lower()) for key, val in mapping.iteritems()) (for at the end of the first line) > Rarely create your own exception types > ... and when you must, don't make too many. Sorry but that's plain stupid. > if item vs if item is not None Yes, you should absolutely care. Not all the times, but sometimes a "NULL" value is different than a filled, but empty value. See zero. It's falsy, but totally acceptable input...
- mydpy 11y agoThese are good points. I can't speak for pixelmonkey, but I bet he would appreciate if you sent him a pull request with your feedback. I don't know how noisy the conversation about Python style guidelines is right now, but could be worth it.
- pixelmonkey 11y agoI'd gladly accept a PR on the issue raised re: "args" vs "cards" for the shuffle function. When I wrote the guideline on using the names "args" and "kwargs", I was thinking more about cases where you have to proxy both, e.g. class SomeClass(SomeOtherClass): def __init__(self, *args, **kwargs): # some logic super(SomeClass, self).__init__(*args, **kwargs)
- Walkman 11y ago
- t0mk 11y agoMy big problem when reading Python code are "from" and "as" in imports. To illustrate: I read a function doing a few calls in file, and I want to understand what happens in one of the calls (a name without any dot prefix). So I do forward search for the name in the current file (a bit silly when thinking of it, but forward slash is so close to my index finger; and in web browser the search is forward by default), and it only finds it in imports in the beginning of the file after "from". At this point I lose the context of the function I was originally reading, because I have seen other chunks of code which I try to understand a bit. Also I lose the position of the original function in the file. If I want to dive deeper to the calls, I look up the module in question. If the name would be use with full namespace prefix, like import datetime datetime.datetime.utcnow() ^ I could immediately see which module it's coming from and go there. Straight up! I wouldn't need finger acrobatics, and it would be readable in e.g. github code viewer. If you are really bothered by the length of the dot-prefixed names, why not do now = datetime.datetime.utcnow() print(now()) ^ That's all clear, and most likely you will fit to 79 chars per line. Namespaces are a good idea, and explicit over implicit, right? I understand that there are some conventions (like _ for gettext), but I see importing names without dot prefixes as killing readability.
- vadskye 11y agoMinor nitpick: I think you mean now = datetime.datetime.utcnow # no parentheses More generally, I think the strongest argument for using "from" is when you're dealing with redundant "foo.foo.bar" names. Would you really be confused at seeing "datetime.utcnow()" instead of "datetime.datetime.utcnow()"?
- majika 11y agoTo me, this sounds like a failure of your tooling rather than the code you're reading. Vim with Jedi, PyCharm and PyDev can all jump straight to definitions. What's your development environment? I'll use `from <module> import <identifiers>` when the identifiers' names express their purpose, and aren't dependent on the module's name. Importing identifiers directly makes the code using them less noisy [0], and makes it possible to replace the source module for the identifiers later on. In Django projects, I'll often need to rename modules or move identifiers between modules, and so using `from` imports makes that a lot easier. I agree that `as` imports should be used sparingly. [0]: your suggested solution is even more noisy than using the qualified name, because now you have a variable hanging around and readers have to work out if `now` is going to be reassigned or used later.
- eugenekolo2 11y agoI can't take a guide seriously when things like this are sprinkled all over it: return [x for x in items if x.endswith(".py")] if (response and "data" in response and response["data"]): return response["data"] Just use 2 lines instead of using valuable horizontal lines, and make the code harder to read. ex: pyfiles = [x for x in items if x.endswith(".py")] return pyfiles
- pixelmonkey 11y agoIt's a fair point, but the style guide is meant to be illustrative. The former example is indented the way longer lines might very well be e.g. if your filter clause were a 2-part bool, you might not be able to fit it in a one-liner. The indentation also clarifies the difference between it and the imperative example above it. The latter example is a rewrite of code written with 2 nested if statements, so the indentation serves to show that now the two nested statements were incorporated into the bool evaluation. If I made it a one-liner, someone might make the argument that the nested version is "more readable", but as written all you can say is that the single-if version is "less nested", which is the point I was trying to get across.
- kevin_thibedeau 11y ago> Use reST for docstrings I would suggest also using the Napoleon [1] extension now included with recent versions of Sphinx. It allows you to use Google or Numpy-style reST formatting to describe parameters and types without all the javadoc-ish noise from :param:, :type:, and others. [1] https://sphinxcontrib-napoleon.readthedocs.org/en/latest/ https://sphinxcontrib-napoleon.readthedocs.org/en/latest/
- pixelmonkey 11y agoThanks -- we are actually discussing this possible change in a Pull Request here: https://github.com/amontalenti/elements-of-python-style/pull/4 https://github.com/amontalenti/elements-of-python-style/pull...
- ivan_ah 11y agoAll great tips, except I still feel could write docstings in markdown. Is there a way to use Sphinx but with markdown?
- ot 11y ago> If the strict 79-character line length rule in flake8 bothers you, feel free to ignore or adjust that rule. Thanks for remarking this. Too many people use flake8/pep8/pylint in commit hooks. From the BDFL himself: > "I personally hate with a vengeance tools named after style guide PEPs that claim to enforce the guidelines from those PEPs." > "[stylechecker] tools' rigidity and simplicity reflects badly on the [style guide] PEPs, which try hard not to be rigid or simplistic" > GvR https://twitter.com/raymondh/status/683793667996303360 https://twitter.com/raymondh/status/683793667996303360 https://twitter.com/raymondh/status/683809696902332416 https://twitter.com/raymondh/status/683809696902332416
- krick 11y agoMost of it is pretty obvious. However, what I am still fighting myself on with Python is a project layout/module structure. In languages with C++-like OOP model (Java, PHP) it's most of the time reasonable to settle on "1 file, 1 class" and to use directory structure the same way you use namespaces. Actually, in some such languages it's enforced. For PHP it still leaves a problem with where you shoud put/expect to find anything beside classes, but it's fine, more or less. I hope some day we'll decide even more generally what works best, but for now it's decided per project/framework and it's mostly bearable. For Ruby it's mostly the same as for PHP, even though the language is a bit more flexible. But it's still normal to use class as the main entity, so pretty much the same guidelines/problems apply. For Python… I cannot decide yet, and what's worse, looking at the code on, say, Github, it doesn't seem like it's less of a problem for many others. Because some of "solutions" are seriously horrible. No project structure whatsoever, tens of classes in the same file. Classes, functions and even app-specific code (like GUI, CLI or HTTP-routing) all in the same file. Multi-thousand LOC files are considered completely normal. Ridiculous amount of code in __init__.py, that completely re-arranges submodule structure or even defines a bunch of new code. Hell, I've seen projects (more than 1) almost entirely written in the __init__.py! Please don't say you think it's normal. I would say that things remind of "project-structure" in C, but (it's scary to suddenly realize that) it would actually be a compliment for Python, because even though large files are considered normal as well, with modern practices of writing C-code and all that "C-lang OOP" model stuff it's mostly clear where you should put something. With Python I'm never sure. The only way to find something is to use grep. With things like that in Python, it's even no use to speak of a "project structure" in the more general sense, aside from what's enforced by the frameworks like Django. So, any suggestions?
- encoderer 11y agoIn PHP you create classes for everything because there is no other encapsulating scope. In that circumstance, with the class loader hook the language gives you, one class per file is a clear win. In Python, with module-level scope, it makes sense to group like-functionality in the same module (aka file). Maybe it only seems "wrong" to you because it's different than other languages you're more familiar with?
- aaronchall 11y agoI agree with a lot of the criticisms. In addition, 1. The prefixed underscore affects exports if you're providing an API in a package - see the standard library for lots of examples of prepended underscores. 2. Don't use lambdas where a regular function definition works. 3. Only use args and *kwargs where these args are completely generic, otherwise be specific as to the semantics. 4. Complex list comps should be rewritten to append to lists. 5. Multiline condition statements should use indentation other than 4 spaces (2 or 8, for example). 6. Don't use `if foo` when you mean `if foo is not None`. Good luck with your style guide, I wish more people would make good style important to them.
- jqm 11y agoGood piece. A (minor) nitpick. On section titled '''Prefer "pure" functions and generators''' two "dedupe" functions are shown. They are stated to be the same but aren't. The first function returns the number of duplicated elements excluded (an int). The second function returns a set of excluding duplicated elements. It's true (in the first function) that the original list "items" will be modified to exclude the duplicated elements but an additional unshown step is needed in the second to determine the number... i.e len(items) - len(set(items)).
- pixelmonkey 11y agoI agree. I opened PR #19 to address this: https://github.com/amontalenti/elements-of-python-style/pull/19 https://github.com/amontalenti/elements-of-python-style/pull... Would love to hear what you think of the changes.
- SFjulie1 11y agoThe grammar capos of python are back. Can we get rid of the stupid indent vs tab stuff and have errors visible on screen without black magic? Oh and maybe why not braces to really control variable lifetime? from __future__ import braces SyntaxError: not a chance Ah ah what a joke. Just remember that use strict exists in Perl and in python you have dyslexia=0 if True: dylesxcia=1 will raise no error. The strawman fallacie of python purity of style
- user9756 11y ago>But another good example is rewriting an if/else chain as a dictionary lookup or repetitive code as a tuple of operations followed by a for loop. I'm familiar with the dictionary lookup method but not the tuple. Perhaps it's obvious but could someone please give an example?