Pythonのクラスでインスタンス変数をNoneとして宣言するのは良い習慣ですか?


68

次のクラスを検討してください。

class Person:
    def __init__(self, name, age):
        self.name = name
        self.age = age

私の同僚は、次のように定義する傾向があります。

class Person:
    name = None
    age = None

    def __init__(self, name, age):
        self.name = name
        self.age = age

これの主な理由は、選択したエディターがオートコンプリートのプロパティを表示することです。

個人的には、後者が嫌いNoneです。クラスにこれらのプロパティがに設定されていることには意味がないからです。

どちらがより良い練習であり、どのような理由でですか?


63
あなたのIDEにあなたが書くコードを指示させないでください?
マーティンピーターズ14

14
ちなみに、適切なpython IDE(PyCharmなど)を使用して、__init__すでにオートコンプリートなどの属性を設定Noneすると、IDEが属性のより適切な型を推測できなくなるため、代わりに適切なデフォルトを使用することをお勧めします(可能)。
バクリウ14

オートコンプリート専用の場合は、タイプヒンティングを使用できます。追加のdocstringsもプラスになります。
-dashesy

3
「あなたのIDEがあなたが書くコードを決定させてはいけません」は議論されている問題です。Python 3.6の時点では、インラインアノテーションとtypingモジュールがあり、IDEやリンターにヒントを提供することができます。そのようなことがあなたの空想をくすぐったら
cz

1
クラスレベルでのこれらの割り当ては、残りのコードには影響しません。それらはに影響しませんself。たとえself.nameself.ageに割り当てられていなかった__init__彼らは、インスタンスには表示されませんself、彼らは唯一のクラスに表示Person
ジョルビ

回答:


70

「これはあなたが思っていることをしない」というルールの下で、私は後者の悪い習慣と呼んでいます。

同僚の立場は次のように書き直すことが__dict__できます。何か。"


4
docstringsに少し似ています;-)もちろん、Pythonには、それらを削除する便利なモード-OOがあります。
スティーブジェソップ14

15
取られたスペースは、この規則が悪臭を放つ最も重要な理由です。名前ごと(プロセスごと)に数十バイトです。回答の一部を読んだだけで時間を無駄にしたように感じます。スペースコストはそれほど重要ではありません。

8
@delnanエントリのメモリサイズが無意味であることに同意します。論理的/精神的なスペースが占有され、内省的なデバッグが行われるため、より多くの読み取りとソートが必要になります。I〜LL
StarWeaver

3
実際、あなたがスペースについてこの妄想者であれば、これはスペースを節約し、時間を浪費することに気付くでしょう。初期化されていないメンバーはクラス値を使用するために失敗するため、None(各インスタンスに1つ)にマップされた変数名のコピーはありません。したがって、クラスでのコストは数バイトであり、節約はインスタンスごとに数バイトです。しかし、変数名の検索に失敗するたびに少し時間がかかります。
ジョンジェイオーバーマーク14

2
8バイトを心配しているなら、Pythonを使用しないでしょう。
cz

26

1.コードを理解しやすくする

コードは書かれているよりもずっと頻繁に読まれます。コードメンテナーのタスクを簡単にします(来年も自分自身になるかもしれません)。

厳格なルールについては知りませんが、将来のインスタンスの状態を明確に宣言することを好みます。でクラッシュするAttributeErrorだけで十分です。インスタンス属性のライフサイクルがはっきりと見えないのは悪いことです。属性の割り当てにつながる可能性のある呼び出しシーケンスを復元するために必要な精神的な体操の量は、簡単ではなく、エラーにつながる可能性があります。

したがって、私は通常、コンストラクターですべてを定義するだけでなく、可変属性の数を最小限に抑えるよう努めています。

2.クラスレベルとインスタンスレベルのメンバーを混在させないでください

class宣言内で定義するものはすべてクラスに属し、クラスのすべてのインスタンスで共有されます。たとえば、クラス内で関数を定義すると、すべてのインスタンスで同じメソッドになります。データメンバーにも同じことが当てはまります。これは、通常で定義するインスタンス属性とはまったく異なります__init__

クラスレベルのデータメンバーは、定数として最も便利です。

class Missile(object):
  MAX_SPEED = 100  # all missiles accelerate up to this speed
  ACCELERATION = 5  # rate of acceleration per game frame

  def move(self):
    self.speed += self.ACCELERATION
    if self.speed > self.MAX_SPEED:
      self.speed = self.MAX_SPEED
    # ...

2
ええ、しかし、クラスレベルのメンバーとインスタンスレベルのメンバーを混在させることは、defが行うことのほとんどです。オブジェクトの属性と考える関数を作成しますが、実際にはクラスのメンバーです。同じことが財産とその同類にも当てはまります。作品が実際にクラスによって媒介されているときに、作品がオブジェクトに属しているという幻想を与えることは、気が狂わない昔からの方法です。Python自体で問題ないのに、どうしてそんなに悪いのでしょうか。
ジョンジェイオーバーマーク14

さて、Pythonでのメソッド解決順序(データメンバーにも適用可能)はそれほど単純ではありません。インスタンスレベルで見つからないものは、クラスレベルで検索され、次にベースクラスなどで検索されます。同じ名前のインスタンスレベルメンバーを割り当てることで、実際にクラスレベルメンバー(データまたはメソッド)をシャドウできます。ただし、インスタンスレベルのメソッドはインスタンスにバインドされて渡されるself必要はありませんselfが、クラスレベルのメソッドはバインドされておらず、見た目では単純な関数defであり、最初の引数としてインスタンスを受け入れます。これらは異なるものです。
9000 14

AttributeErrorバグがあるよりも良い信号だと思います。そうしないと、Noneを飲み込んで意味のない結果が得られます。属性がで定義されている場合、特に重要なのは、属性が__init__欠落している(ただし、クラスレベルに存在する)のは、バグのある継承のみが原因である可能性があるためです。
Davidmh

@Davidmh:検出されたエラーは、検出されていないエラーよりも常に優れています。インスタンスの構築時に属性を作成する必要がNoneあり、この値が意味をなさない場合、アーキテクチャに問題があり、属性の値またはその初期値のライフサイクルを再考する必要があると思います。属性を早期に定義することで、コードを実行することはもちろんのこと、クラスの残りの部分を記述する前であっても、このような問題を検出できることに注意してください。
9000 14

楽しい!ミサイル!とにかく、私は限りクラスレベルは、などのデフォルトを、含まれて...それはクラスレベルのVARSを作成し、それらを混合するためにOKですかなり確信している
エリックAronesty

18

個人的には、__ init __()メソッドでメンバーを定義します。クラス部分でそれらを定義することを考えたことがありません。しかし、私がいつもしていることは、__ init__メソッドで不要なメンバーも含めて、__ init__メソッドですべてのメンバーを初期化します。

例:

class Person:
    def __init__(self, name, age):
        self._name = name
        self._age = age
        self._selected = None

   def setSelected(self, value):
        self._selected = value

すべてのメンバーを1か所で定義することが重要だと思います。コードが読みやすくなります。__ init __()の内部にあるか外部にあるかは、それほど重要ではありません。しかし、チームが多かれ少なかれ同じコーディングスタイルにコミットすることは重要です。

ああ、メンバー変数に接頭辞「_」を追加したことにお気づきかもしれません。


13
コンストラクタですべての値を設定する必要があります。値がクラス自体で設定された場合、インスタンス間で共有されます。これはNoneでは問題ありませんが、ほとんどの値では問題ありません。したがって、それについては何も変更しないでください。;)
Remco Haszing 14

4
パラメータのデフォルト値、および任意の位置引数とキーワード引数をとることができるため、予想以上に苦痛が少なくなります。(実際、他のメソッドやスタンドアロン関数に構築を委任することも可能です。したがって、本当に複数のディスパッチが必要な場合は、それを行うこともできます)。
ショーンビエイラ14

6
@Benedict Pythonにはアクセス制御がありません。先頭のアンダースコアは、実装の詳細について受け入れられている規則です。PEP 8を参照してください。
ドーバル14

3
@Doval属性名の前に特別な理由があります_:それがプライベートであることを示すためです!(このスレッドの多くの人がPythonを他の言語と混同したり、混同したりするのはなぜですか?)

1
ああ、だからあなたはそれらのプロパティまたは機能のすべての露出された相互作用の人々の一人です...誤解して申し訳ありません。そのスタイルはいつも私には過剰に思えますが、次のようなものがあります。
ジョンジェイオーバーマーク14

11

これは悪い習慣です。これらの値は必要ありません。コードが乱雑になり、エラーが発生する可能性があります。

考慮してください:

>>> class WithNone:
...   x = None
...   y = None
...   def __init__(self, x, y):
...     self.x = x
...     self.y = y
... 
>>> class InitOnly:
...   def __init__(self, x, y):
...     self.x = x
...     self.y = y
... 
>>> wn = WithNone(1,2)
>>> wn.x
1
>>> WithNone.x #Note that it returns none, no error
>>> io = InitOnly(1,2)
>>> InitOnly.x
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
AttributeError: class InitOnly has no attribute 'x'

私はそれを「エラーの原因」と呼ぶことは難しいでしょう。ここで 'x'を要求することの意味はあいまいですが、最も可能性の高い初期値が必要な場合があります。
ジョンジェイオーバーマーク14

誤って使用するとエラーが発生する可能性があることを詳しく説明する必要がありました。
デニス14

より高いリスクは、オブジェクトの初期化中です。本当に安全なものはありません。それが引き起こす唯一のエラーは、本当に不自由な例外を飲み込むことです。
ジョンジェイオーバーマーク14

1
エラーがより深刻な問題を防いでいた場合、エラーを引き起こさないことは問題です。
エリックアロネスティー

0

「docstringsのようなビット」を使用して、これが常にである限りNone、または他の値の狭い範囲で、すべて不変である限り、この無害を宣言します。

それは、アタビズムと、静的に型付けされた言語への過度の執着を嫌う。そして、それはコードとしては役に立たない。しかし、ドキュメントには、小さな目的が残っています。

予想される名前が文書化されているので、コードを誰かと組み合わせて、誰かが「username」と他のuser_nameを持っている場合、別の方法で同じ変数を使用していないという手がかりがあります。

完全な初期化をポリシーとして強制すると、同じことをよりPython的な方法で実現できますが、に実際のコードがある場合は__init__、使用中の変数を文書化するより明確な場所を提供します。

明らかにここでの大きな問題は、人々が以外の値で初期化することを誘惑するNoneことです。

class X:
    v = {}
x = X()
x.v[1] = 2

グローバルトレースを残し、xのインスタンスを作成しません。

しかし、これはPython全体でこのプラクティスよりも奇妙なことであり、私たちはすでにそれについて偏執狂的であるはずです。

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