メタクラスの(具体的な)ユースケースは何ですか?


118

私にはメタクラスを使用するのが好きな友人がいて、定期的にそれらをソリューションとして提供しています。

メタクラスを使用する必要はほとんどないということを心に留めています。どうして?私があなたがクラスに対してそのようなことをしているなら、あなたはおそらくそれをオブジェクトに対して行うべきであると考えているので。そして、小さなリデザイン/リファクタリングが必要です。

メタクラスを使用できるようになったことで、多くの場所で多くの人々がクラスをある種の2番目のレートのオブジェクトとして使用するようになりました。プログラミングはメタプログラミングに置き換えられますか?残念ながら、クラスデコレータの追加により、さらに受け入れやすくなっています。

ですから、Pythonでのメタクラスの有効な(具体的な)ユースケースを知りたいのです。あるいは、クラスの変更がオブジェクトの変更よりも優れている理由について啓発されることもあります。

始めます:

サードパーティのライブラリを使用する場合、クラスを特定の方法で変更できると便利な場合があります。

(これは私が考えることができる唯一のケースであり、具体的ではありません)


3
これは素晴らしい質問です。以下の答えから判断すると、メタクラスの具体的な使用法などないことは明らかです。
Marcus Ottosson、2014

回答:


25

Matplotlibのフロントエンドとして、非インタラクティブなプロットを処理するクラスがあります。ただし、インタラクティブなプロットを実行したい場合もあります。いくつかの関数だけで、Figureカウントをインクリメントしたり、drawを手動で呼び出したりできることがわかりましたが、すべてのプロット呼び出しの前後にこれらを実行する必要がありました。したがって、インタラクティブなプロットラッパーとオフスクリーンのプロットラッパーの両方を作成するには、メタクラスを使用してこれを行う方が、適切なメソッドをラップして、次のようにするよりも効率的であることがわかりました。

class PlottingInteractive:
    add_slice = wrap_pylab_newplot(add_slice)

このメソッドはAPIの変更などに対応できませんが、クラス属性を__init__再設定する前にクラス属性を反復処理する方がより効率的であり、最新の状態を維持できます。

class _Interactify(type):
    def __init__(cls, name, bases, d):
        super(_Interactify, cls).__init__(name, bases, d)
        for base in bases:
            for attrname in dir(base):
                if attrname in d: continue # If overridden, don't reset
                attr = getattr(cls, attrname)
                if type(attr) == types.MethodType:
                    if attrname.startswith("add_"):
                        setattr(cls, attrname, wrap_pylab_newplot(attr))
                    elif attrname.startswith("set_"):
                        setattr(cls, attrname, wrap_pylab_show(attr))

もちろん、これを行うにはもっと良い方法があるかもしれませんが、これが効果的であることがわかりました。もちろん、これは__new__または__init__で行うこともできますが、これが私が最も簡単に見つけた解決策でした。


103

私は最近同じ質問をされ、いくつかの答えを思いつきました。言及したいくつかのユースケースについて詳しく説明し、いくつかの新しいケースを追加したかったので、このスレッドを復活させてもいいと思います。

私が見たほとんどのメタクラスは、次の2つのいずれかを行います。

  1. 登録(データ構造へのクラスの追加):

    models = {}
    
    class ModelMetaclass(type):
        def __new__(meta, name, bases, attrs):
            models[name] = cls = type.__new__(meta, name, bases, attrs)
            return cls
    
    class Model(object):
        __metaclass__ = ModelMetaclass

    サブクラスを作成するModelと、クラスは常にmodels辞書に登録されます。

    >>> class A(Model):
    ...     pass
    ...
    >>> class B(A):
    ...     pass
    ...
    >>> models
    {'A': <__main__.A class at 0x...>,
     'B': <__main__.B class at 0x...>}

    これは、クラスデコレータでも実行できます。

    models = {}
    
    def model(cls):
        models[cls.__name__] = cls
        return cls
    
    @model
    class A(object):
        pass

    または、明示的な登録関数を使用します。

    models = {}
    
    def register_model(cls):
        models[cls.__name__] = cls
    
    class A(object):
        pass
    
    register_model(A)

    実際、これはほとんど同じです。クラスデコレータについて不利に言及しますが、実際には、クラスでの関数呼び出しの構文上の砂糖にすぎないため、魔法はありません。

    とにかく、この場合のメタクラスの利点は継承です。メタクラスはすべてのサブクラスで機能しますが、他のソリューションは明示的に装飾または登録されたサブクラスでのみ機能します。

    >>> class B(A):
    ...     pass
    ...
    >>> models
    {'A': <__main__.A class at 0x...> # No B :(
  2. リファクタリング(クラス属性の変更または新しい属性の追加):

    class ModelMetaclass(type):
        def __new__(meta, name, bases, attrs):
            fields = {}
            for key, value in attrs.items():
                if isinstance(value, Field):
                    value.name = '%s.%s' % (name, key)
                    fields[key] = value
            for base in bases:
                if hasattr(base, '_fields'):
                    fields.update(base._fields)
            attrs['_fields'] = fields
            return type.__new__(meta, name, bases, attrs)
    
    class Model(object):
        __metaclass__ = ModelMetaclass

    Model一部のField属性をサブクラス化して定義するときはいつでも、それらに名前が挿入され(たとえば、より有益なエラーメッセージの場合)、_fieldsディクショナリにグループ化されます(簡単な反復のため、すべてのクラス属性とそのすべての基本クラスを調べる必要はありません)。毎回属性):

    >>> class A(Model):
    ...     foo = Integer()
    ...
    >>> class B(A):
    ...     bar = String()
    ...
    >>> B._fields
    {'foo': Integer('A.foo'), 'bar': String('B.bar')}

    繰り返しますが、これは(継承なしで)クラスデコレータで行うことができます。

    def model(cls):
        fields = {}
        for key, value in vars(cls).items():
            if isinstance(value, Field):
                value.name = '%s.%s' % (cls.__name__, key)
                fields[key] = value
        for base in cls.__bases__:
            if hasattr(base, '_fields'):
                fields.update(base._fields)
        cls._fields = fields
        return cls
    
    @model
    class A(object):
        foo = Integer()
    
    class B(A):
        bar = String()
    
    # B.bar has no name :(
    # B._fields is {'foo': Integer('A.foo')} :(

    または明示的に:

    class A(object):
        foo = Integer('A.foo')
        _fields = {'foo': foo} # Don't forget all the base classes' fields, too!

    ただし、可読性と保守性のある非メタプログラミングの擁護に反して、これははるかに扱いにくく、冗長で、エラーが発生しやすくなります。

    class B(A):
        bar = String()
    
    # vs.
    
    class B(A):
        bar = String('bar')
        _fields = {'B.bar': bar, 'A.foo': A.foo}

最も一般的で具体的な使用例を検討した後、メタクラスを絶対に使用しなければならない唯一のケースは、クラス名または基本クラスのリストを変更したい場合です。一度定義すると、これらのパラメーターはクラスにベイクされ、デコレーターはありません。または関数がそれらをベイク解除できます。

class Metaclass(type):
    def __new__(meta, name, bases, attrs):
        return type.__new__(meta, 'foo', (int,), attrs)

class Baseclass(object):
    __metaclass__ = Metaclass

class A(Baseclass):
    pass

class B(A):
    pass

print A.__name__ # foo
print B.__name__ # foo
print issubclass(B, A)   # False
print issubclass(B, int) # True

これは、類似した名前または不完全な継承ツリーを持つクラスが定義されるたびに警告を発行するフレームワークで役立ちますが、これらの値を実際に変更するためにトローリング以外の理由を考えることはできません。たぶんデビッド・ビーズリーはできる。

とにかく、Python 3では、メタクラスにも__prepare__メソッドがあります。これにより、クラス本体を以外のマッピングに評価dictできるため、順序付けられた属性、オーバーロードされた属性、その他の邪悪な要素をサポートできます。

import collections

class Metaclass(type):

    @classmethod
    def __prepare__(meta, name, bases, **kwds):
        return collections.OrderedDict()

    def __new__(meta, name, bases, attrs, **kwds):
        print(list(attrs))
        # Do more stuff...

class A(metaclass=Metaclass):
    x = 1
    y = 2

# prints ['x', 'y'] rather than ['y', 'x']

 

class ListDict(dict):
    def __setitem__(self, key, value):
        self.setdefault(key, []).append(value)

class Metaclass(type):

    @classmethod
    def __prepare__(meta, name, bases, **kwds):
        return ListDict()

    def __new__(meta, name, bases, attrs, **kwds):
        print(attrs['foo'])
        # Do more stuff...

class A(metaclass=Metaclass):

    def foo(self):
        pass

    def foo(self, x):
        pass

# prints [<function foo at 0x...>, <function foo at 0x...>] rather than <function foo at 0x...>

順序付けられた属性は作成カウンターで達成でき、オーバーロードはデフォルトの引数でシミュレートできると主張するかもしれません。

import itertools

class Attribute(object):
    _counter = itertools.count()
    def __init__(self):
        self._count = Attribute._counter.next()

class A(object):
    x = Attribute()
    y = Attribute()

A._order = sorted([(k, v) for k, v in vars(A).items() if isinstance(v, Attribute)],
                  key = lambda (k, v): v._count)

 

class A(object):

    def _foo0(self):
        pass

    def _foo1(self, x):
        pass

    def foo(self, x=None):
        if x is None:
            return self._foo0()
        else:
            return self._foo1(x)

醜いだけでなく、柔軟性も低くなります。整数や文字列などの順序付けされたリテラル属性が必要な場合はどうでしょうか。のNone有効な値はxどうなりますか?

最初の問題を解決する創造的な方法は次のとおりです。

import sys

class Builder(object):
    def __call__(self, cls):
        cls._order = self.frame.f_code.co_names
        return cls

def ordered():
    builder = Builder()
    def trace(frame, event, arg):
        builder.frame = frame
        sys.settrace(None)
    sys.settrace(trace)
    return builder

@ordered()
class A(object):
    x = 1
    y = 'foo'

print A._order # ['x', 'y']

そして、これが2番目のものを解決するための創造的な方法です:

_undefined = object()

class A(object):

    def _foo0(self):
        pass

    def _foo1(self, x):
        pass

    def foo(self, x=_undefined):
        if x is _undefined:
            return self._foo0()
        else:
            return self._foo1(x)

しかし、これは単純なメタクラス(特に最初のメタクラスで、本当に脳を溶かすもの)よりもはるかにブードゥーです。私の要点は、メタクラスを不慣れで直観に反するものと見なすことですが、プログラミング言語の進化の次のステップとしてメタクラスを見なすこともできます。考え方を調整する必要があるだけです。結局のところ、関数ポインタで構造体を定義し、その関数への最初の引数としてそれを渡すことを含めて、おそらくCですべてを行うことができます。C ++を初めて見た人は、「この魔法は何ですか?コンパイラが暗黙的に渡しているのはなぜですか?thisメソッドにではなく、通常の静的関数に?引数については明示的で冗長である方がいい」とは言え、オブジェクト指向プログラミングは、それを手に入れるとはるかに強力になります。これは、ええと...準アスペクト指向プログラミングだと思います。メタクラスを理解すると、それらは実際には非常に単純なので、便利なときにメタクラスを使用しないのはなぜですか?

そして最後に、メタクラスはradであり、プログラミングは楽しいはずです。標準のプログラミング構造とデザインパターンを常に使用することは、退屈で面白みがなく、想像力を妨げます。少し生きて!これがあなただけのメタメタクラスです。

class MetaMetaclass(type):
    def __new__(meta, name, bases, attrs):
        def __new__(meta, name, bases, attrs):
            cls = type.__new__(meta, name, bases, attrs)
            cls._label = 'Made in %s' % meta.__name__
            return cls 
        attrs['__new__'] = __new__
        return type.__new__(meta, name, bases, attrs)

class China(type):
    __metaclass__ = MetaMetaclass

class Taiwan(type):
    __metaclass__ = MetaMetaclass

class A(object):
    __metaclass__ = China

class B(object):
    __metaclass__ = Taiwan

print A._label # Made in China
print B._label # Made in Taiwan

編集する

これはかなり古い質問ですが、まだ賛成票が出ているので、より包括的な回答へのリンクを追加したいと思いました。メタクラスとその使用法の詳細については、こちらの記事をご覧ください


5
それは、時間のためのおかげで、それを書き込み、複数の例を与える素晴らしい答えだ
陳A.

「...この場合のメタクラスの利点は継承であり、サブクラスで機能するため」-Python 3ではないのではないでしょうか。子クラスが__metaclass__属性を継承するため、Python 2でのみ機能すると思いますが、この属性はPython 3では特別ではなくなりました。この「子クラスは、親のメタクラスによっても構築される」ことをPython 3で機能させる方法はありますか?
ForceBru

2
これはPython 3にも当てはまります。メタクラスがMであるAから継承するクラスBもMのタイプであるためです。したがって、Bが評価されると、Mが呼び出されて作成され、これにより効果的に(Aの)「任意のサブクラスで作業する」。そうは言っても、Python 3.6でははるかに単純なが導入されたinit_subclassため、ベースクラスのサブクラスを操作できるようになり、その目的でメタクラスが不要になりました。
Dan Gittik

これは素晴らしいです、私はメタクラスに関する非常に多くのブログ投稿を読みましたが、これだけがメタクラスの長所と短所と代替物を知っています。
オスパイダー

36

メタクラスの目的は、クラス/オブジェクトの区別をメタクラス/クラスで置き換えることではなく、何らかの方法でクラス定義(およびそのインスタンス)の動作を変更することです。実際には、特定のドメインにとってデフォルトよりも役立つ方法で、クラスステートメントの動作を変更することです。私がそれらを使用したものは次のとおりです。

  • サブクラスの追跡。通常はハンドラーを登録します。これは、いくつかのクラス属性をサブクラス化して設定するだけで、特定のもののハンドラーを登録するプラグインスタイルの設定を使用する場合に便利です。例えば。各クラスがそのタイプに適切なメソッド(再生/タグの取得など)を実装するさまざまな音楽フォーマットのハンドラーを作成するとします。新しいタイプのハンドラーを追加すると、次のようになります。

    class Mp3File(MusicFile):
        extensions = ['.mp3']  # Register this type as a handler for mp3 files
        ...
        # Implementation of mp3 methods go here

    メタクラスは、{'.mp3' : MP3File, ... }etcのディクショナリを維持し、ファクトリ関数を介してハンドラーを要求すると、適切なタイプのオブジェクトを構築します。

  • 行動を変える。特定の属性に特別な意味を付けて、属性が存在する場合の動作を変更することができます。たとえば、名前_get_fooを持つメソッドを探して、_set_fooそれらを透過的にプロパティに変換したい場合があります。実際の例として、ここだ、私はより多くのCのような構造体の定義を与えることを書いたレシピ。メタクラスは、宣言されたアイテムを構造体形式の文字列に変換し、継承などを処理し、それを処理できるクラスを生成するために使用されます。

    他の実際の例については、sqlalchemyの ORMやsqlobjectなどのさまざまなORMを見てください。繰り返しますが、目的は特定の意味を持つ定義(ここではSQL列の定義)を解釈することです。


3
ええ、はい、サブクラスを追跡します。しかし、なぜそれが必要なのでしょうか。あなたの例は、register_music_file(Mp3File、['.mp3'])の暗黙的なものであり、明示的な方法の方が読みやすく、保守が容易です。これは私が話している悪いケースの例です。
Ali Afshar

ORMの場合について、テーブルを定義するクラスベースの方法、またはマップされたオブジェクトのメタクラスについて話していますか。SQLAlchemyは(正しく)任意のクラスにマップできるからです(そして、そのアクティビティにメタクラスを使用していないと想定しています)。
Ali Afshar

10
私はすべてのサブクラスに追加の登録メソッドを必要とするのではなく、より宣言的なスタイルを好む-すべてが単一の場所にラップされている場合はより良い。
ブライアン

sqlalchemyの場合、私は主に宣言的なレイヤーについて考えているので、おそらくsqlobjectがより良い例です。ただし、内部で使用されるメタクラスも、意味を宣言するための特定の属性の同様の再解釈の例です。
ブライアン

2
申し訳ありませんが、私のタイムアウトの1つがSOタイムアウトシナリオで失われました。宣言型のクラスはほとんど嫌悪感を抱いています。私は人々がそれを愛していることを知っています、そしてそれは受け入れられた行動です。しかし、(経験から)UN宣言したい状況では使用できないことは知っています。クラスの登録を解除するのは難しいです。
Ali Afshar

17

ティム・ピーターの古典的な引用から始めましょう:

メタクラスは、99%のユーザーが気にする必要があるよりも深い魔法です。それらが必要かどうか疑問に思っても、必要ありません(実際にそれらを必要とする人々は、それらが必要であることを確実に知っており、理由についての説明も必要ありません)。ティムピーターズ(clp post 2002-12-22)

そうは言っても、私は(定期的に)メタクラスの真の使用法に遭遇しました。頭に浮かぶのは、すべてのモデルがmodels.Modelから継承するDjangoです。次に、models.Modelは、DjangoのORMの良さでDBモデルをラップするために、いくつかの深刻な魔法をかけます。その魔法はメタクラスによって起こります。あらゆる種類の例外クラス、マネージャークラスなどを作成します。

ストーリーの始まりについては、django / db / models / base.py、ModelBase()クラスを参照してください。


まあ、はい、私は要点を理解しています。メタクラスを使用する「方法」や「理由」は不思議ではありません。「誰」と「何」かは不思議に思います。ORMがよく見られるケースです。残念ながら、DjangoのORMは、魔法が少ないSQLAlchemyに比べるとかなり貧弱です。マジックは悪く、メタクラスは実際には必要ありません。
Ali Afshar

9
過去にティムピーターズの引用を読んだことがあるので、彼の発言があまり役に立たないことが時を経て明らかになっています。StackOverflowでPythonメタクラスを調査するまで、それらを実装する方法すら明らかになりませんでした。メタクラスを作成して使用する方法を学ぶように自分自身を強制した後、それらの能力は私を驚かせ、Pythonが実際にどのように機能するかについての理解を深めました。クラスは再利用可能なコードを提供でき、メタクラスはそれらのクラスに再利用可能な拡張機能を提供できます。
Noctisスカイタワー2013年

6

メタクラスは、Pythonでドメイン固有言語を構築するのに便利です。具体的な例として、SQLObjectのデータベーススキーマの宣言構文であるDjangoがあります。

Ian Bickingによる保守的なメタクラスの基本的な例:

私が使用したメタクラスは、主にプログラミングの一種の宣言型スタイルをサポートするためのものでした。たとえば、検証スキーマについて考えてみましょう。

class Registration(schema.Schema):
    first_name = validators.String(notEmpty=True)
    last_name = validators.String(notEmpty=True)
    mi = validators.MaxLength(1)
    class Numbers(foreach.ForEach):
        class Number(schema.Schema):
            type = validators.OneOf(['home', 'work'])
            phone_number = validators.PhoneNumber()

その他のいくつかのテクニック:PythonでDSLを構築するための成分(pdf)。

編集(アリによる):コレクションとインスタンスを使用してこれを行う例は、私が好むものです。重要な事実はインスタンスであり、これにより、より多くのパワーが提供され、メタクラスを使用する理由がなくなります。さらに、例ではクラスとインスタンスの混合を使用していることに注意する必要があります。これは、メタクラスですべてを実行できるわけではないことを示しています。そして、それを行うための真に不均一な方法を作成します。

number_validator = [
    v.OneOf('type', ['home', 'work']),
    v.PhoneNumber('phone_number'),
]

validators = [
    v.String('first_name', notEmpty=True),
    v.String('last_name', notEmpty=True),
    v.MaxLength('mi', 1),
    v.ForEach([number_validator,])
]

完璧ではありませんが、すでに魔法はほとんどありません。メタクラスの必要がなく、均一性が向上しています。


これをありがとう。これは、必要のないネストされたコレクションを使用した単純なコレクションインスタンスに基づいて、よりシンプルになる、不必要で、醜く、管理不可能なユースケースの非常に良い例です。
Ali Afshar

1
@Ali A:メタクラスを介した宣言構文と単純なコレクションインスタンスに基づくアプローチを並べて比較する具体的な例を提供していただけます。
jfs 2008

@Ali A:私の回答を編集して、コレクションスタイルの例を追加できます。
jfs 2008

了解しました。申し訳ありませんが、今日は少し急いでいますが、後で/明日はどんな質問にも答えようとします。楽しい休暇をお過ごしください!
Ali Afshar

2
2番目の例は、バリデーターのインスタンスを名前で結び付けなければならないので、醜いです。リストの代わりにディクショナリを使用するのが少し良い方法ですが、Pythonでは、クラスはディクショナリの単なる構文シュガーなので、クラスを使用しないのはなぜですか?Pythonのベイビーはスペースや文字列が可能であった特殊文字を含むことができないため、無料の名前検証も受けられます。
リーライアン

6

メタクラスの使用の合理的なパターンは、同じクラスがインスタンス化されるたびに繰り返しではなく、クラスが定義されるときに一度だけ何かを行うことです。

複数のクラスが同じ特別な動作を共有する場合、繰り返し __metaclass__=Xは、特別な目的のコードを繰り返したり、アドホックな共有スーパークラスを導入したりするよりも明らかに優れています。

しかし、たとえ一つだけの特別なクラスなし予見可能な拡張子を持つ、 __new____init__メタクラスのクリーナー初期クラス変数への道または特殊目的のコードを混ぜる以外のグローバルデータと正常であるdefclass、クラス定義本体内の文。


5

Pythonでメタクラスを使用したのは、Flickr APIのラッパーを作成したときだけです。

私の目標は、flickrのAPIサイトをスクレイピングし、完全なクラス階層を動的に生成して、Pythonオブジェクトを使用してAPIアクセスを許可することでした。

# Both the photo type and the flickr.photos.search API method 
# are generated at "run-time"
for photo in flickr.photos.search(text=balloons):
    print photo.description

そのため、この例では、ウェブサイトからPython Flickr API全体を生成したので、実行時のクラス定義は本当にわかりません。タイプを動的に生成できることは非常に便利でした。


2
メタクラスを使用せずに型を動的に生成できます。>>> help(type)
Ali Afshar

8
あなたはそれを認識していない方でも、あなたはされ、その後メタクラスを使用しました。タイプはメタクラスであり、実際には最も一般的なタイプです。:-)
Veky 2015

5

私は昨日同じことを考えていて、完全に同意しました。コードをより宣言的にしようとする試みによって引き起こされるコードの複雑さは、一般的にコードベースを維持することを難しくし、読みにくくなり、私の意見ではpythonicを少なくします。また、通常は(継承を維持し、クラスからインスタンスにコピーするために)多くのcopy.copy()が必要であり、多くの場所を調べて(常にメタクラスを上から見て)何が起こっているかを確認する必要があります。パイソン粒も。私はformencodeとsqlalchemyのコードを調べて、そのような宣言型のスタイルに価値があるかどうかを確認しましたが、明らかにそうではありません。このようなスタイルは、記述子(プロパティやメソッドなど)と不変データに委ねる必要があります。Rubyはそのような宣言型スタイルをよりよくサポートしており、Pythonのコア言語がその道を進んでいないことをうれしく思います。

私はそれらのデバッグでの使用を確認できます。メタクラスをすべての基本クラスに追加して、より豊富な情報を取得できます。私はまた、いくつかのボイラープレートコードを取り除くための(非常に)大規模なプロジェクトでのみそれらを使用していることも確認しています(ただし、明確性が失われています)。たとえば sqlalchemyは他の場所でそれらを使用して、クラス定義の属性値に基づいてすべてのサブクラスに特定のカスタムメソッドを追加します(例:おもちゃの例)

class test(baseclass_with_metaclass):
    method_maker_value = "hello"

「hello」に基づく特別なプロパティを持つメソッドをそのクラスに生成するメタクラスを持つことができます(たとえば、文字列の末尾に「hello」を追加したメソッド)。作成するすべてのサブクラスでメソッドを作成する必要がなく、代わりにmethod_maker_valueを定義するだけで済むようにすることは、保守性にとって良いことです。

ただし、これが必要になることは非常にまれであり、十分な大規模なコードベースがないかぎり、検討する価値がないほどタイピングの手間を省くだけです。


5

変更するクラスの継承または集約を使用して、必要なことを実行するクラスをいつでも構築できるため、メタクラスを使用する必要はありません。

そうは言っても、既存のクラスを変更できることはSmalltalkとRubyで非常に便利ですが、Pythonはそれを直接行うことを好みません。

優秀ありますdeveloperWorksの記事 Pythonでいるかもしれないのヘルプをmetaclassingには。Wikipediaの記事はまた、かなり良いです。


1
また、オブジェクト指向プログラミングを実行するのにオブジェクトは必要ありません。ファーストクラスの関数で実行できます。したがって、オブジェクトを使用する必要ありません。しかし、それらは便宜上そこにあります。だから、最初の段落であなたが何をしようとしているのか分かりません。
タイラークロンプトン、2016年

1
質問を振り返ってください。
チャーリーマーティン

4

複数のスレッドがGUIライブラリと対話しようとすると、一部のGUIライブラリに問題が発生します。tkinterそのような例の1つです。イベントとキューの問題を明示的に処理できますが、問題を完全に無視する方法でライブラリを使用する方がはるかに簡単です。見よ-メタクラスの魔法。

ライブラリ全体をシームレスに動的に書き換えて、マルチスレッドアプリケーションで期待どおりに正しく機能するようにできると、状況によっては非常に役立ちます。safetkinterのモジュールはによって提供さメタクラスの助けを借りてそれを行うthreadboxモジュール-イベントとキューは必要ありません。

の1つの優れた点threadboxは、どのクラスを複製するかを気にしないことです。必要に応じて、メタクラスがすべての基本クラスにアクセスする方法の例を示します。メタクラスに伴うもう1つの利点は、メタクラスも継承クラスで実行できることです。自分自身を書くプログラム-なぜですか?


4

メタクラスの唯一の正当なユースケースは、他のせんさく好きな開発者がコードに触れないようにすることです。おせっかいな開発者がメタクラスを習得し、メタクラスを使い始めたら、別のレベルを1つまたは2つ投入して、それらを回避します。それが機能しない場合は、使用を開始しますtype.__new__、再帰メタクラスを使用するか、おそらく何らかのスキームを使用してください。

(舌で頬を書いたが、この種の難読化が行われたのを見てきました。Djangoは完璧な例です)


7
動機がDjangoでも同じだったとは思えません。
Ali Afshar、2011年

3

メタクラスはプログラミングに取って代わるものではありません!これらは、いくつかのタスクを自動化したり、よりエレガントにすることができるトリックです。これの良い例は、Pygments構文強調表示ライブラリです。というクラスがありますRegexLexer、ユーザーが一連の字句解析ルールをクラスの正規表現として定義できるあります。メタクラスは、定義を有用なパーサーに変えるために使用されます。

彼らは塩のようなものです。使いすぎるのは簡単です。


まあ、私の意見では、そのPygmentsの訴訟は単に不要です。なぜ、dictのような単純なコレクションがないのか、なぜクラスにこれを強制するのか?
Ali Afshar

4
クラスniceがLexerの概念を包括し、guess_filename()などの他の便利なメソッドがあるため
Benjamin Peterson

3

メタクラスを使用する方法は、クラスにいくつかの属性を提供することでした。例えば:

class NameClass(type):
    def __init__(cls, *args, **kwargs):
       type.__init__(cls, *args, **kwargs)
       cls.name = cls.__name__

置く NameClassを指すようにメタクラスのセットを持っていますすべてのクラスに属性を。


5
はい、これでうまくいきます。スーパークラスを使用することもできます。これは、少なくとも明示的で、コードでフォローできます。興味津々で、何のためにこれを使いましたか?
Ali Afshar

2

これはマイナーな使用法ですが...メタクラスが有用であるとわかったのは、サブクラスが作成されるたびに関数を呼び出すことです。これを__initsubclass__属性を検索するメタクラスにコード化しました。サブクラスが作成されると、そのメソッドを定義するすべての親クラスがで呼び出され__initsubclass__(cls, subcls)ます。これにより、すべてのサブクラスをグローバルレジストリに登録し、サブクラスが定義されるたびに不変チェックを実行し、遅延バインディング操作などを実行する親クラスを作成できます。すべて手動で関数を呼び出したり、を作成できます。、カスタムメタクラスを作成しするこれらの個別の職務のそれぞれを実行します。

ちなみに、私はゆっくりとこの動作の暗黙の魔法がやや望ましくないことに気づきました。コンテキスト外でクラス定義を見ると予期しないためです...そして、私はその解決策を深刻なもの以外に使用することから離れました__super各クラスおよびインスタンスの属性を初期化します。


1

最近、メタクラスを使用して、http://census.ire.org/data/bulkdata.htmlからの米国国勢調査データが入力されたデータベーステーブルの周りにSQLAlchemyモデルを宣言的に定義するのを支援する必要がありました

IREは国勢調査データテーブル用のデータベースシェルを提供します。これは、p012015、p012016、p012017などの国勢調査局の命名規則に従って整数列を作成します。

私はmodel_instance.p012017a)構文を使用してこれらの列にアクセスできるようにしたい、b)私がやっていたことについてかなり明示的である、c)モデルに何十ものフィールドを明示的に定義する必要がないため、SQLAlchemyをサブクラス化しDeclarativeMetaて、列に対応し、列に対応するモデルフィールドを自動的に作成します。

from sqlalchemy.ext.declarative.api import DeclarativeMeta

class CensusTableMeta(DeclarativeMeta):
    def __init__(cls, classname, bases, dict_):
        table = 'p012'
        for i in range(1, 49):
            fname = "%s%03d" % (table, i)
            dict_[fname] = Column(Integer)
            setattr(cls, fname, dict_[fname])

        super(CensusTableMeta, cls).__init__(classname, bases, dict_)

次に、このメタクラスをモデル定義に使用して、モデルの自動的に列挙されたフィールドにアクセスできます。

CensusTableBase = declarative_base(metaclass=CensusTableMeta)

class P12Tract(CensusTableBase):
    __tablename__ = 'ire_p12'

    geoid = Column(String(12), primary_key=True)

    @property
    def male_under_5(self):
        return self.p012003

    ...


0

使いやすくするために、バイナリパーサーで一度使用する必要がありました。ワイヤ上にあるフィールドの属性を使用してメッセージクラスを定義します。それらは、それから最終的なワイヤ形式を構築するように宣言された方法で注文する必要がありました。順序付けされた名前空間辞書を使用する場合、メタクラスを使用してそれを行うことができます。実際、メタクラスの例では次のようになっています。

https://docs.python.org/3/reference/datamodel.html#metaclass-example

ただし、一般的には、メタクラスの複雑さの追加が本当に必要かどうかを慎重に評価してください。


0

@Dan Gittikからの答えはクールです

最後の例は多くのことを明確にすることができたので、それをpython 3に変更して説明します:

class MetaMetaclass(type):
    def __new__(meta, name, bases, attrs):
        def __new__(meta, name, bases, attrs):
            cls = type.__new__(meta, name, bases, attrs)
            cls._label = 'Made in %s' % meta.__name__
            return cls

        attrs['__new__'] = __new__
        return type.__new__(meta, name, bases, attrs)

#China is metaclass and it's __new__ method would be changed by MetaMetaclass(metaclass)
class China(MetaMetaclass, metaclass=MetaMetaclass):
    __metaclass__ = MetaMetaclass

#Taiwan is metaclass and it's __new__ method would be changed by MetaMetaclass(metaclass)
class Taiwan(MetaMetaclass, metaclass=MetaMetaclass):
    __metaclass__ = MetaMetaclass

#A is a normal class and it's __new__ method would be changed by China(metaclass)
class A(metaclass=China):
    __metaclass__ = China

#B is a normal class and it's __new__ method would be changed by Taiwan(metaclass)
class B(metaclass=Taiwan):
    __metaclass__ = Taiwan


print(A._label)  # Made in China
print(B._label)  # Made in Taiwan
  • すべてがオブジェクトなので、クラスはオブジェクトです
  • クラスオブジェクトはメタクラスによって作成されます
  • 型から継承されたすべてのクラスはメタクラスです
  • メタクラスはクラスの作成を制御できます
  • メタクラスはメタクラスの作成も制御できます(そのため、永久にループする可能性があります)
  • これはメタプログラミングです...実行時に型システムを制御できます
  • 繰り返しになりますが、すべてがオブジェクトです。これは統一されたシステムであり、タイプ作成タイプであり、タイプ作成インスタンスです

0

もう1つの使用例は、クラスレベルの属性を変更できるようにして、手元のオブジェクトにのみ影響を与えるようにする場合です。実際には、これはメタクラスとクラスのインスタンス化のフェーズを「マージ」することを意味するため、独自の(一意の)種類のクラスインスタンスのみを処理することになります。

また、(読みやすさ多態性の懸念から)インスタンスレベルの属性に基づいた計算から(場合によっては)返される値を動的に定義する 必要がある場合にも、クラスレベルでのみ実行できるようにする必要がありました。つまり、メタクラスのインスタンス化の後、クラスのインスタンス化の前。property

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.