Repository navigation
typing.Union does not support attribute assignment post gh-105511 #132139
Copy link
Copy link
Closed
Labels
3.14bugs and security fixesbugs and security fixesextension-modulesC modules in the Modules dirC modules in the Modules dirstdlibStandard Library Python modules in the Lib/ directoryStandard Library Python modules in the Lib/ directorytopic-typingtype-bugAn unexpected behavior, bug, or errorAn unexpected behavior, bug, or error
Description
Bug report
Bug description:
Before #105511, typing.Union is implemented in Python, and custom attributes can be assigned to a Union variable.
ta = Union[int, str]
ta.__some_attribute__ = (int, str)However, after #105511, typing.Union is now implemented in C, and no attributes can be assigned to it. The UnionType does not allow subclassing; users cannot bypass this using subclassing.
$ ipython
Python 3.14.0a6+ experimental free-threading build (heads/main:1755157207c, Apr 6 2025, 02:21:10) [Clang 17.0.0 (clang-1700.0.13.3)]
Type 'copyright', 'credits' or 'license' for more information
IPython 9.0.2 -- An enhanced Interactive Python. Type '?' for help.
Tip: Use `object?` to see the help on `object`, `object??` to view it's source
In [1]: import optree
In [2]: optree.PyTree[int]
/Users/PanXuehai/Projects/optree/env/lib/python3.14t/site-packages/IPython/core/interactiveshell.py:3111: SyntaxWarning: 'return' in a 'finally' block
return result
╭────────────────────────────────────────────────────────────────────── Traceback (most recent call last) ───────────────────────────────────────────────────────────────────────╮
│ in <module>:1 │
│ │
│ /Users/PanXuehai/Projects/cpython/.pydev/lib/python3.14t/typing.py:398 in inner │
│ │
│ 395 │ │ @functools.wraps(func) │
│ 396 │ │ def inner(*args, **kwds): │
│ 397 │ │ │ try: │
│ ❱ 398 │ │ │ │ return _caches[func](*args, **kwds) │
│ 399 │ │ │ except TypeError: │
│ 400 │ │ │ │ pass # All real errors (not unhashable args) are raised below. │
│ 401 │ │ │ return func(*args, **kwds) │
│ │
│ /Users/PanXuehai/Projects/optree/optree/typing.py:254 in __class_getitem__ │
│ │
│ 251 │ │ │ Deque[recurse_ref], # type: ignore[valid-type] │
│ 252 │ │ │ CustomTreeNode[recurse_ref], # type: ignore[valid-type] │
│ 253 │ │ ] │
│ ❱ 254 │ │ pytree_alias.__pytree_args__ = item # type: ignore[attr-defined] │
│ 255 │ │ │
│ 256 │ │ # pylint: disable-next=no-member │
│ 257 │ │ original_copy_with = pytree_alias.copy_with # type: ignore[attr-defined] │
╰────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯
AttributeError: 'typing.Union' object has no attribute '__pytree_args__' and no __dict__ for setting new attributes
In [3]: class Foo(type(int | str)):
...: pass
...:
╭────────────────────────────────────────────────────────────────────── Traceback (most recent call last) ───────────────────────────────────────────────────────────────────────╮
│ in <module>:1 │
╰────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯
TypeError: type 'typing.Union' is not an acceptable base typeRepro:
./python -m pip install -v optree
./python -c 'import optree; optree.PyTree[int]'Source: https://github.com/metaopt/optree/blob/v0.15.0/optree/typing.py#L246-L254
CPython versions tested on:
CPython main branch
Operating systems tested on:
macOS
Linked PRs
Reactions are currently unavailable
Metadata
Metadata
Assignees
Labels
3.14bugs and security fixesbugs and security fixesextension-modulesC modules in the Modules dirC modules in the Modules dirstdlibStandard Library Python modules in the Lib/ directoryStandard Library Python modules in the Lib/ directorytopic-typingtype-bugAn unexpected behavior, bug, or errorAn unexpected behavior, bug, or error
Activity
[-][3.14 Regression] `Union` variable does not allow attribute assignment anymore[/-][+][3.14 Regression] `typing.Union` variable does not allow attribute assignment anymore[/+]picnixz commentedon Apr 5, 2025
Was it designed to support custom attributes? is it a documented behavior? (I don't know so I'm just asking if this was deliberate)
JelleZijlstra commentedon Apr 5, 2025
Yeah, don't do that. Setting arbitrary attributes on Union objects (or any other typing objects) was never supported and if you do that sort of thing, be prepared for your code to break.
That said, we could allow setting arbitrary attributes again by adding a managed
__dict__to union objects, at a cost of increasing memory usage for every other user of unions. I'd be curious to hear opinions on whether that's worth it.picnixz commentedon Apr 5, 2025
How much of a cost are we talking about? I think you mentioned something about the fact that unions' size is smaller (or was it larger?) but I can't find the issue (I think I read this yesterday somewhere). If we're only talking about a few bytes when it's not used, then why not. But if we're doubling the union sizes I think it's maybe not worth.
Now, are there other projects for which this could be needed? if there is enough usage, and from large libraries, then maybe it's a possibility? By the way, for what reason do you actually need unions to support assignment?
[-][3.14 Regression] `typing.Union` variable does not allow attribute assignment anymore[/-][+]`typing.Union` variable does not allow attribute assignment anymore post gh-105511[/+][-]`typing.Union` variable does not allow attribute assignment anymore post gh-105511[/-][+]`typing.Union` variable does not allow attribute assignment post gh-105511[/+][-]`typing.Union` variable does not allow attribute assignment post gh-105511[/-][+]`typing.Union` does not support attribute assignment post gh-105511[/+]11 remaining items
gh-132139: Document that you can no longer set attributes on unions (#…
pythongh-132139: 3.14 what's new: elaborate on why you can no longer …
gh-132139: 3.14 what's new: elaborate on why you can no longer set `U…
albanD commentedon Jul 12, 2025
Another interesting side effect of this is for "public API specifications".
Systems like sphinx considers an API public in the current file if the API's
__module__attribute is set to the current file.We use this in PyTorch in particular to define types, make them public API from sphinx point of view to properly document. An example is here: https://github.com/pytorch/pytorch/blob/aacb9440791f4e9567e2123f19bb062e75c2d242/torch/ao/quantization/__init__.py#L34-L36
The logic at
cpython/Objects/unionobject.c
Lines 400 to 403 in c7d24b8
__module__attribute works but trying to set it fails with'typing.Union' object has no attribute '__module__' and no __dict__ for setting new attributes(the attribute actually does exist).Curious to hear what you think here?
Are we misguided on trying to set the
__module__attribute as a marker of public APIs for the Union object? Or we should try and find a way to enable this behavior?XuehaiPan commentedon Jul 12, 2025
@albanD If it is only related to the docs build, I have worked around this by adding a custom
typehint_formmatterand saving theUnionobjects in a weakrefset:https://github.com/metaopt/optree/blob/ebd1d78d5780b9ad8dbbaba89ecedfee3061d84d/docs/source/conf.py#L195-L213
The keys of the
WeakKeyDictionaryare the set ofUnioninstances.https://github.com/metaopt/optree/blob/ebd1d78d5780b9ad8dbbaba89ecedfee3061d84d/optree/typing.py#L205-L211
https://github.com/metaopt/optree/blob/ebd1d78d5780b9ad8dbbaba89ecedfee3061d84d/optree/typing.py#L273-L275
XuehaiPan commentedon Jul 12, 2025
__module__is a class attribute. You cannot assign it via an instance.albanD commentedon Jul 14, 2025
The problem is that it's not just that. We also have programmatic checks to ensure that things in the
__all__field matches with the__module__. But since I cannot change the__module__, I have to remove theUnionfrom the__all__field and thusimport *from that module changes behavior.Also given how it's defined you can't subclass Union to add a
__dict__to it either.Fix compilation and "import torch" issues for cpython 3.14 (#158184)
JelleZijlstra commentedon Jul 16, 2025
To recapitulate why I am not interested in changing this:
Union[int, str]and set its__module__, they would have clobbered each other.|syntax andtypes.GenericAliasdon't support attribute assignments; setting a__module__attribute on anAnnotatedobject sets its for allAnnotatedobjects.As an alternative, I'd suggest using the
typestatement introduced in Python 3.12. Type aliases created through this feature automatically get the__module__they were created in.If you need to support 3.11 and earlier, you can use:
albanD commentedon Jul 16, 2025
Thanks for the in-depth answer. This all makes sense.
I'm afraid that doesn't work as the "type" statement will through an error in <=3.11 even if not executed:
That is a great feature that would definitely solve our issue otherwise!
JelleZijlstra commentedon Jul 16, 2025
Sorry yes, you'd need to use exec. I was thinking about that but forgot to write it when I wrote the comment :). Fixed above.
albanD commentedon Jul 17, 2025
Thanks for the quick answer @JelleZijlstra , given that exec makes our security people jumpy, I was trying to go with the following. Any concern with that approach?
types.Union no longer supports attribute assignment
types.Union no longer supports attribute assignment