[Python-Dev] Re: Proto-PEP part 4: The wonderful third option

2022-05-01 Thread Carl Meyer via Python-Dev
Hi Paul,

On Sun, May 1, 2022 at 3:47 PM Paul Bryan  wrote:
>
> Can someone state what's currently unpalatable about 649? It seemed to 
> address the forward-referencing issues, certainly all of the cases I was 
> expecting to encounter.

Broadly speaking I think there are 3-4 issues to resolve as part of
moving forward with PEP 649:

1) Class decorators (the most relevant being @dataclass) that need to
inspect something about annotations, and because they run right after
class definition, the laziness of PEP 649 is not sufficient to allow
forward references to work. Roughly in a similar boat are `if
TYPE_CHECKING` use cases where annotations reference names that aren't
ever imported.

2) "Documentation" use cases (e.g. built-in "help()") that really
prefer access to the original text of the annotation, not the repr()
of the fully-evaluated object -- this is especially relevant if the
annotation text is a nice short meaningful type alias name, and the
actual value is some massive unreadable Union type.

3) Ensuring that we don't regress import performance too much.

4) A solid migration path from the status quo (where many people have
already started adopting PEP 563) to the best future end state.
Particularly for libraries that want to support the full range of
supported Python versions.

Issues (1) and (2) can be resolved under PEP 649 by providing a way to
run the __co_annotations__ function without erroring on
not-yet-defined names, I think we have agreement on a plan there.
Performance of the latest PEP 649 reference implementation does not
look too bad relative to PEP 563 in my experiments, so I think this is
not an issue -- there are ideas for how we could reduce the overhead
even further. The migration path is maybe the most difficult issue --
specifically how to weigh "medium-term migration pain" (which under
some proposals might last for years) vs "best long-term end state."
Still working on reaching consensus there, but we have options to
choose from. Expect a more thorough proposal (probably in the form of
an update to PEP 649?) sometime after PyCon.

Carl
___
Python-Dev mailing list -- python-dev@python.org
To unsubscribe send an email to python-dev-le...@python.org
https://mail.python.org/mailman3/lists/python-dev.python.org/
Message archived at 
https://mail.python.org/archives/list/python-dev@python.org/message/M3FB6QHB2IOMEXDGHFRHYQEDR3KGZPHG/
Code of Conduct: http://python.org/psf/codeofconduct/


[Python-Dev] Re: Proto-PEP part 1: Forward declaration of classes

2022-04-26 Thread Carl Meyer via Python-Dev
On Tue, Apr 26, 2022 at 7:24 PM Greg Ewing 
wrote:

> On 27/04/22 2:01 am, Chris Angelico wrote:
> > That would be the case if monkeypatching were illegal. Since it's not,
> > wherein lies the difference?
>
> The proposed feature is analogous to forward declaring a
> struct in C. Would you call what C does monkeypatching?
>

It is not analogous; it is a false analogy that obscures the issues with
this proposal in Python.

A C forward declaration (not to mention the full struct declaration also!)
is purely for the compiler; at runtime one can have a pointer to some
memory that the compiler expects to be shaped like that struct, but one can
never get hold of any runtime value that is “the struct definition itself,”
let alone a runtime value that is the nascent forward-declared
yet-to-be-completed struct. So clearly there can be no patching of
something that never exists at runtime at all.

Python is quite different from C in this respect.  Classes are runtime
objects, and so is the “forward declared class” object. The proposal is for
a class object to initially at runtime be the latter, and then later (at
some point that is not well defined if the implementation is in a separate
module, because global module import ordering is an unstable emergent
property of all the imports in the entire codebase) may suddenly,
everywhere and all at once, turn into the former. Any given module that
imports the forward declared name can have no guarantee when (if ever) that
object will magically transform into something that is safe to use.

Whether we call it monkeypatching or not is irrelevant. Having global
singleton objects change from one thing to a very different thing, at an
unpredictable point in time, as a side effect of someone somewhere
importing some other module, causes very specific problems in being able to
locally reason about code. I think it is more useful to discuss the
specific behavior and its consequences than what it is called.

Carl
___
Python-Dev mailing list -- python-dev@python.org
To unsubscribe send an email to python-dev-le...@python.org
https://mail.python.org/mailman3/lists/python-dev.python.org/
Message archived at 
https://mail.python.org/archives/list/python-dev@python.org/message/CQ7TAV6TWGEG2HLVY7T46U6JCPESRACR/
Code of Conduct: http://python.org/psf/codeofconduct/


[Python-Dev] Re: Proto-PEP part 4: The wonderful third option

2022-04-26 Thread Carl Meyer via Python-Dev
On Tue, Apr 26, 2022 at 1:25 PM Guido van Rossum  wrote:
> I also would like to hear more about the problem this is trying to solve, 
> when th real-world examples. (E.g. from pydantic?)

Yes please. I think these threads have jumped far too quickly into
esoteric details of implementation and syntax, without critical
analysis of whether the semantics of the proposal are in fact a good
solution to a real-world problem that someone has.

I've already outlined in a more detailed reply on the first thread why
I don't think forward declarations provide a practically useful
solution to forward reference problems for users of static typing
(every module that imports something that might be a forward reference
would have to import its implementation also, turning every one-line
import of that class into two or three lines) and causes new problems
for every user of Python due to its reliance on import side effects
causing global changes at a distance. See
https://mail.python.org/archives/list/python-dev@python.org/message/NMCS77YFM2V54PUB66AXEFTE4NXFHWPI/
for details.

Under PEP 649, forward references are a small problem confined to the
edge case of early resolution of type annotations. There are simple
and practical appropriately-scoped solutions easily available for that
small problem: providing a way to resolve type annotations at runtime
without raising NameError on not-yet-defined names. Such a facility
(whether default or opt-in) is practically useful for many users of
annotations (including dataclasses and documentation tools), which
have a need to introspect some aspects of annotations without
necessarily needing every part of the annotation to resolve. The
existence of such a facility is a reasonable special case for
annotations specifically, because annotations are fundamentally
special: they provide a description of code, rather than being only a
part of the code. (This special-ness is precisely also why they cause
more forward references in the first place.)

IMO, this forward declaration proposal takes a small problem in a
small corner of the language and turns it into a big problem for the
whole language, without even providing as nice and usable an option
for common use cases as "PEP 649 with option for lax resolution" does.
This seems like a case study in theoretical purity ("resolution of
names in annotations must not be special") running roughshod over
practicality.

Carl
___
Python-Dev mailing list -- python-dev@python.org
To unsubscribe send an email to python-dev-le...@python.org
https://mail.python.org/mailman3/lists/python-dev.python.org/
Message archived at 
https://mail.python.org/archives/list/python-dev@python.org/message/RVQSLD435BFKEVIMY2AIA5MCJB37BPHK/
Code of Conduct: http://python.org/psf/codeofconduct/


[Python-Dev] Re: Proto-PEP part 1: Forward declaration of classes

2022-04-24 Thread Carl Meyer via Python-Dev
On Sun, Apr 24, 2022 at 10:20 AM Joao S. O. Bueno  wrote:
>
> I am not worried about the bikeshed part of which syntax to use -
> and more worried with the possible breaking of a lot of stuff, unless
> we work with creation of a non-identical "forward object" that is
> rebound, as in plain name binding, when the second part
> is declared. I've stated that amidst my ramblings,
> but Nick Coghlan managed to keep it suscint at
> https://mail.python.org/archives/list/python-dev@python.org/message/DMITVTUIQKJW6RYVOPQXHD54VSYE7QHA/

I don't think name rebinding works. That means that if we have
`forward class Bar` in module `foo` and `continue class Bar: ...` in
module `foo.impl`, if module `baz` does `from foo import Bar`, it will
forever have either the forward reference object or the real class,
and which one it has is entirely unpredictable (depends on import
ordering accidents of the rest of the codebase.) If `baz` happens to
be imported before `foo.impl`, the name `Bar` in the `baz` namespace
will never be resolved to the real class, and isn't resolvable to the
real class without some outside intervention.

> """
> Something worth considering: whether forward references need to be
> *transparent* at runtime. If they can just be regular ForwardRef objects
> then much of the runtime complexity will go away (at the cost of some
> potentially confusing identity check failures between forward references
> and the actual class objects).
>
> ForwardRef's constructor could also potentially be enhanced to accept a
> "resolve" callable, and a "resolve()" method added to its public API,
> although the extra complexity that would bring may not be worth it.
> """

I'm not sure how this `resolve()` method is even possible under the
proposed syntax. If `forward class Bar` and `continue class Bar` are
in different modules, then how can `forward class Bar` (which must
create the "forward reference" object) possibly know which module
`continue class Bar: ...` exists in? How can it know how to resolve
itself?

Carl
___
Python-Dev mailing list -- python-dev@python.org
To unsubscribe send an email to python-dev-le...@python.org
https://mail.python.org/mailman3/lists/python-dev.python.org/
Message archived at 
https://mail.python.org/archives/list/python-dev@python.org/message/WLZRZIMPRST52UMINB5VB57TOIQVTYQH/
Code of Conduct: http://python.org/psf/codeofconduct/


[Python-Dev] Re: Proto-PEP part 1: Forward declaration of classes

2022-04-24 Thread Carl Meyer via Python-Dev
Hi Larry,

On Sat, Apr 23, 2022 at 1:53 AM Larry Hastings  wrote:
> But rather than speculate further, perhaps someone who works on one of the 
> static type analysis checkers will join the discussion and render an informed 
> opinion about how easy or hard it would be to support "forward class" and 
> "continue class".

I work on a Python static type checker.

I think a major issue with this proposal is that (in the
separate-modules case) it requires monkey-patching as an import side
effect, which is quite hard for both humans and static analysis tools
to reason effectively about.

Imagine we have a module `foo` that contains `forward class Bar`, a
separate module `foo.impl` that contains `continue class Bar: ...`,
and then a module `baz` that contains `import foo`. What type of
object is `foo.Bar` during the import of `baz`? Will it work for the
module body of `baz` to create a singleton instance `my_bar =
foo.Bar()`?

The answer is that we have no idea. `foo.Bar` might be a
non-instantiable "forward class declaration" (or proxy object, in your
second variation), or it might be a fully-constituted class. Which one
it is depends on accidents of import order anywhere else in the
codebase. If any other module happens to have imported `foo.impl`
before `baz` is imported, then `foo.Bar` will be the full class. If
nothing else has imported `foo.impl`, then it will be a
non-instantiable declaration/proxy. This question of import order
potentially involves any other module in the codebase, and the only
way to reliably answer it is to run the entire program; neither a
static type checker nor a reader of the code can reliably answer it in
the general case. It will be very easy to write a module `baz` that
does `import foo; my_bar = foo.Bar()` and have it semi-accidentally
work initially, then later break mysteriously due to a change in
imports in a seemingly unrelated part of the codebase, which causes
`baz` to now be imported before `foo.impl` is imported, instead of
after.

There is another big problem for static type checkers with this
hypothetical module `baz` that only imports `foo`. The type checker
cannot know the shape of the full class `Bar` unless it sees the right
`continue Bar: ...` statement. When analyzing `baz`, it can't just go
wandering the filesystem aimlessly in hopes of encountering some
module with `continue Bar: ...` in it, and hope that's the right one.
(Even worse considering it might be `continue snodgrass: ...` or
anything else instead.) So this means a type checker must require that
any module that imports `Bar` MUST itself import `foo.impl` so the
type checker has a chance of understanding what `Bar` actually is.

This highlights an important difference between this proposal and
languages with real forward declarations. In, say, C++, a forward
declaration of a function or class contains the full interface of the
function or class, i.e. everything a type checker (or human reader)
would need to know in order to know how it can use the function or
class. In this proposal, that is not true; lots of critical
information about the _interface_ of the class (what methods and
attributes does it have, what are the signatures of its methods?) are
not knowable without also seeing the "implementation." This proposal
does not actually forward declare a class interface; all it declares
is the existence of the class (and its inheritance hierarchy.) That's
not sufficient information for a type checker or a human reader to
make use of the class.

Taken together, this means that every single `import foo` in the
codebase would have to be accompanied by an `import foo.impl` right
next to it. In some cases (if `foo.Bar` is not used in module-level
code and we are working around a cycle) it might be safe for the
`import foo.impl` to be within an `if TYPE_CHECKING:` block; otherwise
it would need to be a real runtime import. But it must always be
there. So every single `import foo` in the codebase must now become
two or three lines rather than one.

There are of course other well-known problems with import-time side
effects. All the imports of `foo.impl` in the codebase would exist
only for their side effect of "completing" Bar, not because anyone
actually uses a name defined in `foo.impl`. Linters would flag these
imports as unused, requiring extra cruft to silence the linter. Even
worse, these imports would tend to appear unused to human readers, who
might remove them and be confused why that breaks the program.

All of these import side-effect problems can be resolved by
dis-allowing module separation and requiring `forward class` and
`continue class` to appear in the same module. But then the proposal
no longer helps with resolving inter-module cycles, only intra-module
ones.

Because of these issues (and others that have been mentioned), I don't
think this proposal is a good solution to forward references. I think
PEP 649, with some tricks that I've mentioned elsewhere to allow
introspecting annotations 

[Python-Dev] Re: Declarative imports

2022-04-09 Thread Carl Meyer via Python-Dev
Hi Malthe,

On Fri, Apr 8, 2022 at 12:04 PM Malthe  wrote:
> Actually, to me the interesting idea is not so much lazy imports – I
> think they should not be lazy, at least that was my initial thought. I
> think they should be immediately resolved before anything else in that
> module:

I'm +0.25 on your idea as simply a streamlined syntax for inline
imports (given actually finding an appropriate syntax, which I haven't
thought much about; @ probably doesn't work due to the conflict with
decorator syntax, but there might be other options.). If it existed I
would probably use it occasionally, but I don't feel a strong need for
it.

But I think your proposal is much stronger if you eliminate the
hoisting from it; with the hoisting I'd be -1. Out-of-source-order
execution like this is just quite surprising in the context of Python.

> 1. This would settle any discussion about performance impact (there
> wouldn't be any).

If the inline import is actually a performance problem because a
certain code path is very hot, the solution is simple: don't use the
inline import there, use a top-of-module import instead.

> 2. This would enable IDEs, typers and other tooling to know the type
> using existing import logic.

I don't think it enables any such thing. Static-analysis tooling has
only the source code to work with, runtime behavior doesn't affect it.
If the runtime executes these imports out-of-order, that won't make
the slightest difference to how easily IDEs and type checkers can
analyze the source code.

> 3. Catch errors early!

The very strong precedent in Python is that errors in code are caught
when the code runs, and the code runs more or less when you'd expect
it to, in source order. If you want to catch errors earlier, use a
static analysis tool to help catch them.

Carl
___
Python-Dev mailing list -- python-dev@python.org
To unsubscribe send an email to python-dev-le...@python.org
https://mail.python.org/mailman3/lists/python-dev.python.org/
Message archived at 
https://mail.python.org/archives/list/python-dev@python.org/message/ARI44O62CRMAF2IKPHJVLU5D2ADR2DP6/
Code of Conduct: http://python.org/psf/codeofconduct/


[Python-Dev] Re: Declarative imports

2022-04-09 Thread Carl Meyer via Python-Dev
Hi Barry,

On Fri, Apr 8, 2022 at 12:44 PM Barry Warsaw  wrote:
>
> Start up overhead due to imports is a real problem for some class of 
> applications, e.g. CLIs, and I’ve seen a lot of hacks implemented to get 
> Python CLIs to be more responsive.  E.g. getting from invocation to —help 
> output is a major UX problem.

Definitely, we have this same problem, and also the same symptom of
people pushing hard to rewrite Python CLIs in Go for this reason.

> It’s often more complicated than just imports alone though.  Expensive module 
> scope initializations and decorators contribute to this problem.

One of our projects that can prevent much of this expensive work being
done at import time is Strict Modules[1]. Currently it's only
available as part of Cinder, though we're hoping to make it
pip-installable as part of our project to make Cinder's features more
easily accessible.

Our experience in practice, though, has been that universally lazy
imports is somewhat easier to adopt than Strict Modules, and has had a
much bigger overall impact on reducing startup time for big CLIs (and
a big web server too; as you note it's not as serious an issue for a
web server in production, but restart time still does make a
difference to dev speed / experience.) Removing slow stuff happening
at import time helps, but it'll never match the speed of not doing the
import at all! We've seen startup time improvements up to 70% in
real-world CLIs just by making imports lazy. We've also opened an
issue to discuss the possibility of upstreaming this. [2]

[1] https://github.com/facebookincubator/cinder/#strict-modules
[2] https://bugs.python.org/issue46963
___
Python-Dev mailing list -- python-dev@python.org
To unsubscribe send an email to python-dev-le...@python.org
https://mail.python.org/mailman3/lists/python-dev.python.org/
Message archived at 
https://mail.python.org/archives/list/python-dev@python.org/message/62OTFJMAMQ2WHZ4H3TUEJTECMPJDQ557/
Code of Conduct: http://python.org/psf/codeofconduct/


[Python-Dev] Re: Declarative imports

2022-04-08 Thread Carl Meyer via Python-Dev
An interesting point in the lazy imports design space that I hadn't
previously considered could be:

- lazy imports are explicitly marked and usage of the imported name
within the module is transparent, but
- lazily imported names are _not_ visible in the module namespace;
they can't be accessed by other modules or re-exported; they are
internal-use-only within the module

This compromise would, I think, make it possible to implement lazy
imports entirely in the compiler (effectively as syntax sugar for an
inline import at every usage site), which is definitely an
implementation improvement.

I think in practice explicitly marking lazy imports would make it
somewhat harder to gain the benefits of lazy imports for e.g. speeding
up startup time in a large CLI, compared to an implicit/automatic
approach. But still could be usable to get significant benefits.

Carl
___
Python-Dev mailing list -- python-dev@python.org
To unsubscribe send an email to python-dev-le...@python.org
https://mail.python.org/mailman3/lists/python-dev.python.org/
Message archived at 
https://mail.python.org/archives/list/python-dev@python.org/message/ZT6CXQPFCWZD2M65YXCSAPPGNDGA6WNE/
Code of Conduct: http://python.org/psf/codeofconduct/


[Python-Dev] Re: Declarative imports

2022-04-08 Thread Carl Meyer via Python-Dev
You only get the ease-of-implementation benefit if you are willing to
explicitly mark every _use_ of a lazy-imported name as special (and
give the fully qualified name at every usage site). This is rather
more cumbersome (assuming multiple uses in a module) than just
explicitly marking an import as lazy in one location and then using
the imported name in multiple places normally.

Other "lazy import" solutions are trying to solve a problem where you
want the name to be usable (without special syntax or marking) in many
different places in a module, and visible in the module namespace
always -- but not actually imported until someone accesses/uses it.
The difficulty arises because in this case you need some kind of
placeholder for the "deferred import", but you need to avoid this
"deferred object" escaping and becoming visible to Python code without
being resolved first. Explicitly marking which imports are lazy is
fine if you want it (it's just a matter of syntax), but it doesn't do
anything to solve the problem of allowing usage of the lazy-imported
name to be transparent.

I agree that the idea that top-of-module imports help readability is
overstated; it sounds slightly Stockholm-syndrome-ish to me :)
Top-of-module imports are frankly a pain to maintain and a pain to
read (because they are often distant from the uses of the names). But
they are a necessary evil if you want a) namespaces and b) not
constantly retyping fully-qualified names at every usage site. Python
is pretty committed to namespaces at this point (and I wouldn't want
to change that), so that leaves the choice between top-of-module
imports vs fully qualifying every use of every name; pick your poison.
(Inline imports in a scope with multiple uses are a middle ground.)

Carl
___
Python-Dev mailing list -- python-dev@python.org
To unsubscribe send an email to python-dev-le...@python.org
https://mail.python.org/mailman3/lists/python-dev.python.org/
Message archived at 
https://mail.python.org/archives/list/python-dev@python.org/message/N4T2YMPHBLJXKCFA5CIPBFIZJJKO7SHR/
Code of Conduct: http://python.org/psf/codeofconduct/


[Python-Dev] Re: PEP 649: Deferred Evaluation Of Annotations Using Descriptors, round 2

2021-04-14 Thread Carl Meyer via Python-Dev
Hi Larry,

On 4/14/21, 1:56 PM, "Larry Hastings"  wrote:

>My plan was to post it here and see what the response was first.  Back in 
> January, when I posted the first draft, I got some very useful feedback that 
> resulted in some dramatic changes.  This time around, so far, nobody has 
> suggested even minor changes.  Folks have just expressed their opinions about 
> it (which is fine).

This is not true. I suggested yesterday (in 
https://mail.python.org/archives/list/python-dev@python.org/message/DSZFE7XTRK2ESRJDPQPZIDP2I67E76WH/
 ) that PEP 649 could avoid making life worse for users of type annotations 
(relative to PEP 563) if it replaced runtime-undefined names with forward 
reference markers, as implemented in 
https://github.com/larryhastings/co_annotations/pull/3

Perhaps you've chosen to ignore the suggestion, but that's not the same as 
nobody suggesting any changes ;)

Carl

___
Python-Dev mailing list -- python-dev@python.org
To unsubscribe send an email to python-dev-le...@python.org
https://mail.python.org/mailman3/lists/python-dev.python.org/
Message archived at 
https://mail.python.org/archives/list/python-dev@python.org/message/4LXOED3ABKDSNUDJ3JTNEGTXD3R7TEWT/
Code of Conduct: http://python.org/psf/codeofconduct/


[Python-Dev] Re: PEP 649: Deferred Evaluation Of Annotations Using Descriptors, round 2

2021-04-13 Thread Carl Meyer via Python-Dev
Hi Larry,

On 4/12/21, 6:57 PM, "Larry Hastings"  wrote:
Again, by "works on PEP 563 semantics", you mean "doesn't raise an error".  
But the code has an error.  It's just that it has been hidden by PEP 563 
semantics.
I don't agree that changing Python to automatically hide errors is an 
improvement.  As the Zen says: "Errors should never pass silently."

This is really the heart of the debate over PEP 649 vs PEP 563.  If you 
examine an annotation, and it references an undefined symbol, should that throw 
an error?  There is definitely a contingent of people who say "no, that's 
inconvenient for us".  I think it should raise an error.  Again from the Zen: 
"Special cases aren't special enough to break the rules."  Annotations are 
expressions, and if evaluating an expression fails because of an undefined 
name, it should raise a NameError.

Normally in Python, if you reference a symbol in a function definition line, 
the symbol must be defined at that point in module execution. Forward 
references are not permitted, and will raise `NameError`.

And yet you have implemented PEP 649, whose entire raison d'être is to 
implement a "special case" to "break the rules" by delaying evaluation of 
annotations such that a type annotation, unlike any other expression in the 
function definition line, may include forward reference names which will not be 
defined until later in the module.

The use case for `if TYPE_CHECKING` imports is effectively the same. They are 
just forward references to names in other modules which can't be imported 
eagerly, because e.g. it would cause a cycle. Those who have used type 
annotations in earnest are likely to confirm that such inter-module forward 
references are just as necessary as intra-module forward references for the 
usability of type annotations.

So it doesn't seem that we have here is a firm stand on principle of the Zen, 
it appears to rather be a disagreement about exactly where to draw the line on 
the "special case" that we all already seem to agree is needed.

The Zen argument seems to be a bit of a circular one: I have defined PEP 649 
semantics in precisely this way, therefore code that works with PEP 649 does 
not have an error, and code that does not work with PEP 649 "has an error" 
which must be surfaced!

With PEP 563, although `get_type_hints()` cannot natively resolve inter-module 
forward references and raises `NameError`, it is possible to work around this 
by supplying a globals dict to `get_type_hints()` that has been augmented with 
those forward-referenced names. Under the current version of PEP 649, it 
becomes impossible to get access to such type annotations at runtime at all, 
without reverting to manually stringifying the annotation and then using 
something like `get_type_hints()`. So for users of type annotations who need 
`if TYPE_CHECKING` (which I think is most users of type annotations), the 
best-case overall effect of PEP 649 will be that a) some type annotations have 
to go back to being ugly strings in the source, and b) if type annotation 
values are needed at runtime, `get_type_hints()` will still be as necessary as 
it ever was.

It is possible for PEP 649 to draw the line differently and support both 
intra-module and inter-module forward references in annotations, by doing 
something like https://github.com/larryhastings/co_annotations/pull/3 and 
replacing unknown names with forward-reference markers, so the annotation 
values are still accessible at runtime. This meets the real needs of users of 
type annotations better, and gives up none of the benefits of PEP 649.

Carl



___
Python-Dev mailing list -- python-dev@python.org
To unsubscribe send an email to python-dev-le...@python.org
https://mail.python.org/mailman3/lists/python-dev.python.org/
Message archived at 
https://mail.python.org/archives/list/python-dev@python.org/message/DSZFE7XTRK2ESRJDPQPZIDP2I67E76WH/
Code of Conduct: http://python.org/psf/codeofconduct/