Live data from Hacker News

A few things to remember while coding in Python

satyajit.ranjeev.in

71–80 of 146 posts

Re: A few things to remember while coding in Python

#71

Earlier quoted context omitted.

But 'def' doesn't have to work that way. Consider, in CL: > (defvar *fn* (let ((x 3)) (lambda (&optional (y (list nil x))) (push 7 (car y)) ; modifies the list y))) *FN* > (funcall *fn*) ((7) 3) > (funcall *fn*) ((7) 3) From this example you can see two things. First, the binding of 'x' is closed over when the lambda expression is evaluated. And second, the expression that provides the default value of 'y' is evaluat…

Expensive, though. http://stackoverflow.com/questions/1651154/why-are-default-a...

Surely not. The implementation already has to check that the number of provided arguments is valid. The decision of whether to evaluate the default expression can be part of that.

The code that evaluates the default expression doesn't need to be in a separate function, either, so the argument that calling that function is too expensive also doesn't hold water.

I just tried a test in SBCL:

  (defun foo1 (x) x)
  (defun test1 (n) (dotimes (i n) (foo1 (cons nil nil))))
  (time (test1 100000000))
  => 4.4 sec, or 44ns / iteration
  (defun foo2 (&optional (x (cons nil nil))) x)
  (defun test2 (n) (dotimes (i n) (foo2)))
  (time (test2 100000000))
  => 4.1 sec, or 41ns / iteration
The version with the optional parameter is actually slightly faster, which completely blows a hole in the performance argument.

Look, no language is perfect -- not even Common Lisp :-) I think users are better served when design flaws in a language are acknowledged without defensiveness than when bogus justifications are offered.

Re: A few things to remember while coding in Python

#72

Earlier quoted context omitted.

Actually, if you understand the way Python is evaluated (dig in, the core is pretty transparent), it's the only behavior that makes sense in this case. It's also documented as such[1], so it's quite expected. Default parameters are still just as useful for constants, such as: def f(x=0, y="foo", z=3.14159): This, however, is a perfectly Pythonic idiom: def f(L=None): if L is None: L = [] [1]: http://docs.python.org/r…

I am but an egg, but isn't this the same but shorter? def f(L=None): L = L or []

It might work the same depending on your use of it but it's not the same. In that instance L will become a blank list if it is equal to None, zero, or a zero length string. There are many cases where this wouldn't affect anything, but there can also be instances where that will cause you to define L as a blank list when you really wanted to keep L's value. I think it's always better to be explicit and test for the value(s) you expect.

Re: A few things to remember while coding in Python

#73

Earlier quoted context omitted.

Expensive, though. http://stackoverflow.com/questions/1651154/why-are-default-a...

Surely not. The implementation already has to check that the number of provided arguments is valid. The decision of whether to evaluate the default expression can be part of that. The code that evaluates the default expression doesn't need to be in a separate function, either, so the argument that calling that function is too expensive also doesn't hold water. I just tried a test in SBCL: (defun foo1 (x) x) (defun te…

You're assuming that the function-calling overhead is the same in python as in CL. I don't think that's the case, and it definitely wasn't at the start.

I don't agree that this is a design flaw. As I recall it bit me once as a beginner, and never again in over a decade of using python, and as a lisp hacker you know you don't design a language for beginners. :-)

Re: A few things to remember while coding in Python

#74

Earlier quoted context omitted.

It's most certainly not a mistake; Python 3 would probably have fixed it, if it were. It is an (admittedly, strange) side effect of the way 'def' works.

But 'def' doesn't have to work that way. Consider, in CL: > (defvar *fn* (let ((x 3)) (lambda (&optional (y (list nil x))) (push 7 (car y)) ; modifies the list y))) *FN* > (funcall *fn*) ((7) 3) > (funcall *fn*) ((7) 3) From this example you can see two things. First, the binding of 'x' is closed over when the lambda expression is evaluated. And second, the expression that provides the default value of 'y' is evaluat…

My lisp is a bit rusty, but it looks like what you're doing there is returning a function which gets redefined every time you reuse the function.

The equivalent Python would be something like this:

    def function():
        x = 3
        def internal(x, foo=[]):
            foo.append([7])
            foo.append(x)
            return foo
        return internal(x)
        
    print function()
    print function()
Which does what you would expect:

    [[7], 3]
    [[7], 3]

Re: A few things to remember while coding in Python

#75
post #30

Earlier quoted context omitted.

Actually, if you understand the way Python is evaluated (dig in, the core is pretty transparent), it's the only behavior that makes sense in this case. It's also documented as such[1], so it's quite expected. Default parameters are still just as useful for constants, such as: def f(x=0, y="foo", z=3.14159): This, however, is a perfectly Pythonic idiom: def f(L=None): if L is None: L = [] [1]: http://docs.python.org/r…

While it seems logical when you understand what's going on, from a practical point of view I can't see how this would ever be useful. The tradeoff appears to be that the functions are first class objects. I'm not sure what the benefit here is though. Does having them as first class objects allow some useful idioms? (I'm a ruby dev but I'm genuinely curious to know what this allows you to do)

There are a few use cases for default variables on effbot's site: http://effbot.org/zone/default-values.htm

Basically, sometimes you do want to reuse the mutable between function calls, and in those cases it can save a fair bit of code passing it in repeatedly.

Re: A few things to remember while coding in Python

#76

Earlier quoted context omitted.

Well, yes, but I'd also like to think that code should raise the level of the programmers reading it. You shouldn't avoid language features just because some people don't know about them. That's as ridiculous as the folks who say "Don't use the ?: operator because folks who haven't taken Intro CS 101 may not be familiar with it." So you spent a couple seconds Googling defaultdict. Great, you now know what a defaultdi…

Well, yes, but I'd also like to think that code should raise the level of the programmers reading it...You should avoid gratuitous complexity I have a different set of policies than most programmers, which arises from my observation that our field's priorities are out of whack with the actual cost-benefit. Our greatest costs involve understanding systems, so our first priority should typically be to produce readable…

I have a different set of policies than most programmers, which arises from my observation that our field's priorities are out of whack with the actual cost-benefit.

What metrics do you use to determine if code is readable or not? What metrics do you use to determine "actual cost-benefit"?

Re: A few things to remember while coding in Python

#77

Earlier quoted context omitted.

But 'def' doesn't have to work that way. Consider, in CL: > (defvar *fn* (let ((x 3)) (lambda (&optional (y (list nil x))) (push 7 (car y)) ; modifies the list y))) *FN* > (funcall *fn*) ((7) 3) > (funcall *fn*) ((7) 3) From this example you can see two things. First, the binding of 'x' is closed over when the lambda expression is evaluated. And second, the expression that provides the default value of 'y' is evaluat…

My lisp is a bit rusty, but it looks like what you're doing there is returning a function which gets redefined every time you reuse the function. The equivalent Python would be something like this: def function(): x = 3 def internal(x, foo=[]): foo.append([7]) foo.append(x) return foo return internal(x) print function() print function() Which does what you would expect: [[7], 3] [[7], 3]

No. In my example the function is created only once, and called twice.

Re: A few things to remember while coding in Python

#78

Generally most use this: freqs = {} for c in "abracadabra": try: freqs[c] += 1 except: freqs[c] = 1 If this is really the common idiom, I'd say this is a sign that professional programming has yet to fully mature as a field. Some may say a better solution would be: freqs = {} for c in "abracadabra": freqs[c] = freqs.get(c, 0) + 1 Okay, so I understood immediately what was going on with the 2nd bit of code. Rather go…

Or just say: from collections import Counter freqs = Counter("abracadabra") I was surprised to see that missing, given that Counter was mentioned in the next section.

Ah, Guido's time machine strikes again :)

Here's my (now mostly obsolete) version:

    def count(string):
        counts = {}
        for item in set(string):
            counts[item] = string.count(item)
        return counts
        
    print count("abracadabra")
I had a look into the collections library, and it just uses iterable.iteritems(). I suspect that this might be faster for larger strings with multiple repeating characters, since set() and count() will pass the string directly to C.

Re: A few things to remember while coding in Python

#79
post #18

Earlier quoted context omitted.

That seems like decidedly unexpected behaviour and makes default params far less useful.

The thing is, None is always a possible value for a parameter so it's actually more robust if functions are written to expect None. If you say "f(x=[])" (assuming that worked without the actual side effects it has), someone could still say "x(None)" instead of "x()", causing the function to die. Since a robust program isn't able to avoid checking for None, it might as well set defaults there too. There is another cas…

I disagree. If you want your programs to be robust like that, you now have to check for every case where someone might pass in something stupid (dict instead of int, maybe?). Much better to catch errors further up the chain and keep your low level code simple (ie. pass me something other than an iterable and I blow up).

In the expensive case, I'd just calculate it once and store it somewhere (possibly as a lookup dictionary if there are multiple inputs) and access that from within the function.

Re: A few things to remember while coding in Python

#80
post #30

Earlier quoted context omitted.

While it seems logical when you understand what's going on, from a practical point of view I can't see how this would ever be useful. The tradeoff appears to be that the functions are first class objects. I'm not sure what the benefit here is though. Does having them as first class objects allow some useful idioms? (I'm a ruby dev but I'm genuinely curious to know what this allows you to do)

Functions (methods) are first class objects also in Ruby: methods are instances of the class Method, while Procs are a lightweight alternative. Default arguments in Ruby are not mutable in any kind of function object, be it a lambda proc, a regular proc, or a method. You can use a mutable default argument as an ersatz static variable, e.g. for memoization.

> Default arguments in Ruby are not mutable in any kind of function object

Maybe I misunderstood you, but they are perfectly mutable:

    class A
      attr_accessor :a
      def initialize
        @a = []
      end

      def b x = a
         x  # 
    obj.b
    # => [1] 
    obj.b
    # => [1, 1] 
    obj.a
    # => [1, 1] 
If you mean "inline default arguments are not mutable", that's not true either. What is true is that the default argument is evaluated when the function is called, not when it is defined:

    a = 0
    # => 0 
    x = lambda {|y = (a + 1)| y }
    # => # 
    x[]
    # => 1 
    a = 5
    # => 5 
    x[]
    # => 6
Post reply on HN