Pythonのインスタンス変数とクラス変数


120

私はPythonクラスを持っていますが、実行時に必要なインスタンスは1つだけなので、インスタンスごとではなく、クラスごとに1回だけ属性があれば十分です。複数のインスタンスがある場合(これは発生しません)、すべてのインスタンスは同じ構成にする必要があります。次のオプションのどれがより良いか、より「慣用的な」Pythonであるかと思います。

クラス変数:

class MyController(Controller):

  path = "something/"
  children = [AController, BController]

  def action(self, request):
    pass

インスタンス変数:

class MyController(Controller):

  def __init__(self):
    self.path = "something/"
    self.children = [AController, BController]

  def action(self, request):
    pass

4
この質問を読んで答えを確認した後の最初の質問の1つは、「クラス変数にアクセスするにはどうすればよいですか」でした。-これは、これまではインスタンス変数のみを使用してきたためです。私自身の質問への回答では、クラス名自体を使用して行いますが、技術的にはインスタンスを介して行うこともできます。同じ質問を持つ他の人のために読むリンクは次のとおりです。stackoverflow.com
Gabriel Staples

回答:


158

いずれにしてもインスタンスが1つしかない場合は、すべての変数をインスタンスごとに作成することをお勧めします。これは、変数へのアクセスが(少し)速くなるためです(クラスからインスタンスへの「継承」により、「ルックアップ」のレベルが1つ低くなります)。そして、この小さな利点と比較検討する欠点はありません。


7
ボルグのパターンを聞いたことがない?そもそもインスタンスが1つしかないというのは、それを最初から使用するのは間違った方法でした。
デヴィンジャンピエール

434
@Devin、うん、私はボーグパターンを聞いたことがあります。それは私がそれを導入した人だからです(2001年にはcfrcode.activestate.com/recipes/… ;-)。しかし、単純なケースでは、何も問題はありません。強制を行わない単一のインスタンスがあるだけです。
Alex Martelli、2010

2
@ user1767754、それらを自分で作るのは簡単ですが、python3.4でpython -mtimeit行ったばかりintですが、クラス変数へのアクセスは、実際には古いワークステーションのインスタンス変数と同じよりも5〜11 ナノ秒速いことに注意してください。コードパスはそうします。
Alex Martelli、2016年

45

さらにマイクをエコーし、アレックスのアドバイスを、自分の色を追加します...

インスタンス属性の使用は典型的です...より慣用的なPythonです。クラス属性は、ユースケースが特定であるため、あまり使用されていません。静的メソッドとクラスメソッドと「通常の」メソッドについても同じことが言えます。これらは特定のユースケースに対処する特別な構成要素です。それ以外の場合は、Pythonプログラミングのあいまいなコーナーを知っていることを見せびらかしたい異常なプログラマーによって作成されたコードです。

Alexは返答の中で、検索のレベルが1つ少ないため、アクセスが(少し)速くなると述べています。これは変数アクセスと非常に似ています-検索順序は次のとおりです。

  1. 地元の人
  2. 非ローカル
  3. グローバル
  4. ビルトイン

属性アクセスの場合、順序は次のとおりです。

  1. インスタンス
  2. クラス
  3. MRO(メソッド解決順序)によって決定される基本クラス

どちらの手法も「裏返し」の方法で機能します。つまり、最もローカルなオブジェクトが最初にチェックされ、次に外側のレイヤーが続けてチェックされます。

上記の例では、path属性を検索しているとしましょう。「self.path」のような参照に遭遇すると、Pythonは一致するインスタンス属性を最初に調べます。それが失敗すると、オブジェクトのインスタンス化元のクラスをチェックします。最後に、基本クラスを検索します。Alexが述べたように、インスタンスに属性が見つかった場合、他の場所を探す必要がないため、時間を少し節約できます。

ただし、クラス属性を主張する場合は、追加のルックアップが必要です。それとも、あなたの他の代替は、例えば、クラスの代わりに、インスタンスを介してオブジェクトを参照することでMyController.pathはなく、self.path。これは遅延ルックアップを回避する直接ルックアップですが、アレックスが後述するように、それはグローバル変数であるため、([グローバル]クラス名へのローカル参照を作成しない限り)保存するつもりであったと思っていたビットを失います)。

つまり、ほとんどの場合、インスタンス属性を使用する必要があります。ただし、クラス属性がジョブに適したツールである場合があります。両方を同時に使用するコードではself、インスタンス属性オブジェクトと同じ名前のクラス属性へのシャドウアクセスのみが得られるため、最も注意が必要です。この場合、属性を参照するには、クラス名で属性にアクセスする必要あります。


@wescpyが、MyControllerトータルコストがより高くなるように、グローバルに検索されるself.path場合path(以降、インスタンス変数でselfあるローカル方法==超高速検索します)。
Alex Martelli、2010

ああ、そうです。良いキャッチ。唯一の回避策はローカル参照を作成することだと思います...この時点では、それは本当に価値がありません。
wescpy 2010

24

疑問がある場合は、おそらくインスタンス属性が必要です。

クラス属性は、意味のある特殊な場合に予約するのが最適です。非常に一般的な唯一のユースケースはメソッドです。インスタンスが知る必要のある読み取り専用定数にクラス属性を使用することは珍しいことではありませ(ただし、これの唯一の利点は、クラスの外部からのアクセスも必要な場合です)。 、これはめったにあなたが望むものではありません。インスタンスが1つしかない場合でも、他のインスタンスと同じようにクラスを作成する必要があります。これは、通常、インスタンス属性を使用することを意味します。


1
クラス変数は、一種の読み取り専用定数です。Pythonで定数を定義できる場合は、定数として記述します。
デーモンは、

1
@deamon、定数を完全にクラス定義の外に置き、すべて大文字で名前を付ける可能性が少し高くなります。それらをクラスの中に置くことも大丈夫です。それらをインスタンス属性にしても何も害はありませんが、少し変わっているかもしれません。これは、コミュニティが選択肢の1つを取りすぎている問題ではないと思います。
マイク・グラハム、

@MikeGraham FWIW、GoogleのPythonスタイルガイドは、クラス変数を優先してグローバル変数を回避することを提案しています。ただし、例外もあります。
デニス

これは、GoogleのPythonスタイルガイドへの新しいリンクです。これで単純に書かれました:avoid global variablesそして、その定義は、グローバル変数はクラス属性として宣言される変数でもあるということです。ただし、この種の質問には、Python独自のスタイルガイド(PEP-8)を最初に使用する必要があります。そうすれば、自分の心がツールになるはずです(もちろん、たとえばGoogleからアイデアを得ることができます)。
コリディア

4

Pythonでクラス変数にアクセスするパフォーマンスに関する同じ質問-@Edward Loperから改造されたコード

ローカル変数はアクセスが最も速く、モジュール変数、クラス変数、インスタンス変数の順で結ばれています。

変数にアクセスできるスコープは4つあります。

  1. インスタンス変数(self.varname)
  2. クラス変数(Classname.varname)
  3. モジュール変数(VARNAME)
  4. ローカル変数(varname)

テスト:

import timeit

setup='''
XGLOBAL= 5
class A:
    xclass = 5
    def __init__(self):
        self.xinstance = 5
    def f1(self):
        xlocal = 5
        x = self.xinstance
    def f2(self):
        xlocal = 5
        x = A.xclass
    def f3(self):
        xlocal = 5
        x = XGLOBAL
    def f4(self):
        xlocal = 5
        x = xlocal
a = A()
'''
print('access via instance variable: %.3f' % timeit.timeit('a.f1()', setup=setup, number=300000000) )
print('access via class variable: %.3f' % timeit.timeit('a.f2()', setup=setup, number=300000000) )
print('access via module variable: %.3f' % timeit.timeit('a.f3()', setup=setup, number=300000000) )
print('access via local variable: %.3f' % timeit.timeit('a.f4()', setup=setup, number=300000000) )

結果:

access via instance variable: 93.456
access via class variable: 82.169
access via module variable: 72.634
access via local variable: 72.199
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.