Python types ライブラリ。標準ライブラリでありながら名前から何をしてくれるライブラリなのかがわかりにくい一品です。そんな types を紹介します。
頭の片隅に置いておくと、ふとした時に役に立つ、そんないぶし銀なライブラリです。
組み込み型へのアクセス
組み込みインスタンス None の型は? 関数オブジェクトの型は? types ライブラリはそういった「builtin 空間に置くほどではない、しかしそれでもインタプリタが使用していて存在している型」へのアクセスのための口であります。
>>> types.NoneType is type(None)
True
>>> def a(): pass
>>> isinstance(a, types.FunctionType)
True
Python 2.7 のドキュメントを確認すると、この機能だけが記述されています。 types の当初の役目はこれだけだったのでしょう。しかし、その後、型関連のユーティリティ関数が追加されていくことになりました。
MappingProxyType 読み出し専用のマッピングのプロキシ
組み込みのシーケンスには読み書きができる list と読み出し専用の tuple がありますが、組み込みのマッピングには読み書きができる dict だけがあり読み出し専用のものがありません。集合型 set にすら対になる読み出し専用集合 frozenset があるにもかかわらず、です。更新不可能なマッピングをつくりたい。そんな希望を間接的に解決してくれるのが types.MappingProxyType です。
MappingProxyType に dict などのマッピングを渡すと、読み出し専用のビューが得られます。元となったマッピングは依然として存在しており変更が加わるとビュー側にも影響が及ぶため真に不変というわけではないです。それでも元のマッピングを外部から隠しておくことで、誤操作で変更されるといった事態を防ぐことができます。
proxy = types.MappingProxyType({0: 1})
print(proxy[0]) # 1
print(0 in proxy) # True
proxy[0] = 100 # TypeError
SimpleNamespace 名前空間の作成
データ、たとえばとあるアプリケーションの設定や定数をおいておいて後で取り出すための名前空間が欲しい。でもモジュールグローバルは汚したくない。これのためだけのモジュールは過剰、空のクラスでも過剰。 object だと不足。 dict だとカッコと文字列が面倒。そんな要望に応えてくれるのが types.SimpleNamespace です。
>>> conf = types.SimpleNamespace()
>>> import datetime
>>> conf.date = datetime.date(2022, 12, 6)
>>> conf.title = 'Python 標準ライブラリ types の紹介'
>>> conf
namespace(date=datetime.date(2022, 12, 6), title='Python 標準ライブラリ types の紹介')
>>> print(f'{conf.title}: 公開日 {conf.date}')
Python 標準ライブラリ types の紹介: 公開日 2022-12-06
ドキュメントにもあるようになんらかのデータソースからの入力を表現するような、インスタンスを大量に作っては破棄してをくり返す予定のレコード型を用意したい場合は collections.namedtuple, typing.NamedTuple のほうが適しています。
new_class 動的な型生成
class 文を使わずにクラスを作る方法として、 type をはじめとするメタクラスを実行するという方法があります。しかし、その作法はどうしても複雑になってしまっているものです。そこで、いつもの手順とでもいうべき処理をまとめてくれているのが types.new_class です。
>>> X = types.new_class('X')
>>> obj = X()
>>> obj
<types.X object at 0x0000015F145AA610>
types.new_class コード(Python 3.11.0): https://github.com/python/cpython/blob/v3.11.0/Lib/types.py#L67
DynamicClassAttribute クラス属性のカスタマイズ
property と同じように使えるデコレータ、それが types.DynamicClassAttribute です。 property との違いは、クラス越しにアクセスされたときに AttributeError を送出することです。このエラーで __getattr__ メソッドの呼び出しを促すことにより、インスタンス越しの属性アクセスとクラス越しでの属性アクセスの結果を出しわける実装が可能になります。 enum.Enum の name, value の機構で使われているものです。
import types
class A:
@types.DynamicClassAttribute
def d(self):
return 'dynamic_class_attr'
@property
def p(self):
return 'property'
a = A()
print(a.d) # 'dynamic_class_attr'
print(a.p) # 'property'
# A.d # AttributeError
print(A.p) # <property object at 0x0000015F14652630>
class BMeta(type):
def __getattr__(self, name):
return f'metaclass __getattr__ {name}'
class B(metaclass=BMeta):
@types.DynamicClassAttribute
def d(self):
return 'dynamic_class_attr'
b = B()
print(b.d) # dynamic_class_attr
print(B.d) # metaclass __getattr__ d
eum.Enum 当該箇所のコード(Python 3.11.0)1: https://github.com/python/cpython/blob/v3.11.0/Lib/enum.py#L1221-L1229
object.__getattr__ の公式ドキュメント: https://docs.python.org/ja/3.11/reference/datamodel.html?highlight=__getattr__#object.__getattr__
言語リファレンス - データモデル - メタクラス: https://docs.python.org/ja/3.11/reference/datamodel.html#metaclasses
coroutine
ジェネレータ関数として書かれた旧式のコルーチンを、 async def で作られる現行のコルーチン関数に変換するユーティリティーです。 Python 3.4 時代のコードの変換に用いられるもので、 2022 年現在、新たに使うことはないものです。
おわりに
汎用的名前をしているうえに、複数の方向性をもつ関数が寄せ集められているのでつかみどころがない標準ライブラリ typesでしたが紹介してみました。ぜひ、つかってあげてください。
-
このコードの property はビルトインのではなく class property(DynamicClassAttribute) と定義されている enum ライブラリ独自のもの ↩