Python Metaclass初探査

4290 ワード

まず、Python Metapgrammingに関する大きな牛の有名な言葉で始まります.
Metaclasses are deeper magic than 99% of users should ever worry about. If you wonder whether you need them, you don’t (the people who actually need them know with certainty that they need them, and don’t need an explanation about why). – Tim Peters
Metaclassesは99%のユーザーが気を使う必要のないブラックテクノロジーです.もしあなたがまだそれを必要としているかどうか悩んでいるなら、答えはNOです(本当に必要な人は説明する必要はありません)-Tim Peters
これは何のでたらめですか.道可道、非常道ですか?
Meta?
はい、Bはもう終わりました.これは確かに辺鄙で、よく使われない話題です.1編の短文はきっと終わらないに違いない.だから初探と呼ばれています.
英語metaという言葉は実はギリシャ語から借りたものです.wikipediaの説明は次のとおりです.
indicate a concept which is an abstraction behind another concept, used to complete or add to the latter
見なくてもいいですが、実は見たほうがもっとめまいがします.幸い後の説明には「より高い抽象」という言葉があり、理解に役立つ.実は私たちはこのように理解することができます.metaの意味は「何について」です.例えばmetadataは「データに関するデータ」と理解でき、metaprogrammingは「プログラミングに関するプログラミング」と理解できます.これは「より高い抽象」と一致している.同時に、プログラミングのもう一つの永遠のテーマ-recursionと結びついています.
また、metaという言葉は天朝側から「元」、海峡対岸側から「後設」と訳されています.実は私はどこから来たのかよく分かりません.
≪インスタンス|Instance|emdw≫
今日のトピックに焦点を当てて、metaprogrammingはコードを生成するためのコードを作成します.
任意の複雑な演算式の値を計算するためにNBの関数を書いたと仮定します:1+2,3*6+10のように、何でも計算に任せることができます.このような関数のアルゴリズムは私たちのテーマではありませんので、pythonが持っている大きな技eval()を出してください.1行でできます.
def calc(expression):
    return eval(expression)

入力の可能性は無限なので、この関数をよくテストしなければなりません.100以上のtest caseを考えたと仮定します.また、unittestというmoduleでテストを行ったと仮定します.このようなテストプログラムは一般的にこのように成長します.
import unittest

class TestStringMethods(unittest.TestCase):

    def test_upper(self):
        self.assertEqual('foo'.upper(), 'FOO')

    def test_isupper(self):
        self.assertTrue('FOO'.isupper())
        self.assertFalse('Foo'.isupper())

    def test_split(self):
        s = 'hello world'
        self.assertEqual(s.split(), ['hello', 'world'])
        # check that s.split fails when the separator is not a string
        with self.assertRaises(TypeError):
            s.split(2)

if __name__ == '__main__':
    unittest.main()

そこで私たちの目的はmetaprogrammingで上記のようなテストクラスを自動的に生成することです.
先にプログラムを行ってから説明します.
#!/usr/bin/python3
import unittest

def calc(expression):
    return eval(expression)

def add_test(name, asserts):
    def test_method(asserts):
        def fn(self):
            left, right = asserts.split('=')
            expected = str(calc(left))
            self.assertEqual(expected, right)
        return fn

    d = {'test1': test_method(asserts)}
    cls = type(name, (unittest.TestCase,), d)
    globals()[name] = cls

if __name__ == '__main__':
    for i, t in enumerate([
            "1+2=3",
            "3*5*6=90"]):
        add_test("Test%d" % i, t)
    unittest.main()

NBのcalc()関数を説明した.mainのこの段落も簡単です.私たちは宣言でテストのセットを定義し、unittestで実行します.
ちょっと複雑なのはadd_test()です.まず、最内層のfn(self)という方法を見てみましょう.論理的には、入力されたテスト例を2つに分け、1つはcalc()の入力であり、1つは私たちが期待している結果である.次いで、calc()が呼び出され、次いでassertEqual()でテストされる.
しかし、このselfはちょっとおかしいです.ここには類がありません.どこから来たのselfですか.実はfn(self)は確かにクラスの方法ですが、このクラスは私たちがコードによって動的に生成したものです.つまり、次の行です.
cls = type(name, (unittest.TestCase,), d)

ここでのtype()は、通常、ある変数のタイプをチェックするために使用される関数です.ただ、あまり知られていないもう一つの形式があります.
class type(name, bases, dict)

この2つ目の形式は、新しいタイプを生み出します.我々のプログラムを例にとると、unit.TestCaseをbaseclassとして、TestNという新しいタイプが生成され、タイプ変更の実装はdによって与えられ、dはclosureによって返されるfn(self)という方法を含む.ただ、この新しいクラスでは、test1()と呼ばれています.
最後に,この新しく生成されたクラスを現在のグローバルシンボルテーブルに追加することは,上述したunittestの例に相当する.
だから、まとめてみましょう.このスクリプトを実行すると、この短いコードは、テストの式ごとに新しいテストクラスを生成し、テストを動的に生成する方法をクラスにロードします.unitestは、globalsからこれらのクラスを見つけ、テストを1つずつ実行する.
上の例では、実は1行1行calc(1+2) == 3を手で打っても大したことはありません.しかし、表現する論理が複雑な場合、metaprogrammingの強さが現れます.
まとめ
では、この文章を読んで、私たちもTimが言った1%のプログラム猿になりました!実は、99%のプログラミング作業ではこのようなテクニックが使えないという意味かもしれません.いくつかの特殊な場合、例えばあるフレームワークを書くとき、metaprogrammingは仕事の半分の仕事をすることができます.実践の中でこのような機会に出会うことを祈っています.