[Python] build-in objectに独自のメソッドを追加する禁断の果実

Pythonはクラスに後からメソッドが追加できる言語です。

class A:
    pass

A().hello()
# Traceback (most recent call last):
#   File "<stdin>", line 1, in <module>
# AttributeError: 'A' object has no attribute 'hello'

def hello(self):
    print("hello")

setattr(A,'hello',hello)
A().hello()
# hello

しかし、それはあくまでpythonで定義されたクラスのみで、build-in クラスであるintstrはその限りではありません。

setattr(int,'hello',hello)
# Traceback (most recent call last):
#   File "<stdin>", line 1, in <module>
# TypeError: can't set attributes of built-in/extension type 'int'

実際にやってみるとこのようにエラーが出てしまいます。

しかし、その制限を突破してbuild-inクラスにメソッドを追加できるライブラリを発見したので記しておきます。

禁断の果実

そのライブラリはforbidden fruit(禁断の果実)と名付けられており、いかにもな感じです。

forbiddenfruit
https://github.com/clarete/forbiddenfruit

インストール

pip install forbiddenfruit

メソッドの追加

from forbiddenfruit import curse

def rmspace(self):
    return self.replace(" ", "")

curse(str, "rmspace", rmspace)
print("hello world !".rmspace())

# "helloworld !"

curse(呪い)なんていう関数名になってます。
禁断の関数ってことですかね?

メソッドの削除

from forbiddenfruit import reverse

reverse(str,"rmspace")

print("hello world !".rmspace())
# Traceback (most recent call last):
#   File "<stdin>", line 1, in <module>
# AttributeError: 'str' object has no attribute 'rmspace'

一方、削除する方はreverseなので呪いをはね返すとかそんな意味なんでしょうか?

ちょっとお試し

reverse(str,"split")
"a".split()
# Traceback (most recent call last):
#   File "<stdin>", line 1, in <module>
# AttributeError: 'str' object has no attribute 'split'

なんと、元からあるメソッドを消し去ることも出来るようです。
恐ろしい…

どんな原理?

どんな原理、方法でこのようなことを実現しているのか気になったので少し、実装を眺めてみました。

本体は、forbiddenfruit/__init__.pyのようです。

コードを貼ると大変そうなので、貼ったりはしませんが、中でctypesモジュールを利用してcの低レベルAPIを触っているようです。

PyObjectが分かる人ならわかると思いますが、オブジェクトの持つ属性の辞書にcレベルでメソッドを追加、削除しています。

これなら同じ内容をcで書いても作れそうです(と言うかcで書いてあると思ってました)。

Python本体の実装を読んだことがある人ならある程度わかる内容ですね。

さいごに

build-in クラスにメソッドを追加できて便利そうではありますが、他の人が読んだ時に可読性が著しく低下しそうな気もするので、使い所には気をつけたいですね。

参考

https://github.com/clarete/forbiddenfruit