Hacker Newsnew | past | comments | ask | show | jobs | submitlogin
What Python learned from economics (lazypython.blogspot.com)
46 points by naish on Nov 18, 2008 | hide | past | favorite | 26 comments


There is a concept I've come to appreciate called "Code Debt", its the technical liabilities you've accumulated through using shortcuts and bad practices, things you've chosen to patch so they work in the short term but will bite your ass in the long term and require much more effort to fix than was originally saved.

Always remember the interest on Code Debt is brutal so the wise entrepreneur strives to avoid it.


I went through the presentation he references at http://python.net/%7Egoodger/projects/pycon/2007/idiomatic/p...:

A lot of what he says makes sense, but perhaps someone can explain why this is wrong:

    def bad_append(new_item, a_list=[]):
        a_list.append(new_item)
        return a_list
and this is correct:

    def good_append(new_item, a_list=None):
        if a_list is None:
            a_list = []
        a_list.append(new_item)
        return a_list


Expressions for default parameters are evaluated only once. So the default a_list is the same list on subsequent calls to bad_append

    good_append(1) -> [1]
    good_append(2) -> [2]

    bad_append(1) -> [1]
    bad_append(2) -> [1, 2]
Presumably, keeping an ever-growing shared list is not the desired behavior.


Wow, is that by design? Why would you want such a side effect?


"Is that by design" - Well, yes and no. Try to forget the specific context for the moment.

It is by design that the default parameter to a function parameter is evaluated only once, as the alternative has bad performance implications.

It is by design that when you have a reference, that modifications to that value are still available on the same reference. (That is, Python has mutable objects with a couple of immutable exceptions.) This is obviously a fairly normal choice, very basic to the language design.

Now, take these two normal things together and you get that if you make your default function parameter a mutable object, it'll be the same mutable object every time. The relevant "import this" aphorism would be "Special cases aren't special enough to break the rules."

So, is this "by design"? Well, not directly, it's a natural consequence of other aspects of the design. All languages have corner cases of one sort or another. Making this an exception would itself be a wart, after all:

      l = []
      def f(param = l): pass
      def g(param = []): pass
Shouldn't f and g have similar behavior? If you propose that the value be evaluated every time, they won't, and then people would complain about that, too.


> It is by design that the default parameter to a function parameter is evaluated only once, as the alternative has bad performance implications.

Do people actually use the results of long-running or side-effectful functions as default parameters? It seems the only time this issue comes up is when people get tripped up by mutating simple [] or {} default parameters.

I would expect a default expression to be simple and pure. If this expression should only be executed once, requiring a programmer to be explicit about the complexity wouldn't be a bad thing.


It can be a cute hack to get around the absence of static variables.

Lists have other quirks related to mutability; there are other obscure Pythonisms that use this. If you want a nice magic-free immutable container, tuples are a better bet.


I often evolve type in a single var into something more complex,

    foo = makeSomethingDifferent( foo )
And I need to catch myself when foo is a default param


Regarding the code:

  l = []
  def f(param = l): pass
  def g(param = []): pass
I would say f should be a syntax error, as languages shouldn't allow a default parameter to be an expression. If people wanted that behavour, they could write:

   def f(param =[]):
      if param==[]: param = l
      #...etc...


In Python, [] is an expression too; it is an expression that constructs an empty list and returns it.

Again, if you change this just for argument lists, you're looking at another wart.

I'm not disagreeing with you. Your logic makes sense in isolation. However, language design invariably requires this sort of tradeoff.


I was pleased to find that Ruby isn't like this. So param=[] means param=Array.new, and it gets called every single time you call the method with a parameter missing.


> So param=[] means param=Array.new, and it gets called every single time you call the method with a parameter missing.

How far down does Ruby copy? (A default value could be deep.) How does the programer specify a different level (including no copying)?

Python's rule seems bad until you try to use the alternatives outside the single case that they were designed for. Python's rule handles all cases reasonably.


It doesn't copy anything - it literally is calling "Array.new()" to generate a new array, every time one is needed.

To do it the python way, you'd have to use a global variable to store the array - which is exactly what python is doing for you. Doesn't sound so handy when its put like that, huh?


does anyone have something from the BDFL on this?


It's an obscure Python quirk that default parameters are constructed during compilation time and then never re-constructed. So, to illustrate:

  >>> def hello(a=[]): a.append('hello'); return a;
  ... 
  >>> hello()
  ['hello']
  >>> hello()
  ['hello', 'hello']
On post, what mindslight said.


Try it!

The empty list in the first one is only created at the time the 'def' is executed. All subsequent calls to 'bad_append' hold the reference to this list instead of a new empty one.

The 'good' one uses a sentinel value to indicate that you want to start with an empty list but also gives you the ability to pass in an existing one if needed.


The Python default parameter rules cause a_list=[] to share the same mutable list between all invocations of the function. That's probably not what you want. If you set it to the immutable None then it doesn't matter that the instance is shared.


That concept only works as long as the makers of python really have found the optimum way of doing things. It sounds like it could be stiffling innovation to me. At the very least it doesn't encourage flexibility of the mind - rather you are trained to believe that there is only one right way.

Also, the approach sounds familiar: didn't Java have the same rationale? And of course Apple - to each their own, I guess (I don't like "the Apple way", but others love it - probably would be the same with Python).

That article is really putting me off learning python, to be honest.


it's not the rationale that's important, it's the particular things that are incentivized that's important. I'd certainly argue that Python incentivizes a better set of practices than does Java, despite the fact that they each incentivize things.

Furthermore, language design by its very nature involves incentivizing some things and disincentivizing others. What this blog post is truly claiming, IMHO, is that Python did it willfully and well, whereas many other languages grew by accretion and without intentional thought as to what they were incentivizing.

So don't judge Python because it intentionally incentivizes some behaviors at the cost of others - every language does that - judge the things it incentivizes.


There is a python philosophy that says there should be one and preferably only one obvious way to do things.

You can do things in a million different ways, monkey patch everything under the sun but in the end, having just one way to do things makes code more reliable and readable.

If you don't like the way the framework is built, and its one preferred way of doing things, select a different framework or write your own. Trying to undermine the assumptions of an existing framework has a tendency to cause subtle hard to detect bugs. And trying to support all approaches tend to create stuff that is bloated, ugly and slow.


..having just one obvious way to do things..

obvious is mandatory here.


> flexibility of the mind

Is not a good thing. It leads to curse of choice, analysis paralysis, bikeshedding, and all the related drains on productivity.

I submit that "There should be one obvious way to do it" is the #1 reason devs are/feel more productive with Python.

Sadly Python is growing too many exceptions to that rule.


That's no reason not to learn python. Python is an elegant language with many unique aspects, and for those reasons alone is worth playing with some.


It seems like the author provides a counterexample in the article itself: `from foo import *` doesn't at all enforce good coding habits. Now, admittedly, Java (my language of choice) allows the same bad code, but it still contradicts the idea that Python enforces long-term readability.


"import this": "Although practicality beats purity."

"from X import *" is for REPL usage, where it is damned handy, not to mention a 20-line script. The community strongly discourages its use in big programs.


> Ultimately, this is why I like Python. The belief that best practices shouldn't be optional, and that they shouldn't be difficult creates a community where you actively want to go and learn from people's code.

This place I know was one of the earliest adopters of Python, and the code they barfed up must be the buggiest, ugliest (GUI), slowest in its class software I've ever seen. It's so buggy, it's insane: typos, calling methods that don't exist, assuming arbitrary user input does not have 'keywords' in them...

They couldn't write code in C, so they thought the problem was with the language and switched to Python.

I say, lousy programmers, if they don't want to look for another occupation, should stick with Java - at least the typos will be caught more frequently.




Consider applying for YC's Winter 2026 batch! Applications are open till Nov 10

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

Search: