On Mon, 16 Nov 2020 08:39:30 +1100 Steven D'Aprano email@example.com wrote:
The baseline of my version is much simpler:
# This makes "const" a kind of hard keyword for this module from __future__ import const
FOO: const = 1 # obviously, this is constant
To start with, in the original thread I wanted to concentrate on issues of the PEP634/PEP635, and whether these issues are prominent enough to hope for them being addressed. So, I changed the subject (and we'd soon be excused to python-ideas, indeed, I cc: there).
if all it takes is to add a new keyword, then constants are easy!
A new annotation. And the new subject is "constants in Python: Starting simple and gradually adding more", which hopefully should set the theme. Please remember how we arrived here: it's from the fact that PEP634 doesn't allow for the following trivial code:
MY_CONST = 1 match foo: case MY_CONST:
We're considering (another alternative) how to address that. Please don't make a leap towards supporting (at once) const-ness equivalent to statically typed languages.
No need to worry about how constantness affects name resolution, or how the syntax interacts with type-hints:
"const" is *the* type hint.
spam: const: Widget = build_widget(*args)
That syntax is clearly invalid. And composability of type annotations (aka type hints) is a known, looming issue. We now have https://www.python.org/dev/peps/pep-0593/ , but in all fairness, it seems like a stopgap measure rather than an elegant solution. (In full fairness, entire "typing" module's annotations aren't very elegant, but as we know, it's not a part of language core, but part of CPython's stdlib. That means it's only *one* of possible annotation schemes for *Python*).
So, under PEP593, the above would be written
spam: Annotated[Widget, const] = build_widget(*args)
If you want a glimpse of what alternatives might look, then: given that "|" is going to be used for union types, why not try "&" for composition?
spam: Widget & const = build_widget(*args)
But again, for "initial support of constants in Python, prompted by the introduction of pattern matching facilities", we don't need to worry about all that right away.
# Maybe this is better? # let spam: Widget = build_widget(*args)
As you remember, at the beginning of my proposal, I wrote "The baseline of my version ...". Baseline == level 0. For "level 2", I considered
const spam: Widget = ...
So, what's "level 1"?
As I mentioned, under "level 0", "from __future__ import const" has a magic meaning (akin to other "from __future__"'s). Under "level 1", "const" is just a normal annotation symbol imported from a module. So, following would be possible:
========= from __future__ import const
FOO: const = 1
def fun(): const = 1 # Just a variable in function scope
def sub(): # Gimme powerz back from __future__ import const BAR: const = 2
# Back to annotation in global scope BAZ: const = 3 =========
"level 0" should be implementable in CPython in ~1hr. "level 1" is realistically what we should shoot for. "level 2" (the dedicated keyword), I'm still not sure about. "const" is very baseline annotation, and deserves a dedicated keyword. But the underlying problem is composability of (arbitrary) annotations. I'd rather keep searching for graal in that regard, before giving up and introduce a dedicated thing just for "const".
or how "constant" interacts with mutability:
spam: const =  spam.append(99) # Allowed? spam.extend(items) spam.sort()
"const" is an annotation just like any other. And it affects *runtime* in the same as any other annotation in CPython affects it: in no way.
"const" is however introduced as a hint for *compile-time*, so compiler could make some simple inferences and maybe even optimizations based on it.
or for that matter, whether or not constants are actually constant.
spam: const = 1 spam = 2
Compiler can, and thus would, catch that as an error (warning for a beta version?).
If constants aren't actually constant, but just a tag on symbols, then you would be right, it probably would be trivially easy to add "constants" to Python.
But I think most people agree making them behave as constants is a pretty important feature.
As mentioned, "const" is just an annotation like any other, except compiler has some insight into it. Dealing with runtime is distant goal for CPython. (Which is for now offloaded to static type checkers and libraries/alternative Python implementations.)
*The* most critical feature of all, really.
Given a declared constant:
# Module a.py x: const = None
how do you prevent not just code in module `a` from rebinding the value:
Outside of the current scope of the discussion.
However, if you're interested in that topic, then I've implemented it in my Python dialect, Pycopy (https://github.com/pfalcon/pycopy). It's on my TODO to post an RFC to python-ideas for beating.