13

SqlAlchemyのクラス構築プロセスで自分のコードの一部を挿入しようとしています。コードを理解しようとすると、メタクラスの実装に少し混乱します。関連するスニペットは次のとおりです。

SqlAlchemyのデフォルトの「メタクラス」:

class DeclarativeMeta(type):
    def __init__(cls, classname, bases, dict_):
        if '_decl_class_registry' in cls.__dict__:
            return type.__init__(cls, classname, bases, dict_)
        else:
            _as_declarative(cls, classname, cls.__dict__)
        return type.__init__(cls, classname, bases, dict_)

    def __setattr__(cls, key, value):
        _add_attribute(cls, key, value)

declarative_baseこのように実装されます:

def declarative_base(bind=None, metadata=None, mapper=None, cls=object,
                     name='Base', constructor=_declarative_constructor,
                     class_registry=None,
                     metaclass=DeclarativeMeta):
     # some code which should not matter here
     return metaclass(name, bases, class_dict)

これは次のように使用されます:

Base = declarative_base()

class SomeModel(Base):
    pass

これで、次のような独自のメタクラスを派生させました。

class MyDeclarativeMeta(DeclarativeMeta):
    def __init__(cls, classname, bases, dict_):
        result = DeclarativeMeta.__init__(cls, classname, bases, dict_)
        print result
        # here I would add my custom code, which does not work
        return result

そして、次のように使用します。

Base = declarative_base(metaclass=MyDeclarativeMeta)

さて、私の問題です:

  • print result私自身のクラスでは常に印刷しNoneます。
  • コードはとにかく動作するようです!?
  • メタクラスが使用しているのはなぜ__init__ですか__new__
  • declarative_baseこのクラスのインスタンスを返します。__metaclass__値として属性を持つクラスを返すべきではありませんMyDeclarativeMetaか?

それで、なぜコードがまったく機能するのか疑問に思います。SqlAlchemyの人々は明らかに彼らが何をしているのかを知っているので、私は完全に間違った方向に進んでいると思います。誰かがここで何が起こっているのか説明できますか?

4

3 に答える 3

17

まず最初に。__init__を返す必要Noneがあります。Pythonのドキュメントには「値が返されない可能性がある」と記載されていますが、Pythonでは、returnステートメントをヒットせずに関数の「終了をドロップオフ」することは。と同等ですreturn None。したがって、明示的にNone(リテラルとして、または結果として得られる式の値を返すことによってNone)返すことも害にはなりません。

ですから、あなたが引用する__init__方法はDeclarativeMeta私には少し奇妙に見えますが、それは何も悪いことではありません。ここでも、私が追加したコメントがいくつかあります。

def __init__(cls, classname, bases, dict_):
    if '_decl_class_registry' in cls.__dict__:
        # return whatever type's (our superclass) __init__ returns
        # __init__ must return None, so this returns None, which is okay
        return type.__init__(cls, classname, bases, dict_)
    else:
        # call _as_declarative without caring about the return value
        _as_declarative(cls, classname, cls.__dict__)
    # then return whatever type's __init__ returns
    return type.__init__(cls, classname, bases, dict_)

これは、より簡潔かつ明確に次のように書くことができます。

def __init__(cls, classname, bases, dict_):
    if '_decl_class_registry' not in cls.__dict__:
        _as_declarative(cls, classname, cls.__dict__)
    type.__init__(cls, classname, bases, dict_)

SqlAlchemyの開発者が、何をtype.__init__返す必要があると感じたのかわかりません(これはに制限されていますNone)。__init__おそらくそれは、何かを返すかもしれない未来に対する証拠です。おそらくそれは、コア実装がスーパークラスに延期されることによる他のメソッドとの一貫性のためだけです。通常、後処理する場合を除いて、スーパークラス呼び出しが返すものは何でも返します。しかし、それは確かに実際には何もしません。

つまり、print result印刷Noneはすべてが意図したとおりに機能していることを示しているだけです。


次に、メタクラスが実際に何を意味するのかを詳しく見てみましょう。メタクラスは、クラスのクラスにすぎません。他のクラスと同様に、メタクラスを呼び出すことにより、メタクラス(つまり、クラス)のインスタンスを作成します。クラスブロック構文は、実際にはクラスを作成するものではありません。ディクショナリを定義し、それをメタクラス呼び出しに渡してクラスオブジェクトを作成するための非常に便利な構文糖衣です。

属性は魔法のようなものではありません。適切な情報がないため、バックチャネルを介して「__metaclass__このクラスブロックに「のインスタンスではなくこのメタクラスのインスタンスを作成してもらいたい」という情報を伝達するための巨大なハックです。typeその情報をインタプリタに伝達するためのチャネル。1

これは、例を使用するとおそらくより明確になります。次のクラスブロックを取ります。

class MyClass(Look, Ma, Multiple, Inheritance):
    __metaclass__ = MyMeta

    CLASS_CONST = 'some value'

    def __init__(self, x):
        self.x = x

    def some_method(self):
        return self.x - 76

これは、次の2を実行するための大まかな構文糖衣です。

dict_ = {}

dict_['__metaclass__'] = MyMeta
dict_['CLASS_CONST'] = 'some value'

def __init__(self, x):
    self.x = x
dict_['__init__'] = __init__

def some_method(self):
    return self.x - 76
dict_['some_method'] = some_method

metaclass = dict_.get('__metaclass__', type)
bases = (Look, Ma, Multiple, Inheritance)
classname = 'MyClass'

MyClass = metaclass(classname, bases, dict_)

したがって、「__metaclass__[メタクラス]を値として持つ属性を持つクラス」はメタクラスのインスタンスです!それらはまったく同じものです。唯一の違いは、クラスブロックと属性を使用するのではなく、(メタクラスを呼び出すことによって)クラスを直接作成する場合、__metaclass__必ずしも__metaclass__属性として持つ必要がないことです。3

最後のの呼び出しはmetaclass、他のクラスの呼び出しとまったく同じです。metaclass.__new__(classname, bases, dict_)クラスオブジェクトを作成するために呼び出し、次に結果__init__のオブジェクトを呼び出して初期化します。

デフォルトのメタクラスである、はtype、で興味深いことを行うだけ__new__です。そして、私が例で見たメタクラスのほとんどの使用法は、実際にはクラスデコレータを実装するための複雑な方法にすぎません。彼らはクラスが作成されたときに何らかの処理を行いたいので、その後は気にしません。したがって、__new__前と後の両方で実行できるため、使用しますtype.__new____new__最終的な結果は、それがメタクラスに実装するものであると誰もが考えるということです。

しかし、実際には__init__メソッドを持つことができます。作成後、新しいクラスオブジェクトで呼び出されます。クラスにいくつかの属性を追加する必要がある場合、またはクラスオブジェクトをレジストリのどこかに記録する必要がある場合、これは実際には、よりも少し便利な場所(および論理的に正しい場所)です__new__


1 Python3では、これはmetaclass、クラスの属性としてではなく、基本クラスリストに「キーワード引数」として追加することで対処されます。

2実際には、構築中のクラスとすべてのベースの間でメタクラスの互換性が必要なため、少し複雑になりますが、これが中心的なアイデアです。

3type通常の方法で作成されたメタクラス(以外)を持つクラスでさえ、必ずしも属性として持つ必要があるわけではありません。__metaclass__クラスのクラスをチェックする正しい方法は、他のもののクラスをチェックするのと同じ方法です。を使用するcls.__class__か、適用しますtype(cls)

于 2012-10-02T22:55:53.863 に答える
11

基本的に__init__、SQLAlchemyのバージョンは間違っています。おそらく3年前にどこかからメタクラスを切り取って貼り付けることでそのように書かれたか、あるいは__init__後でなって変更されていない別のメソッドとして始まったのかもしれません。0.5を最初に書いたときにチェックしたところ、ほとんど同じように見えますが、不要な「return」ステートメントがあります。今すぐ修正して、混乱してすみません。

于 2012-10-04T14:31:21.023 に答える
6
  • print result私自身のクラスでは常にNoneを出力します。

これは、コンストラクターが何も返さないためです:)

  • メタクラスが使用しているのはなぜ__init__ですか__new__

SQLAlchemyがclsの参照を宣言型クラスレジストリに格納する必要があるためだと思います。では__new__、クラスはまだ存在していません(https://stackoverflow.com/a/1840466を参照)。

私がサブクラス化したときDeclarativeMeta、私は実際に__init__SQLAlchemyのコードに従ってすべてを行いました。振り返ってみると、あなたの質問を読んだ後、私のコードは__new__代わりに使用する必要があります。

  • declarative_baseこのクラスのインスタンスを返します。__metaclass__値として属性を持つクラスを返すべきではありませんMyDeclarativeMetaか?

ベンはこれをとてもよく説明したと思います。とにかく、必要に応じて(非推奨)、呼び出しをスキップdeclarative_base()して独自のBaseクラスを作成できます。

# Almost the same as:
#   Base = declarative_base(cls=Entity, name='Base', metaclass=MyDeclarativeMeta)
# minus the _declarative_constructor.
class Base(Entity):
    __metaclass__ = MyDeclarativeMeta

    _decl_class_registry = dict()
    metadata = MetaData()

この場合、__metaclass__属性はそこにあります。私は実際にこのようなBaseクラスを作成して、PyCharmがで定義されたもののオートコンプリートを取得できるようにしましたEntity

于 2012-10-02T23:32:21.600 に答える