Repository navigation
[Feature Request] Raise explanatory exceptions when confronted with unusable unsubscripted multiprocessing.(Simple)?Queue "type hints"
#397
Copy link
Copy link
Open
Description
Breaking example I found in my own usage:
from beartype import beartype
from multiprocessing import Queue
from typing import TypedDict
class SpecialDict(TypedDict):
pass
class Example:
@beartype
def __init__(self, list1: Queue) -> None:
self.list1: Queue[SpecialDict] = list1
q1: Queue[SpecialDict] = Queue()
Example(q1)Now, to my knowledge, nothing about this breaks PEP conformance and I can't do list1: Queue[SpecialDict] because that gives TypeError: 'method' object is not subscriptable.
Error log:
Traceback (most recent call last):
File "/Users/Moosems/Desktop/test.py", line 8, in <module>
class Example:
File "/Users/Moosems/salve/test.py", line 9, in Example
@beartype
^^^^^^^^
File "/usr/local/lib/python3.12/site-packages/beartype/_decor/decorcache.py", line 77, in beartype
return beartype_object(obj, conf)
^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/usr/local/lib/python3.12/site-packages/beartype/_decor/decorcore.py", line 87, in beartype_object
_beartype_object_fatal(obj, conf=conf, **kwargs)
File "/usr/local/lib/python3.12/site-packages/beartype/_decor/decorcore.py", line 136, in _beartype_object_fatal
beartype_nontype(obj, **kwargs) # type: ignore[return-value]
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/usr/local/lib/python3.12/site-packages/beartype/_decor/_decornontype.py", line 182, in beartype_nontype
return beartype_func(obj, **kwargs) # type: ignore[return-value]
^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/usr/local/lib/python3.12/site-packages/beartype/_decor/_decornontype.py", line 247, in beartype_func
func_wrapper_code = generate_code(bear_call)
^^^^^^^^^^^^^^^^^^^^^^^^
File "/usr/local/lib/python3.12/site-packages/beartype/_decor/wrap/wrapmain.py", line 118, in generate_code
code_check_params = _code_check_args(bear_call)
^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/usr/local/lib/python3.12/site-packages/beartype/_decor/wrap/_wrapargs.py", line 334, in code_check_args
reraise_exception_placeholder(
File "/usr/local/lib/python3.12/site-packages/beartype/_util/error/utilerrraise.py", line 138, in reraise_exception_placeholder
raise exception.with_traceback(exception.__traceback__)
File "/usr/local/lib/python3.12/site-packages/beartype/_decor/wrap/_wrapargs.py", line 189, in code_check_args
hint = sanify_hint_root_func(
^^^^^^^^^^^^^^^^^^^^^^
File "/usr/local/lib/python3.12/site-packages/beartype/_check/convert/convsanify.py", line 178, in sanify_hint_root_func
hint = reduce_hint(
^^^^^^^^^^^^
File "/usr/local/lib/python3.12/site-packages/beartype/_check/convert/convreduce.py", line 210, in reduce_hint
hint = _reduce_hint_cached(hint, conf, exception_prefix)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/usr/local/lib/python3.12/site-packages/beartype/_util/cache/utilcachecall.py", line 250, in _callable_cached
raise exception
File "/usr/local/lib/python3.12/site-packages/beartype/_util/cache/utilcachecall.py", line 242, in _callable_cached
return_value = args_flat_to_return_value[args_flat] = func(
^^^^^
File "/usr/local/lib/python3.12/site-packages/beartype/_check/convert/convreduce.py", line 367, in _reduce_hint_cached
hint = hint_reducer( # type: ignore[call-arg]
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/usr/local/lib/python3.12/site-packages/beartype/_util/hint/pep/utilpepreduce.py", line 96, in reduce_hint_pep_unsigned
die_unless_hint(hint=hint, exception_prefix=exception_prefix)
File "/usr/local/lib/python3.12/site-packages/beartype/_util/hint/utilhinttest.py", line 99, in die_unless_hint
die_unless_hint_nonpep(hint=hint, exception_prefix=exception_prefix)
File "/usr/local/lib/python3.12/site-packages/beartype/_util/hint/nonpep/utilnonpeptest.py", line 203, in die_unless_hint_nonpep
raise exception_cls(
beartype.roar.BeartypeDecorHintNonpepException: Method __main__.Example.__init__() parameter "list1" type hint <bound method BaseContext.Queue of <multiprocessing.context.DefaultContext object at 0x10cc0da30>> either PEP-noncompliant or currently unsupported by @beartype.
Beartype says however that this either breaks PEP compliance or is unsupported. I believe its the latter. Love the project and the work you do!
Reactions are currently unavailable
Metadata
Metadata
Assignees
Labels
No labels
Activity
leycec commentedon Jun 14, 2024
...heh. More non-standard mostly undocumented type hints, huh? No problem. @beartype chews up non-standard mostly undocumented type hints for breakfast. It turns out everyone else is confused about
multiprocessing.Queue[...]type hints, too. Indeed, it looks like this probably effects a constellation of interrelated non-standard mostly undocumented type hints that scare everybody – including:multiprocessing.Queue[...]type hints.multiprocessing.SimpleQueue[...]type hints.queue.Queue[...]type hints.queue.SimpleQueue[...]type hints.It seems likely that the
multiprocessingvariants are "simple" aliases of their corresponding originalqueuetypes. Moreover, if I'm reading the officialqueuedocumentation correctly, then @beartype has no means of deeply type-checkingqueuetypes.Kinda unfortunate, but what can you do? The most @beartype can do here is shallowly type-check that
queuetypes are of the expected classes; @beartype can't actually peek inside the items queued in aqueuetype without destroying that queue. This is probably by design to ensure that queues are safeguarded against multithreading race conditions. Still...Kinda unfortunate. Let's do our best!
[-]Additional context seems to break beartype in certain cases[/-][+][Feature Request] `(multiprocessing|queue).(Simple)?Queue[...]` type hint support[/+]leycec commentedon Jun 15, 2024
Hoh, boy. I just spelunked into this. I have good news and I have bad news.
But first... the tl;dr! Ain't nobody got the time to read all the madness below. So, this is what you want to do:
...which behaves as expected under the current stable
@beartype 0.18.5release. Indeed, the above is the only usable way of typing what you want to type. The initial example you gave is improperly typed – but it's not your fault. Why? Because...Tell @Moosems What He's Won, Folks! ...yeah, it's bad news.
The bad news is that the
multiprocessing.Queue()andmultiprocessing.SimpleQueue()attributes are balls-crazy methods. Even though the official documentation states that both are classes, the official documentation lies. They're not classes. They have nothing to do with classes. They're standard pure-Python bound methods. Clearly, methods:Sadly, this means that @beartype is actually right about everything. From the perspective of runtime type-checking (...so, @beartype), PEP-compliance means "valid as type hints." Since
multiprocessing.Queue()andmultiprocessing.SimpleQueue()are invalid as type hints, they're PEP-noncompliant.Moreover, since neither
multiprocessing.Queue()normultiprocessing.SimpleQueue()are subscriptable, no code anywhere should ever try doing stuff likemultiprocessing.Queue[SpecialDict]. Yeah, sure, technically that "works." ...for certain specious definitions of "works." Pragmatically, though, that really doesn't work at all. Trying to subscript something that can't be subscripted is syntactically invalid code. Right? Those "type hints" don't actually run at runtime; they raise exceptions at runtime.If you were to automatically apply the
@beartypedecorator to the above code with an import hook likebeartype.claw.beartype_this_package(), you'd immediately receive the same fatal exception as you've exhibited above:Why? Because @beartype import hooks necessarily evaluate type hints annotating global variable assignments (e.g., the
Queue[SpecialDict]inq1: Queue[SpecialDict] = Queue()) at module importation runtime. Since @beartype import hooks are really the standard mode of operation for @beartype in 2024, this means that the module you've defined above is basically unimportable by most @beartype users in 2024.But Wait! There's Good News.
The good news is twofold:
multiprocessing.Queue()ormultiprocessing.SimpleQueue()methods as type hints. Not even once. They're busted. Friends never let friends use busted.multiprocessing.queues.Queueandmultiprocessing.queues.SimpleQueuetypes as type hints. Unlikemultiprocessing.Queue()andmultiprocessing.SimpleQueue(),multiprocessing.queues.Queueandmultiprocessing.queues.SimpleQueueare both valid type hints and subscriptable in the standard way.Consider refactoring your initial example for the good of well-typed APIs and your long-suffering coworker who is now ripping out his last hair:
Flex it, polar @beartype. 💪 🐻❄️
But Wait! There Is Something @beartype Should Do Here.
@beartype can't fix the fact that
multiprocessing.Queue()andmultiprocessing.SimpleQueue()are balls-crazy methods (rather than the balls-normal types they should be). That's CPython's fault, unfortunately. They should have resolved this obvious issue decades ago. They didn't. My emoji weeps. 😭@beartype can fix the exception it raises when confronted with either
multiprocessing.Queue()ormultiprocessing.SimpleQueue()as type hints. The current exception engenders confusion. Instead, @beartype should clarify this madness by explicitly informing funny guys with great personalities who are amazing devs such as yourself, @Moosems, that onlymultiprocessing.queues.Queueandmultiprocessing.queues.SimpleQueueshould be used as type hints.Let's edit this feature request title for great justice.
[-][Feature Request] `(multiprocessing|queue).(Simple)?Queue[...]` type hint support[/-][+][Feature Request] Raise explanatory exceptions when confronted with unusable `multiprocessing.(Simple)?Queue[...]` "type hints" :rage4: [/+][-][Feature Request] Raise explanatory exceptions when confronted with unusable `multiprocessing.(Simple)?Queue[...]` "type hints" :rage4: [/-][+][Feature Request] Raise explanatory exceptions when confronted with unusable unsubscripted `multiprocessing.(Simple)?Queue` "type hints" :rage4: [/+]Moosems commentedon Jun 15, 2024
@leycec Thank you so so much for looking into that! Despite your claim "Ain't nobody got the time to read all the madness below" I actually found your explanation to be quite interesting and even a very enjoyable read 😄.
leycec commentedon Jun 18, 2024
...heh.
multiprocessing.Queueinternals, huh? Now we're really getting into the guts of the beast. To quote core CPython dev @JelleZijlstra at CPython PR python/cpython#99511:Preach, brother @JelleZijlstra.
multiprocessingscares us all.The First Rule of Type Subscriptability Is...
Wait. What the heck is "type subscriptability," anyway? Okay. Let's back up.
Back when we were all younger and had muscles and smiled blissfully (albeit ignorantly), Python 3.9 implemented PEP 585. PEP 585 allowed core types (like
listanddict) to be subscripted with child type hints (like thetyping.Union[int, str]child type hint subscriptinglist[typing.Union[int, str]]). That's type subscriptability. So far so good, right?Clearly, not all types are subscriptable. Only types explicitly designed to be subscripted are subscriptable. The usual way this happens is with a crazy one-liner like
__class_getitem__ = classmethod(types.GenericAlias). It's best not to ask questions here. But the point I'm trying but failing to make is that type subscriptability doesn't happen automatically. It's hard, keyboard-breaking work. Type subscriptability only happens when someone who is not us makes it happen. So far so good, right?Clearly, this implies that most types are not subscriptable. Although the general expectation is that more and more standard CPython types will become subscriptable over time, this is a gradual and frankly boring process that takes years and a great deal of pounding sand over CPython PRs.
In this case, the
multiprocessing.queues.Queueclass only became subscriptable with Python 3.12. This is why your GitHub Actions-based continuous integration workflow is failing. That workflow is running Python 3.11, which lacks support for subscriptingmultiprocessing.queues.Queue. Consider this output that is beyond boring:You Have Two Equally Awful Solutions Here
Of course, I have tested nothing. Let's pretend that works.
Oh – and thanks so much for the kind words! It's a pleasure to dissect the CPython standard library on a workday that I should be dutifully working instead. Why, oh why, did no work get done yet again!? Oh, well. Let's just listen to music and pretend I did something. 🎶 🎧 🎶
Moosems commentedon Jun 18, 2024
It's quite unfortunate how messy multiprocessing is 😄.