160

From reading PEP-8, I get it that you should put the closing parenthesis on the same line as the last argument in function calls:

ShortName.objects.distinct().filter(
    product__photo__stockitem__isnull=False)

Probably, long expressions are best to avoid at all. But if it's undesirable, how would you go about multiple chained method calls? Should the closing paren be on a new line?

ShortName.objects.distinct().filter(
    product__photo__stockitem__isnull=False
).values_list('value', flat=True)

What about no-arguments methods? How to write them on multiple lines without referencing the intermediate return values?

ShortName.objects.distinct(
    ).filter().values() # looks ugly

Update: There's a duplicate question of How to break a line of chained methods in Python?. The accepted answer suggests a familiar from jQuery style of starting each new line with a dot. The author doesn't provide any reasons or authoritative references, so I'd like to get a confirmation on such style or an alternative.

Community
  • 1
  • 1
katspaugh
  • 17,449
  • 11
  • 66
  • 103
  • 1
    If this is an easter egg, it's a cool one - my browser tab shows the title of this page as "django - Chained method ..." before I can't see any more. The only mention of django is the 2nd of 4 tags just above, how did SO decide to put that one into the title bar? But it's funny if you're aware of the Tarantino movie "Django unchained". – Starman Apr 04 '22 at 21:30
  • Teehee the HTML title of this page is `django - Chained (...)` – Zoltán Aug 17 '22 at 17:06

2 Answers2

253

This is a case where a line continuation character is preferred to open parentheses.

ShortName.objects.distinct() \
         .filter().values()      # looks better

The need for this style becomes more obvious as method names get longer and as methods start taking arguments:

return some_collection.get_objects(locator=l5) \
                      .get_distinct(case_insensitive=True) \
                      .filter(predicate=query(q5)) \
                      .values()

PEP 8 is intend to be interpreted with a measure of common-sense and an eye for both the practical and the beautiful. Happily violate any PEP 8 guideline that results in ugly or hard to read code.

That being said, if you frequently find yourself at odds with PEP 8, it may be a sign that there are readability issues that transcend your choice of whitespace :-)

Raymond Hettinger
  • 216,523
  • 63
  • 388
  • 485
  • 1
    I like this approach. It also has the big advantage that you can move lines around without worrying about punctuation (except for the first and last lines). I've mainly use chained calls when dealing with complex SQLAlchemy queries and it's nice to be able to rearrange them easily. – Kirk Strauser Dec 30 '11 at 19:32
  • 1
    Looks better indeed, and you also indented the comment. Thanks! – katspaugh Dec 30 '11 at 19:37
  • 1
    Raymond, what's the reason Python doesn't allow new line before and after a dot? This makes good formatting harder not easier... – Piotr Dobrogost Apr 01 '13 at 18:45
  • 11
    It's unfortunate that this doesn't allow comments or even whitespaces after `\\`. – roldugin Dec 17 '14 at 23:43
  • 1
    This is one of the few things I wish python black did by default. Fluent apis turn ugly real quick. – aaaaaa Dec 26 '18 at 23:16
  • 1
    I prefer the flexibility of user97370's answer. – ctpenrose Dec 10 '19 at 17:17
  • Based on [PEP8 guide](https://www.python.org/dev/peps/pep-0008/#maximum-line-length), [the other answer](https://stackoverflow.com/questions/8683178/chained-method-calls-indentation-style-in-python#8683263) is better: "Long lines can be broken over multiple lines by wrapping expressions in parentheses. These should be used in preference to using a backslash for line continuation." – hashlash Feb 12 '22 at 12:05
  • @hashlash PEP 8 is for internal use in CPython. For other contexts, such as Pandas user code, it makes less sensel – Raymond Hettinger Feb 12 '22 at 14:57
93

I think the best is to use () to force line joining, and to do this:

(ShortName.objects.distinct() # Look ma!
 .filter(product__photo__stickitem__isnull=False) # Comments are allowed
 .values_list('value', flat=True))

It's not ideal, but I like that it stands out visually and makes it somewhat obvious what the chain of calls is. It allows end-of-line comments, which \ newline does not.

Flimm
  • 136,138
  • 45
  • 251
  • 267
  • 49
    I don't care for that. The open parenthesis screams "I'M BUILDING A TUPLE!" at me and my eyes start hunting around to see what's going to be done with it. – Kirk Strauser Dec 30 '11 at 19:45
  • Building a `tuple` may also mean *start an (series of) interpretations* - wow, that's what I mean..! – Thiago Macedo Apr 01 '15 at 02:06
  • 1
    How does Python actually distinguish this from a tuple? o0 – Zelphir Kaltstahl Nov 25 '16 at 16:01
  • 6
    @Zelphir Python would recognise it as a tuple if it had multiple values (i.e. `(1, 2)`), or a single value followed by a comma (i.e. `(1,)`). Otherwise (when there's only one expression within the parenthesis) it simply executes it. – Bilal Akil Dec 27 '16 at 05:31
  • 13
    The advantage of this approach is that it allows for comments. – Flimm Jan 19 '17 at 11:44
  • 37
    @Zelphir The tuple operator in Python is the comma, not the parentheses. That's why you have to write ("a",) to make a 1-tuple. That is, the comma is an infix operator that means "make a tuple from the left and right terms". If the left term is a tuple, the operator creates a tuple with the terms of the left tuple and the right non-tuple. This is why deconstruction in Python is `a, b = [1, 2]` while in languages like JavaScript it's `[a, b] = [1, 2];`. – John Christopher Jones Sep 20 '17 at 18:18
  • 3
    @KirkStrauser - ...so you fear every time you use parenthesis to build an expression because you think you are "building a tuple"? :) I am sure you wrote a code like `a2minusb2 = (a - b) * (a + b)` WITHOUT ANY FEAR that you are building tuples! :) Parenthesis are not building tuples - comma (operator) does. `x = 4, 9, 2` is perfectly valid Python code, x is a tuple. – DejanLekic Sep 19 '18 at 09:23
  • 6
    @DejanLekic It looked wrong 7 years ago when I wrote that. I would not have written that now, 7 years later, but neither am I going to go back and update all my old answers. – Kirk Strauser Sep 19 '18 at 21:26
  • My bad - I should have looked at the time that was written, apologies... – DejanLekic Sep 20 '18 at 06:55
  • 6
    The lack of a comma screams "I'M NOT BUILDING A TUPLE!". This is my favorite answer. – Benjamin Atkin Oct 19 '18 at 23:41
  • 1
    @BenjaminAtkin: _The lack of a comma screams "I'M NOT BUILDING A TUPLE!"._ This is my favorite comment to my favorite answer. :) – Super-intelligent Shade Aug 22 '19 at 20:46
  • 3
    This should be the *better* answer. The PEP8 guide on [Maximum Line Length](https://www.python.org/dev/peps/pep-0008/#maximum-line-length) stated: "Long lines can be broken over multiple lines by wrapping expressions in parentheses. These should be used in preference to using a backslash for line continuation." – hashlash Feb 12 '22 at 12:02