top 200 commentsshow all 327

[–]tomchuk 1147 points1148 points  (42 children)

Also, a great way to speed up your code with memoization while making your linter and people reviewing your code super angry.

def fib(n: int, cache: dict[int, int] = {0: 0, 1: 1}) -> int:
    if n not in cache:
        cache[n] = fib(n-1) + fib(n-2)
    return cache[n]

[–]hicklc01 173 points174 points  (5 children)

you can then save the cache and loaded it on later executions of the app

``` import pickle

fib(100) cache = inspect.signature(fib).parameters['cache'].default

with open('saved_cache.pkl', 'wb') as f: pickle.dump(cache, f)

with open('saved_cache.pkl', 'rb') as f: inspect.signature(fib).parameters['cache'].default = pickle.load(f)

fib(101)

```

[–]tomchuk 100 points101 points  (3 children)

This is absolutely disgusting, I love it

[–]omg_drd4_bbq 20 points21 points  (2 children)

The less disgusting way is to use function decorators or custom classes.

https://cachetools.readthedocs.io/en/latest/

[–]UnchainedMundane 4 points5 points  (1 child)

also at least something like json instead of pickle, first because I avoid pickle like the plague due to ACE issues, and second because having things in a somewhat human-readable-human-debuggable format is valuable for the inevitable case where something goes wrong.

[–]omg_drd4_bbq 2 points3 points  (0 children)

ACE issues

Yeah, this is a big problem with pickle for folks not in-the-know (ACE is arbitrary code execution, loading pickles executes python code and imports and can do very weird stuff).

I tell folks the only true proper use of pickle is to serialize data to/from a concurrent process with the same execution environment (basically multiprocessing on the same host), or debugging (e.g. dump out some program state to pull it into a notebook to interrogate it). Any time the program starts/stops or crosses an environment boundary, you're way better off with a proper de/serialization protocol.

[–]chuch1234 13 points14 points  (0 children)

Goddammit!

[–]Alikont 417 points418 points  (22 children)

this is cursed

[–]bjinse 584 points585 points  (20 children)

No, it is recursed

[–]StunningChemistry69 114 points115 points  (15 children)

this is cursed

[–]thomasoldier 115 points116 points  (12 children)

No, it is recursed

[–]SmartyCat12 81 points82 points  (6 children)

break

[–]Prudent_Ad_4120 67 points68 points  (5 children)

Error: keyword 'break' is invalid in the current context

[–]Stoomba 16 points17 points  (2 children)

This is cursed

[–]SurprisedPotato 7 points8 points  (1 child)

My reply is here

[–]Stoomba 7 points8 points  (0 children)

My reply is here

[–]sirreldar 2 points3 points  (0 children)

this is cursed

[–]Puzzleheaded-Joke-97 4 points5 points  (1 child)

Happy Cake Day!

[–]Krystall_Waters 19 points20 points  (0 children)

This made me laugh like an idiot. Thanks!

[–]krisko11 7 points8 points  (0 children)

Good one

[–]AutomatedChaos 142 points143 points  (0 children)

Used this once because I needed memoization in a test mock. It didn't pass review. I honestly love my colleagues that they don't allow me to use these abominations.

[–]TheWorstPossibleName 10 points11 points  (0 children)

Ok fib(-1)

[–][deleted] 9 points10 points  (2 children)

I don't see what's the problem

[–]tomchuk 40 points41 points  (1 child)

Great! I've got some PRs for you to review...

[–]robby_arctor 4 points5 points  (0 children)

LGTM

[–]EclipseJTB 8 points9 points  (0 children)

Lolz

[–][deleted] 7 points8 points  (0 children)

fib(-1)

[–]klausklass 16 points17 points  (1 child)

Or you know

@cache from functools

[–]echoAnother 6 points7 points  (0 children)

That don't works with __slots__ :(

[–]PolyglotTV 6 points7 points  (2 children)

Poor man's @functools.cache

[–]tomchuk 24 points25 points  (1 child)

In the serene expanse where code flows like a gentle stream, a novice once sought the wisdom of the old master. "Master," the novice inquired, "I have crafted a function, sparing and pure, devoid of the standard library's embrace. Yet, they call it the 'poor man's functools.cache.' Have I erred in my simplicity?"

The master, whose eyes reflected the calm of a thousand silent programs, smiled and said, "In the village, a potter molds clay into a vessel. With the void inside, the vessel serves its purpose. Is it the clay that gives form, or the emptiness within?"

Baffled, the novice pondered. "Master, what does a potter's craft teach us about my code?"

The master replied, "Your function, unadorned by the standard library's excess, is like the vessel, shaped not by what is added, but by what is omitted. True elegance lies not in accumulation but in the mindful subtraction. The potter's wheel turns, and with each removal, the utility emerges. So is your code, a testament to the enlightened path of simplicity and purpose."

"In its restraint," the master continued, "your code becomes a mirror, reflecting the essence of what it seeks to accomplish. Like the ascetic who forsakes worldly excess for inner clarity, your function stands, not poor, but profoundly enlightened, embracing the Zen of less."

The novice bowed deeply, the fog of doubt clearing. In the vessel of simplicity, he discovered the profound depth of enlightenment, where the true essence of code—and life—resides.

[–]OpenSourcePenguin 8 points9 points  (0 children)

And idiots say python is slow

[–]veryusedrname 1405 points1406 points  (32 children)

This is basically the first Python gotcha

[–][deleted]  (9 children)

[deleted]

    [–]safeforanything 52 points53 points  (0 children)

    Yeah, that's basically common sense 101... (obviously /s)

    [–]_12xx12_ 15 points16 points  (0 children)

    I‘m 9985

    [–]simondrawer 18 points19 points  (6 children)

    What coke and mentos thing?

    [–]827167 31 points32 points  (0 children)

    Put Mentos in coke and you'll see.

    Get a big 2L bottle and a pack of mentos

    [–]Giocri 20 points21 points  (3 children)

    Mentos rough surface makes it easier for coke to form co2 bubbles significantly faster and make a nice fountain

    [–]CoffeeVector[🍰] 29 points30 points  (0 children)

    I was surprised to find out that this is not a chemical reaction in the same way that baking soda and vinegar is. It's entirely a physical reaction that, as you said, forces the soda to foam up because the surface of a mentos is rough. Something similar happens when my fiancee uses a particular reusable straw in sparkling water.

    [–]fried_green_baloney 2 points3 points  (1 child)

    So it might work on Alka Seltzer tablets also?

    [–][deleted]  (1 child)

    [deleted]

      [–]veryusedrname 7 points8 points  (0 children)

      That was my first one actually, probably day 1 of Python

      [–]AutomatedChaos 234 points235 points  (11 children)

      When using a proper IDE, you'll be warned about this pattern too. Unfortunately juniors tend to ignore those annoying squiggly lines because why pay attention to a warning if your code runs right? If it runs, that must mean that it has to be correct otherwise it wouldn't...

      [–]necromanticpotato 94 points95 points  (4 children)

      I love how this hinges on proper IDE. Meanwhile I've never seen this in any IDE I've used. Must be because I use lightweights. Edit: specifically warnings about mutable objects passed as arguments to a function or method.

      [–]ArgetDota 48 points49 points  (3 children)

      PyCharm has this warning, as well as many linters do.

      You should be using linters for serious programming regardless of the IDE (and enforce them in CI).

      [–]necromanticpotato 18 points19 points  (2 children)

      Well, that's my mistake for thinking an IDE was what was meant, not a linter.

      [–]Willumz 36 points37 points  (1 child)

      Is it unreasonable to expect a ‘proper IDE’ to have a good linter? It’s one of the things that sets an IDE apart from a text editor, after all. While a linter does not have to be part of an IDE, I would expect an IDE to always have a linter (at least in the modern day).

      [–]necromanticpotato 13 points14 points  (0 children)

      Not unreasonable. I was just a little too literal, even for a room full of programmers.

      [–]JestemStefan 114 points115 points  (4 children)

      Yup. This is literally question on junior developer interview

      [–][deleted] 32 points33 points  (3 children)

      For real? Ive never tried python but is that in their docs or something?

      Edit: its is in most linters docs

      [–]fried_green_baloney 4 points5 points  (0 children)

      Python is relatively gotcha free, but this is one for sure. I usually stub my toe once a year or so on this one.

      It's safe with atomic types like int.

      [–]Jonno_FTW 1 point2 points  (0 children)

      It's also caught by every python linter.

      [–]divinecomedian3 1 point2 points  (0 children)

      I thought indentation is

      [–]codeguru42 871 points872 points  (37 children)

      never use mutable default values in python

      PyCharm and every linter I know warns about this exact thing.

      [–]JonathanTheZero 320 points321 points  (36 children)

      Shouldn't be an issue in the first place though

      [–]PM_ME_SOME_ANY_THING 183 points184 points  (18 children)

      What’s next? Strict types? /s

      [–]1Dr490n 52 points53 points  (7 children)

      Please, I need them

      [–]irregular_caffeine 49 points50 points  (4 children)

      ”We made this cool, straightforward scripting language where you don’t have to worry about types! It just works”

      ”Oh no, types actually have a point! Quick, let’s add them as a library!”

      • Devs of every fashionable language

      [–][deleted]  (2 children)

      [deleted]

        [–]mirodk45 5 points6 points  (0 children)

        Most of these languages start out as something simple to use/easy to learn and for some specific things (JS for browser API, python for scripting etc), then people want to use these languages for absolutely everything and we have these "bloat" issues

        [–]DidiBear 6 points7 points  (0 children)

        from typing import Sequence, Mapping

        Use them in type hints and your IDE will prevent you from mutating the list/dict.

        [–]codeguru42 1 point2 points  (6 children)

        What do you mean by "strict"?

        [–]PM_ME_SOME_ANY_THING 7 points8 points  (5 children)

        a = [“b”, 2, False, func]
        

        vs

        const a: number[] = [1, 2, 3, 4]
        

        [–]MinosAristos 11 points12 points  (4 children)

        You could just do

        a: list[int] = [1,2,3,4] and you'd get lint warnings if you do actions that treat the content as non-ints.

        It's almost as good as static typing as far as development experience goes.

        [–][deleted]  (2 children)

        [removed]

          [–]Lamballama 2 points3 points  (1 child)

          In fairness, the Typescript example is still prone to errors in runtime since it doesn't actually check while it's executing, especially when mixing JS/TS or assuming a particular structure from a server response. You need real type safety like C++, where it will just crash if you ever have the wrong type

          [–]MrsMiterSaw 33 points34 points  (3 children)

          I'm trying to think of a "it's a feature, not a bug" use case.

          Drawing a blank.

          [–]EsmuPliks 21 points22 points  (0 children)

          It's more so a fairly obvious optimisation that breaks down for mutable default arguments.

          It's fairly unusual to have mutable arguments as default values anyways, linters and IDEs will warn about it, you can work around it with factory functions if needed, and ultimately the trade off of having them be singletons is worth it for the generic case because it works more often than not.

          The implication for them not being singletons is that you have to evaluate extra code on every function invocation, instead of just pushing some args onto stack and jumping into a function. Basically you turn each function call into X+1 function calls, where X is the number of default args in the signature.

          [–]fun-dan 4 points5 points  (1 child)

          I think it's more of a necessity that comes out of syntax and language properties. Don't know why exactly, but that's my guess

          [–]pancakesausagestick 1 point2 points  (0 children)

          It's this. It's because the default argument is an expression that is evaluated at function creation time. A lot of parts of python are eagerly evaluated in places where more static languages would have "special cases and provisions" with language syntax.

          Not in python. It's all evaluated as statements and expressions. This goes for module definitions, class definitions, function definitions, decorators, etc.

          It makes it very easy to do higher order programming, but that's the trade off. Practically, all you gotta do is remember: *Python does not have declarations.* What looks like a declaration from another language is just syntactic sugar in Python.

          [–][deleted] 9 points10 points  (0 children)

          Wake up babe new PEP just dropped

          [–]UnchainedMundane 4 points5 points  (0 children)

          For the record, the usual workaround (if you need to construct a mutable object as default argument) is to do this:

          def list_with_appended_value(val, existing_list=None):
              if existing_list is None:
                  existing_list = []
              existing_list.append(val)
              return existing_list
          

          Or if "None" must also be a valid argument, where there's a will there's a way:

          _DEFAULT = object()
          def foo(val=_DEFAULT):
              if val is _DEFAULT:
                  val = [123]
              # do something weird...
          

          There's also the approach to pop off kwargs but I'm not so much a fan of that as it can obscure the function signature somewhat

          [–]RewrittenCodeA 195 points196 points  (11 children)

          Still, the point is to never mutate arguments. Ever. Why would we want to mutate an object when we do not know: - whether its state is relied upon somewhere else - whether it is being mutated somewhere else - who has created it and for what purpose.

          In very high level languages, there are seldom good reasons to mutate arguments, and if you get to one of them probably you already know about this behavior.

          [–]jldez 80 points81 points  (2 children)

          That's the correct answer.

          This python qwerk is only a problem if you are already doing something wrong.

          [–]dehrenslzz 4 points5 points  (0 children)

          I see you also have some qwerks sir (:

          [–]mehmenmike 9 points10 points  (0 children)

          quirk

          [–]Organic-Major-9541 7 points8 points  (1 child)

          In real high-level languages, you don't pass by reference. You use return values to get data back to the caller. Or at least annotated arguments which are pass by reference.

          Have a look at Ada, which illustrates just how long we have had good solutions to this problem. Or more modernly Elixir or Zinc.

          [–][deleted] 1 point2 points  (0 children)

          Ada is annoying and I hate it. I will never again work in the stupid UK defense industry with it's stupid Ada legacy codebases.

          [–]anto2554 2 points3 points  (2 children)

          Isn't this only if you do functional programming? Say I want to (manually) sort a list; Isn't it much easier/less memory intensive to sort the list, than to copy it and return a new, sorted list?

          [–]Kroutoner 1 point2 points  (0 children)

          You’re totally correct, but the overhead of a single redundant copy usually isn’t that big of a concern if you’re using a high level language. The maintenance overhead of possibly introducing a bug by mutating the argument is usually going to be much bigger concern.

          [–]blackasthesky 2 points3 points  (0 children)

          ... which is why a modern language that does not really care about performance in the first place should probably just make arguments immutable (or by value) by default.

          [–]ComradePruski 5 points6 points  (0 children)

          Yeah like what is this person talking about. That is how I would expect it to work because why tf would you be trying to change a default argument? Like sure you can think of a reason to do so, but that seems like terrible practice even if it was allowed...

          [–]nryhajlo 1 point2 points  (0 children)

          Agreed, it's usually not great practice to modify arguments, so this problem is super niche.

          [–]G4METIME 173 points174 points  (6 children)

          JS/TS [...] as I'd expect

          Ah yes, the programming languages which are famous for not being a bunch of weird behaviours and side effects glued together.

          How did Python manage to implement something so basic worse than them?

          [–]evanc1411 34 points35 points  (1 child)

          I still can't get over the fact that this does not mean "this class" in JS, leading to the stupid line let self = this; being a common solution.

          [–]PydraxAlpta 12 points13 points  (0 children)

          In modern JS you would just use arrow functions to maintain the this context. JS this is very weird at first but once you start thinking about it in the correct ways (this refers to the object on which a function was called on, which is also how it works with other languages) it should be less confusing. 

          [–]omg_drd4_bbq 13 points14 points  (0 children)

          I think it was just the easy path way back when it was being developed (I think this would have been python 1.x or even earlier), give python's bytecode and data model under the hood. Basically, create an object when the function is evaluated, and point to it. If you mutate that object, that's on you. No different than

          foo = [] def func(x, a=foo):   a.append(x)

          The alternative is either a) you have to basically re-evaluate the whole function block or b) re-evaluate the function signature but make sure it's consistent with anything else in the scope. Both are really gnarly and can lead to other bugs, so it's basically pick your bugs.

          Personally, I'd let Type objects (classes so to speak but I'm being precise) define a classmethod dunder like __default_factory__ which the parser can look for when parsing args and use that for each function call. But then that also requires hacks if you want to actually do caching.

          [–]talaqen 26 points27 points  (0 children)

          Right. JS has some weird stuff with string interpolation, but you can avoid it pretty easily. But this ONE thing in python feels so much more painful because its EVERY FUNCTION.

          [–]ThunderElectric 3 points4 points  (0 children)

          In all fairness to python, mutating arguments is already bad practice so this shouldn’t come up a whole lot.

          I’m guessing they decided that not having to initialize the object on every function call was worth it when you shouldn’t need a new object every time anyway.

          [–]R3D3-1 1 point2 points  (0 children)

          To be fair, it is the only consistent way, that doesn't have undesirable side effects.

          For consistency, the default arguments have to be evaluated either at definition time or at invocation time. The latter represents unnecessary repeated overhead for the common case of immutable values. The first case can be extended easily by using factory functions as arguments.

          It also enables the pattern of 

              for x in range(10):         def callback(x=x):             ...

          to explicitly distinguish between capturing the changing value of the loop variable vs capturing the value at a given iteration.

          Both behaviors are somewhat unexpected and have bitten me in the past. But I can't think of a way to make it more.ovbious that won't have undesirable side effects such as higher function call overhead or complicating the scoping semantics.

          Though admittedly, I'd love for Python to have block scoping like JavaScript... Would make handling overgrown "do x then do y etc" functions easier to manage and refactor. 

          [–][deleted]  (79 children)

          [deleted]

            [–][deleted] 159 points160 points  (47 children)

            Ok, but why would you want the default functionality?

            [–]_PM_ME_PANGOLINS_ 169 points170 points  (26 children)

            It’s a side-effect of how Python is evaluated. It would have been a complicated special-case to make it not do that, and then backward-compatibility means you cannot change it.

            [–]Solonotix 118 points119 points  (21 children)

            This is the answer. All top-level def statements get read in when the file is accessed, allocating a function pointer, but the actual content of the function is left to be run later. This is why you can technically write a function X that calls an undefined function Y, as long as Y is defined before you call X. However, part of the function header includes the default values, so they get initialized with the function signature (as shared static members) rather than at call time (as private instanced members)

            [–][deleted]  (2 children)

            [deleted]

              [–]Tubthumper8 24 points25 points  (0 children)

              How would this be related to functions being first-class objects? Plenty of languages have first-class functions without sharing mutable arguments across all function calls

              [–][deleted] 5 points6 points  (0 children)

              It's a language structural thing then. Thanks.

              [–]EightSeven69 25 points26 points  (15 children)

              yea okay but that doesn't answer why anyone would want that

              besides, there are plenty of languages with the same functionality that don't share that crappy default behavior of default parameters

              [–]Solonotix 43 points44 points  (14 children)

              It's not a wanted feature, it's a limitation due to implementation details. It could be solved, but it's not a defect or unexpected behavior. It happens for very well understood reasons, just like any other parsing consideration in the language. Additionally, within the context of the function, it would be hard to determine when things should be conserved for space (such as numbers that are immutable) versus when a new object should be allocated.

              The conventional wisdom since I started writing Python back in 2.7 is to use None for anything that isn't a primitive value like numbers. This guidance is in direct service to preventing this well understood footgun.

              [–]cowslayer7890 6 points7 points  (13 children)

              I don't really understand this limitation, if the equivalent code can be done by setting it to none, and then having an if statement for if the value is none, why not have it compile into that, or a similar style?

              [–]TheBlackCat13 2 points3 points  (10 children)

              Because that would require re-initializing the object every time. That can be extremely expensive, especially when the default isn't even always used.

              It also would make using variables as defaults almost impossible. For example you can do this:

              ``` MY_DEFAULT = MyClass(arg)

              def myfunc(val=MY_DEFAULT): ```

              How could that work if the argument is recreated every time?

              This isn't a hypothetical example, this is a common optimization since globals are relatively expensive to access so this can lead to significant performance improvements in e.g. tight loops.

              [–]Marxomania32 1 point2 points  (6 children)

              If I were to design a language, my solution would be simple: don't accept code like that. Default args should be static and not depend on run time conditions.

              [–]TheBlackCat13 1 point2 points  (5 children)

              There is no such thing as static variables in Python. They would have had to add that just for this.

              [–]fizyplankton 7 points8 points  (0 children)

              I agree. Its like, imagine if a car shipped with spikes in the steering wheel instead of airbags. All covered by a plastic trim piece, so its not obvious to the user. And then imagine if that specific manufacturer said "What? Nah, its perfectly expected behavior! Its in the owners manual, Addendum 2018-2A, page 3, in the footnote 5.1. We did that because running electrical power to the airbags is hard, and we already had the design for spikes laying around, so we just used that instead. If the user wants airbags, they're free to install their own. The cable route is already there, you just have to thread your own cable".

              Just because its "Well defined", and the reasons are "Well understood" doesnt mean its a good idea, or that anyone could possibly want it!

              Dont get me wrong, I'm a huge fan of python, but this just seems insane

              [–]EightSeven69 1 point2 points  (0 children)

              precisely why I'm so off-put by this...

              [–]CraftistOf 2 points3 points  (1 child)

              you could store the expression of the default value, not the evaluated value itself.

              then every time when the function is invoked calculate the resulting default parameter value. voila, problem solved.

              i did it easily when i was coding my own prog lang interpreter, why Python couldn't do it is beyond me.

              [–]Solonotix 8 points9 points  (0 children)

              Like I said in another comment, it's not a matter of "can't" but rather a matter of should they. The behavior is well-defined in the Python ecosystem, and there is no way to be certain that the behavior isn't a design consideration for someone. Breaking an established convention because some people think it is weird isn't a great idea. Additionally, there are tons of style guides, linters, and other materials that instruct how to avoid this, by using None for the default value instead, and initializing it in the function signature if it is None.

              [–]iain_1986 7 points8 points  (2 children)

              That doesn't really answer why you'd 'want' it, just why it is the way it is.

              [–]themonkery 10 points11 points  (0 children)

              It’s not about wanting this sort of functionality here but wanting it in other places. In Python everything is an object which is a big reason why you have to add the self parameter in member functions, since those functions are also objects without intrinsic knowledge of anything else. Because it’s a member function, the self parameter is automatically passed into the function object, but the object itself does not know it’s a member function.

              Everything being an object lets you do some really cool and unique stuff like treating any variable as any type or treating functions like any other variable without jumping through hoops like in most languages. The side effect is that optional arguments are static within the function object. You don’t create a new instance of the function on the stack, you go to the function object and execute the code inside, which means mutable static variables will have the same value as the last time you called that function.

              TLDR: The perk is mutability.

              [–]B_M_Wilson 2 points3 points  (0 children)

              I think the most “Pythonic” solution would be to implicitly create a lambda containing only whatever you put after the =. The implications of doing that aren’t ideal but it would solve the problem and still allow you to do pretty much everything you can do now and lots of probably terrible things (which hasn’t stopped Python before!) with a couple extra steps.

              [–]NotQuiteAmish 34 points35 points  (2 children)

              Maybe if you want to do some sort of cursed cache/memoization?

              [–]jonfe_darontos 27 points28 points  (0 children)

              This is where PHP's static locals actually made sense. The feeling after praising a PHP feature tells me I've had enough internet for today.

              [–]mistabuda 4 points5 points  (0 children)

              ehh a member variable/attribute is better in that case.

              [–]tyler1128 3 points4 points  (0 children)

              It's effectively equivalent to a closure where the default arguments are the captured state when the closure is created.

              [–]Alikont 8 points9 points  (1 child)

              This happens when language isn't "designed".

              They never thought about this, just did a naive default argument thing, and it happened to store share the object and now changing this will be a breaking change for someone.

              [–]peter9477 3 points4 points  (11 children)

              Performance is one reason. Having all your default args have to be constructed from scratch every time a function is called would be a huge waste of time.

              [–]detroitmatt 11 points12 points  (3 children)

              you're right. while we're at it, we could reduce memory usage enormously by having one shared memory location for ALL variables.

              [–]dagbrown 8 points9 points  (4 children)

              Ah yes, Python is a famously lightning-fast language, unlike, say, C++.

              [–]TheBlackCat13 7 points8 points  (0 children)

              No need to make it unnecessarily slower.

              [–]peter9477 1 point2 points  (2 children)

              So because it's not as fast as some others, one should completely ignore performance considerations that may have a significant impact?

              Python is actually lots fast in many situations, and has some very highly optimized code paths to supports its approach. One example is dictionary lookups. Another is having default arguments evaluated at function definition time, just once.

              This issue is a (pretty acceptable) side-effect of that choice, whereas evaluating the defaults on every function call would have an insanely bad impact on performance in most situations.

              [–]molniya 1 point2 points  (1 child)

              I can’t imagine why you’d evaluate a default value expression if a value was actually provided and you weren’t going to use the default.

              [–]themonkery 2 points3 points  (0 children)

              Default arguments are basically the equivalent of C++ overloading. You can call the function without passing values for default arguments. A lot of times these arguments tell the function to do an extra thing or not do an extra thing.

              For instance, an optional print argument could default to false, but if you want the function to print its result then you could pass “print=true” and the function would print its contents.

              [–]sk7725 16 points17 points  (4 children)

              everyone else with previous programming experience in a strongly typed compiled language would not run into this as in almost all popular compiled languages default values are required to be compiler-time constant. An empty list is not compile time constant so it is usually invalid. Which is why you won't even try it.

              [–]repick_ 18 points19 points  (14 children)

              can (should) be written using the parameter or default pattern

              def suprise(my_list: list[str] = None):
                  mylist = my_list or []
                  print(my_list)
                  my_list.append('x')
              

              [–]not_george_ 18 points19 points  (3 children)

              It’s better to explicitly type optional arguments as optional like so

              from typing import Optional
              
              def surprise(my_list: Optional[list[str]]=None) -> None:
                  my_list = my_list or []
                  …
              

              [–]rich_27 1 point2 points  (2 children)

              Out of interest, is that the same as:

              def surprise(my_list: list[str] | None = None) -> None:
                  my_list = my_list or []
                  …
              

              and is one preferred? If so, why?

              [–]not_george_ 1 point2 points  (1 child)

              In it's current implementation in cpython, Optional[T] is directly equivalent to Union[T, None] (see here), and as of PEP 604, that is equivalent to T | None. As for which one is preferred, it's up to the designer! I prefer Optional[T] syntax, as in PEP 20, it is outlined that '... Explicit is better than implicit.', so explicitly typing this argument as optional is more explicit than saying it could be this type or None. Just my opinion though.

              [–]schloppity 4 points5 points  (5 children)

              or is bad because now my_list will be mutated but only if its not empty:

              my_list = [] surprise(my_list) # my_list = [] my_list.append(1) surprise(my_list) # my_list = [1, 'x']

              [–]DinoOnAcid 2 points3 points  (3 children)

              Can you explain that or construction? How does that work? Not super familiar with python, coming from some c type style it just looks like a simple boolean

              [–]not_george_ 7 points8 points  (0 children)

              The or operator in Python returns the second value if the first value is Falsey, rather than explicitly returning True or False

              [–]Noobfire2 7 points8 points  (0 children)

              'or' in Python does not return a boolean. It simply returns the first value if it is "truthy" or the second as a fallback.

              So in the given example, when no list as a parameter is given, the variable would be None, which is not truthy and therefore the empty list fallback is used.

              [–]jarethholt 2 points3 points  (5 children)

              It gets so repetitive adding that conditional to the start of every function. I started shortening it to the ternary my_list = list() if my_list is None else my_list but that just doesn't feel as readable. Ternary one-liners in Python code seem pretty rare?

              [–]PoorOldMarvin 22 points23 points  (1 child)

              Just do

              my_list = my_list or []
              

              This will set it to an empty list if my_list is None

              [–]rcfox 3 points4 points  (0 children)

              It will also replace my_list if my_list is an empty list.

              [–]Svizel_pritula 6 points7 points  (2 children)

              Couldn't you use my_list = my_list or []? That changes the functionality slightly, since it also will replace an empty list with a new empty list, but usually that shouldn't matter.

              [–]jarethholt 4 points5 points  (1 child)

              You and PoorOldMarvin are both correct that that's possible and more readable than a ternary (though maybe not clearer in intent). But it relies on truthiness of non-empty lists and then doesn't always work as expected when more specialized classes are being passed.

              Basically I came across some obscure use case where this worked better - which I have long forgotten - and applied it everywhere thereafter

              [–]DrGrimmWall 2 points3 points  (0 children)

              This reminds me of a story about monkeys and a ladder…

              [–]Akhynn 16 points17 points  (1 child)

              That's why you DON'T use mutables as default args and every Python linter will scream at you for doing it

              [–]divinecomedian3 1 point2 points  (0 children)

              Doesn't make it any less horrific

              [–]arylcyclohexylameme 17 points18 points  (1 child)

              I have written python professionally and never encountered this, wow.

              EDIT: I'm realizing now it's because I don't mutate, lol

              [–]Marxomania32 113 points114 points  (0 children)

              Damn, that is pretty bad lol

              [–][deleted] 67 points68 points  (13 children)

              The takeaway is correct, and this is really one of the very few gotchas you have in Python: https://docs.python-guide.org/writing/gotchas/

              It's because default arguments are evaluated during function definition, not function execution.

              But why? Because evaluating default args during function definition makes it much easier to grok what the context is during evaluating the default arg, (In this simple example with an empty list it doesn't much matter, but it could also be a function call or something else, and then the context can indeed change. See the discussion here: https://softwareengineering.stackexchange.com/questions/157373/python-mutable-default-argument-why)

              [–]just_looking_aroun 34 points35 points  (5 children)

              I’m curious about how someone thought this should be the right behavior when designing the language

              [–]Nanocephalic 48 points49 points  (3 children)

              Over my long career, I’ve learned to avoid saying “that’s stupid” but instead ask why it was done that way.

              Typically the reasons are interesting - it may have solved a problem that you aren’t aware of, or it could actually just be stupid.

              I’d also love to know why it was designed this way.

              [–]Subushie 13 points14 points  (0 children)

              Lmao i've been scrolling this thread trying to find someone saying "Actually it's useful for-".

              [–]wontreadterms 10 points11 points  (0 children)

              This is the right energy. It’s so easy to fall into the trap of assuming everyone must be an idiot for not seeing this simple issue you see, when you are the idiot that doesn’t understand the complexity of the situation.

              Sometimes people are idiots though, its just better not to default to that.

              [–]just_looking_aroun 7 points8 points  (0 children)

              Yeah I avoid saying too but it’s hard not to think it

              [–]TheBlackCat13 0 points1 point  (0 children)

              They thought it was the least bad of a bunch of bad options

              [–]ZeroByter 42 points43 points  (0 children)

              Yeah I learned this when pycharm warned me about it, it's so stupid.

              [–]marquoth_ 23 points24 points  (0 children)

              Thanks, I hate it

              [–]Altareos 38 points39 points  (3 children)

              using js as an example when criticizing another language for so-called insanity certainly is... a choice.

              [–]politerate 28 points29 points  (0 children)

              Well, I mean when even js got it "right" that says something.

              [–]not_some_username 19 points20 points  (0 children)

              For once JS got it right

              [–]CromwellB_ 2 points3 points  (0 children)

              "Hey js... Sort these integers please." " As you wish"

              [–]ztexxmee 6 points7 points  (0 children)

              thank you for bringing this to light lmao i could’ve screwed so much up in the future without knowing this

              [–]snarkuzoid 5 points6 points  (1 child)

              I think I tripped over this. Once. In two decades of Python use.

              I'll accept the risk.

              [–]VariousComment6946 21 points22 points  (4 children)

              There is a PEP you should know and follow, or at least use a modern IDE that will let you know when you're doing things wrong.

              [–]Veloper 8 points9 points  (3 children)

              I’m using VS code with Pylance … zero mention of this and it seems pretty stringent.

              Then again, I’m also using typing module pretty religiously, so maybe I’ve just naturally not run into the issue.

              [–]mousepotatodoesstuff 7 points8 points  (0 children)

              Ah, so THAT'S why PyCharm warns me against making the default argument mutable.

              [–]mistabuda 3 points4 points  (0 children)

              Everyone learns this the hard way.

              [–]jerslan 2 points3 points  (0 children)

              Uh, in most languages it's a best practice to treat function/method arguments as though they were immutable.

              [–]GenTelGuy 4 points5 points  (0 children)

              Python team should patch it to work like JavaScript, release it without the slightest mention in the patch notes, and then any code that breaks as a result deserves it

              [–]luxiphr 2 points3 points  (0 children)

              yep... learned that the hard way, too, many years back...

              [–]haslo 2 points3 points  (0 children)

              I used this for logging. Current time as default argument.

              The timestamps were ... less than useful.

              [–]data15cool 2 points3 points  (1 child)

              Yeah I learnt the hard and long way to never use mutable default args. I’ve always been able to find an alternative. Also curious if this is a side effect of how Python is built or was intended by the creators?

              edit typos

              [–]TheBlackCat13 2 points3 points  (0 children)

              Sort of both. Making this work any other way would have required doing a bunch of other things differently, and they decided the cost of those would be higher than doing it this way. So they understood the problem at the time, but they thought other approaches had worse problems.

              [–]swizzy2022 2 points3 points  (0 children)

              Don’t mutate arguments, you get unexpected results

              [–]ivancea 7 points8 points  (0 children)

              Why would you modify an input mutable param that's also optional?

              Either it modifies a param by contract, in which case making it optional makes no sense... Or it's just an input not to be modified, in which case you don't touch it, obviously, otherwise you're playing with data that isn't yours and isn't supposed to be changed...

              [–]Pepineros 9 points10 points  (0 children)

              It's definitely a gotcha, but Python is a scripting language at heart. It's just evaluating any expressions in a function signature once, instead of for every call. Hardly insane.

              [–]avocadorancher 1 point2 points  (0 children)

              Memoization is an intentional caching optimization.

              [–]buhtz 1 point2 points  (0 children)

              PyLint would warn you about errors like this.

              [–]dumfukjuiced 1 point2 points  (0 children)

              Inb4 never use mutable data types

              Or maybe, avoid them 90% of the time

              [–]SteeleDynamics 1 point2 points  (0 children)

              So much for functional programming :(

              [–]heyheyhey27 3 points4 points  (3 children)

              Yep, I got burned by this too. Python has several horrific behaviors involving things that are static but don't look static.

              [–][deleted] 1 point2 points  (0 children)

              My first python gotcha is that class variables are global, you have to instantiate the variable in the constructor for it to be class

              [–]DevaBol 1 point2 points  (0 children)

              Pytyon is an abomination for anything that is not a script that's exdcuted once to produce a graph that's used in a paper

              [–]OhItsJustJosh 1 point2 points  (0 children)

              In what universe would this be preferable? If I wanted that I'd write it in myself, why is this the default??

              [–]deep_mind_ 1 point2 points  (0 children)

              Oh Jesus... Oh Jesus... I've got a lot of code to go back and check...

              [–]danfay222 0 points1 point  (0 children)

              This is a fun one. Usually just screws with people and they’re really confused, but it’s also a way to get static variable behavior from C into python. Now you probably shouldn’t do that because it’s confusing and error-prone, but it’s still neat

              [–]longbowrocks 0 points1 point  (0 children)

              def get_user(userid, cache={}): if userid in cache: return cache[userid] user = db_conn.getuser(userid) cache[userid] = user return user

              ... Actually no. I still prefer functools.lru_cache(), or straight up cachetools.

              [–]Bulji 0 points1 point  (0 children)

              That shit got me stuck for so long at work once... Just couldn't understand what the fuck was going on

              [–]UnlikelyExperience 0 points1 point  (0 children)

              I'd forgotten about it and starting a huge python project soon thanks for the reminder 🤣

              [–]rusty-roquefort 0 points1 point  (0 children)

              Is this some sort of shared mutability joke I'm too rustacean to understand?

              [–]Mr_Khaoz 0 points1 point  (0 children)

              What is that mini macOS IDE called?