4

Python widely uses built-ins (or module function) and not class methods.

So

  • len([]) instead of [].length()
  • filter(f, []) instead of [].filter(f)
  • str(2) instead of 2.to_str
  • same for map, foreach etc

These prevent you from doing nice chaining which is possible in other languages like Ruby or Scala:

(sorry very artificial example)

my_list.map(f).filter(g).length()

In Python you would need to either split into several lines:

list_of_something_else = map(f, my_list)
list_of_something_else_without_blah = filter(g, list_of_something_else)
length = len(list_of_something_else_without_blah)

In a single expression it doesn't look readable:

len(filter(g, map(f, my_list))

Is it considered to be not-Pythonic to chain methods?

Or do people usually extend classes with a dozen of helper functions to make it easier?

  • What's so great about "chaining"? Why is my_list.map(f).filter(g).length() any better or more readable than len(filter(g, map(f, my_list))? IMHO the second form is more readable and I prefer it -- but that's just my opinion. I won't pretend it's objective. Another problem with this question is that you're confusing syntax -- a.b(c) versus b(a, c) -- with how methods/functions are organized (inside or outside of classes/objects). These are two distinct concepts. – user39685 Mar 6 '14 at 13:47
7

The Python functions are universally applicable; anything that has a __len__ method will work for len(), anything that's iterable will work for map(), etc.

Another reason for len() to be a function is that it guarantees that the name is now the standard way of determining the length of any container; add-on types will have to implement __len__ to be compatible; if Python picked list.length() instead, other types could've implemented foo.get_length() or bar.len() or anything. Python followed the Principle of Least Astonishment instead here.

And Guido van Rossum found len() to be more readable (I agree).

And no, most of the Python stdlib doesn't support chaining; this can lead to overly-long lines that aren't very readable either. Instead, Python's syntax is generally powerful enough that you don't need much chaining.

Your last expression would be better rewritten as:

sum(1 for i in my_list if g(f(i)))

as that'd save you having to create an intermediary list just to get the count of elements that were left after filtering.

  • 5
    I have a high opinion of Python, Guide can Rossum, and you, but none of the pro-len() reasons you list are convincing. Let's be honest (like the FAQ): It's historical baggage, period. That does not make Python as terribly as OP implies, but it's not the greatest design decision since sliced bread either. – user7043 Mar 5 '14 at 19:25
  • @delnan: Guido expressed it differently. But hey. – Martijn Pieters Mar 5 '14 at 19:29
  • @delnan: And as the blog post I link to points out, in Ruby .length and .size are confusing, conflicting and often inconsistent as to what they mean. – Martijn Pieters Mar 5 '14 at 19:32
  • 2
    Consistency and least astonishment is important, but the indirect nature of len() and __len__ seems at best like a small incentive in the right direction, not a major contribution to it. One can (and good designers often are) consistent with methods. The Python stdlib already manages, it could deal with some more. Ruby having .size and .length is a testament to Ruby's philosophy (TMTOWTDI), not to shortcomings of methods. And as countless beginners' classes with methods named getSize + variations show, it still takes some knowledge and intent to hook into len() in custom classes. – user7043 Mar 5 '14 at 19:41
  • 1
    Hasn't there been discussion about removing these built in functions and moving to a purely OOP paradigm? Python has functional elements like len because at the time it started, the functional paradigm was big and people wanted functional features. The argument is that stuff like len violates OOP design principles in the language. – Zeroth Mar 7 '14 at 20:37
2

What you really want is an infix function for chaining like Haskell's $. The sigil need not be $, it could be something more evocative like |> or ->. len, filter, etc are all free functions, so giving them object syntax just for the sake of chaining feels like a kludge to me.

  • 2
    or .: length . filter g . map f – user39685 Mar 6 '14 at 13:54
0

It's worth noting that Python isn't without chain functions, or class methods. For example, imagine a JSON-esque dict that may contain dicts as well:

persondict = {"name": "Joe Foo",
          "DOB": "06/07/75",
          "address": {"street_addr": "123 Some Street",
                      "city": "Anywhere",
                      "state": "MA",
                      "zip": 12345}
          "children": None}

Dicts do have a get method which returns the value for a named key, as well as an optional argument for a dict with no such key. So in the above situation, if we were to assume that the children were also going to be similar dicts, one could write a function as follows:

def get_person_states(jsonlikedb):
    """Gets a list of the states the passed persondicts live in."""
    results = []
    for persondict in jsonlikedb:
        addr = persondict.get('address', {}).get('state')
        if addr is not None:
            results.append(addr)
    return results

This would search each persondict for the 'address' key, and then search the return value for 'state' and return that. If the first get() fails to find an 'address' key, it will return an empty dict; this allows the second call to get() to gracefully bail if the persondict has no address.

Having said that, what's considered 'Pythonic' does not necessarily equate to 'what follows the most conventions' but rather 'what follows the most Python-like convention'. To that end, principles like duck typing and eafp take precedence over things which may work in other languages as a matter of tradition. As an example of both:

def cut_in_half(someobj):
    try:
        halfway = len(someobj) / 2
        return someobj[:halfway], someobj[halfway:]
    except TypeError:
        print "This object is indivisible!!"
        return someobj

In this way the function can never try to split an object that does not have a __len__ method.

As long as it's being discussed -- while it is not necessarily Pythonic, one is well able to call __len__ directly, so if you're pining for doing it a different way, it can technically be done in this direct fashion:

print my_obj_with_length.__len__()

…however, it's not particularly Pythonic to do so.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.