web-dev-qa-db-ja.com

コンストラクター(Python)の外部のメソッドのインスタンス変数-なぜ、どのように?

私の質問は、初期化されるインスタンス変数に関するものですクラスコンストラクター外のメソッド内。これはPython用です。

私が理解していることを最初に述べます:

  1. クラスはコンストラクターを定義する場合があり、他のメソッドを定義する場合もあります。
  2. インスタンス変数は通常、コンストラクター内で定義/初期化されます。
  3. ただし、インスタンス変数は定義/初期化することもできますoutsideコンストラクター、例:同じクラスの他のメソッドで。
  4. (2)と(3)の例-Catクラスのself.meowself.roarを参照未満:

    class Cat():
    
        def __init__(self):
            self.meow = "Meow!"
        def meow_bigger(self):
            self.roar = "Roar!"
    

私の質問:

  1. コンストラクター内でインスタンス変数を初期化することがベストプラクティスであるのはなぜですか?

  2. インスタンス変数がコンストラクター以外のメソッドで定期的に初期化される場合、どのような一般的/特定の混乱が発生する可能性がありますか? (たとえば、MarkLutzのProgrammingPythonのTkinterガイドを読んだところ、すばらしいと思いました。PhotoImageオブジェクト/参照を保持するために使用されるインスタンス変数は、コンストラクターではなく、以降のメソッドで初期化されていることに気付きました。そこに問題がありますが、その慣行は長期的に問題を引き起こす可能性がありますか?)

  3. コンストラクターではなく、他のメソッドでインスタンス変数を初期化するのは、どのようなシナリオでbetterでしょうか?


  1. 私の知る限り、インスタンス変数はクラスオブジェクトが作成されたときではなく、afterクラスオブジェクトがインスタンス化されたときに存在します。上記のコードに進んで、これを示します。

    >> c = Cat() 
    >> c.meow
    'Meow!'
    >> c.roar
    Traceback (most recent call last):
     File "<stdin>", line 1, in <module>
    AttributeError: 'Cat' object has no attribute 'roar'
    >>> c.meow_bigger()
    >>> c.roar
    'Roar!'
    

    いわば:

    • 最初はインスタンス変数(c.roar)にアクセスできません。
    • ただし、インスタンスメソッドc.meow_bigger()を一度呼び出した後、突然インスタンス変数c.roarにアクセスできるようになりました。
    • 上記の動作はなぜそうなのですか?

私の理解を助けてくれてありがとう。

11
anonnoir

コンストラクター内でインスタンス変数を初期化することがベストプラクティスであるのはなぜですか?

明快さ。

クラスのすべての属性を一目で簡単に確認できるためです。複数のメソッドで変数を初期化すると、コードのすべての行を読み取らずに完全なデータ構造を理解することが困難になります。

__init__内で初期化すると、ドキュメント化も簡単になります。あなたの例では、「Catのインスタンスにはroar属性があります」と書くことはできません。代わりに、Catのインスタンスに「roar」属性がある可能性があることを説明する段落を追加する必要がありますが、これは「meow_louder」メソッドを呼び出した後でのみです。

明晰さが王様です。私が今まで出会った中で最も賢いプログラマーの1人が、「データ構造を見せてください。コードを見なくても、コードがどのように機能するかを教えてくれます」と言ったことがあります。それはほんの少し双曲的ですが、それには間違いなく真実の輪があります。コードベースを学習する上での最大のハードルの1つは、コードベースが操作するデータを理解することです。

インスタンス変数がコンストラクター以外のメソッドで定期的に初期化される場合、どのような一般的/特定の混乱が発生する可能性がありますか?

最も明白なものは、オブジェクトがプログラムのすべての部分で使用可能な属性を持っていない可能性があり、属性が定義されていない場合を処理するために多くの余分なコードを追加する必要があることです。

どのシナリオで、コンストラクターではなく、他のメソッドでインスタンス変数を初期化する方がよいでしょうか?

何もないと思います。

注:属性を最終値で初期化する必要は必ずしもありません。あなたの場合、roarNoneに初期化することは許容されます。 somethingに初期化されているという単なる事実は、それがクラスが維持しているデータの一部であることを示しています。後で値が変更されても問題ありません。

5
Bryan Oakley

「pure」のクラスメンバーPythonは単なるディクショナリです。メンバーは、定義されている関数を実行するまでインスタンスのディクショナリに追加されません。理想的には、これがコンストラクタです。これにより、関数が呼び出される順序に関係なく、メンバーがすべて存在することが保証されます。

上記の例は次のように翻訳できると思います。

class Cat():
    def __init__(self):
        self.__dict__['meow'] = "Meow!"
    def meow_bigger(self):
        self.__dict__['roar'] = "Roar!"

>>> c = Cat()        # c.__dict__ = { 'meow': "Meow!" }
>>> c.meow_bigger()  # c.__dict__ = { 'meow': "Meow!", 'roar': "Roar!" }
7
0x5453

コンストラクター内でインスタンス変数を初期化することは、すでに指摘したように、Pythonでのみ推奨されます。

まず第一に、コンストラクター内ですべてのインスタンス変数を定義することは、クラスを文書化するための良い方法です。コードを見ると、誰もがインスタンスの内部状態の種類を知っています。

第二に、順序が重要です。関数Vでインスタンス変数Aを定義し、Vにアクセスする別の関数Bがある場合、Aの前にBを呼び出すことが重要です。そうしないと、Vが定義されていないため、Bは失敗します。たぶん、ABの前に呼び出す必要がありますが、その場合は、インスタンス変数である内部状態によって保証する必要があります。

他にも多くの例があります。一般に、__init__メソッドですべてを定義し、初期化時に初期化できない/初期化すべきでない場合は、Noneに設定することをお勧めします。

もちろん、hasattrメソッドを使用して、状態の情報を取得することもできます。ただし、インスタンス変数VがたとえばNoneであるかどうかを確認することもできます。これは、同じことを意味する場合があります。したがって、私のopinionでは、コンストラクターのように他の場所にインスタンス変数を定義することは決して良い考えではありません。

あなたの例は、Pythonのいくつかの基本的なプロパティを示しています。 Pythonのオブジェクトは基本的に単なる辞書です。辞書を使用しましょう:その辞書に関数と値を追加して、ある種のOOPを構築できます。classステートメントを使用すると、すべてがクリーンになります。構文と 魔法のメソッド のような追加のものを提供します。

他の言語では、インスタンス変数と関数に関するすべての情報は、オブジェクトが初期化される前に存在します。 Pythonは実行時にそれを行います。クラス定義外の任意のオブジェクトに新しいメソッドを追加することもできます: 既存のオブジェクトインスタンスへのメソッドの追加

3
keksnicoh

3.)ただし、インスタンス変数はコンストラクターの外部で定義/初期化することもできます。同じクラスの他のメソッドで。

クラスが何を期待すべきかを明確にするために、初期化時にデフォルトの状態を提供することをお勧めします。静的に型付けされた言語では、これを行う必要があります。Pythonではこれをお勧めします。

変数roarを_has_roared_のようなより意味のある変数に置き換えて、これを伝えましょう。

この場合、meow_bigger()メソッドに_has_roar_を設定する理由があります。猫はインスタンス化時にまだ咆哮していないため、___init___でfalseに初期化します。

_class Cat():
    def __init__(self):
        self.meow = "Meow!"
        self.has_roared = False

    def meow_bigger(self):
         print self.meow + "!!!"
         self.has_roared = True
_

属性をデフォルト値で初期化することがしばしば理にかなっている理由がわかりますか?

そうは言っても、なぜpython ___init___メソッドで変数を定義する必要があることを強制しないのですか?動的言語なので、このようなことができるようになりました。

_>>> cat1 = Cat()
>>> cat2 = Cat()
>>> cat1.name = "steve"
>>> cat2.name = "sarah"
>>> print cat1.name
... "steve"
_

name属性は___init___メソッドで定義されていませんが、とにかく追加できます。これは、___init___でデフォルト設定されていない変数を設定するより現実的なユースケースです。

2
Adam Hughes

私はあなたがそうするであろうケースを提供しようとします:

3.)ただし、インスタンス変数はコンストラクターの外部で定義/初期化することもできます。同じクラスの他のメソッドで。

コンストラクターにインスタンスフィールドを含めることは明確で整理されていることに同意しますが、他の人によって作成され、多くのインスタンスフィールドとAPIを持つ他のクラスを継承する場合があります。

ただし、特定のAPIに対してのみ継承し、独自のAPIに対して独自のインスタンスフィールドを作成する場合は、この場合、メソッドで追加のインスタンスフィールドを宣言するだけで、他のコンストラクターをオーバーライドする方が簡単です。ソースコードに。これは、Adam Hughesの回答もサポートします。この場合、最初に独自のAPIを呼び出すことが保証されるため、常に定義済みのインスタンスがあります。

たとえば、Web開発用にパッケージのハンドラークラスを継承し、ハンドラー用にserという新しいインスタンスフィールドを含めたい場合は、メソッドで直接宣言するだけです--initializeコンストラクターをオーバーライドせずに、そうすることがより一般的であることがわかりました。

class BlogHandler(webapp2.RequestHandler):
     def initialize(self, *a, **kw):
         webapp2.RequestHandler.initialize(self, *a, **kw)
         uid = self.read_cookie('user_id') #get user_id by read cookie in the browser
         self.user = User.by_id(int(uid)) #run query in data base find the user and return user
1
tom wood

これらは非常にオープンな質問です。

Pythonは、たとえそれがばかげているように見えても、何もすることを決して制限しようとしないという意味で、非常に「無料の」言語です。これが、クラスをブール値に置き換えるなど、まったく役に立たないことを実行できる理由です(はい、できます)。

あなたが言及する振る舞いは、同じロジックに従います。オブジェクト(または関数-はい、できます)に属性を動的に、どこでも、必ずしもコンストラクター内で追加したい場合は、そうです...

しかし、そうすることができるからではありません。コンストラクターで属性を初期化する主な理由は、メンテナンスの前提条件である読みやすさです。 Bryan Oakleyが彼の回答で説明しています のように、クラスフィールドは、名前とタイプがメソッドよりも意図を明らかにすることが多いため、コードを理解するための鍵となります。

そうは言っても、属性定義をコンストラクターの初期化から分離する方法があります: pyfields 。このライブラリは、コンストラクタで初期化する必要がなく、属性の観点からクラスの「コントラクト」を定義できるように作成しました。これにより、特に、属性とこれらの属性に依存するメソッドが定義されているが、コンストラクターが提供されていない「ミックスインクラス」を作成できます。

例と詳細については、 この他の回答 を参照してください。

0
smarie

特定のクラスメソッドをコンパイルしなくても直接呼び出すことができるように、クラスコンストラクターでクラス変数を初期化する方が簡単でわかりやすいと思います。

class Cat():

        def __init__(self,Meow,Roar):
            self.meow = Meow
            self.roar = Roar
        def meow_bigger(self):
            return self.roar
        def mix(self):
            return self.meow+self.roar

    
c=Cat("Meow!","Roar!")
print(c.meow_bigger())
print(c.mix())

出力

咆哮!

咆哮!

ニャー!咆哮!

0
Khan