# Why does \`typing.Union\` (\`types.UnionType\`) not implement the \`collections.abc.Collection\` (or \`Set\`) Protocol?

**URL:** <https://discuss.python.org/t/why-does-typing-union-types-uniontype-not-implement-the-collections-abc-collection-or-set-protocol/33117>\
**Category:** Python Help\
**Created:** [September 6, 2023, 2:03pm UTC](https://discuss.python.org/t/why-does-typing-union-types-uniontype-not-implement-the-collections-abc-collection-or-set-protocol/33117 "2023-09-06T14:03:17Z")\
**Posts on this page:** 20\
**Page:** 1

<div class="post-metadata">

**Author:** ![randolf-scholz](https://sea2.discourse-cdn.com/flex002/user_avatar/discuss.python.org/randolf-scholz/32/11123_2.png) [@randolf-scholz](https://discuss.python.org/u/randolf-scholz)\
**Post date:** [September 6, 2023, 2:03pm UTC](https://discuss.python.org/t/why-does-typing-union-types-uniontype-not-implement-the-collections-abc-collection-or-set-protocol/33117/1 "2023-09-06T14:03:17Z")

</div>

It seems extremely natural that the union type should support ` __iter__ `, ` __len__ ` and ` __contains__ `, what’s the design rationale behind not supporting these operations?

---

<div class="post-metadata">

**Author:** ![kpfleming](https://sea2.discourse-cdn.com/flex002/user_avatar/discuss.python.org/kpfleming/32/2761_2.png) [@kpfleming](https://discuss.python.org/u/kpfleming)\
**Post date:** [September 6, 2023, 2:04pm UTC](https://discuss.python.org/t/why-does-typing-union-types-uniontype-not-implement-the-collections-abc-collection-or-set-protocol/33117/2 "2023-09-06T14:04:56Z")

</div>

A Union is an object of a type selected from the list of specified types. It’s not a container.

---

<div class="post-metadata">

**Author:** ![randolf-scholz](https://sea2.discourse-cdn.com/flex002/user_avatar/discuss.python.org/randolf-scholz/32/11123_2.png) [@randolf-scholz](https://discuss.python.org/u/randolf-scholz)\
**Post date:** [September 6, 2023, 2:07pm UTC](https://discuss.python.org/t/why-does-typing-union-types-uniontype-not-implement-the-collections-abc-collection-or-set-protocol/33117/3 "2023-09-06T14:07:00Z")

</div>

But why not? What’s the rationale? Not allowing it seems to make introspection unnecessarily complicated.

---

<div class="post-metadata">

**Author:** ![kpfleming](https://sea2.discourse-cdn.com/flex002/user_avatar/discuss.python.org/kpfleming/32/2761_2.png) [@kpfleming](https://discuss.python.org/u/kpfleming)\
**Post date:** [September 6, 2023, 2:14pm UTC](https://discuss.python.org/t/why-does-typing-union-types-uniontype-not-implement-the-collections-abc-collection-or-set-protocol/33117/4 "2023-09-06T14:14:16Z")

</div>

I don’t understand the question. Does an `int` support the Collection protocol? An object, with a type hint based on Union, could very well contain an `int`.

Also ‘introspection’ typically refers to type analysis, not content analysis. The Collection protocol is used to iterate over the contents.

Can you provide an example of how this would be beneficial to you?

---

<div class="post-metadata">

**Author:** ![randolf-scholz](https://sea2.discourse-cdn.com/flex002/user_avatar/discuss.python.org/randolf-scholz/32/11123_2.png) [@randolf-scholz](https://discuss.python.org/u/randolf-scholz)\
**Post date:** [September 6, 2023, 2:22pm UTC](https://discuss.python.org/t/why-does-typing-union-types-uniontype-not-implement-the-collections-abc-collection-or-set-protocol/33117/5 "2023-09-06T14:22:55Z")

</div>

> Also ‘introspection’ typically refers to type analysis, not content analysis. The Collection protocol is used to iterate over the contents.

The contents of a union are the constituents of the union. For example, one may want to write code with runtime behavior based on type hints, like for example `@dataclass` does.

From a design rationale, what’s gained from disallowing `tuple(int | str | float)`? Instead, one has to use `typing.get_args(int | str | float)`, but it would be _natural_ from a basic mathematical point of view for union to provide `collections.abc.Collection` Protocol.

---

<div class="post-metadata">

**Author:** ![AlexWaygood](https://sea2.discourse-cdn.com/flex002/user_avatar/discuss.python.org/alexwaygood/32/4722_2.png) [@AlexWaygood](https://discuss.python.org/u/AlexWaygood)\
**Post date:** [September 6, 2023, 2:27pm UTC](https://discuss.python.org/t/why-does-typing-union-types-uniontype-not-implement-the-collections-abc-collection-or-set-protocol/33117/6 "2023-09-06T14:27:31Z")

</div>

The object returned from `typing.get_args(<union_object>)` is a container that you can easily inspect.

At runtime, I don’t think it would be the worst thing in the world if you could do this:

```py
X = Union[str, int]

if str in X:
    ...

```

That’s not code that a type checker is ever going to be happy with, though (unless they add a bunch of extra special-casing), due to how heavily they already special-case `Union`. If you want to do introspection on special forms in a way that won’t make the type checker complain, I suggest you use the public-API helper function `get_args`. Most users of the `typing` module find it important to be able to write code type checkers are happy with, so I think it’s unlikely that we’ll be adding these dunders.

---

<div class="post-metadata">

**Author:** ![randolf-scholz](https://sea2.discourse-cdn.com/flex002/user_avatar/discuss.python.org/randolf-scholz/32/11123_2.png) [@randolf-scholz](https://discuss.python.org/u/randolf-scholz)\
**Post date:** [September 6, 2023, 2:34pm UTC](https://discuss.python.org/t/why-does-typing-union-types-uniontype-not-implement-the-collections-abc-collection-or-set-protocol/33117/7 "2023-09-06T14:34:25Z")

</div>

Wouldn’t this be easily solved if `Union` was its own `UnionType`? Like this whole `typing._SpecialForm` seems like the black arts from time to time.

Like, why did it need [PEP604](https://peps.python.org/pep-0604/) to allow `isinstance(x, Union[A,B])`, when the `UnionType` could simply implement custom ` __isinstancecheck__ `?

---

<div class="post-metadata">

**Author:** ![kpfleming](https://sea2.discourse-cdn.com/flex002/user_avatar/discuss.python.org/kpfleming/32/2761_2.png) [@kpfleming](https://discuss.python.org/u/kpfleming)\
**Post date:** [September 6, 2023, 2:34pm UTC](https://discuss.python.org/t/why-does-typing-union-types-uniontype-not-implement-the-collections-abc-collection-or-set-protocol/33117/8 "2023-09-06T14:34:41Z")

</div>

> [@randolf-scholz](#):
>
> contents of a union are the constituents of the union

I don’t think this is correct. A Union in Python is not at all like a C/C++ union. A Python Union is a type hint indicating that the object is one of the specified types, but that’s it. It does not participate in converting the content of the object from one type to another, which can be done in C/C++, nor does it mean that the objects would share the same storage in memory.

There are no ‘constituents of the union’, in Python. There is a single object, which may be one of a limited number of types.

---

<div class="post-metadata">

**Author:** ![randolf-scholz](https://sea2.discourse-cdn.com/flex002/user_avatar/discuss.python.org/randolf-scholz/32/11123_2.png) [@randolf-scholz](https://discuss.python.org/u/randolf-scholz)\
**Post date:** [September 6, 2023, 2:36pm UTC](https://discuss.python.org/t/why-does-typing-union-types-uniontype-not-implement-the-collections-abc-collection-or-set-protocol/33117/9 "2023-09-06T14:36:52Z")

</div>

> I don’t think this is correct. A Union in Python is not at all like a C/C++ union.

It’s still a set-theoretical union of types. It has constituents, which one can acquire in an awkward way via `typing.get_args`.

---

<div class="post-metadata">

**Author:** ![kknechtel](https://avatars.discourse-cdn.com/v4/letter/k/e47c2d/32.png) [@kknechtel](https://discuss.python.org/u/kknechtel)\
**Post date:** [September 6, 2023, 8:32pm UTC](https://discuss.python.org/t/why-does-typing-union-types-uniontype-not-implement-the-collections-abc-collection-or-set-protocol/33117/10 "2023-09-06T20:32:14Z")

</div>

I can’t understand what it is what you want to allow.

You say that you think a union type should support ` __iter__ `. What should be returned? What should happen the first time that you call `next` on this result? The second time, etc.? According to what logic?

You say that you think a union type should support ` __len__ `. What should be the result, and why?

You say that you think a union type should support ` __contains__ `. What logic should it use in order to decide whether something is `in` the union?

Could you give some examples?

> [@randolf-scholz](#):
>
> From a design rationale, what’s gained from disallowing `tuple(int | str | float)`?

Did you mean "I want to annotate that some variable will store a tuple, and every element of the tuple is either an `int`, a `str` or a `float`? That annotation is spelled (in a sufficiently recent version of Python) `tuple[int | str | float]`, with square brackets. For example, in one of my 3.11 virtual environments:

```python
>>> tuple(int | str | float)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: 'types.UnionType' object is not iterable
>>> tuple[int | str | float]
tuple[int | str | float]

```

`tuple(int | str | float)` means “take every element that is in the union type `int | str | float`, and **create a tuple object, right now** , that contains those elements”. It is **completely unrelated** to a proper type annotation, and it is also complete nonsense. **Even if** we agreed “okay, the union type `int | str | float` conceptually ‘contains’ the `int` type, the `str` type and the `float` type”, the result would be an **instance of** `tuple`, not any kind of type.

---

<div class="post-metadata">

**Author:** ![randolf-scholz](https://sea2.discourse-cdn.com/flex002/user_avatar/discuss.python.org/randolf-scholz/32/11123_2.png) [@randolf-scholz](https://discuss.python.org/u/randolf-scholz)\
**Post date:** [September 6, 2023, 8:53pm UTC](https://discuss.python.org/t/why-does-typing-union-types-uniontype-not-implement-the-collections-abc-collection-or-set-protocol/33117/11 "2023-09-06T20:53:34Z")

</div>

> You say that you think a union type should support ` __iter__ ` . What should be returned?

The constituents of the union

> You say that you think a union type should support **len**. What should be the result, and why?

The number of elements of the union

> You say that you think a union type should support ` __contains__ ` . What logic should it use in order to decide whether something is `in` the union?

If it is an element of the union. `y in x ⟺ any(y==x for x in union_object)`.

Here’s an example:

```python
import types
import typing

def int_in_union(union_object: types.UnionType) -> None:
    # if int in union_object: # sad_trombone.mp3
    if int in typing.get_args(union_object): # ugly
        print("int is in the union")
    else:
        print("int is not in the union")

int_in_union(float | str) # prints "int is not in the union"

```

`types.UnionType` is a natural set-like container type. This follows immediately from the properties:

- `Union[x, x]` = `Union[x]`
- `Union[x, y]` = `Union[y, x]`
- `Union[x, Union[y, z]]` = `Union[Union[x, y], z]` = `Union[x, y, z]`

So if it looks like a duck, quacks like a duck, why isn’t it a duck?

> Did you mean "I want to annotate that some variable will store a tuple, and every element of the tuple is either an `int` , a `str` or a `float` ?

No. I want to introspect type-annotations at runtime.

> It is **completely unrelated** to a proper type annotation

It is not supposed to be a type annotation?? This is about runtime introspection of type-hints.

> **Even if** we agreed “okay, the union type `int | str | float` conceptually ‘contains’ the `int` type, the `str` type and the `float` type”, the result would be an **instance of** `tuple` , not any kind of type.

Yes!

---

<div class="post-metadata">

**Author:** ![bryevdv](https://sea2.discourse-cdn.com/flex002/user_avatar/discuss.python.org/bryevdv/32/7424_2.png) [@bryevdv](https://discuss.python.org/u/bryevdv)\
**Post date:** [September 6, 2023, 9:47pm UTC](https://discuss.python.org/t/why-does-typing-union-types-uniontype-not-implement-the-collections-abc-collection-or-set-protocol/33117/12 "2023-09-06T21:47:22Z")

</div>

How are these supposed to work

```auto
Union[int, str, "MyClass"] # you want the string back?

if TYPE_CHECKING:
    from foo import Bar # not otherwise imported

Union[Bar, None] # what is the first iteration value? 

```

---

<div class="post-metadata">

**Author:** ![flyinghyrax](https://sea2.discourse-cdn.com/flex002/user_avatar/discuss.python.org/flyinghyrax/32/12394_2.png) [@flyinghyrax](https://discuss.python.org/u/flyinghyrax)\
**Post date:** [September 6, 2023, 10:43pm UTC](https://discuss.python.org/t/why-does-typing-union-types-uniontype-not-implement-the-collections-abc-collection-or-set-protocol/33117/13 "2023-09-06T22:43:56Z")

</div>

I really like this proposal and agree with it conceptually. I’ve been experimenting with introspecting GenericAlias recently and this makes a lot of sense to me.

That said… oof, that snippet @bryevdv posted makes this look kinda intractable. 🙃

---

<div class="post-metadata">

**Author:** ![sirosen](https://sea2.discourse-cdn.com/flex002/user_avatar/discuss.python.org/sirosen/32/8478_2.png) [@sirosen](https://discuss.python.org/u/sirosen)\
**Post date:** [September 7, 2023, 12:24am UTC](https://discuss.python.org/t/why-does-typing-union-types-uniontype-not-implement-the-collections-abc-collection-or-set-protocol/33117/14 "2023-09-07T00:24:51Z")

</div>

This request “makes sense” at an intuitive level, but I think it’s messy enough in practice to not be worth the marginal gain.

Optional, nested Unions, TypeVarTuple, NewType, and other “corner cases” appear, IMO, to be too numerous to safely add this.  
Implementing ` __contains__ ` would require a lot of decisions and tradeoffs.

I’m not sure the other dunders make sense if containment isn’t included.

---

<div class="post-metadata">

**Author:** ![randolf-scholz](https://sea2.discourse-cdn.com/flex002/user_avatar/discuss.python.org/randolf-scholz/32/11123_2.png) [@randolf-scholz](https://discuss.python.org/u/randolf-scholz)\
**Post date:** [September 7, 2023, 9:09am UTC](https://discuss.python.org/t/why-does-typing-union-types-uniontype-not-implement-the-collections-abc-collection-or-set-protocol/33117/15 "2023-09-07T09:09:01Z")

</div>

That code snippet is not executable, so it’s meaningless to ask about what the first iteration value would be. The first value should be the same what we get back if we iterate `typing.get_args(union_object)`. Since Union is mathematically a set-like container, iteration order probably would not be guaranteed (it is currently by implementation since `Union` stores a `tuple`, but treats it like a `set` whenever necessary.

For instance, `_UnionGenericAlias` casts to `set` for equality comparison:

> <https://github.com/python/cpython/blob/3e53ac99038920550358c1ea0212c3907a8cb385/Lib/typing.py#L1535-L1538>

> Implementing **contains** would require a lot of decisions and tradeoffs.

Given that `Union` already implements set-equality, there is only one sensible definition of ` __contains__ `, which is the one that makes it compatible with ` __eq__ `, in the sense that `Union1 == Union2` if and only if `all(x in Union2 for all x in Union1) and all(y in Union1 for y in Union2)`

---

<div class="post-metadata">

**Author:** ![bryevdv](https://sea2.discourse-cdn.com/flex002/user_avatar/discuss.python.org/bryevdv/32/7424_2.png) [@bryevdv](https://discuss.python.org/u/bryevdv)\
**Post date:** [September 7, 2023, 4:26pm UTC](https://discuss.python.org/t/why-does-typing-union-types-uniontype-not-implement-the-collections-abc-collection-or-set-protocol/33117/16 "2023-09-07T16:26:39Z")

</div>

My question was not about the order, it was about deferred annotations. Here is a complete, valid, runnable script:

```python
from __future__ import annotations

def foo(arg: Bar | None): pass

```

What are you hoping to see reported for “Bar” _at runtime_.

Maybe a sensible answer is in [PEP 649](https://peps.python.org/pep-0649/), which will change how deferred annotations work, but I think the onus is on you to demonstrate that.

---

<div class="post-metadata">

**Author:** ![randolf-scholz](https://sea2.discourse-cdn.com/flex002/user_avatar/discuss.python.org/randolf-scholz/32/11123_2.png) [@randolf-scholz](https://discuss.python.org/u/randolf-scholz)\
**Post date:** [September 7, 2023, 4:42pm UTC](https://discuss.python.org/t/why-does-typing-union-types-uniontype-not-implement-the-collections-abc-collection-or-set-protocol/33117/17 "2023-09-07T16:42:07Z")

</div>

> [@bryevdv](#):
>
> What are you hoping to see reported for “Bar” _at runtime_.

The same thing that `typing.get_args(union_object)` would report. If that changes with PEP649, so be it.

---

<div class="post-metadata">

**Author:** ![bryevdv](https://sea2.discourse-cdn.com/flex002/user_avatar/discuss.python.org/bryevdv/32/7424_2.png) [@bryevdv](https://discuss.python.org/u/bryevdv)\
**Post date:** [September 7, 2023, 4:46pm UTC](https://discuss.python.org/t/why-does-typing-union-types-uniontype-not-implement-the-collections-abc-collection-or-set-protocol/33117/18 "2023-09-07T16:46:32Z")

</div>

Using your code above, with deferred annotations, `get_args` returns nothing:

```auto
In [1]: from __future__ import annotations
   ...:
   ...: import typing
   ...:
   ...: def foo(arg: Bar | None = None):
   ...: print(typing.get_args(arg))
   ...:
   ...: foo()
()

```

Edit: I guess you actually pass `int | str` as a _parameter_ in your code? That’s not actually how anyone uses type annotations. Perhaps more relevantly, deferrerd annotations are just _strings_ (currently, pre-PEP 649). There is no “union object” to inspect:

```python
In [8]: def foo(arg: Union[Bar, None]): pass

In [9]: get_annotations(foo)
Out[9]: {'arg': 'Union[Bar, None]'}

```

---

<div class="post-metadata">

**Author:** ![randolf-scholz](https://sea2.discourse-cdn.com/flex002/user_avatar/discuss.python.org/randolf-scholz/32/11123_2.png) [@randolf-scholz](https://discuss.python.org/u/randolf-scholz)\
**Post date:** [September 7, 2023, 4:54pm UTC](https://discuss.python.org/t/why-does-typing-union-types-uniontype-not-implement-the-collections-abc-collection-or-set-protocol/33117/19 "2023-09-07T16:54:12Z")

</div>

~~That might actually be a bug. I am not using deferred annotations a lot, but I’d expect in this case that `typing.get_args` would give something like `("Bar", int)`.~~

EDIT: deferred annotations simply turns everything into strings.

---

<div class="post-metadata">

**Author:** ![randolf-scholz](https://sea2.discourse-cdn.com/flex002/user_avatar/discuss.python.org/randolf-scholz/32/11123_2.png) [@randolf-scholz](https://discuss.python.org/u/randolf-scholz)\
**Post date:** [September 7, 2023, 4:59pm UTC](https://discuss.python.org/t/why-does-typing-union-types-uniontype-not-implement-the-collections-abc-collection-or-set-protocol/33117/20 "2023-09-07T16:59:37Z")

</div>

> Edit: I guess you actually pass `int | str` as a prameter in your code? That’s not actually how anyone uses type annotations.

That’s just not true lol, popular libraries like [`pydantic`](https://docs.pydantic.dev/latest/usage/models/) introspect type-annotations at runtime to modify runtime behavior.

[Next page](https://discuss.python.org/t/why-does-typing-union-types-uniontype-not-implement-the-collections-abc-collection-or-set-protocol/33117.md?page=2)
