April 26, 2005

Python keeps rubbing me the wrong way

In response to Luke Hutteman's post on continuations, someone offered the following Python snippet to illustrate filtering:

values = range(10)
for nr in (nr for nr in values if nr%2==0):
    print nr
There are several things that bother me with this code:

  • I count four different meanings for the variable nr, each with a different semantics. You should not need more than two (one as a parameter to for and the other one used inside the body)
  • It mixes procedural, and inverted style and doesn't contain anything that is object-oriented.  Python does support some object orientation, so for more complex pieces of code, you will find yourself constantly mixing three different styles of programming.
  • It mixes filtering (nr % 2 == 0) and business logic (print), making it hard to parameterize either.

Here is a Ruby way of doing the same thing:

(0..10)
  .find_all { |it|
    it %2 == 0
  }
  .each { |it|
    puts it;
  }

What I like about it:

  • It's object-oriented, which makes it regular:  each object is applied a message and the result is then piped into the next treatment.
  • Filtering and business logic are clearly separated, so you can improve this example by substituting blocks (strategy pattern)
  • It minimizes the number of intermediate variables (only one:  it and it is only used as a parameter and inside the body of the closure.

The latter solution feels much more natural and intuitive to me, whereas most of my time in Python is typically spent

  • Filtering out the omnipresent and totally useless keyword self.
  • Wondering if the object I am staring at responds to a method or if I need to call a global function on it (such as range in the example above).

I guess I'm too young for Python and spoiled by modern languages :-)

Posted by cedric at April 26, 2005 03:22 PM
Comments

Amen brotha

Posted by: Jonathan Aquino at April 26, 2005 08:20 PM

You can be elegant without being OO and vice versa... the python code is much easier on my eyes.

Posted by: Jonathan Ellis at April 26, 2005 09:23 PM

I really just don't want to think about you rubbing your Python, Cedric.

Posted by: Patrick Calahan at April 26, 2005 09:42 PM

Or yet simpler (Ruby) :

(0..10).each {|it| puts it if it % 2 == 0 }

I like this language.

Posted by: Martin at April 26, 2005 11:06 PM

yes, I gave up on python as soon as I saw the "self" keyword. It just made me feel really uncomfortable.

I think these experiments with languages are really great. Something good is going to come out of it. At some point I may even feel like learning Ruby or Groovy. But learning a new language is a lot of work, especially when I can get the above easily in java with

class tmp {
public static void main(String [] args) {
for (int i = 0; i <= 10; i+=2) {
System.out.println(i);
}}}

Posted by: Henry Story at April 27, 2005 12:21 AM

I think it's just a matter of what you're used to. For me as a Python and Java programmer, the Ruby code you show looks just as unnatural and weird as the Python code looks to you.
But maybe that's just because I'm not used to seeing Ruby, just as you're not used to seeing Python code.

What I suggest, if you want to get a better feel for Python, is to do the Python tutorial (available at www.python.org, also available in any Python distribution you download) which takes you on a (really quick) trip through Python's features. Shouldn't take more than a couple hours.

It's definitely a mis-characterization when you say "Python does support some object orientation". Complete object orientation was built in from the start, and it goes further than Java does in a lot of ways : Python allows multiple inheritance and operator overloading, for instance.
It's true that it allows you to mix object-oriented and script-style code but I really don't see what's wrong with that, since you get the best of both worlds.

Posted by: Someone at April 27, 2005 12:59 AM

Did you forgot groovy?
(1..10).findAll { it % 2 == 0 }.each { println it }

Posted by: se7en at April 27, 2005 01:21 AM

But Henry - you example is not filtering; it's simply iterating through a list with an index increment of 2.
You may not like self but it's a lot cleaner than a wrapper class "tmp", a static method "main" and an array of unused params "args" - just for the sake of getting it to compile and run...

Posted by: Simon at April 27, 2005 01:25 AM

Or in python if you want to cheat with the filtering (increment of 2) -

for x in range(10)[::2]:
print x

Posted by: Simon at April 27, 2005 01:40 AM

try this:

for nr in filter(lambda arg: arg%2 == 0, range(10)): print nr

Posted by: at April 27, 2005 05:26 AM

As someone who is familiar with neither Python nor Ruby I find the Ruby code easier to understand. (BTW I program mostly in Java)

Posted by: James Stauffer at April 27, 2005 05:33 AM

My python code was not about filtering as much as it was about generators and the fact that because they are useful, sometime you can use expression syntax aka expedite and yet be lazy.
If arguing about the architecture of a scalable, robust, flexible, elegant and maintainable piece of 3 lines of code is what you do I am at lost.
What about creating an entire MVC for the sake of it? Let's not be ridiculous when it's not the case.
By the way your code has 4 "it" and I'll let you count the different semantics to yourself.

Posted by: at April 27, 2005 01:57 PM

I'm not sure I see those four semantically different uses of nr.

There are two for loops. In both cases "for nr" declares the nr variable. The "nr" at the start of the list comprehension expression and the nr in "nr % 2 == 0" are both the number (taken from values) for the current iteration of the for loop - exactly the same as the nr in "print nr" in the outer for loop.

So that's two meanings for the string "nr": declaration of a variable, and accessing the value it contains. That would be the same number of meanings as "it" has in your Ruby code, right? What did I miss?

You seems to object to the fact that Python doesn't force every possible operation into the obj.function(arg) syntax pigeonhole. I'm not sure that's a bad thing.

Seriously though, I wonder if an utterly trivial rearrangement of the Python code would make you feel better about the filtering/writing separation:

values = range(10)
filtered = (nr for nr in values if nr % 2 == 0)

for nr in filtered:
print nr

Posted by: Bitter at April 27, 2005 04:54 PM

Here is a totally different code:

values = range(10)
for nr in [nr for nr in values if nr%2 == 0]:
print nr


which may be better as:

even = lambda x: x%2 == 0
for nr in filter(even, values):
print nr


However this versions are actually building the resulting list of even numbers then the outer loop will consume it.
The generator version is different in the sense that it produces the even numbers as the outer loop requests them so
you could say there is only one traversal while without the generator there are 2 traversals.
I'm not sure how the ruby code you posted behaves, is it like a generator or is it more like this second version actually?

As far as comic OO design goes:

class RubyList(list):
def find_all(self, predicate):
return RubyList(filter(predicate, self))
def each(self, func):
map(func, self)
even = lambda x: x%2 == 0
def write(x):
print x
RubyList(range(10)).find_all(even).each(write)


-code blocks are indeed nice, however I figure on larger scale coding conceptualizing the block into an abstraction
(naming it) may be in order so the end is the same as above
-reducing usage of temporary variable and actually better yet reducing side effects are almost crying for functional
composition map(write, filter(even, values) in the inverse (mathematics) notation or the latin
(conversational - call it object message passing if you will) notation values.filter(even).map(write).
-combining paradigms are common not only since Derrida persuaded us to do nothing but deconstruct reality in new ways

Posted by: gheorghe milas at April 27, 2005 07:41 PM

And here's the same thing a few ways in perl:

print for ( grep { $_ % 2 == 0 } ( 0 .. 10 ) );

for ( 0 .. 10 ) {
print if ( $_ % 2 == 0 );
}

foreach my $value ( 0 .. 10 ) {
print $value if ( $value % 2 == 0 );
}

Here's what I like about them:

- I can use shortcuts if I want (that scary $_ is really just a form of "it")

- I can use more the often more readable form of post-conditions if I want ("print it if this is true")

- I can use functional constructs alongside everything else (grep filters a list from right to left using a block or regex)

And I don't think I've heard Derrida brought up in discussions of combining paradigms before, nice. (But I'm probably just not in on the right discussions...)

Posted by: Chris Winters at April 27, 2005 09:17 PM

Re: Chris Winters' comment above:

I prefer this one with fewer parentheses:

print for grep {$_ % 2 == 0} 0..10;

Another perl variant:

map {print} grep {$_ % 2 == 0} 0..10;

Posted by: Prakash Kailasa at May 3, 2005 08:37 AM

I really recommend Jython(JavaPython). You can write Python code if you have to as a simple scripting glue over your Java frameworks plus you don't have to get too deep inside of Python if you don't want to. It really makes coding a lot smoother.

Posted by: Berlin Brown at May 4, 2005 04:11 AM

You can separate out the filtering and printing:

evens = [n for n in range(10) if n%2==0]
for number in evens:
print number

But yeah, Python does have its warts. In boo ( http://boo.codehaus.org/ ) the "self" is optional. The colon at the end of lines is redundant and could be made optional too.

Posted by: at May 6, 2005 01:12 PM

This is better:

for nr in range(10):
_if not nr%2:
__print nr

Posted by: at May 9, 2005 05:28 AM

dont call both variables nr and your problem is solved

as to the ruby example, I think it is nowhere near as "obvious" as the python code.


Posted by: at May 9, 2005 05:29 AM

You can write unreadable code in any language...

Doesn't mean you have to, though.

Posted by: Paul at May 9, 2005 06:28 AM

What have happened to Pythons "Only one obvious way to do it"-slogan?

As more and more constructs get added to Python the more time I use to find the optimal.
In classic python (before list and generator expressions) the one proposed by Berlin Brown would have been the obvious.

for nr in range(10):
_if not nr%2:
__print nr

Wonderful, and what made me love python! Easy to read and obvious even to non python programmers.

I'm worried as I cant recommend Python to beginners as much as I used to.

Posted by: Fredrik C at May 9, 2005 08:05 AM

Python:
print [it for it in range(10) if not it % 2]

Ruby:
(0..10).each {|it| puts it if it % 2 == 0 }

Please observe that nobody has a leg to stand on in this argument.

Posted by: Daniel at May 9, 2005 09:20 AM

I think everyone missed the most straight forward solution in Python:

for number in range(0,10,2): print number

Posted by: Curtis at May 9, 2005 10:54 AM

Using a generator for the expression in a for loop is quite awkward, no doubt. I never ever do that. So identifying and critisizing that awkwardness is not particularly insightful, since it's entirely resolveable. Python is resistent to confusing programming structures, but certainly not invulnerable.

Anyway, generally Python style is that you give objects names if things seem confusing, in this case "evens = (n for n in values if n % 2 == 0)". There's your abstract logic. Then "for n in evens: print n" is your display logic. Separate them into functions if you want more abstraction, they are quite ammeniable to such separation and reuse.

The OO equivalent of adding an "evens" method to all sequences is rather absurd (at least if you care about large systems, localized changes, and safe integration of multiple libraries). I general for this kind of code OO is inappropriate; OO offers little improvement to algorithmic code.

The critique of self here doesn't even apply; self doesn't appear anywhere in the Python code, and the Ruby code doesn't use any construct where Python would use self either. It seems like "self" is the new irrational reason to dislike Python these days, now that significant whitespace seems to have gained some degree of acceptance. While its presence in method signatures is largely superfluous, I think it is very justifiable in code bodies; that Ruby shortens it to "@" doesn't change much. Leaving out any localized distinction between globals functions/variables, and instance methods/variables (as in, say, C++), is (IMHO, as always) far more confusing.

Posted by: Ian Bicking at May 9, 2005 11:26 AM

Ruby's equivalent of
for number in range(0,10,2): print number
is
0.step(10,2) {|i| puts i}
The python code seems slightly more readable to me.

Posted by: John at May 9, 2005 11:32 AM

"self" is not a keyword in Python, it is a convention. What is built-in to Python is that every instance method defined in the body of a class definition (which is any function that isn't explicitly made into a class method or whatever) has as its first argument the instance that the method is called on. The convention is to use the name "self" for this argument. But you could just as well use "bob" as long as you are consistent.

Posted by: random python surfer at May 9, 2005 11:38 AM

So basically you like ruby because their list/sequence types have a method called 'find_all' and 'each'. Where 'find_all' accepts and expression ('it %2 == 0') and 'each' accepts another method ('puts').

'Cause that's what I see happening in the Ruby code. Although I don't speak ruby.

Posted by: edwardam at May 9, 2005 03:58 PM

class slist(list):
def find_all(self, var, expr):
exec """a = [%s for %s in self if %s]""" % (var,var,expr)
return slist(a)
def each(self, var, expr):
exec """for %s in self: %s""" % (var, expr)

if __name__ == "__main__":
slist(range(10)).find_all('n', "n % 2 == 0").each("n","print n")

Posted by: edwardam at May 9, 2005 04:16 PM

I am fascinated that you spend so much time complaining about Python. Is someone making you use it for a job?

There is a reason that there are so many programming languages - and that is that there are many ways of expressing these concepts. Python clearly doesn't suit the way you think or your sense of aesthetics. That doesn't mean that there is anything wrong with you or Python.

Posted by: mark at May 10, 2005 03:56 AM

Anyhow, seing the first complain was about unclear syntax. Python promotes clarity, but certainly does not enforce it. In any real application this is how you should do

def listfilter(argument):
return argument % 2 == 0

unfilteredlist = range(10)
filteredlist = filter( listfilter, unfilteredlist )

for item in filteredlist:
print item

One-liners are never pretty, there is always a way to make things obfuscated. Take a look at this C code example and see if its clear :D

http://remus.rutgers.edu/~rhoads/Obfuscated_C/pi.c

Posted by: Stefan at May 10, 2005 01:03 PM

To preserve formatting:
print '\n'.join([str(it) for it in range(10) if not it % 2])

Posted by: at May 22, 2005 10:47 PM

Why don't do it like this?

for nr in range(0,10,2):
print nr

Posted by: Tuomas at May 31, 2005 03:37 AM

Why not

for nr in range(0,10,2):
print nr

Come on folks, you must compare the most beautiful way in a language to another language.

I am in love with Ruby, but the original post of cedric is really unfair if there are more beautiful and still short ways to achieve something in Python.

Posted by: shev at September 30, 2005 01:30 AM

href

Posted by: Charge Back at November 14, 2005 06:26 AM

in c# 3.0:
Int32.Range(0,20).WhereIndexIs(i=>i.Even()).ForEach(v=>Console.WriteLine(v));
or:
Int32.Range(0,20,2).ForEach(v=>Console.WriteLine(v));

It is truly beautiful but needs some extension methods to be included.

Posted by: exe at April 20, 2007 10:06 PM

in c# 3.0:
Int32.Range(0,20).WhereIndexIs(i=>i.Even()).ForEach(v=>Console.WriteLine(v));
or:
Int32.Range(0,20,2).ForEach(v=>Console.WriteLine(v));

It is truly beautiful but needs some extension methods to be included.

Posted by: exe at April 20, 2007 10:06 PM

obviously it is
Int32.Range(0,20).Where(i=>i.Even()).ForEach(v=>Console.WriteLine(v)); - no "WhereIndexIs"

Posted by: exe at April 20, 2007 10:11 PM

ボイストレーニング
20万以上の賃貸
霊園
治験
格安航空券 国内
債務整理
債務整理
肺がん
レンタルオフィス
酸素カプセル
ダイエットサプリメント
FX取引
インプラント
店舗デザイン
婚活
センチュリー21 埼玉
地デジ
結婚式
FX取引
大森プロストシティレジデンス
鳩ヶ谷市 一戸建て
ノベルティ
塗装
手の若返り
筋肉増強
春日部市 不動産
貸し事務所
吉田不動産
板橋 マンション
結婚指輪
ウエディングドレス
携帯 サイト 作成
スリッパ
大田区 不動産売却
レゴ
海外留学
大学留学
中国留学
留学
阿佐ヶ谷 不動産
SEO対策
新宿 アートメイク
結婚相談所 中央区日本橋
チラシデザイン
新座 不動産
弁護士 銀座
習い事
トイレつまり
トイレつまり
トイレつまり
板橋区 一戸建て
CSR
Bloom
小顔
サーモシェイプ
お見合いパーティー
まつげエクステ
相続
会社設立
会計システム
ブライダルエステ
農商工連携
エルメス

Posted by: nguyen at July 9, 2009 07:06 AM

nt32.Range(0,20).WhereIndexIs(i=>i.Even()).ForEach(v=>Console.WriteLine(v));
or:
Int32.Range(0,20,2).ForEach(v=>Console.WriteLine(v));

It is truly beautiful but needs some extension methods to be included.
Posted by: exe at April 20, 2007 10:06 PM

obviously it is

Posted by: nguyen at July 9, 2009 07:06 AM
Post a comment






Remember personal info?