10
8

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

Pythonのメタプログラミングについて

Posted at

はじめに

エキスパートPython第二版を読んで、メタプログラミングについて学んだことを備忘録としてまとめる.

メタプログラミング

メタプログラミングとは自分自身をデータとして扱うことができるコンピュータプログラミングを作成する技術.いわば,通常のプログラミングが実行できるようにするために行うプログラミングのことを指す.

実行中に自分自身のイントロスペクション(注1)を行ったり,コード生成及び修正が可能.

注1.イントロスペクションとは、メモリ上の他のモジュールや関数をオブジェクトとして参照し、それらの情報を取得し、あやつるためのコードのこと

また,メタプログラミングについてwikipedia では以下のように定義されている.

メタプログラミング (metaprogramming) とはプログラミング技法の一種で、ロジックを直接コーディングするのではなく、あるパターンをもったロジックを生成する高位ロジックによってプログラミングを行う方法

Pythonには,メタプログラミングの2つの主要な方法の区別がある.

  • 方法①:関数,クラス,型などの基本要素のイントロスペクションを行い,その場で作成したり変更する方法
    • 該当するツール:既存の関数,メソッド,クラスに対して,何らかの機能を追加するデコレータ
  • 方法②:クラスのインスタンス作成プロセスに干渉できる特別なクラスメソッド
    • 該当するツール:メタクラス

方法①デコレータとクラスデコレータについて

関数デコレータは関数オブジェクトを引数にとって,実行時に,引数でとった関数オブジェクトに変更を加えるもの.
これは,オリジナルの関数がどのように実装されているかをインスペクションして,異なる結果を作り出すことが可能.

クラスデコレータも関数デコレータと同様な役割を果たす.違いとして,クラスデコレータは関数オブジェクトではなくクラスを返す.

また,関数デコレータは,クラスデコレータにも適用できる.それにより,クロージャが使用可能で,パラメータの設定が可能.
ただし,最終的に生成されるクラスがデコレータ内部で動的に作られるクラスであるため,__name____doc__属性に影響を与える.


def parametrized_short_repr(max_width=8):
    """文字列表現を短縮する,パラメータつきデコレータ"""
    def parametrized(cls):
        """実際のデコレータとして使用される内部ラッパー関数"""
        class ShortlyRepresented(cls):
            """デコレートされた動作を提供するサブクラス"""
            def __repr__(self):
                        return super().__repr__()[:max_width]
        
        return ShortlyRepresented
    
    return parametrized

@parametrized_short_repr(8)
class ClassWithLittleBitLongerLongName:
    pass

ClassWithLittleBitLongerLongName().__doc__ #'デコレートされた動作を提供するサブクラス'

方法②メタクラスについて

メタクラスは,他のクラス自体を定義するためのクラス.

メタクラスのポイント

  • オブジェクトのインスタンスを定義するクラスもオブジェクトである点
  • クラス定義で使われる型(クラス)は組み込みのtype型
  • 通常,新しいメタクラスはtypeクラスのサブクラスを使用(typeクラスを継承したメタクラスのテンプレートを後述)

type()クラスを使用すると,クラス構文を使ってクラスを作成するのと同等のことができる.

クラス構文を使ってクラスを作成する場合,このときのメタクラスはtypeクラスを指定したことになる.

組み込み関数type()の使い方

書式:type(name, bases, namespace)

  • 引数name:クラス名を指定.__name__属性に格納
  • 引数bases:親クラスのリストを指定.__bases__属性に格納.MRO(メソッド順序解決)構築で使用される.
  • 引数namespace:クラス本体の定義が入った名前空間.__dict__属性に格納.
def func(self):
    print("funcメソッド")
cls = type('MyClass',(object,),{'method':func})
instance = cls()
instance.method() #「funcメソッド」と標準出力される
print(dir(instance))
# 以下,print(dir(instance))の結果
['__class__', '__delattr__', '__dict__', '__dir__', '__doc__', 
'__eq__', '__format__', '__ge__', '__getattribute__', '__gt__', 
'__hash__', '__init__', '__init_subclass__', '__le__', '__lt__', 
'__module__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', 
'__repr__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__', 
'__weakref__', 'method']

typeクラスを継承したクラスのテンプレ

  • __new__(mcs, name, bases ,namespace):クラスのインスタンスの生成する責務を持った静的メソッド.クラス構文と同様,インスタンスの生成を行い,__init__()メソッドよりも前に呼ばれる.

  • __prepare__(mcs, name, bases,**kwargs):空の名前空間オブジェクトを作成する

  • __init__(cls, mcs, name, bases, **kwargs):__new__()で作成したインスタンス作成を追加で行う

  • __call__(cls, *args, **kwargs):メタクラスのインスタンスが呼び出されたときに呼ばれるメソッド

metaclass.py
if __name__ == "__main__":
        
    class Metaclass(type):
        def __new__(mcs, name, bases ,namespace):
            print(mcs,'__new__()発動')
            return super().__new__(mcs, name, bases, namespace)
        
        @classmethod
        def __prepare__(mcs, name, bases,**kwargs):
            print(mcs,'__prepare__()発動')
            return super().__prepare__(mcs, name, bases,**kwargs)
        
        def __init__(cls,name, bases, namespace, **kwargs):
            print(cls,'__init__()発動')
            super().__init__(name, bases, namespace)
        
        def __call__(cls, *args, **kwargs):
            print(cls,'__call__()発動')
            return super().__call__(*args, **kwargs)

    class RevealingClass(metaclass=Metaclass):
        def __new__(cls):
            print(cls,"__new__発動")
            return super().__new__(cls)
        
        def __init__(self):
            print(self,"__init__発動")
            super().__init__()

$python metaclass.py
<class '__main__.Metaclass'> __prepare__()発動
<class '__main__.Metaclass'> __new__()発動
<class '__main__.RevealingClass'> __init__()発動

なお,metaclass.pyをもとに以下の文を実行

instance = RevealingClass()
<class '__main__.RevealingClass'> __call__()発動
<class '__main__.RevealingClass'> __new__発動
<__main__.RevealingClass object at 0x0000019B8509D320> __init__発動

RevealClass()によるインスタンスの生成と同時に,Metaclassの__call__()が最初に呼び出されていることがわかる.その後,RevealClassのnew()メソッドとinit()メソッドが順に呼び出されている.

メタクラスの使用について

  • メタクラスの機能はかなり有能,しかし,コードは大体複雑になる.
  • すべてのクラスで問題なく動くことを確認するのは困難かつコードの信頼性も下がる
  • 属性値の挙動を変更したり,追加したする程度であれば,プロパティやディスクリプタなどの代替手法が存在するためメタクラスは使用しなくてもよい
10
8
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
10
8

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?