oop とは メタプログラミング - Pythonのメタクラスは何ですか?




7 Answers

オブジェクトとしてのクラス

メタクラスを理解する前に、Pythonでクラスをマスターする必要があります。 そして、Pythonには、Smalltalk言語から借りてきたクラスについての非常に特殊な考えがあります。

ほとんどの言語では、クラスはオブジェクトを生成する方法を記述する単なるコードの一部です。 それはPythonでもまあまあです。

>>> class ObjectCreator(object):
...       pass
...

>>> my_object = ObjectCreator()
>>> print(my_object)
<__main__.ObjectCreator object at 0x8974f2c>

しかし、クラスはPythonのそれ以上のものです。 クラスもオブジェクトです。

はい、オブジェクト。

キーワードclassを使用するとすぐに、Pythonはそれを実行してOBJECTを作成します。 命令

>>> class ObjectCreator(object):
...       pass
...

メモリに "ObjectCreator"という名前のオブジェクトを作成します。

このオブジェクト(クラス)はそれ自体がオブジェクト(インスタンス)を作成することができ、これがクラスです。

しかしそれでも、それは目的であり、したがって:

  • それを変数に割り当てることができます
  • あなたはそれをコピーすることができます
  • あなたはそれに属性を追加することができます
  • 関数のパラメータとして渡すことができます

例えば:

>>> print(ObjectCreator) # you can print a class because it's an object
<class '__main__.ObjectCreator'>
>>> def echo(o):
...       print(o)
...
>>> echo(ObjectCreator) # you can pass a class as a parameter
<class '__main__.ObjectCreator'>
>>> print(hasattr(ObjectCreator, 'new_attribute'))
False
>>> ObjectCreator.new_attribute = 'foo' # you can add attributes to a class
>>> print(hasattr(ObjectCreator, 'new_attribute'))
True
>>> print(ObjectCreator.new_attribute)
foo
>>> ObjectCreatorMirror = ObjectCreator # you can assign a class to a variable
>>> print(ObjectCreatorMirror.new_attribute)
foo
>>> print(ObjectCreatorMirror())
<__main__.ObjectCreator object at 0x8997b4c>

動的にクラスを作成する

クラスはオブジェクトなので、他のオブジェクトと同様に、その場で作成することができます。

まず、クラスを使用して関数内にクラスを作成できます。

>>> def choose_class(name):
...     if name == 'foo':
...         class Foo(object):
...             pass
...         return Foo # return the class, not an instance
...     else:
...         class Bar(object):
...             pass
...         return Bar
...
>>> MyClass = choose_class('foo')
>>> print(MyClass) # the function returns a class, not an instance
<class '__main__.Foo'>
>>> print(MyClass()) # you can create an object from this class
<__main__.Foo object at 0x89c6d4c>

しかし、ダイナミックではありません。クラス全体を自分で書く必要があるからです。

クラスはオブジェクトなので、何かによって生成されなければなりません。

classキーワードを使用すると、Pythonはこのオブジェクトを自動的に作成します。 しかし、Pythonのほとんどのものと同様、手動で行う方法もあります。

関数type覚えていますか? オブジェクトがどのような型であるかを知る良い古い関数です:

>>> print(type(1))
<type 'int'>
>>> print(type("1"))
<type 'str'>
>>> print(type(ObjectCreator))
<type 'type'>
>>> print(type(ObjectCreator()))
<class '__main__.ObjectCreator'>

まあ、 typeはまったく違った能力を持っていますし、その場でクラスを作成することもできます。 typeはクラスの記述をパラメータとして取り、クラスを返すことができます。

(私が知っているのは、渡したパラメータに応じて同じ関数が2つの全く異なる用途を持つことができないということは愚かです。これはPythonの下位互換性のために問題になります)

typeは次のように動作します:

type(name of the class,
     tuple of the parent class (for inheritance, can be empty),
     dictionary containing attributes names and values)

例えば:

>>> class MyShinyClass(object):
...       pass

この方法で手動で作成することができます:

>>> MyShinyClass = type('MyShinyClass', (), {}) # returns a class object
>>> print(MyShinyClass)
<class '__main__.MyShinyClass'>
>>> print(MyShinyClass()) # create an instance with the class
<__main__.MyShinyClass object at 0x8997cec>

クラスの名前として "MyShinyClass"を使用し、クラス参照を保持する変数として使用することに気づくでしょう。 彼らは違うかもしれませんが、事を複雑にする理由はありません。

typeは、クラスの属性を定義するための辞書を受け入れます。 そう:

>>> class Foo(object):
...       bar = True

翻訳できる:

>>> Foo = type('Foo', (), {'bar':True})

そして通常のクラスとして使用されます:

>>> print(Foo)
<class '__main__.Foo'>
>>> print(Foo.bar)
True
>>> f = Foo()
>>> print(f)
<__main__.Foo object at 0x8a9b84c>
>>> print(f.bar)
True

もちろん、あなたはそれを継承することができます:

>>>   class FooChild(Foo):
...         pass

だろう:

>>> FooChild = type('FooChild', (Foo,), {})
>>> print(FooChild)
<class '__main__.FooChild'>
>>> print(FooChild.bar) # bar is inherited from Foo
True

最終的に、クラスにメソッドを追加したいと思うでしょう。 適切な署名を持つ関数を定義し、それを属性として割り当てます。

>>> def echo_bar(self):
...       print(self.bar)
...
>>> FooChild = type('FooChild', (Foo,), {'echo_bar': echo_bar})
>>> hasattr(Foo, 'echo_bar')
False
>>> hasattr(FooChild, 'echo_bar')
True
>>> my_foo = FooChild()
>>> my_foo.echo_bar()
True

また、通常作成されたクラスオブジェクトにメソッドを追加するのと同様に、クラスを動的に作成した後でさらにメソッドを追加できます。

>>> def echo_bar_more(self):
...       print('yet another method')
...
>>> FooChild.echo_bar_more = echo_bar_more
>>> hasattr(FooChild, 'echo_bar_more')
True

私たちはどこに行くのか見ていきます。Pythonでは、クラスはオブジェクトであり、動的にクラスを動的に作成することができます。

これは、キーワードclassを使用するときにPythonが実行する動作で、メタクラスを使用することによって実行されます。

メタクラスとは(最終的に)

メタクラスはクラスを作成する 'もの'です。

オブジェクトを作成するためにクラスを定義するのは正しいでしょうか?

しかし、私たちはPythonクラスがオブジェクトであることを学びました。

さて、これらのオブジェクトを作成するのはメタクラスです。 彼らはクラスのクラスです、あなたはそれらをこのように描くことができます:

MyClass = MetaClass()
my_object = MyClass()

あなたは、あなたが次のようなことをすることができることを見てきました:

MyClass = type('MyClass', (), {})

これは、関数typeが実際にはメタクラスであるためです。 typeは、Pythonがバックグラウンドですべてのクラスを作成するために使用するメタクラスです。

さて、なぜあなたはそれが小文字で書かれているのだろうと思っています。

文字列オブジェクトを作成するクラスstrと整数オブジェクトを作成するクラスintとの整合性の問題だと思います。 typeはクラスオブジェクトを作成するクラスです。

それは__class__属性をチェックすることで__class__ます。

すべて、そして私はすべてを意味し、Pythonのオブジェクトです。 これには、int、文字列、関数、クラスが含まれます。 それらはすべてオブジェクトです。 そしてそれらのすべてはクラスから作成されています:

>>> age = 35
>>> age.__class__
<type 'int'>
>>> name = 'bob'
>>> name.__class__
<type 'str'>
>>> def foo(): pass
>>> foo.__class__
<type 'function'>
>>> class Bar(object): pass
>>> b = Bar()
>>> b.__class__
<class '__main__.Bar'>

さて、どの__class__は何__class__

>>> age.__class__.__class__
<type 'type'>
>>> name.__class__.__class__
<type 'type'>
>>> foo.__class__.__class__
<type 'type'>
>>> b.__class__.__class__
<type 'type'>

ですから、メタクラスはクラスオブジェクトを作成するだけのものです。

あなたが望むなら、それを「クラスファクトリー」と呼ぶことができます。

typeは組み込みのメタクラスPythonが使用するものですが、もちろん独自のメタクラスを作成することもできます。

__metaclass__属性

Python 2では、クラスを書くときに__metaclass__属性を追加することができます(次のPython 3の構文を参照)。

class Foo(object):
    __metaclass__ = something...
    [...]

そうすると、Pythonはメタクラスを使ってクラスFooを作成します。

細心の注意を払っています。

class Foo(object)最初に書きclass Foo(object)が、クラスオブジェクトFooはまだメモリに作成されていません。

Pythonはクラス定義で__metaclass__を探します。 見つかった場合は、それを使用してオブジェクトクラスFooを作成します。 そうでなければ、 typeを使ってクラスを作成します。

それを数回読んでください。

あなたがするとき:

class Foo(Bar):
    pass

Pythonは以下を行います:

Foo __metaclass__属性がありますか?

もしそうなら、 __metaclass__ものを使ってFooという名前でクラスオブジェクト(私はここでクラスオブジェクトと言っています)をメモリに作成します。

Pythonが__metaclass__見つけられない場合は、MODULEレベルで__metaclass__を探し、 __metaclass__を継承しないクラス(基本的に古いスタイルのクラスのみ)で同じことを試みます。

そして、__metaclass__がまったく見つからない場合は、クラスのオブジェクトを作成するためにBar (最初の親)のメタクラス(デフォルトのtypeかもしれません)を使用します。

__metaclass__属性が継承されないように注意してください。親( Bar.__class__ )のメタクラスが継承されます。 Bartype()type.__new__()type.__new__()ではなくBarを作成した__metaclass__属性を使用した場合、サブクラスはその動作を継承しません。

今大きな問題は、 __metaclass__何を入れることができるかということです。

答えは、クラスを作成できるものです。

クラスを作ることができるのは何ですか? type 、またはそれをサブクラス化または使用するもの。

Python 3のメタクラス

メタクラスを設定する構文はPython 3で変更されています:

class Foo(object, metaclass=something):
    [...]

つまり、 __metaclass__属性は使用されなくなり、基本クラスのリストのキーワード引数が優先されます。

しかし、メタクラスの動作はほとんど変わりません。

カスタムメタクラス

メタクラスの主な目的は、作成時にクラスを自動的に変更することです。

通常は、現在のコンテキストと一致するクラスを作成するAPIに対してこれを行います。

あなたのモジュール内のすべてのクラスが大文字で書かれた属性を持つべきだと決める愚かな例を想像してみてください。 これを行うにはいくつかの方法がありますが、モジュールレベルで__metaclass__を設定する方法もあります。

このようにして、このモジュールのすべてのクラスはこのメタクラスを使用して作成され、すべての属性を大文字にするようにメタクラスに指示するだけで済みます。

幸いなことに、 __metaclass__は実際には__metaclass__ことができますが、正式なクラスである必要はありません(私の知っているように、クラスの名前はクラスである必要はありません。

だから、関数を使って簡単な例から始めましょう。

# the metaclass will automatically get passed the same argument
# that you usually pass to `type`
def upper_attr(future_class_name, future_class_parents, future_class_attr):
    """
      Return a class object, with the list of its attribute turned
      into uppercase.
    """

    # pick up any attribute that doesn't start with '__' and uppercase it
    uppercase_attr = {}
    for name, val in future_class_attr.items():
        if not name.startswith('__'):
            uppercase_attr[name.upper()] = val
        else:
            uppercase_attr[name] = val

    # let `type` do the class creation
    return type(future_class_name, future_class_parents, uppercase_attr)

__metaclass__ = upper_attr # this will affect all classes in the module

class Foo(): # global __metaclass__ won't work with "object" though
    # but we can define __metaclass__ here instead to affect only this class
    # and this will work with "object" children
    bar = 'bip'

print(hasattr(Foo, 'bar'))
# Out: False
print(hasattr(Foo, 'BAR'))
# Out: True

f = Foo()
print(f.BAR)
# Out: 'bip'

さて、まったく同じようにしましょうが、実際のクラスをメタクラスに使用しましょう:

# remember that `type` is actually a class like `str` and `int`
# so you can inherit from it
class UpperAttrMetaclass(type):
    # __new__ is the method called before __init__
    # it's the method that creates the object and returns it
    # while __init__ just initializes the object passed as parameter
    # you rarely use __new__, except when you want to control how the object
    # is created.
    # here the created object is the class, and we want to customize it
    # so we override __new__
    # you can do some stuff in __init__ too if you wish
    # some advanced use involves overriding __call__ as well, but we won't
    # see this
    def __new__(upperattr_metaclass, future_class_name,
                future_class_parents, future_class_attr):

        uppercase_attr = {}
        for name, val in future_class_attr.items():
            if not name.startswith('__'):
                uppercase_attr[name.upper()] = val
            else:
                uppercase_attr[name] = val

        return type(future_class_name, future_class_parents, uppercase_attr)

しかし、これは本当にOOPではありません。 type直接呼びますが、親をオーバーライドしたり__new__呼び出すことはありません。 やってみましょう:

class UpperAttrMetaclass(type):

    def __new__(upperattr_metaclass, future_class_name,
                future_class_parents, future_class_attr):

        uppercase_attr = {}
        for name, val in future_class_attr.items():
            if not name.startswith('__'):
                uppercase_attr[name.upper()] = val
            else:
                uppercase_attr[name] = val

        # reuse the type.__new__ method
        # this is basic OOP, nothing magic in there
        return type.__new__(upperattr_metaclass, future_class_name,
                            future_class_parents, uppercase_attr)

あなたは余分な引数upperattr_metaclass気づいたかもしれません。 特別なことは何もありません__new__は、最初に定義されたクラスを常に最初のパラメータとして受け取ります。 インスタンスを最初のパラメータとして受け取る通常のメソッド、またはクラスメソッドの定義クラスのためのselfを持っているように。

もちろん、私がここで使った名前は分かりやすくするために長いですが、 selfように、すべての議論は従来の名前を持っています。 したがって、実際の制作メタクラスは次のようになります。

class UpperAttrMetaclass(type):

    def __new__(cls, clsname, bases, dct):

        uppercase_attr = {}
        for name, val in dct.items():
            if not name.startswith('__'):
                uppercase_attr[name.upper()] = val
            else:
                uppercase_attr[name] = val

        return type.__new__(cls, clsname, bases, uppercase_attr)

継承を簡単にするsuperを使用することで、より洗練されたものにすることができます(メタクラスを継承し、メタクラスを継承し、継承します)。

class UpperAttrMetaclass(type):

    def __new__(cls, clsname, bases, dct):

        uppercase_attr = {}
        for name, val in dct.items():
            if not name.startswith('__'):
                uppercase_attr[name.upper()] = val
            else:
                uppercase_attr[name] = val

        return super(UpperAttrMetaclass, cls).__new__(cls, clsname, bases, uppercase_attr)

それでおしまい。 実際にはメタクラスについては何もありません。

メタクラスを使用するコードの複雑さの背後にある理由は、メタクラスのためではありません。なぜなら、通常、メタクラスを使用してイントロスペクション、継承の操作、__dict__などの変数などに依存するねじれた処理を行うからです。

確かに、メタクラスは黒い魔法を実行するのに特に役に立ち、したがって複雑なものです。 しかし、それ自体はシンプルです。

  • クラス作成を傍受する
  • クラスを変更する
  • 変更されたクラスを返す

なぜ関数の代わりにメタクラスクラスを使うのですか?

__metaclass__は呼び出し可能なものを受け入れることができるので、明らかにもっと複雑なので、なぜクラスを使うのですか?

これにはいくつかの理由があります。

  • その意図ははっきりしています。 あなたがUpperAttrMetaclass(type)を読むとき、あなたは何が続くのか知っています
  • あなたはOOPを使うことができます。 メタクラスはメタクラスを継承し、親メソッドをオーバーライドすることができます。 メタクラスはメタクラスを使用することもできます。
  • クラスのサブクラスは、メタクラスクラスを指定した場合はそのメタクラスのインスタンスになりますが、メタクラス関数ではない場合は、そのメタクラスのインスタンスになります。
  • コードをよりよく構造化することができます。 上記の例のように、何かのことでメタクラスを使用することは決してありません。 これは通常、何か複雑なものです。 複数のメソッドを作成して1つのクラスにグループ化することは、コードを読みやすくするために非常に便利です。
  • __new____new____new__にフックでき__call__ 。 これは、あなたが別のものを行うことができます。 通常は__new__ですべてを行うことができますが、 __new__を使うほうが快適です。
  • これらはメタクラスと呼ばれています。 何かを意味する必要があります!

なぜメタクラスを使用しますか?

今大きな問題です。 なぜ、あいまいなエラーが発生しやすい機能を使用しますか?

まあ、あなたはしない:

メタクラスは、ユーザーの99%が心配するべきではない深い魔法です。 あなたがそれらを必要としているかどうか疑問に思うなら、あなたは(実際に必要とする人々は、彼らが必要とすることを確実に知り、理由について説明する必要はありません)。

Python Guru Tim Peters

メタクラスの主な使用例は、APIを作成することです。 これの典型的な例は、Django ORMです。

このように定義することができます:

class Person(models.Model):
    name = models.CharField(max_length=30)
    age = models.IntegerField()

しかし、あなたがこれを行う場合:

guy = Person(name='bob', age='35')
print(guy.age)

IntegerFieldオブジェクトは返されません。 intを返し、データベースから直接取得することもできます。

これは可能ですmodels.Model__metaclass__を定義しており、単純な文で定義したPersonをデータベースフィールドの複雑なフックに変換する魔法を使用しています。

Djangoは、シンプルなAPIを公開し、メタクラスを使用してこのAPIのコードを再作成することで複雑な外観をシンプルにしています。

最後の言葉

まず、クラスはインスタンスを作成できるオブジェクトであることがわかります。

実際、クラス自体はインスタンスです。 メタクラスの

>>> class Foo(object): pass
>>> id(Foo)
142630324

すべてがPythonのオブジェクトであり、それらはすべてクラスのインスタンスまたはメタクラスのインスタンスのいずれかです。

type除く。

typeは実際には独自のメタクラスです。 これは、純粋なPythonで再現できるものではなく、実装レベルでちょっとした欺瞞をしています。

第二に、メタクラスは複雑です。 非常に簡単なクラス変更には使用したくないかもしれません。 次の2つの方法を使用してクラスを変更できます。

クラスの変更が必要な時間の99%は、これらを使用する方がよいでしょう。

しかし、98%の時間で、クラスの変更はまったく必要ありません。

python3 複数 class

メタクラスとは何ですか?私たちはそれを何のために使用しますか?




メタクラスの用途の1つは、新しいプロパティとメソッドをインスタンスに自動的に追加することです。

たとえば、 Djangoモデルを見ると、その定義はちょっと混乱しています。 クラスプロパティのみを定義しているかのように見えます:

class Person(models.Model):
    first_name = models.CharField(max_length=30)
    last_name = models.CharField(max_length=30)

しかし、実行時に、Personオブジェクトはあらゆる種類の便利なメソッドで埋められます。 いくつかの素晴らしいメタクラスについてはsourceを参照してください。




私は、ONLampのメタクラスプログラミングの導入はよく書かれていると思います。

http://www.onlamp.com/pub/a/python/2003/04/17/metaclasses.html (アーカイブhttps://web.archive.org/web/20080206005253/http://www.onlamp.com/pub/a/python/2003/04/17/metaclasses.html

つまり、クラスはインスタンスの作成のための青写真であり、メタクラスはクラスの作成のための青写真です。 Pythonクラスでは、この動作を可能にするファーストクラスのオブジェクトである必要があることは容易にわかります。

私は自分自身を書いたことは一度もありませんでしたが、私はDjangoフレームワークでメタクラスの最も良い使い方の1つを見ることができると思います。 モデルクラスは、メタクラスアプローチを使用して、新しいモデルやフォームクラスを記述する宣言的スタイルを可能にします。 メタクラスがクラスを作成している間、すべてのメンバーがクラス自体をカスタマイズする可能性があります。

残っていることは、次のとおりです。メタクラスの種類がわからない場合、 必要としない確率は99%です。




Python 3アップデート

メタクラスには、この時点で2つの重要なメソッドがあります。

  • __prepare__ 、および
  • __new__

__prepare__OrderedDictクラスが作成されている間に名前空間として使用されるカスタムマッピング(anなど)を指定できます。選択した名前空間のインスタンスを返す必要があります。あなたが実装し__prepare__ていない場合、通常dictが使用されます。

__new__ 最終クラスの実際の作成/変更を担当します。

裸の骨、無駄な余分なメタクラスは、次のようになります:

class Meta(type):

    def __prepare__(metaclass, cls, bases):
        return dict()

    def __new__(metacls, cls, bases, clsdict):
        return super().__new__(metacls, cls, bases, clsdict)

簡単な例:

それは常になければなりませんように-あなたはいくつかの簡単な検証コードは、あなたの属性に実行したいと言いますintstr。メタクラスがなければ、クラスは次のようになります。

class Person:
    weight = ValidateType('weight', int)
    age = ValidateType('age', int)
    name = ValidateType('name', str)

ご覧のとおり、属性の名前を2回繰り返す必要があります。これは、刺激的なバグと共にタイプミスを可能にします。

単純なメタクラスはその問題に対処できます:

class Person(metaclass=Validator):
    weight = ValidateType(int)
    age = ValidateType(int)
    name = ValidateType(str)

これは、メタクラスがどのように見えるかです(__prepare__必要ないので使用しません)。

class Validator(type):
    def __new__(metacls, cls, bases, clsdict):
        # search clsdict looking for ValidateType descriptors
        for name, attr in clsdict.items():
            if isinstance(attr, ValidateType):
                attr.name = name
                attr.attr = '_' + name
        # create final class and return it
        return super().__new__(metacls, cls, bases, clsdict)

サンプルラン:

p = Person()
p.weight = 9
print(p.weight)
p.weight = '9'

次を生成する:

9
Traceback (most recent call last):
  File "simple_meta.py", line 36, in <module>
    p.weight = '9'
  File "simple_meta.py", line 24, in __set__
    (self.name, self.type, value))
TypeError: weight must be of type(s) <class 'int'> (got '9')

:この例は、クラスデコレータでも実現できたほどシンプルですが、実際のメタクラスはもっと効果的です。

参照用の 'ValidateType'クラス:

class ValidateType:
    def __init__(self, type):
        self.name = None  # will be set by metaclass
        self.attr = None  # will be set by metaclass
        self.type = type
    def __get__(self, inst, cls):
        if inst is None:
            return self
        else:
            return inst.__dict__[self.attr]
    def __set__(self, inst, value):
        if not isinstance(value, self.type):
            raise TypeError('%s must be of type(s) %s (got %r)' %
                    (self.name, self.type, value))
        else:
            inst.__dict__[self.attr] = value



メタクラスは、他のクラスをどのように作成するかを指示するクラスです。

これは、私が問題の解決策としてメタクラスを見た場合です。私は本当に複雑な問題がありました。多分解決できたかもしれませんが、私はメタクラスを使って解決することを選択しました。複雑さのため、モジュール内のコメントが書かれたコードの量を上回ったところで私が書いた数少ないモジュールの1つです。ここにあります...

#!/usr/bin/env python

# Copyright (C) 2013-2014 Craig Phillips.  All rights reserved.

# This requires some explaining.  The point of this metaclass excercise is to
# create a static abstract class that is in one way or another, dormant until
# queried.  I experimented with creating a singlton on import, but that did
# not quite behave how I wanted it to.  See now here, we are creating a class
# called GsyncOptions, that on import, will do nothing except state that its
# class creator is GsyncOptionsType.  This means, docopt doesn't parse any
# of the help document, nor does it start processing command line options.
# So importing this module becomes really efficient.  The complicated bit
# comes from requiring the GsyncOptions class to be static.  By that, I mean
# any property on it, may or may not exist, since they are not statically
# defined; so I can't simply just define the class with a whole bunch of
# properties that are @property @staticmethods.
#
# So here's how it works:
#
# Executing 'from libgsync.options import GsyncOptions' does nothing more
# than load up this module, define the Type and the Class and import them
# into the callers namespace.  Simple.
#
# Invoking 'GsyncOptions.debug' for the first time, or any other property
# causes the __metaclass__ __getattr__ method to be called, since the class
# is not instantiated as a class instance yet.  The __getattr__ method on
# the type then initialises the class (GsyncOptions) via the __initialiseClass
# method.  This is the first and only time the class will actually have its
# dictionary statically populated.  The docopt module is invoked to parse the
# usage document and generate command line options from it.  These are then
# paired with their defaults and what's in sys.argv.  After all that, we
# setup some dynamic properties that could not be defined by their name in
# the usage, before everything is then transplanted onto the actual class
# object (or static class GsyncOptions).
#
# Another piece of magic, is to allow command line options to be set in
# in their native form and be translated into argparse style properties.
#
# Finally, the GsyncListOptions class is actually where the options are
# stored.  This only acts as a mechanism for storing options as lists, to
# allow aggregation of duplicate options or options that can be specified
# multiple times.  The __getattr__ call hides this by default, returning the
# last item in a property's list.  However, if the entire list is required,
# calling the 'list()' method on the GsyncOptions class, returns a reference
# to the GsyncListOptions class, which contains all of the same properties
# but as lists and without the duplication of having them as both lists and
# static singlton values.
#
# So this actually means that GsyncOptions is actually a static proxy class...
#
# ...And all this is neatly hidden within a closure for safe keeping.
def GetGsyncOptionsType():
    class GsyncListOptions(object):
        __initialised = False

    class GsyncOptionsType(type):
        def __initialiseClass(cls):
            if GsyncListOptions._GsyncListOptions__initialised: return

            from docopt import docopt
            from libgsync.options import doc
            from libgsync import __version__

            options = docopt(
                doc.__doc__ % __version__,
                version = __version__,
                options_first = True
            )

            paths = options.pop('<path>', None)
            setattr(cls, "destination_path", paths.pop() if paths else None)
            setattr(cls, "source_paths", paths)
            setattr(cls, "options", options)

            for k, v in options.iteritems():
                setattr(cls, k, v)

            GsyncListOptions._GsyncListOptions__initialised = True

        def list(cls):
            return GsyncListOptions

        def __getattr__(cls, name):
            cls.__initialiseClass()
            return getattr(GsyncListOptions, name)[-1]

        def __setattr__(cls, name, value):
            # Substitut option names: --an-option-name for an_option_name
            import re
            name = re.sub(r'^__', "", re.sub(r'-', "_", name))
            listvalue = []

            # Ensure value is converted to a list type for GsyncListOptions
            if isinstance(value, list):
                if value:
                    listvalue = [] + value
                else:
                    listvalue = [ None ]
            else:
                listvalue = [ value ]

            type.__setattr__(GsyncListOptions, name, listvalue)

    # Cleanup this module to prevent tinkering.
    import sys
    module = sys.modules[__name__]
    del module.__dict__['GetGsyncOptionsType']

    return GsyncOptionsType

# Our singlton abstract proxy class.
class GsyncOptions(object):
    __metaclass__ = GetGsyncOptionsType()



tl; drバージョン

このtype(obj)関数は、オブジェクトの型を取得します。

type()クラスのは、そのですメタクラス

メタクラスを使用するには:

class Foo(object):
    __metaclass__ = MyMetaClass



type()関数は、オブジェクトの型を返すことも、新しい型を作成することもできます。

たとえば、type()関数を使ってHiクラスを作成することができ、クラスHi(object)でこの方法を使用する必要はありません。

def func(self, name='mike'):
    print('Hi, %s.' % name)

Hi = type('Hi', (object,), dict(hi=func))
h = Hi()
h.hi()
Hi, mike.

type(Hi)
type

type(h)
__main__.Hi

type()を使用してクラスを動的に作成することに加えて、クラスの作成動作を制御してメタクラスを使用できます。

Pythonオブジェクトモデルによれば、クラスはオブジェクトなので、クラスは別の特定のクラスのインスタンスでなければなりません。デフォルトでは、Pythonクラスは型クラスのインスタンスです。つまり、typeは、ユーザー定義のクラスの組み込みクラスとメタクラスのほとんどのメタクラスです。

class ListMetaclass(type):
    def __new__(cls, name, bases, attrs):
        attrs['add'] = lambda self, value: self.append(value)
        return type.__new__(cls, name, bases, attrs)

class CustomList(list, metaclass=ListMetaclass):
    pass

lst = CustomList()
lst.add('custom_list_1')
lst.add('custom_list_2')

lst
['custom_list_1', 'custom_list_2']

Magicは、メタクラスでキーワード引数を渡したときに有効になります。これは、ListMetaclassを使用してCustomListを作成するPythonインタプリタを示します。new()では、この時点でクラス定義を変更したり、新しいメソッドを追加したり、改訂された定義を返すことができます。




Related