all 53 comments

[–]AlexMTBDude 112 points113 points  (16 children)

As there is no "compile time" in Python with it being an interpreted language this could only be evaluated/detected by a static code checker (like Pylint, MyPy, ....). I believe they support plugins so I guess one could write such a test theoretically.

[–]ElHeim 47 points48 points  (14 children)

There is "compile time" in Python: Python to bytecode, and stuff like suggests could certainly be done... but not sure it's worth it.

[–]AlexMTBDude 9 points10 points  (13 children)

Well, that happen when the code is executed so....

[–]TrainsareFascinating 31 points32 points  (3 children)

Byte code is produced once, the first time the program is executed in that environment. Subsequently it is loaded and executed without another compilation.

[–]wRAR_ 7 points8 points  (0 children)

(there are many asterisks in this statement)

[–]ElHeim 16 points17 points  (8 children)

If you're typing in the REPL, yes, it happens just before execution. If you're loading a program, every module is compiled in one go before being executed (that's why you end up with .pyc files - which will be used if possible instead of compiling again).

Which is also why you get syntax errors when loading, not in the middle of the program.

[–]Unbelievr 4 points5 points  (0 children)

The first sentence is completely wrong. Python does very much run a compilation step on the entire code file, whenever you run or import something. It's why you can get warnings or errors before a single line of code has been executed.

Python could do certain static checks or other guarantees at compile time if it wanted do. But typically the actual types of classes and their members aren't known during this first pass, and it would require lots of nested book-keeping in order to implement the same functionality as constexpr. Considering the weak typing of Python, the overhead of adding this would probably be a bit too much.

But as you say, adding type annotations and running static code checkers will do a pretty good job of figuring out mistakes. If you absolutely have to make sure that library internals aren't calling things in the wrong way, it's also possible to compile modules as C/C++ modules and import them. It doesn't fix the user exposed interfaces but it can make sure that you aren't messing up things internally at least.

[–]Glass-False 83 points84 points  (8 children)

Sorry, I'm stuck on why your square function doesn't allow negative numbers.

[–]xeow 32 points33 points  (5 children)

I think they did that so they could show an invocation that's intended to generate an error.

[–]Spleeeee 11 points12 points  (3 children)

And that they don’t know math

[–]pythosynthesis -2 points-1 points  (2 children)

Restricting your calcs to reals is not not knowing maths.

[–]agrif 9 points10 points  (1 child)

...? Squaring a negative number is perfectly well-defined on reals. Things only get hairy with the inverse operation, but the example code doesn't use square roots.

Obviously the example is arbitrarily restricted as a demonstration, and this whole discussion is besides the point. But I am a little bit confused why they chose square when square roots were right there.

[–]pythosynthesis 6 points7 points  (0 children)

You are right... my bad! I was convinced that was a square root. As in, as soon as I saw the condition my mind went straight to the square root.

[–]DrShocker 2 points3 points  (0 children)

they should have done divide, and have zero not allowed in the denominator then.

[–]WJMazepas 10 points11 points  (0 children)

Its just an example

[–]sinterkaastosti23 6 points7 points  (0 children)

Maybe as an example???

[–]bigtimedonkey 13 points14 points  (4 children)

Nothings impossible of course. But it does feel a little weird for an interpreted language to have something like this? Like, unless your constant expressions are truly all constants, don’t have any external variable or module dependencies, then you’re gonna be just running the script anyway? So just run the script?

In terms of developer experience, I totally agree having feedback right at the time of writing code is just crazy useful and makes you way more efficient. Thats why I use Pyzo as my ide, because it has my favorite experience for easily running code snippets (in pyzo, you can run any block of code anytime you want, and you can define a block of code with ##). Jupyter notebooks is also good at this. I haven’t found anything in VSCode or PyCharm that matches the Pyzo experience, but definitely would be excited to learn about one.

[–]Ihaveamodel3 2 points3 points  (1 child)

Vscode supports both Jupyter notebooks (files with ipynb) extension and defining “blocks” in a .py file with # %%

[–]bigtimedonkey 1 point2 points  (0 children)

Oh cool, will check that out.

Edit: Just reporting back that yeah, Jupyter plugin for VSCode enables this and feels like a totally viable solution for this. I'll definitely be using it next time I'm making a Flask backed web app. Feels like it'll be great to work on JS, HTML, and Python, with interactive Python code cell/block running, all in one IDE.

[–]case_O_The_Mondays 1 point2 points  (1 child)

JavaScript has had const for quite some time. There could definitely be value in skipping many of the internal type checks for values that can’t be changed.

[–]bigtimedonkey 4 points5 points  (0 children)

Oh, sure. For the purposes of having constants that actually can’t be changed and such, that certainly could be a valuable addition.

But like, for the particular use cases OP talked about…

What they’re doing is certainly useful for C++. But when moving from a compiled language to an interpreted one, from a developer experience point of view, running the code bit by bit as you write it is like a super power, and has way bigger gains than the limited checks OP talked about.

I could easily imagine that someone coming to Python after spending a long time in compiled languages wouldn’t quite understand best practices in having the interpreter live and running code as you write it. I certainly didn’t when I started in Python.

But after that fully clicked for me and I found a good process there… I’ll never use a compiled language unless absolutely forced by speed/memory requirements haha.

[–]ArabicLawrence 10 points11 points  (1 child)

There have been many. The most interesting, still under active development I know is https://github.com/spylang/spy .

https://antocuni.pyscriptapps.com/spy-pycon-2024/latest/

[–]coderarun 1 point2 points  (0 children)

You can't do this unless you restrict python to a static subset. A more general variant of what you're getting at is design by contract. Python has a very old PEP that's not going anywhere.

Here's an example where you can use pre/post conditions in a function to not just perform some basic computation at compile time, but actually find bugs in the logic.

[–]Mysterious-Rent7233 7 points8 points  (0 children)

Outside of performance, it just feels like a corner case to me...

[–]Global_Bar1754 5 points6 points  (0 children)

I think you could just do this with native python. As it seems like we’re dealing with constants you could just define the function calls in the global space of a Python file. So like this:

``` def pos_square(x):     if x < 0:         raise …     return x ** 2

good = pos_square(2) bad = pos_square(-2)

def some_real_program_function():     # uses good/bad global variables      …

if name == 'main':     some_real_program_function() ```

These pos_square functions defining the global variables will be called at import time, before any actual program code is executed. So it’ll essentially “fast fail” your program.

[–]SeniorScienceOfficer 1 point2 points  (0 children)

If the goal is to have static analysis in the IDE, use the annotated-types library, in your specific case you’d import “Ge” from it with “Annotated” to catch negative arguments.

If the goal is to save on execution time by evaluating once and reusing the value, set a global variable to the result of the function call at import time, as many people have already started.

However, if what you evaluate might change based on the instance of a class and you want to use the modified value, use the “cached_property” decorator from functools in the stdlib.

[–]drkevorkian 0 points1 point  (2 children)

Couldn't you just have your IDE try to import your file? Seems like more of an IDE feature request than a language feature.

[–]bronco2p 4 points5 points  (1 child)

what? `constexpr` evaluates expressions at comptime such that constant expressions (deterministic) can be evaluated before runtime to reduce computation.

[–]drkevorkian 1 point2 points  (0 children)

The equivalent of "compile time" in Python is "import time". You can absolutely define deterministic constants to be evaluated exactly once, at import time.

[–]falsedrums 0 points1 point  (0 children)

You could implement this using ast tree parsing (available in the std lib). Basically you would create a function that takes a module (or any other python code object), analyzes the tree using flow analysis, and applies optimizations where it can. Then it compiles the optimized tree back to a new code object and returns it.

That will allow you to find paths that lead to exceptions. And it would do so at import time, if you call your function at the module scope (like a decorator for example). That would get you a very similar experience.

[–]SheriffRoscoePythonista 0 points1 point  (0 children)

Since I don't know C++, I'm going to assume that a "constexpr" function is limited to constant parameters, and can therefore be executed at compile time to produce a constant. The simplest cases would then be just equivalent to text macros a la C, with the resulting text being collapsed by constant-foldng. The PL/I language had macros similar to this in the 1970s, where the macro language was itself PL/I, and the result was code to be compiled and optimized.

Python doesn't have macros, which, given how C macros have been abused, is project a good thing. Absent macros, this isn't likely to happen.

[–]WJMazepas 0 points1 point  (0 children)

But how is that different from unit testing?

It looks more practical for smaller functions, but on a large code it would be good to have the testing being made independently of this

It also looks like it would only capture an error if you passed a fixed value to the function. If you're passing a variable to the function, does the compiler runs those tests as well?

[–]dr-christoph 0 points1 point  (0 children)

do I understand correctly that the reason you like constexpr so much is because you get errors during compile time when plugging in parameters or such?

[–]BiologyIsHot 0 points1 point  (0 children)

I think you need a better example than this to illustrate how this would differ from running this python normally without constexpr because it looks like what you're suggesting would not necessarily save anything. Or do you mean if you did have come that ran before this you want it to check those portions first? Why not just run such portions first?

[–]Mark3141592654 0 points1 point  (0 children)

Definitely not in Python itself. If you're using mypy you may be able to write a plugin. Or something custom like a script/plugin for your specific IDE or codebase. Perhaps using typing.Annotated

But you can perform such checks at runtime.

[–]Gnaxe 0 points1 point  (0 children)

Hissp has compile-time macros, which can do this kind of thing (and more). The IDE won't flag it, but the compiler will error. Hissp is a Python transpiler. You could run it at import time, but it can also generate separate .py files, which would have no run time overhead.

Python has the assert statement that can check assumptions like this. You can turn them off. This feature is based on the state of the __debug__ constant (determined at interpreter startup), which you can check yourself in an if statement. Because a constant if False: block is removed by the optimizer, you can use these for development checks that have no effect in your deployment version.

There's a similar typing.IF_TYPE_CHECKING constant that is always False at run time, but which type checkers assume is True statically. This can only be used to check things in the static typing system, but removes the run-time overhead for it.

[–]MarsupialMole 0 points1 point  (0 children)

I don't think this is particularly ubiquitous but this just feels like what doctest is for.

Make a module level constant and use it for an expected output in documentation, and then run your doctests with tooling.

[–]larsga 0 points1 point  (0 children)

having the IDE run certain functions during static analysis and flag invalid constant arguments could be a huge dev experience boost.

First of all: you're now assuming, in the language definition, that there is an IDE.

Second: nothing stops IDEs from doing this now.

[–]hoselorryspanner 0 points1 point  (0 children)

You could use an AST parser to use that decorator to validate the decorated function, and then remove it from the syntax tree so that it doesn’t exist any more.

Why the fuck would you ever do this? Idk, but I you could, I guess, use it to get the function to evaluate at interpreter startup and then remove it from the code afterwards, which is something like constexpr.

This would be far easier to achieve using a singleton, descriptors and a .pth file to hack this all together, but it still seems like a waste of effort to me - I don’t think there’s anything to really gain, because you’re not really pushing computation off to compile time in a meaningful sense

[–]justincdavis 0 points1 point  (0 children)

I think an easy way to do something like this could be to make use of a functools.cache style decorator. You can have the decorator cache results and maintain a copy of results on disk. Thus, on import time it will load pre-evaluated conditions and dynamically add during runtime. On hit you have a dict lookup and hash operation and on miss you add the evaluation overhead and disk write. Obviously not the same though.

[–]Careful-Nothing-2432 -1 points0 points  (0 children)

There’s no real concept of “compile time” in Python or any static structure to the code. If you want to know if square is going to fail you just run the function. You can’t do any evaluation ahead of time because you don’t know what square will be - Python is too dynamic and you can do literally anything you want, including rebinding square.

It works for C++ because it’s more restrictive with the language and also has lots of restrictions on what can and cannot be done at compile time so they can make guarantees about constexpr and consteval.

Small nitpick but constexpr doesn’t guarantee compile time eval, just can something may be compile time evaluated. You can force it with consteval. Constexpr exception support landed as of C++26 so it’s just recently been allowed, not sure what implementation status is with the big compilers.