Python の黒魔術
このスライドにインスパイアされて書きました. このブログに議事録があります.
俺はここ半年くらい Python にはまり, 色々細かいところまで調べて勉強していました. その結果, このスライドに載っている Ruby コードを黒魔術と感じない身体になってしまいました. そんな俺が同じネタを Python で書いたらどうなるんだろう? と思い, (いつものことですが) 勢いのみで書きました.
構成はスライドに合わせてあります.
python3.1 を使用してます.
自己紹介
Python の特徴
Ruby の特徴と同じじゃないか, と言われそうですが, 実際両者は良く似ていると思います. 表記の簡潔さを目指したり, できることを制限しない思想が共通していると感じを受けます.
動的: 黒魔術
メソッドが実行時に生成される
hoge(123) # NameError def hoge(value): pass # -------------------- def hoge(value): pass hoge(123) # OK!
クラスが実行時に生成される
A() # NameError class A: pass # -------------------- class A: pass A() # OK!
パース時ではなくて実行時
if random.randint(0, 1) == 1: class A(str): pass else: class A(int): pass
オープンクラス
残念ながら str や int などは書き換えられないようです.
http://www.nishiohirokazu.org/blog/2006/10/intstr.html
その代わりと言ってはなんですが, 以下のようにインスタンスの所属するクラスを差し替えることができます. こんな言語だとチーム開発大変そう…….
class Person: def balse(self): print('こいつは君の手にある時にしか働かない') muska = Person() muska.balse() class Ur: def balse(self): print('目が, 目がぁ!') muska.__class__ = Ur muska.balse()
特異メソッド
オブジェクトにメソッドが追加できちゃいます. Ruby と違って特異メソッド用の構文があるわけではないので, types モジュールを使用します.
import types class Person: pass muska = Person() def balse(): print('目が, 目がぁ!') muska.balse = types.MethodType(balse, muska) muska.balse() sheeta = Person() sheeta.balse() # AttributeError: 'Person' object has no attribute 'balse'
演算子もメソッド
3 + 5 # 8 (3).__add__(5) # 8 class StrangeInt(int): def __add__(self, i): return self * i three = StrangeInt(3) three + 5 # 15
クラスもオブジェクト
c = type('', (), {}) # 名無しクラス (名前が '') o = c() # インスタンス化もできちゃう
メソッドも動的に生成
特異メソッドと同じように types モジュールを使ってます.
import types hoge = 'hoge' class Hoge: def __init__(self): def hemo(self): return hoge self.__dict__[hoge] = types.MethodType(hemo, self) h = Hoge() h.hoge() # hoge
たとえばこんな感じ
しかし, こうすると上手く行かんなぁ. for 文を回す中での name の変更の影響を受けちゃうみたいです.
import types names = ['hoge', 'fuga', 'hemo'] class Hoge: def __init__(self): for name in names: def hemo(self): return name self.__dict__[hoge] = types.MethodType(hemo, self) h = Hoge() h.hoge() # hemo h.fuga() # hemo h.hemo() # hemo
定数の動的取得
Python には定数という言語要素は無いので, 代わりにクラスフィールドを取得してみます.
[f for f in dir(Hoge) if not re.match('__.*__', f) and not hasattr(Hoge.__dict__[f], '__call__')]
文字列からメソッド呼び出し
__dict__ を駆使します.
class Hoge: def fuga(self): print('FUGA') name = 'Hoge.fuga' cname, mname = name.split('.') klass = globals()[cname] obj = klass() klass.__dict__[mname](obj)
宣言っぽいもの
Python では関数呼び出しの丸括弧は省略できないので, Ruby みたいには行きませんね. 残念.
:port ってここで初めて出てくるのかな? 正直, このコロン付きの変数の意味が良く分からんので, 知ってる方教えてください.
設定ファイル
# setting.py proto = 'smtp' hostname = 'example.com' port = 25
これを
from setting import *
もしくは
import setting
辞書 (dict) を使った設定も同様にできます. 面倒なので省略します.
その他
- __getattr__ # 存在しない属性を取得しようとしたときに呼び出されます.
- __setattr__ # 属性に値をセットしようとしたときに呼び出されます.