← Back to context

Comment by crabbone

12 days ago

I'll give you one better:

    >>> strings = tuple((lambda: f'{i}') for i in range(10))
    >>> strings[1]()
    '9'

The f-strings are supposed to be evaluated at... what time exactly?

And some more stuff:

    >>> class Elephant:
    ...   def __init__(self, i):
    ...     self.i = i
    ...   def __call__(self):
    ...     return self.i
    ... 
    >>> elephants = tuple(Elephant(i) for i in range(10))
    >>> elephants[0]()
    0
    >>> elephants[5]()
    5

The answer is simple. f strings are evaluated at run time. The behaviour is correct. Up until you call the lambda, they are just a function definition.

What's so interesting about the Elephant case? You store the value of i at run time, so it can be accessed later. lambda doesn't have that capability, as it's inheriting i from the block.

  • > The answer is simple. f strings are evaluated at run time.

    You didn't understand the question. Everything in Python is evaluated at runtime. Bytecode is just a form of caching.

    > The behaviour is correct.

    Python doesn't have a standard. Nobody knows what behavior is correct. That's the only behavior Python has, so, again, calling it correct is just not saying anything.

    > What's so interesting about the Elephant case?

    This class, in principle, shouldn't be different from lambda from the previous example, yet it acts differently.

    You don't need to lecture me on why things behave the way they are. Since I found these examples, you can trust me, I know why they behave like this. The idea is to drive your attention to the inconsistencies that a "naive" reader would discover.

    • > Everything in Python is evaluated at runtime

      That is not correct. Python generally has two "runtimes": one is on import, when all of the module level code is evaluated; and the runtime, when the bodies of various functions and methods are evaluated. The two can be combined, e.g. by importing a module from within a function, but generally they are separate.

      > Bytecode is just a form of caching

      Not quite. It is perfectly possible to deploy just the bytecode and disable recompiling on import.

      > Python doesn't have a standard

      Of course it does -- CPython is the reference implementation, so its behaviour is "correct".

      2 replies →

    • > You didn't understand the question. Everything in Python is evaluated at runtime. Bytecode is just a form of caching.

      You don't understand what runtime in python is. Compilation into bytecode is an implementation detail and not part of runtime. Even when you start a python scripts from a bytecode state, there is a runtime. f-strings are evaluated at runtime. When you declare them within a function, yes they will be parsed, but they will not be run.

      > This class, in principle, shouldn't be different from lambda from the previous example, yet it acts differently.

      In your case, you put them in lambdas, which is equivalent to putting them into a function context. The f-string will only be evaluated when lambda/function is called. In your Elephant case the runtime happens when i=n, whereas in lambda case, the runtime is when i=9 (because there is no local copy of the variable/context). Plain and simple. If you don't understand such a simple distinction, I recommend you to start reading the bytecode as it's much clearer at that level.

      > You don't need to lecture me on why things behave the way they are. Since I found these examples, you can trust me, I know why they behave like this. The idea is to drive your attention to the inconsistencies that a "naive" reader would discover.

      It's not an inconsistency, if you understand the principle.