11

更新-2012/12/13

明確にするために-私はクラスにメソッドを追加する方法にはあまり興味がありません-以下の私の質問と人々の回答でわかるように、それを行うには複数の方法があります(頬の舌と帽子の先端私のPerl自己)。

私が興味を持っているのは、さまざまなアプローチを使用してクラスにメソッドを追加することの根本的な違いを学ぶことです。大きな問題は、なぜメタクラスを使用する必要があるのか​​ということです。たとえば、Pythonメタクラスプログラミングの入門書には次のように記載されています。

おそらく、メタクラスの最も一般的な使用法[...]:生成されたクラスで定義されたメソッドの追加、削除、名前の変更、または置換。

そして、それを行う方法は他にもあるので、私は戸惑い、説明を探しています。

ありがとう!

オリジナル-2012/12/12

クラス(およびそのクラスに基づいて新しく生成されたクラス)にメソッドを動的に追加する必要があります。私は2つのアプローチを思いつきました。1つはメタクラスを含み、もう1つはメタクラスなしで行います。後者が「黒魔術」メタクラスを含まないという事実を除いて、2つのアプローチが私に与えるものに違いは見られません;)

メタクラスを使用したアプローチ#1 :

class Meta(type):
        def __init__(cls, *args, **kwargs):
                setattr(cls, "foo", lambda self: "foo@%s(class %s)" % (self,
                        cls.__name__))

class Y(object):
        __metaclass__ = Meta

y = Y()
y.foo() # Gives 'foo@<__main__.Y object at 0x10e4afd10>(class Y)'

メタクラスなしのアプローチ#2 :

class Z(object):
        def __init__(self):
                setattr(self.__class__, "foo",
                        lambda self: "foo@%s(class %s)" %
                        (self, self.__class__.__name__))

z = Z()
z.foo() # Gives 'foo@<__main__.Z object at 0x10c865dd0>(class Z)'

私の知る限り、どちらのアプローチでも同じ結果と「表現度」が得られます。を使用して新しいクラスを作成しようとしても、type("NewClassY", (Y, ), {})またはtype("NewClassZ", (Z, ), {})2つのアプローチ間で異ならない同じ期待される結果が得られます。

それで、アプローチに本当に「根本的な」違いがあるのか​​、それとも、#1または#2のいずれかを使用した場合、またはそれが単なる構文糖衣である場合に、後で私を「噛む」何かがあるのだろうか。

PS:はい、Pythonとpythonicデータモデルのメタクラスについて話している他のスレッドをここで読みました。

4

2 に答える 2

9

メタクラスを使用する明白な理由は、クラスが認識されるとすぐに、オブジェクトの存在に関係なく、クラスに関するメタデータを実際に提供するためです。些細なことですよね?さて、これが何を意味するかを確認するために、元のクラスZとクラスで実行したいくつかのコマンドを示しましょう。Y

In [287]: hasattr(Y,'foo')
Out[287]: True

In [288]: hasattr(Z,'foo')
Out[288]: False

In [289]: Y.__dict__
Out[289]:
<dictproxy {..., 'foo': <function __main__.<lambda>>}>

In [290]: Z.__dict__
Out[290]:
<dictproxy {...}>

In [291]: z= Z()

In [292]: hasattr(Z,'foo')
Out[292]: True

In [293]: Z.__dict__
Out[293]:
<dictproxy {..., 'foo': <function __main__.<lambda>>}>

In [294]: y = Y()

In [295]: hasattr(Y,'foo')
Out[295]: True

In [296]: Y.__dict__
Out[296]:
<dictproxy {..., 'foo': <function __main__.<lambda>>}>

ご覧のように、2 番目のバージョンでは、クラス Z が宣言された後に実際に大幅に変更されています。これは、しばしば回避したいことです。Pythonが「型」(クラスオブジェクト)に対して操作を行うことは確かに珍しい操作ではなく、おそらくこの場合(メソッドが実行時に実際には動的ではなく、宣言時にのみ動的である場合)に、可能な限り一貫性を持たせたいと思うでしょう。時間)。

思い浮かぶアプリケーションの 1 つにドキュメントがあります。メタクラスを使用するために docstring を追加するとfoo、メソッドを介してドキュメントがそれを取得する可能性がありますが、__init__これはほとんどありません。

また、見つけにくいバグにつながる可能性もあります。クラスのメタ情報を使用するコードを考えてみましょう。99.99% のケースで、これは のインスタンスZが既に作成された後に実行される可能性がありますが、その 0.01% は、クラッシュしない場合でも、奇妙な動作につながる可能性があります。

また、親のコンストラクターを呼び出す場所に細心の注意を払う必要がある階層チェーンでは、注意が必要です。たとえば、このようなクラスでは問題が発生する可能性があります。

class Zd(Z):
    def __init__(self):
        self.foo()
        Z.__init__(self)
a = Zd()
...
AttributeError: 'Zd' object has no attribute 'foo'

これはうまくいきますが:

class Yd(Y):
    def __init__(self):
        self.foo()
        Y.__init__(self)
a = Yd()

関連するメソッドを使用せずにメソッドを呼び出すのはばかげているように思えるかもしれませんが__init__、注意を怠ると、MRO がすぐに明らかにならないより複雑な階層で発生する可能性があります。Z.__init__また、ほとんどの場合、Zd() は以前どこかで呼び出されるとすぐに成功するため、このエラーを見つけるのは困難です。

于 2012-12-13T13:17:23.100 に答える
2

問題がメソッドを動的に追加することである場合、python は非常に簡単な方法でそれを処理します。次のようになります。

#!/usr/bin/python
class Alpha():

    def __init__(self):
        self.a = 10
        self.b = 100

alpha = Alpha()
print alpha.a
print alpha.b

def foo(self):
    print self.a * self.b * self.c

Alpha.c = 1000
Alpha.d = foo

beta = Alpha()
beta.d()

出力:

$ python script.py 
10
100
1000000

よろしく!

PS: ここには黒魔術のようなものは見当たりません (:

編集:

マルティノーのコメントを考慮してdata attributes、 ではなくを追加していmethod function attributesます。追加された関数のラッパーとしてラムダ関数を使用することを除けば、Alpha.d = fooとの実行に違いはありません。Alpha.d = lambda self: foo(self)foo

メソッドの追加は同じで、python 自体は両方の追加に同じ名前を付けます。

#!/usr/bin/python
class Alpha():

    def __init__(self):
        self.a = 10
        self.b = 100

alpha = Alpha()
print alpha.a
print alpha.b

def foo(self):
    print self.a * self.b * self.c

Alpha.c = 1000

Alpha.d = lambda self: foo(self)
Alpha.e = foo

print Alpha.d
print Alpha.e

a = Alpha()
a.d()
a.e()

出力:

10
100
<unbound method Alpha.<lambda>>
<unbound method Alpha.foo>
1000000
1000000

示されているように、python 自体は両方の結果の追加をメソッドとして命名します。唯一の違いは、一方が function への参照であり、もう一方が定義本体でfoo関数を使用するラムダ関数への参照であることです。foo

私が何か間違ったことを言った場合は、訂正してください。

よろしく!

于 2012-12-12T23:24:16.187 に答える