1
2

More than 1 year has passed since last update.

Pythonのバグ技 —— クラス以外を継承する

Posted at

どういうこと??

Pythonでのクラスの継承は次のように書きます。

class B:
    """The base class."""

class C(B):
    """A new class that inherits B."""

ここでのBはクラス、言い換えればtype型である必要が...ありません。

クラス以外を継承する方法

適当なオブジェクトを用意します。

class _X:
    pass
B = _X()

このオブジェクトに__mro_entries__を追加します。__mro_entries__は、(クラス以外のオブジェクトを含む)全て継承元の型を引数に、(クラスのみからなる)実際の継承元の型を返すようにします。この特殊メソッドが継承時に呼び出されます。

B.__mro_entries__ = lambda bases: (str,)

これでstrのサブクラスが定義できます。

class C(B):
    pass
c = C("abc")
type(c)  # Out: __main__.C

__mro_entries__さえあれば何でもよいので、__mro_entries__を無理やりセットできるものであればなんでも行けます。たとえば、こういうやばい使い方もできます。

import math

math.__mro_entries__ = lambda bases: (str,)

class C(math):
    pass
c = C("abc")
type(c)  # Out: __main__.C

実際にどう使われるの?

継承の時点で、たとえば多重継承を検出してエラーにすることとかができます。

def _mro_entries(bases: tuple):
    if len(bases) > 1:
        raise TypeError("Multiple inheritance is forbidden")
    return (str,)
B.__mro_entries__ = _mro_entries

通常はメタクラスを定義することで実現されますが、継承元を確認するだけならこっちの方が楽だと思います。

ちなみに標準実装のNamedTupleも、常に継承する形で使われますが、その実態は関数です。

from typing import NamedTuple
type(NamedTuple)  # Out: function

そのため、怖いことにisinstanceチェックができません。注意しましょう。

class Point(NamedTuple):
    x: int
    y: int

p = Point(x=0, y=1)
isinstance(p, NamedTuple)  # TypeError: isinstance() arg 2 must be a type or tuple of types
1
2
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
2