4 ms·
While "weird" when first encountered, once you wrap your head around why this happens, then this is a feature, not a bug. Both in terms of enhancing one's unde
by tpoacher 5y ago
While "weird" when first encountered, once you wrap your head around why this happens, then this is a feature, not a bug.
Both in terms of enhancing one's understanding of python's memory model, and in terms of it being an incredibly useful feature, e.g. for introducing static/persistent variables to a function (without the need for global declarations)
I would not want to see this "fixed"
- remexre 5y agoCommon Lisp takes expressions for default arguments, and has the "fixed" behavior; when one wants a persistent value, they do either: (let ((my-list '(1 2 3))) (defun foo (&optional (l my-list)) (setf (car l) 'hello) l)) or, more often, just make it a special variable of some sort: (defvar *my-list* '(1 2 3)) (defun foo (&optional (l *my-list*)) (setf (car l) 'hello) l)) This seems to work fine, and brings attention to "I have an object that supports mutation, and I'm actually going to mutate it," which is an unusual enough case to be worth calling out.
- lispm 5y agoYou are modifying literal data, which is not a good idea.
- armchairhacker 5y agoi mean it should at least be clear, because it’s not like that in most languages. And there could be 2 forms of syntax for default arguments. singletons or each-call
- justinpombrio 5y agoAs Python is now, this code: def myfun(person = Person()): ... will evaluate `Person()` only once. If you want it to be evaluated every time, you have to say: def myfun(person = None): if person is None: person = Person() ... What's being suggested, is to change the language so that the default-arg code: def myfun(person = Person()): ... will evaluate `Person()` on every call. If you wanted it evaluated only once, you would have to say: __default_person = Person() def myfun(person = __default_person): ... In either case, it's pretty easy to obtain the other case, and the question is what the best default is. My impression is that "evaluate every time" is both a lot less surprising and more commonly what you want, so it would be the better default.
- meowface 5y agoRight. They were saying they much prefer the current way over the proposed way. It makes sense if you realize that the function is defined once, and for the function need to be defined, its signature needs to be defined with it. Default arguments are part of the signature, not part of the function body. It would make sense to pass a callable without calling it, like def myfun(person = Person): ... but if the call occurs in the function signature, it doesn't really make sense that the call would somehow happen again and again. You're not defining a function in the signature; you're specifying a value right there. The current PEP proposal is to enable the "evaluate at function call time" ability with new syntax, which I think makes the most sense semantically and also doesn't break existing code.
- justinpombrio 5y ago> if the call occurs in the function signature, it doesn't really make sense that the call would somehow happen again and again. You're not defining a function in the signature; you're specifying a value right there. There are things besides functions and values! They're called "expressions" or "code blocks", and in an impure language like Python they can produce a different value each time you evaluate them. In my second example, "Person()" is neither a value nor a function -- it's an expression that would be evaluated each time the function is run. This isn't that weird; in `while condition: body`, `body` is a block that's evaluated multiple times. It would need to capture variables from the local environment though, which makes it more complicated to implement. > The current PEP proposal is to enable the "evaluate at function call time" ability with new syntax, which I think makes the most sense semantically and also doesn't break existing code. Oh yeah I wasn't suggesting breaking existing code. Just that if I was designing a language from scratch, it would probably avoid the Python foot-gun and have default arguments evaluated each time.
- mannykannot 5y ago> Then this is a feature, not a bug. > Both in terms of enhancing one's understanding of python's memory model... In much the same way as having your company bricked by a buffer-overflow ransomware attack does in the C world? I'm not a big fan of elucidation by gotcha. > [...and] for introducing static/persistent variables to a function (without the need for global declarations) Except when you are at risk of someone calling with their own variable(s) that do not have the static-singleton semantics that the default would have.
- chc 5y agoThis seems pretty iffy for introducing static/persistent variables to a function. I mean, it can work, but it's semantically very confusing. Parameters are part of a function's interface. A global variable would be much better.
- Nihilartikel 5y agoI won't bite on the feature/bug argument, but it's enough of a foot-gun that I have tracked down no less than 3 real world bugs relating to it in large python code bases. Usually when people use an empty list literal as default and shit gets real when it retains values on the next call. When performing Python interviews I usually try to have that behavior come up in the coding question - not to quiz for trivia so much as to get a feeling for adding on battle-scar bonus points. In practice, for any non plain-ol-data defaults I just have it default to None and handle it in logic. Totally understand why it may be useful for introducing a persistent store to a function, but as a matter of taste I like to do that in an explicit decorator if needed.
- patrick451 5y agoI agree, this behavior is super useful. If python introduced a static keyword, that would cover some use cases, but wouldn't be as flexible.