Hacker News new | past | comments | ask | show | jobs | submit login
Essential Python Tips, Tricks, and Hacks (siafoo.net)
90 points by rockstar9 on May 21, 2008 | hide | past | favorite | 25 comments



An improvement on the switch-using-dictionaries example: instead of doing this...

  keycode = 2
  functions = {1: key_1_pressed, 2: key_2_pressed, 3: key_3_pressed}
  if functions.has_key(keycode):
      functions[keycode]()
  else:
      unknown_key_pressed()
you can save three lines by replacing the if-else block with:

  functions.get(keycode, unknown_key_pressed)()


Wow, yeah, that one is fairly obvious huh...


The article really has something against map and filter. Specifically:

  squares_under_10 = filter(lambda x: x < 10, map(lambda x: x*x, numbers))
  
  squares_under_10 = [number*number for number in numbers if number*number < 10]
Those two lines are functionally different; the second one (the one the article prefers) performs a costly multiplication twice.

I'm not saying list comprehensions are bad... I use them a ton. I just acknowledge that there are times where map and filter make a lot of sense. Even for trivial reasons like wrapping lines, using map allows you to break at commas instead of embedding backslashes in your code and trying to decide how it should all align.

If you haven't, check out the itertools standard library module. The power held in these functional, generator-based functions is amazing. Since I started using it, I don't know how many times I've used:

  imap(func, izip(somelist, repeat(somevalue)))
and other such paradigms.

Hmm... more problems with the article:

  numbers = [1,10,100,1000,10000]
  if [number for number in numbers if number < 10]:
      print 'Success'
  # Output: 'Success!'
This is terrible. It may look nice all on one line, but it goes through the whole list when it doesn't need to. Better is:

  for number in numbers:
      if number < 10:
          print "Success"
          break
A problem with using the and ... or paradigm:

  result = test and 'Test is True' or 'Test is False'
The page mentions that "Test is True" must evaluate to True or else it will always be skipped over even if test evaluates to True. It fails to mention, however that "Test is False" must also evaluate to True. Otherwise, if test evaluates to False, the entire expression returns False. Thus

  s = foo and "word: "+foo or ""
will assign False to s instead of the desired "". In this case, it is quite clear that

  s = "word: "+foo if foo else ""
is preferable.

I didn't find the article all that helpful, personally. It puts forward inefficient solutions as the recommended methods, and it doesn't offer much that I didn't already know. The two things that I learned, which I'm not sure that I'll ever use, are function decorators and the "string in substring" syntax.


"using map allows you to break at commas instead of embedding backslashes in your code"

I usually break list comprehensions before the 'for', and then again before the 'if' if they still need it. No backslashes. Implicit line continuation works as well within list comprehensions as it does in function calls.

"if [number for number in numbers if number < 10]:"

I would've used:

  if any(number < 10 for number in numbers):
Or alternatively

  if any(itertools.imap(lambda x: x < 10, numbers)):
'any' short circuits its evaluation if it finds a true item , and the generator expression doesn't need to be evaluated fully. (I think the first is more readable.)

I also didn't find all that much that was new and interesting in the article, but that's because I went hunting for articles like this when I first started getting into Python seriously, and have been programming full-time in it for 8 months now.


I usually break list comprehensions before the 'for', and then again before the 'if' if they still need it. No backslashes. Implicit line continuation works as well within list comprehensions as it does in function calls.

Wow, I learned more from this comment than I did from the entire wiki page. I wonder why I never realized this before... I tend to break my list comprehensions the same way, and for some reason I thought the backslashes were necessary.

  if any(number < 10 for number in numbers):
Very nice. I need to start using some of these builtins a little more. In particular, I'm pretty sure that I've used

  for foo, index in izip(list, count())
before instead of simply using enumerate. any is one that I'll definitely have to keep in mind.


  if any(number < 10 for number in numbers):
Okay this is has to be the best way to do this, much better than the example in the article.


except it is not working?

>>> numbers = [1,10,100,1000,10000,100000,1000000] >>> if any(number < 10 for number in numbers):print number

Traceback (most recent call last): File "<pyshell#4>", line 1, in <module> if any(number < 10 for number in numbers):print number NameError: name 'number' is not defined >>> if any(number < 10 for number in numbers): print number

Traceback (most recent call last): File "<pyshell#7>", line 2, in <module> print number NameError: name 'number' is not defined >>>


It is working; the variable "number" is local to the generator expression and can't be used in the print statement. It works if you print something else instead (e.g. "Success!" in the article).

If you just want to print the numbers less than 10, it's easier to do:

  print [number for number in numbers if number < 10]
or

  print '\n'.join(str(number) for number in numbers if number < 10)
if you want the same formatting.


This is where haskell makes me jealous

    if any (< 10) numbers
is so much prettier.

I really want a dynamic language with haskell's beautiful function composition.


You're right about the map function, it is slightly more efficient in this case. But if you're multiplying 2 numbers together 10 times instead of 5, is it really significantly more efficient to use map? I suppose map/filter vs. list comprehensions/generator functions is really a matter of personal taste.

And not to start a flame war, but the code:

  numbers = [1,10,100,1000,10000]
  if [number for number in numbers if number < 10]:
      print 'Success'
  # Output: 'Success!'
actually does say that it's less efficient... granted it's buried in a paragraph so it's maybe not too obvious ; )... if you're using Genshi or something similar though you need to be able to toss crazy things in one line sometimes.

And 'Test is False' doesn't have to evaluate to True... if it is false, it is still the value returned by the entire expression. Not the actual value 'False'. I actually disagree with your example

  s = foo and "word: "+foo or ""
I tried it and "" got assigned to s...

And dude, the string in substring syntax is awesome!


You're right about the map function, it is slightly more efficient in this case. But if you're multiplying 2 numbers together 10 times instead of 5, is it really significantly more efficient to use map? I suppose map/filter vs. list comprehensions/generator functions is really a matter of personal taste.

I don't understand what you mean here. No matter how many numbers you're multiplying, the result is that you're doing twice as many as necessary. Now, with list comprehensions (and a generator for efficiency), it could have been written

  squares_under_10 = [sq for sq in (n*n for n in numbers)
                      if sq < 10]
That is the best of both worlds if you are against the use of map and filter.

And 'Test is False' doesn't have to evaluate to True... if it is false, it is still the value returned by the entire expression. Not the actual value 'False'. I actually disagree with your example

Hmm... very strange, you are absolutely right. I'm almost positive I've been bitten by a bug related to and... or before. It must have been something else, although I'm sure it wasn't the case where the "Test is True" value evaluated to False. Very weird...

And dude, the string in substring syntax is awesome!

I admit that is awesome, I just couldn't remember the last time I'd used index or find, anyways. I'll certainly be using it the next time the opportunity presents itself, though.


This is completely unrelated, but why do some comments render at a lighter shade of gray than other comments? This comment renders at #323232, and the rest of them render at #000


Comments with 1 point or fewer are rendered in progressively grayer text, presumably to make them easier to filter out visually.


Preferring the lambda expression because the list comprehensive performs a multiplication operation twice is, I think, misguided. The multiplication probably isn't that expensive compared to the rest of the code.


5.1: I prefer to use the rule "don't mutate objects" instead. I would've defined the example as:

  def function(item, stuff=[]):
      print stuff + [item]
Mutating the passed-in 'stuff' creates a leaky abstraction that then has to be documented. In rare cases this is what you want, but most of the time you're better off creating new objects and working with them. There's less to remember in the API, and fewer ways to screw up.

In particular, this code - which looks safe - blows up horribly with the article's function:

   MODULE_LEVEL_CONSTANT = ['foo', 'bar']
   function('baz', MODULE_LEVEL_CONSTANT)
   function('quux', MODULE_LEVEL_CONSTANT)
The second call will print ['foo', 'bar', 'baz', 'quux'] instead of the expected ['foo', 'bar', 'quux'].


Not for me...

  >>> def function(item, stuff=[]):
        print stuff + [item]

  >>> MODULE_LEVEL_CONSTANT = ['foo', 'bar']
  >>> function('baz', MODULE_LEVEL_CONSTANT)
  ['foo', 'bar', 'baz']
  >>> MODULE_LEVEL_CONSTANT
  ['foo', 'bar']
  >>> function('quux', MODULE_LEVEL_CONSTANT)
  ['foo', 'bar', 'quux']
I think you're confusing + and .append (the latter affects the object).


I believe he was referring to use of .append by the author. You defined it the way he recommends, which has the desired behavior.


Huh? I expect ['foo', 'bar', 'baz', 'quux']. I expect accumulation, esp with a list. If I wanted ['foo', 'bar', 'quux'] I would send in a fresh copy of a list instead of a list I'd been whoring around to other functions.

    function('baz', list(MODULE_LEVEL_CONSTANT))
    function('quux', list(MODULE_LEVEL_NOT_CONSTANT))
I would also change MODULE_LEVEL_CONSTANT to not say CONSTANT cause a list is no such thing and doesn't become constant just cause a label says so. Maybe it even leads people to expect silly things.


I dunno, the Python convention is "if you don't mutate, return a value; if you do mutate, return None." Since you wrote function('baz', MODULE_LEVEL_CONSTANT) without an assignment in front of it, I would assume that function must mutate something. Since strings are immutable, that just leaves either the display (print) or the second argument. I guess the real point is don't mutate your arguments and print them. Do one or the other, but not both.


Less syntax in languages, please. The problem with the Perlish "There's more than one way to do it!" is that one ends up expending extra cycles comprehending semantic equivalence. But equivalence should be a fulcrum -- these are things leveraged to be able to reduce the effective complexity of what we're looking at. Anything that obscures the visibility of equivalence is bad on the balance.


If your language allows both (+ 1 2) and (+ 2 1) then there's more than one way to do it. There's always more than one way to do it. That's just the nature of languages. Even in pure arithmetic, both 1.99999… and 2 represent the same number. It's inescapable. The point isn't "let's only have one way to do it." The point is to have One Obvious Way To Do It. And that's the Python motto. What this article is doing is showing the mappings between the way you might be used to from other languages to the preferred One Obvious Way of Python.

Decrying syntax is nuts. Humans work better if they can pick out visually what's going on. Even Lisp people indent their ifs and defuns. (And if Lisp doesn't have syntax, what the hell do ' or # or (in Arc) [] do?) Now, if we have to have at least a little syntax it's also nice if it's easy to pick out from the visual impression of the syntax what it is that the programmer is getting at. In my opinion, Python does a great job of this.


(+ 1 2) vs (+ 2 1) is a far cry from "map(lambda x: x * x, numbers)" versus "[x * x for x in numbers]".

I call straw man. I am not asking for zero syntax, or that everything be done in Scheme.

For one thing, I'm not a Lisper, which you think I am. For another thing, Lisp has syntax, so you are incorrect. For yet another thing, I am decrying Too Much Syntax that encourages too many ways of doing things, not taking a dogmatic stand for absolute syntactic minimums. True one cannot make a syntax so pure that there is only one way of doing something. That's a straw man. But on the other hand, one can make a syntax so complicated that it seems to strongly encourage doing the same thing in many ways. Perl and Ruby are guilty of this. Python not nearly as much, but still more than I would like.

Syntactic constructs are a tool. Tools have overhead. Why have redundant syntactic tools? It's enough to get one's head around the problems to be solved.


You know if you create an account on the site you can edit the article, like a wiki.


That is a great idea for getting new users... make subtle mistakes in articles and then require people to register in order to correct them.

I had actually looked for an "edit" link or a way to e-mail the original author, but I didn't want to go through the hassle of registering before being sure that I'd be able to fix the problems.


lol... that's true ;)

Although I'm going to try to add all the corrections you guys had when I get a chance (probably tomorrow).




Guidelines | FAQ | Lists | API | Security | Legal | Apply to YC | Contact

Search: