メタクラスを使用するのが好きな友人がいて、それらを解決策として定期的に提供しています。
私は、メタクラスを使用する必要はほとんどないことを心に留めています。どうして?あなたがクラスに対してそのようなことをしているなら、おそらくオブジェクトに対してそれをしているべきだと思うからです。そして、小さな再設計/リファクタリングが整然としています。
メタクラスを使用できるようになったことで、多くの場所の多くの人々がクラスをある種のセカンドレートオブジェクトとして使用するようになりました。プログラミングはメタプログラミングに置き換えられますか?クラスデコレータの追加により、残念なことにさらに受け入れやすくなりました。
Pythonのメタクラスの有効な(具体的な)ユースケースを知りたいと思います。または、クラスを変更することがオブジェクトを変更するよりも優れている理由について啓発されることもあります。
始めます:
サードパーティのライブラリを使用する場合、特定の方法でクラスを変更できると便利な場合があります。
(これは私が考えることができる唯一のケースであり、具体的ではありません)
Matplotlibのフロントエンドとして、非インタラクティブプロットを処理するクラスがあります。ただし、インタラクティブなプロットを行いたい場合があります。いくつかの関数だけで、数字のカウントを増やしたり、手動で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__
でも実行できますが、これは私が最も簡単に見つけた解決策でした。
私は最近同じ質問をされ、いくつかの答えを思いつきました。言及されたいくつかのユースケースについて詳しく説明し、いくつかの新しいケースを追加したかったので、このスレッドを復活させてもいいと思います。
私が見たほとんどのメタクラスは、次の2つのいずれかを実行します。
登録(クラスをデータ構造に追加):
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 :(
リファクタリング(クラス属性の変更または新しい属性の追加):
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.__# foo
print B.__# 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)
Muchいだけでなく、柔軟性も劣ります。整数や文字列などのリテラル属性を順序付けする場合はどうでしょうか。 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
をメソッドに渡すが、通常の関数や静的な関数には渡さないのはなぜですか? 「。しかし、その後、オブジェクト指向プログラミングは、一度取得すると、はるかに強力になります。これもそうですね…準アスペクト指向のプログラミングだと思います。そして、メタクラスを理解すれば、実際には非常に単純なので、便利なときに使用してみませんか?
最後に、メタクラスは非常に優れており、プログラミングは楽しいはずです。標準のプログラミング構造とデザインパターンを常に使用することは、退屈で刺激のないものであり、想像力を妨げます。少し生きて!メタメタクラスは次のとおりです。
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
メタクラスの目的は、クラス/オブジェクトの区別をメタクラス/クラスに置き換えることではなく、何らかの方法でクラス定義(およびそのインスタンス)の動作を変更することです。事実上、特定のドメインにとってデフォルトよりも便利な方法でクラス文の動作を変更することです。私がそれらを使用したものは次のとおりです。
通常、ハンドラーを登録するためのサブクラスの追跡。これは、いくつかのクラス属性をサブクラス化して設定するだけで、特定のもののハンドラーを登録するプラグインスタイルのセットアップを使用する場合に便利です。例えば。各クラスがそのタイプに適したメソッド(play/getタグなど)を実装する、さまざまな音楽形式のハンドラーを作成するとします。新しい型のハンドラーを追加すると、次のようになります。
class Mp3File(MusicFile):
extensions = ['.mp3'] # Register this type as a handler for mp3 files
...
# Implementation of mp3 methods go here
次に、メタクラスは{'.mp3' : MP3File, ... }
などのディクショナリを保持し、ファクトリ関数を介してハンドラーを要求すると、適切なタイプのオブジェクトを構築します。
行動を変える。特定の属性に特別な意味を付けて、それらが存在する場合に動作を変更することができます。たとえば、_get_foo
および_set_foo
という名前のメソッドを探し、それらを透過的にプロパティに変換することができます。実際の例として、 here's は、Cのような構造体の定義を追加するために書いたレシピです。メタクラスは、宣言された項目を構造体形式の文字列に変換し、継承などを処理し、それを処理できるクラスを生成するために使用されます。
他の実際の例については、 sqlalchemy's ORMまたは sqlobject などのさまざまなORMを見てください。繰り返しますが、目的は定義(ここではSQL列の定義)を特定の意味で解釈することです。
ティムピーターの古典的な引用から始めましょう。
メタクラスは、ユーザーの99%が心配する必要のあるよりも深い魔法です。あなたがそれらを必要とするかどうか疑問に思うならば、あなたは彼らを必要としない(実際にそれらを必要とする人々は彼らがそれらを必要とすることを確実に知っており、理由についての説明を必要としない)。ティムピーターズ(c.l.p post 2002-12-22)
そうは言っても、メタクラスの真の用途に(定期的に)遭遇しています。頭に浮かぶのは、Djangoで、すべてのモデルがmodels.Model。models.Modelを継承し、DBモデルをDjangoのORMの良さでラップするための深刻なマジックを行います。この魔法はメタクラスによって発生し、あらゆる種類の例外クラス、マネージャークラスなどを作成します。
ストーリーの始まりについては、Django/db/models/base.py、ModelBase()クラスを参照してください。
メタクラスの使用の合理的なパターンは、同じクラスがインスタンス化されるたびに繰り返されるのではなく、クラスが定義されたときに一度だけ何かをすることです。
複数のクラスが同じ特別な動作を共有する場合、__metaclass__=X
を繰り返すことは、特別な目的のコードを繰り返したり、アドホックな共有スーパークラスを導入するよりも明らかに優れています。
しかし、特別なクラスが1つだけで、予測可能な拡張がない場合でも、メタクラスの__new__
および__init__
は、特殊な目的のコードと通常のdef
およびclass
ステートメントはクラス定義本体にあります。
メタクラスは、Pythonでのドメイン固有言語の構築に便利です。具体的な例は、データベーススキーマのSQLObjectの宣言構文であるDjangoです。
A Conservative Metaclass from 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)。
編集(ALi):コレクションとインスタンスを使用してこれを行う例は、私が好むものです。重要な事実はインスタンスです。これにより、より強力になり、メタクラスを使用する理由がなくなります。あなたの例がクラスとインスタンスの混合物を使用していることは注目に値します。これは確かにメタクラスですべてを行うことができないことを示しています。そして、それを行うための真に不均一な方法を作成します。
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,])
]
完全ではありませんが、すでにほとんど魔法がなく、メタクラスが不要であり、均一性が向上しています。
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
したがって、その例では、WebサイトからPython Flickr API全体を生成したため、実行時のクラス定義は実際にはわかりません。型を動的に生成できることは非常に便利でした。
昨日も同じことを考えていて、完全に同意しました。私の意見では、より宣言的にしようとする試みによって引き起こされるコードの複雑さにより、一般に、コードベースの保守が難しくなり、読みにくくなり、Pythonが少なくなります。また、通常、多くのcopy.copy()ing(継承を維持し、クラスからインスタンスにコピーする)が必要であり、何が起こっているか(常にメタクラスから見て)を見るために多くの場所を調べる必要があります。 python粒。私はそのような宣言的なスタイルが価値があるかどうかを確認するためにformencodeとsqlalchemyコードを選択してきました。そのようなスタイルは記述子(プロパティやメソッドなど) )および不変データ。Rubyはそのような宣言型のサポートが改善されており、コアpython言語がそのルートを進んでいないことを嬉しく思います。
デバッグでの使用を確認し、メタクラスをすべての基本クラスに追加して、より詳細な情報を取得できます。また、(非常に)大規模なプロジェクトでのみ使用して、定型コードを削除することも考えています(ただし、明確さを失います)。 example のsqlalchemyは、他の場所でそれらを使用して、クラス定義の属性値に基づいてすべてのサブクラスに特定のカスタムメソッドを追加します(例:おもちゃの例)
class test(baseclass_with_metaclass):
method_maker_value = "hello"
「hello」(文字列の末尾に「hello」を追加したメソッドなど)に基づいた特別なプロパティを持つクラスのメソッドを生成するメタクラスを持つことができます。保守性のために、作成するすべてのサブクラスでメソッドを記述する必要がなく、代わりにmethod_maker_valueのみを定義する必要があることを確認するとよいでしょう。
ただし、この必要性は非常にまれであり、入力するビット数が少なくなるため、十分なコードベースがない限り、考慮する価値はありません。
メタクラスを絶対に必要とすることはありません。変更するクラスの継承または集約を使用して、必要な処理を行うクラスをいつでも構築できます。 。
とは言っても、Smalltalkでは非常に便利で、既存のクラスを変更できるRubyですが、Pythonはそれを直接行うのは好きではありません。
優れた DeveloperWorksの記事 のメタクラス化に関するPythonが役立つ場合があります。 Wikipediaの記事 もかなり良いです。
私がメタクラスを使用した方法は、クラスにいくつかの属性を提供することでした。例:
class NameClass(type):
def __init__(cls, *args, **kwargs):
type.__init__(cls, *args, **kwargs)
cls.name = cls.__name__
nameClassを指すように設定されたメタクラスを持つすべてのクラスにname属性を配置します。
メタクラスはプログラミングに取って代わるものではありません!それらは、いくつかのタスクを自動化またはよりエレガントにする単なるトリックです。この良い例は、 Pygments 構文強調ライブラリです。 RegexLexer
というクラスがあり、ユーザーが一連のレキシング規則をクラスの正規表現として定義できるようにします。メタクラスは、定義を便利なパーサーに変換するために使用されます。
彼らは塩のようなものです。使いすぎです。
メタクラスの唯一の正当な使用例は、他のうるさい開発者がコードに触れないようにすることです。おせっかいな開発者がメタクラスを習得し、あなたのメタクラスをいじり始めたら、それらを締め出すために別のレベルまたは2つを投入します。それでもうまくいかない場合は、type.__new__
またはおそらく再帰的なメタクラスを使用する何らかのスキーム。
(頬に舌を書いたが、私はこの種の難読化が行われているのを見た。Djangoは完璧な例である)
複数のスレッドがそれらと対話しようとすると、一部のGUIライブラリに問題が発生します。 tkinter
はその一例です。また、イベントとキューを使用して問題を明示的に処理できますが、問題を完全に無視する方法でライブラリを使用する方がはるかに簡単です。見よ-メタクラスの魔法。
ライブラリ全体をシームレスに動的に書き換えて、マルチスレッドアプリケーションで期待どおりに適切に動作できるようにすることは、状況によっては非常に役立ちます。 safetkinter モジュールは、 threadbox module-不要なイベントとキューによって提供されるメタクラスの助けを借りてそれを行います。
threadbox
のすばらしい側面の1つは、どのクラスを複製するかを気にしないことです。必要に応じて、メタクラスがすべての基本クラスに触れる方法の例を示します。メタクラスのさらなる利点は、継承クラスでも実行されることです。自分で書くプログラム-なぜですか?
これはマイナーな使用法ですが、...サブクラスが作成されるたびに関数を呼び出すことは、メタクラスが有用だとわかったことの1つです。これを___initsubclass__
_属性を探すメタクラスにコード化しました。サブクラスが作成されるたびに、そのメソッドを定義するすべての親クラスは__initsubclass__(cls, subcls)
で呼び出されます。これにより、すべてのサブクラスをグローバルレジストリに登録する親クラスを作成し、サブクラスが定義されるたびに不変チェックを実行し、遅延バインディング操作を実行するなど、すべて手動で関数を呼び出す必要がありませんまたはこれらの個別の職務をそれぞれ実行するカスタムメタクラスを作成します。
覚えておいてください、この動作の暗黙の魔法は、コンテキスト外でクラス定義を見ると予想外であるため、やや望ましくないことに気付くようになりました...クラスとインスタンスごとに___super
_属性を初期化します。
ここ -書き換えPythonメタクラスを持つDocstrings。
私は最近、メタクラスを使用して、 http://census.ire.org/data/bulkdata.html からの米国国勢調査データが入力されたデータベーステーブルの周りにSQLAlchemyモデルを宣言的に定義するのを助けなければなりませんでした
IREは、国勢調査データテーブルに データベースシェル を提供します。これは、p012015、p012016、p012017などの国勢調査局の命名規則に従って整数列を作成します。
私は、a)model_instance.p012017
構文を使用してこれらの列にアクセスできるようにし、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):
__table= 'ire_p12'
geoid = Column(String(12), primary_key=True)
@property
def male_under_5(self):
return self.p012003
...
使いやすくするために、バイナリパーサーに一度使用する必要がありました。ワイヤ上に存在するフィールドの属性を使用してメッセージクラスを定義します。最終的なワイヤフォーマットを構築するために宣言された方法で注文する必要がありました。順序付けられた名前空間dictを使用する場合、メタクラスでそれを行うことができます。実際、メタクラスの例では:
https://docs.python.org/3/reference/datamodel.html#metaclass-example
しかし、一般的に:本当に複雑なメタクラスを追加する必要がある場合は、非常に慎重に評価してください。
@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