67

私はPythonを研究しており、Pythonの概念と概念全体を理解していると思いますが、今日、完全には理解していないコードに出くわしました。

サークルを定義することになっているが、ボディがないクラスがあるとします。

class Circle():
    pass

属性を定義していないので、どうすればこれを行うことができますか?

my_circle = Circle()
my_circle.radius = 12

奇妙な部分は、Pythonが上記のステートメントを受け入れることです。Pythonがを上げない理由がわかりませんundefined name error動的型付けを介して、必要なときにいつでも変数をオブジェクトにバインドするだけであることを理解していますが、これを可能にする属性をクラスにradius存在させるべきではありませんか?Circle

編集:あなたの答えにはたくさんの素晴らしい情報があります!素晴らしい答えをありがとうございました!答えとして1つしかマークできないのは残念です。

4

9 に答える 9

56

主要な原則は、宣言のようなものは存在しないということです。つまり、「このクラスにはメソッドfooがある」または「このクラスのインスタンスには属性バーがある」と宣言することはなく、そこに格納されるオブジェクトのタイプに関するステートメントを作成することはできません。メソッド、属性、クラスなどを定義するだけで、それが追加されます。JBernardoが指摘しているように、どの__init__方法でもまったく同じことができます。新しい属性の作成を。という名前のメソッドに任意に制限することはあまり意味がありません__init____init__また、実際にはその名前を持たない関数(デコレータなど)を格納すると便利な場合があり、そのような制限はそれを破ります。

さて、これは普遍的に真実ではありません。組み込み型では、最適化としてこの機能が省略されています。を介し__slots__て、ユーザー定義クラスでこれを防ぐこともできます。しかし、これは単にスペースの最適化であり(すべてのオブジェクトに辞書は必要ありません)、正確さではありません。

セーフティネットが必要な場合は、まあ、残念です。Pythonはそれを提供しておらず、合理的に追加することはできません。そして最も重要なことは、その言語を採用しているPythonプログラマーに敬遠されることです(読みたい人のほとんどすべて)。テストと規律は、それでも正確さを保証するのに大いに役立ちます。__init__ 回避できる場合は、自由を使用して属性を作成しないでください。また、自動テストを実行してください。このようなトリックが原因で論理的なエラーが発生することはめったにありません。AttributeError発生したエラーのうち、ほとんどすべてがテストによって検出されます。

于 2012-09-24T16:28:41.113 に答える
46

ここでの議論におけるいくつかの誤解を明確にするためだけに。このコード:

class Foo(object):
    def __init__(self, bar):
        self.bar = bar

foo = Foo(5)

そしてこのコード:

class Foo(object):
    pass

foo = Foo()
foo.bar = 5

まったく同じです。本当に違いはありません。それはまったく同じことをします。この違いは、最初のケースではカプセル化されており、bar属性がFooタイプのオブジェクトの通常の部分であることは明らかです。2番目のケースでは、これがそうであるかどうかは明らかではありません。

前者の場合、bar属性を持たないFooオブジェクトを作成することはできません(おそらく可能ですが、簡単ではありません)。後者の場合、Fooオブジェクトは設定しない限りbar属性を持ちません。

したがって、コードはプログラム的に同等ですが、さまざまな場合に使用されます。

于 2012-09-24T17:02:08.747 に答える
20

Pythonを使用すると、事実上すべてのインスタンス(またはクラス)に任意の名前の属性を格納できます。組み込み型のようにCでクラスを記述するか、__slots__特定の名前のみを許可するを使用することにより、これをブロックすることができます。

それが機能する理由は、ほとんどのインスタンスが属性を辞書に保存するためです。はい、で定義するような通常のPython辞書です{}。辞書は、と呼ばれるインスタンス属性に格納されます__dict__。実際、「クラスは辞書の単なる構文糖衣」と言う人もいます。つまり、辞書を使用してクラスで実行できるすべてのことを実行できます。クラスはそれを簡単にします。

コンパイル時にすべての属性を定義する必要がある静的言語に慣れています。Pythonでは、クラス定義はコンパイルではなく実行されます。クラスは他のクラスと同じようにオブジェクトです。属性の追加は、辞書にアイテムを追加するのと同じくらい簡単です。これが、Pythonが動的言語と見なされる理由です。

于 2012-09-24T16:27:14.137 に答える
18

いいえ、Pythonはそのように柔軟性があり、ユーザー定義クラスに格納できる属性を強制しません。

ただし、トリックがありますが、クラス定義で__slots__属性__slots__を使用すると、シーケンスで定義されていない追加の属性を作成できなくなります。

>>> class Foo(object):
...     __slots__ = ()
... 
>>> f = Foo()
>>> f.bar = 'spam'
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
AttributeError: 'Foo' object has no attribute 'bar'
>>> class Foo(object):
...     __slots__ = ('bar',)
... 
>>> f = Foo()
>>> f.bar
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
AttributeError: bar
>>> f.bar = 'spam'
于 2012-09-24T16:22:31.473 に答える
6

radiusのデータメンバーを作成しますmy_circle

あなたがそれを求めていたら、my_circle.radiusそれは例外を投げたでしょう:

>>> print my_circle.radius # AttributeError

興味深いことに、これはクラスを変更しません。その1つのインスタンスだけ。それで:

>>> my_circle = Circle()
>>> my_circle.radius = 5
>>> my_other_circle = Circle()
>>> print my_other_circle.radius # AttributeError
于 2012-09-24T16:24:16.633 に答える
4

Pythonには2つのタイプの属性があります-Class Data AttributesInstance Data Attributes

PythonはData Attributes、その場で作成する柔軟性を提供します。

インスタンスデータ属性はインスタンスに関連しているため、__init__メソッドでそれを行うことも、インスタンスを作成した後に行うこともできます。

class Demo(object):
    classAttr = 30
    def __init__(self):
         self.inInit = 10

demo = Demo()
demo.outInit = 20
Demo.new_class_attr = 45; # You can also create class attribute here.

print demo.classAttr  # Can access it 

del demo.classAttr         # Cannot do this.. Should delete only through class

demo.classAttr = 67  # creates an instance attribute for this instance.
del demo.classAttr   # Now OK.
print Demo.classAttr  

したがって、インスタンスが作成された後、2つのインスタンス属性が作成されたことがわかります。1つは内部__init__、もう1つは外部です。

ただし、違いは、内部で作成されたインスタンス属性が__init__すべてのインスタンスに設定されるのに対し、外部で作成された場合は、インスタンスごとに異なるインスタンス属性を持つことができることです。

これは、クラスの各インスタンスが同じインスタンス変数のセットを持つJavaとは異なります。

  • 注:-インスタンスを介してクラス属性にアクセスすることはできますが、削除することはできません。また、インスタンスを介してクラス属性を変更しようとすると、実際にはクラス属性をシャドウするインスタンス属性が作成されます。
于 2012-09-24T16:34:27.953 に答える
2

新しい属性の作成を防ぐ方法は?

クラスの使用

新しい属性の作成を制御するために、__setattr__メソッドを上書きできます。が呼び出されるたびにmy_obj.x = 123呼び出されます。

ドキュメントを参照してください:

class A:
  def __init__(self):
    # Call object.__setattr__ to bypass the attribute checking
    super().__setattr__('x', 123)

  def __setattr__(self, name, value):
    # Cannot create new attributes
    if not hasattr(self, name):
      raise AttributeError('Cannot set new attributes')
    # Can update existing attributes
    super().__setattr__(name, value)

a = A()
a.x = 123  # Allowed
a.y = 456  # raise AttributeError

ユーザーが直接呼び出す場合でも、チェックをバイパスできることに注意してくださいobject.__setattr__(a, 'attr_name', attr_value)

データクラスの使用

を使用dataclassesすると、を使用して新しい属性の作成を禁止できますfrozen=True。また、既存の属性が更新されないようにします。

@dataclasses.dataclass(frozen=True)
class A:
  x: int


a = A(x=123)
a.y = 123  # Raise FrozenInstanceError
a.x = 123  # Raise FrozenInstanceError

注:dataclasses.FrozenInstanceErrorはAttributeErrorのサブクラスです

于 2020-01-13T06:59:13.767 に答える
1

__slots__デルナンが言ったように、あなたは属性でこの振る舞いを得ることができます。しかし、それがメモリスペースとアクセスタイプを節約する方法であるという事実は、動的属性を無効にする手段であるという事実を(また)破棄しません。

スペルミスによる微妙なバグを防ぐためだけに、動的属性を無効にすることは合理的な方法です。「テストと規律」は問題ありませんが、自動検証に依存することも間違いなく間違いではありません。また、必ずしも非Pythonである必要もありません。

また、attrsライブラリが2016年にバージョン16に到達してから(明らかに元の質問と回答の後で)、スロットを使用したクローズドクラスの作成がこれまでになく簡単になりました。

>>> import attr
...
... @attr.s(slots=True)
... class Circle:
...   radius = attr.ib()
...
... f = Circle(radius=2)
... f.color = 'red'
AttributeError: 'Circle' object has no attribute 'color'
于 2018-03-07T13:07:10.880 に答える
0

Conchylicultorの回答に追加するために、 Python3.10はに新しいパラメーターを追加しましたdataclass

slotsパラメータはクラス内に属性を作成し、の__slots__外部での新しい属性の作成を防ぎますが__init__、既存の属性への割り当てを許可します。

の場合slots=True、定義されていない属性に割り当てると、がスローされますAttributeError

slotsこれがとの例ですfrozen

from dataclasses import dataclass

@dataclass
class Data:
    x:float=0
    y:float=0

@dataclass(frozen=True)
class DataFrozen:
    x:float=0
    y:float=0

@dataclass(slots=True)
class DataSlots:
    x:float=0
    y:float=0

p = Data(1,2)
p.x = 5 # ok
p.z = 8 # ok

p = DataFrozen(1,2)
p.x = 5 # FrozenInstanceError
p.z = 8 # FrozenInstanceError

p = DataSlots(1,2)
p.x = 5 # ok
p.z = 8 # AttributeError
于 2022-02-10T16:25:09.480 に答える