ホームページ >バックエンド開発 >Python チュートリアル >Python のクラス、継承、ポリモーフィズムの詳細な例
この記事では、Python クラスの定義と使用法、継承、ポリモーフィズムを例を通して詳しく説明します。必要な場合は、
クラスの定義
を参照してください。クラスを定義したい場合は、それは 2 次元座標点を意味します:
# point.py class Point: def init(self, x=0, y=0): self.x, self.y = x, y
最も基本的なものは init メソッドで、C++/Java の constructor に相当します。二重アンダースコアが付いたメソッドは、init 以外にも多くのメソッドがありますが、後で紹介します。
パラメーター self は C++ のこれに相当し、すべてのメソッドにこのパラメーターがあることを示しますが、呼び出し時に指定する必要はありません。
>>> from point import * >>> p = Point(10, 10) # init 被调用 >>> type(p) <class 'point.Point'> >>> p.x, p.y (10, 10)
ほとんどすべての特別なメソッド (init を含む) は暗黙的に (直接呼び出されるのではなく) 呼び出されます。
すべてがオブジェクトである Python の場合、クラス自体ももちろんオブジェクトです。
>>> type(Point) <class 'type'> >>> dir(Point) ['class', 'delattr', 'dict', ..., 'init', ...] >>> Point.class <class 'type'>
Point は type のインスタンスであり、これは p が Point のインスタンスであるのと同じです。
次に、メソッド set を追加します。
class Point: ... def set(self, x, y): self.x, self.y = x, y
>>> p = Point(10, 10) >>> p.set(0, 0) >>> p.x, p.y (0, 0)
p.set(...) は、実際には単なる構文糖衣です。これを明確に理解できるように、Point.set(p, ...) として記述することもできます。 p は self パラメータです。:
>>> Point.set(p, 0, 0) >>> p.x, p.y (0, 0)
self はキーワードではなく、次のような他の名前に置き換えることもできることに注意してください:
class Point: ... def set(this, x, y): this.x, this.y = x, y
C++ との違いは、「メンバー変数」の接頭辞が必要であることです。それ以外の場合は、オブジェクト プロパティではなくクラス プロパティ (C++ 静的メンバーと同等) になります。
Pythonにはパブリック/プロテクト/プライベートアクセス制御がありません。「プライベート」を表現する必要がある場合は、二重アンダースコア接頭辞を追加するのが通例です。
class Point: def init(self, x=0, y=0): self.x, self.y = x, y def set(self, x, y): self.x, self.y = x, y def f(self): pass
x、y、f は private と同等です:
>>> p = Point(10, 10) >>> p.x ... AttributeError: 'Point' object has no attribute 'x' >>> p.f() ... AttributeError: 'Point' object has no attribute 'f'
_repr_
Point インスタンスを印刷してみてください:
>>> p = Point(10, 10) >>> p <point.Point object at 0x000000000272AA20>
通常、これは必要な出力ではありませんが、必要なのは次のとおりです:
>>> p Point(10, 10)
これは次のようになります。特別なメソッド repr を追加することで実現されます:
class Point: def repr(self): return 'Point({}, {})'.format(self.x, self.y)
p を出力するときに対話モードが実際に repr(p) を呼び出すことは難しくありません:
>>> repr(p)
'Point(10, 10) ) '
_str_
str が指定されていない場合、str() はデフォルトで repr() の結果になります。
どちらも string 形式のオブジェクトの表現ですが、それでもいくつかの違いがあります。簡単に言うと、 repr() の結果はインタプリタ用であり、通常は Point(10, 10) などの正当な Python コードですが、 str() の結果はユーザー用であり、( 10、10)。
この原則に従って、次のように Point の str の定義を提供します:
class Point: def str(self): return '({}, {})'.format(self.x, self.y)
_add_
2 つの座標点の追加は非常に合理的な要件です。
>>> p1 = Point(10, 10) >>> p2 = Point(10, 10) >>> p3 = p1 + p2 Traceback (most recent call last): File "<stdin>", line 1, in <module> TypeError: unsupported operand type(s) for +: 'Point' and 'Point'
特別なメソッド add to do を追加します:
class Point: def add(self, other): return Point(self.x + other.x, self.y + other.y)
>>> p3 = p1 + p2 >>> p3 Point(20, 20)
これは、C++ の operator オーバーロードとまったく同じです。
文字列やリストなどの Python の組み込み型はすべて + 演算子を「オーバーロード」します。
特別な方法がたくさんあるので、ここでは一つずつ紹介しません。
相続
教科書で最も一般的な例の 1 つを挙げてください。円と長方形は形状を継承し、形状が異なると面積の計算方法も異なります。
# shape.py class Shape: def area(self): return 0.0 class Circle(Shape): def init(self, r=0.0): self.r = r def area(self): return math.pi * self.r * self.r class Rectangle(Shape): def init(self, a, b): self.a, self.b = a, b def area(self): return self.a * self.b
使い方は比較的簡単です:
>>> from shape import * >>> circle = Circle(3.0) >>> circle.area() 28.274333882308138 >>> rectangle = Rectangle(2.0, 3.0) >>> rectangle.area() 6.0
Circle が独自の領域を定義しない場合:
class Circle(Shape): pass
次に、親クラス Shape の領域を継承します:
>>> Shape.area is Circle.area True
Circle が独自の領域を定義すると、その領域は継承されますfrom Shape 上書きされました:
>>> from shape import * >>> Shape.area is Circle.area False
これは、クラス辞書を介してより明確に確認できます:
>>> Shape.dict['area'] <function Shape.area at 0x0000000001FDB9D8> >>> Circle.dict['area'] <function Circle.area at 0x0000000001FDBB70>
したがって、サブクラスが親クラスのメソッドをオーバーライドするとき、実際には同じ属性名を異なる関数オブジェクトにバインドしているだけです。 Python にはオーバーライドの概念がないことがわかります。
同様に、Shapeは「インターフェース」としての領域を定義しなくても大丈夫です。
メソッドを動的に追加することもできます:
class Circle(Shape): ... # def area(self): # return math.pi * self.r * self.r # 为 Circle 添加 area 方法。 Circle.area = lambda self: math.pi * self.r * self.r
動的言語は一般に非常に柔軟であり、Python も例外ではありません。
公式 Python チュートリアル「9. クラス」の最初の文は次のとおりです:
他のプログラミング言語と比較して、Python のクラス メカニズムは最小限の新しい構文とセマンティクスを持つクラスを追加します。
Python は最小限の新しい構文で実装されますクラスのメカニズムは確かに素晴らしいですが、C++/Java プログラマにとっては非常に不快な思いをさせるものでもあります。
ポリモーフィズム
前に述べたように、Python にはオーバーライドの概念がありません。厳密に言えば、Python は「ポリモーフィズム」をサポートしていません。
継承構造におけるインターフェースと実装の問題を解決するため、または (デザインパターン によって提唱されているように) インターフェース指向プログラミングに Python をより効果的に使用するには、いくつかの人為的な標準を設定する必要があります。
Shape.area() を検討してください。単に 0.0 を返す以外に、より良い実装はありますか?
以内建模块 asyncio 为例,AbstractEventLoop 原则上是一个接口,类似于 Java 中的接口或 C++ 中的纯虚类,但是 Python 并没有语法去保证这一点,为了尽量体现 AbstractEventLoop 是一个接口,首先在名字上标志它是抽象的(Abstract),然后让每个方法都抛出异常 NotImplementedError。
class AbstractEventLoop: def run_forever(self): raise NotImplementedError ...
纵然如此,你是无法禁止用户实例化 AbstractEventLoop 的:
loop = asyncio.AbstractEventLoop() try: loop.run_forever() except NotImplementedError: pass
C++ 可以通过纯虚函数或设构造函数为 protected 来避免接口被实例化,Java 就更不用说了,接口就是接口,有完整的语法支持。
你也无法强制子类必须实现“接口”中定义的每一个方法,C++ 的纯虚函数可以强制这一点(Java 更不必说)。
就算子类「自以为」实现了“接口”中的方法,也不能保证方法的名字没有写错,C++ 的 override 关键字可以保证这一点(Java 更不必说)。
静态类型的缺失,让 Python 很难实现 C++ / Java 那样严格的多态检查机制。所以面向接口的编程,对 Python 来说,更多的要依靠程序员的素养。
回到 Shape 的例子,仿照 asyncio,我们把“接口”改成这样:
class AbstractShape: def area(self): raise NotImplementedError
这样,它才更像一个接口。
super
有时候,需要在子类中调用父类的方法。
比如图形都有颜色这个属性,所以不妨加一个参数 color 到 init:
class AbstractShape: def init(self, color): self.color = color
那么子类的 init() 势必也要跟着改动:
class Circle(AbstractShape): def init(self, color, r=0.0): super().init(color) self.r = r
通过 super 把 color 传给父类的 init()。其实不用 super 也行:
class Circle(AbstractShape): def init(self, color, r=0.0): AbstractShape.init(self, color) self.r = r
但是 super 是推荐的做法,因为它避免了硬编码,也能处理多继承的情况。
以上がPython のクラス、継承、ポリモーフィズムの詳細な例の詳細内容です。詳細については、PHP 中国語 Web サイトの他の関連記事を参照してください。