generated from cotes2020/chirpy-starter
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
1 parent
c9a515b
commit 68e99ed
Showing
1 changed file
with
102 additions
and
0 deletions.
There are no files selected for viewing
102 changes: 102 additions & 0 deletions
102
_posts/2023-12-20-til-list-from-typing-module-is-inheritable.md
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,102 @@ | ||
--- | ||
title: List from Typing module is inheritable | ||
date: 2023-12-20 11:55:00 +05:30 | ||
categories: [Python, TIL, List, Typing] | ||
tags: [python, til, list, typing] # TAG names should always be lowercase | ||
--- | ||
|
||
> TIL - "Today I learned" series of Posts are short notes of new things I encounter with Python. | ||
While browsing the source of SQLAlchemy, I came across a [custom list class](https://github.com/sqlalchemy/sqlalchemy/blob/b12b7b120559d07a6f24fb2d6d29c7049084b4a5/lib/sqlalchemy/ext/orderinglist.py#L231) | ||
|
||
The custom class is created by inheriting from `List` from `typing`, rather than the builtin `list` or the `UserList` class provided by [collections](https://docs.python.org/3/library/collections.html#userlist-objects) | ||
|
||
It works. | ||
|
||
```python | ||
from typing import List | ||
|
||
|
||
class CustomList(List): | ||
pass | ||
|
||
|
||
print(CustomList.__mro__) | ||
|
||
# (<class '__main__.CustomList'>, <class 'list'>, <class 'typing.Generic'>, <class 'object'>) | ||
``` | ||
|
||
The builtin `list` can be seen as a parent class of CustomList. | ||
|
||
Normal list operations also work as expected. | ||
|
||
```python | ||
from typing import List | ||
|
||
|
||
class CustomList(List): | ||
pass | ||
|
||
|
||
c = CustomList() | ||
c.append("foo") | ||
print(c) | ||
|
||
# ['foo'] | ||
``` | ||
|
||
Now, what if we try to instantiate `List` directly? | ||
|
||
```python | ||
list_obj1 = list() # Works | ||
list_obj2 = List() # TypeError: Type List cannot be instantiated; use list() instead | ||
``` | ||
|
||
Let's see why this happens. | ||
|
||
The `List` class is defined in typing as an alias of the `list` class. | ||
|
||
```python | ||
# CPython - typing.py (https://github.com/python/cpython/blob/4afa7be32da32fac2a2bcde4b881db174e81240c/Lib/typing.py#L2609) | ||
|
||
List = _alias(list, 1, inst=False, name='List') | ||
``` | ||
|
||
This `_alias` creates `List` as a child instance of `_BaseGenericAlias`. See that the `inst` attribute is set to False above. | ||
|
||
|
||
Now, let's take a look at the `_BaseGenericAlias` class: | ||
```python | ||
# CPython - typing.py (https://github.com/python/cpython/blob/4afa7be32da32fac2a2bcde4b881db174e81240c/Lib/typing.py#L1107) | ||
|
||
class _BaseGenericAlias(_Final, _root=True): | ||
"""The central part of internal API. | ||
This represents a generic version of type 'origin' with type arguments 'params'. | ||
There are two kind of these aliases: user defined and special. The special ones | ||
are wrappers around builtin collections and ABCs in collections.abc. These must | ||
have 'name' always set. If 'inst' is False, then the alias can't be instantiated, | ||
this is used by e.g. typing.List and typing.Dict. | ||
""" | ||
def __init__(self, origin, *, inst=True, name=None): | ||
self._inst = inst | ||
self._name = name | ||
self.__origin__ = origin | ||
self.__slots__ = None # This is not documented. | ||
|
||
def __call__(self, *args, **kwargs): | ||
if not self._inst: | ||
raise TypeError(f"Type {self._name} cannot be instantiated; " | ||
f"use {self.__origin__.__name__}() instead") | ||
... | ||
``` | ||
|
||
The call dunder is overridden to raise a type error if the `_inst` attribute of the alias is False. The `__call__` function gets invoked when we call the children of the `_BaseGenericAlias` class, in our case, the `List`. | ||
|
||
As you can see in the docstring of `_BaseGenericAlias`: | ||
|
||
> If 'inst' is False, then the alias can't be instantiated, this is used by e.g. typing.List and typing.Dict. | ||
Summarizing, | ||
- `typing.List` behaves like a `list` when used for inheritance | ||
- But can't be used directly, just like the `list` for instantiation. |