Hello,
(As the thread continues, and goes from "why did you do that" to
actual technical details/implication, I have little choice (if to
maintain coherent discussion) but go thru existing posts looking into
technical aspects of the proposal.)
On Wed, 2 Dec 2020 10:42:25 +1100
Steven D'Aprano
# Way to define a constant. my_cnst: const = 1
That clashes with type annotations.
"const" is a *type annotation*.
Constantness is not a type of the value, it's a statement about the name binding.
Your interpretation, my interpretation: 'const is a type annotation like any other'. This poses a question of type annotation composability. Outside the scope of this discussion (a solution exists). All this was mentioned already in previous discussions. []
It would be better to introduce an independent syntax for constants. Let's say `const`:
const x: int = some_expression
Yes, and that was mentioned already. But we'll do that only when we play enough with "const" as an annotation and see that it's so useful that is worth being promoted to a keyword. (I'm sure we'll end up there; but the current state is that not everyone is convinced of useful of "const" at all. So, no hurry, step by step.) []
# Leads to a warning: replacing (monkey-patching) a constant slot. my_cnst: const = 2
A warning? What's the use of that?
A use of warning in programming language? A generic question outside the scope of the current discussion.
Are your constants *actually constant* or are they just advisory?
I already answered how my proposal works in this thread. And it's fully described in the proposal. So, please refer to it.
If they're just advisory, then we might as well stick with the convention to use UPPERCASE names and use a linter that warns if you re-bind a "constant".
Maybe you can stick with UPPERCASE names. I'd prefer something more generic.
If you can rebind constants by just suppressing or ignoring the warning, then people will rebind constants.
If they no what they're doing, let them do that (while they can, until the "run-time" phase starts.)
Relevant:
Sometimes I run GUI applications from the command line. Invariably they generate a flood of runtime warnings. Clearly developers are paying no attention to warnings.
Outside the scope of the discussion. E.g. C compilers have -Werror for that.
def fun(): # Imports are not allowed at run-time import mod2 # But you can re-import module previously imported at import-time. import mod
That seems like a pointless complication.
If mod is already imported, why would I need to import it again from inside the function?
You missed the point, it's the opposite situation: "mod" is imported inside the function to start with. The rest of the discussion is how to accommodate that in the strict mode, given that "on the surface", that's prohibited in the strict mode. []
# RuntimeError my_cnst = 3
`my_cnst` is inside a function, so it should create a local variable, not attempt to rebind a global constant.
Good catch. "global my_cnst" was clearly missing before that. Fixed in https://github.com/pycopy/PycoEPs/blob/master/StrictModeTLDR.md
# RuntimeError mod.func2 = lambda x: 1
Yes you already make it clear that rebinding functions is not allowed.
No, you missed the critical part of the proposal: that there're 2 phases of the execution of a Python program: "import-time" and "run-time". Things at the global scope of that sample show rules for import-time (lax, even const's can be redefined), while code inside function executes at "run-time" (strict in all its full glory). The information about 2 distinct execution phases is presented clearly even in the TLDR version. Thanks for missing it.
global glb1, new # RuntimeError: Cannot create new global nameslots at runtime. new = 1 # Nor can delete existing del glb1
I know "global variables considered harmful", but this looks to me like punishing users of globals for being bad by restricting what they can do to make their use of globals *worse* rather than better.
They won't be punished much more than the already existing "considered harmful" background establishes ;-).
- all globals must be pre-declared and initialised before use;
Already the best practice.
- functions cannot clean-up after themselves by deleting their unneeded globals.
Modules can. Functions? Never saw that, and that's clearly "not Pythonic". (But one of "dirty Python tricks".) []
# Cheats don't work globals()["new"] = 1
That seems like it will probably break a lot of code, assuming you can even enforce it.
The code which will be broken - it won't be able to run in the strict mode. It either will need to be fixed to "not do dirty Python tricks", or be left forever to work in "standard" mode.
Is your proposal for globals() to no longer return the global namespace dict?
At run-time, it would be wrapped in a read-only proxy. That's all explained in the proposal. Sorry, did you read it?
# Leads to a warning: replacing (monkey-patching) a constant slot (function). def fun(): pass
# fun_var is a variable storing a reference to a function (can store ref # to another func). fun_var = fun
So is `fun`.
In with the strict mode.
Are you aware that Python's execution model treats:
I'm aware, yes. Are you aware that the strict mode proposal seeks to change that?
- function *objects* as first-class values, same as ints, strings, floats, lists, etc
- and *names* bound to function objects ("functions") as no different from names bound to any other object?
You seem to be introducing a complicated execution model,
Not *that* complicated is you think about it.
where names bound to functions are different from other names, and functions are not first-class values any longer, but either privileged or restricted, depending on whether you think "functions are values" is a misfeature to be removed or a feature to be cherished.
No, the model is different: function bindings are by default immutable bindings, which is the best practice for most languages. Only when proven otherwise, a function binding is promoted (or demoted, depending on your outlook) to a mutable binding.
# Run-time execution starts with this function. This clearly delineates # import-time from run-time: a module top-level code is executed at # import-time (including import statements, which execute top-level code # of other modules recursively). When that is complete, strict mode # interpreter switches to run-time mode (restrictions enabled) and # executes __main__(). def __main__(): fun()
# This statement is not executed when program runs in strict mode. # It is executed when it is run in normal mode, and allow to have # the same startup sequence (execution of __main__()) for both cases. if __name__ == "__main__": __main__() ```
I don't really understand the purpose of your two modes here. In one mode, the interpreter automatically calls `__main__`; in the other mode, the interpreter runs the standard `if __name__ ...` idiom and then calls `__main__`.
Well, that's because one mode already exists, and another I'm adding in the proposal.
How does the interpreter know which mode it is in?
Described in the proposal.
Presumably there must be a "strict" modal switch. If that's the case, why not use the presense of that switch to distinguish strict mode from Python mode, instead of this convoluted plan of sometimes automatically calling `__main__` and sometimes not?
Described in the proposal.
This mandatory-but-only-sometimes special function seems pointless to me. Just because we're running under strict mode, why should the `if __name__` idiom stop working? It's just code. Your strict mode has to go out of its way to prevent it from working.
Described in the proposal.
I'm sure you know that if this proposal goes ahead, people will immediately demand that `__main__` is automatically called in non-strict mode too.
What the people may demand some time later is outside the scope of the proposal.
So I can't help but feel you are using this as a Trojan Horse to sneak in a stylistic change: replace the `if __name__` idiom for automatically calling a special, magic function.
I do not. For the people that you mention above my response is "no". (Aka: use the strict mode if you want to get tasty goodies, or stick with the old stuff.)
That seems to have zero technical benefit.
What if I want to call my application entry point `main()` or `Haupt()` or `entrypoint()`?
What if I put the `if __name__` idiom *above* the special magic function? Does it still get magically ignored?
Strict mode doesn't change statement-by-statement sequential execution of the Python programs. Nor there's any magic in it. All is explained in the proposal.
if __name__ == '__main__': print('Starting')
def __main__(): # magic print('Running')
__main__()
if __name__ == '__main__': print('Goodbye')
I *think* that under your proposal, under regular Python mode, it will print Starting, Running, Goodbye, but under your strict mode, it will print Starting, Running *twice*, and then exit.
No, it will print Running twice and nothing else.
Maybe. It's not very clear. It could print Running only, and not print Starting or Goodbye at all.
Your proposed strict mode doesn't seem to actually be limited to making Python "stricter" for the purposes of optimization or clarity, but also seems to include changes which seems to be more *stylistic* changes which (probably) aren't necessary from the implementation:
That's true, and fully disclosed in the proposal (at the very beginning): "However, it is also believed that the "strict mode" may also help on its own with clarity and maintenance of large(r) Python codebases."
* globals must be defined in the top level of the module;
* global constants override local variables;
* functions aren't variables like everything else;
* enforced and automatic special entry-point function;
* discourage the `if __name__` idiom by making it half redundant;
No, it must be presented for compatibility.
etc.
-- Steve
[] -- Best regards, Paul mailto:pmiscml@gmail.com