Python の黒魔術

このスライドにインスパイアされて書きました. このブログに議事録があります.
俺はここ半年くらい Python にはまり, 色々細かいところまで調べて勉強していました. その結果, このスライドに載っている Ruby コードを黒魔術と感じない身体になってしまいました. そんな俺が同じネタを Python で書いたらどうなるんだろう? と思い, (いつものことですが) 勢いのみで書きました.

構成はスライドに合わせてあります.
python3.1 を使用してます.

自己紹介

基本プロフィール
プログラミング歴
  • C 歴 6 年. 大学の講義 (数値計算) で C を始める.
  • Python 歴 6 ヶ月. 実は jython から入った.
  • java 歴 2 年.
  • ※本は書いたことないです.

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() # インスタンス化もできちゃう

参考: http://blog.livedoor.jp/kikwai/archives/51274308.html

メソッドも動的に生成

特異メソッドと同じように 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__ # 属性に値をセットしようとしたときに呼び出されます.

後書き

竜頭蛇尾な文章になっちゃいましたが, とりあえず載せます. ツッコミ歓迎ですので「こんなんじゃ黒魔術と呼べん. 本当の黒魔術とはこれだ!」という意見や「もっとここ詳しく」という意見まで何でもお待ちしております.

そして本当の黒魔術「メタクラス」についてはまた後日扱いたいと思います. それでは(^-^)/