ホームページ  >  記事  >  バックエンド開発  >  Pythonのクラス、継承、ポリモーフィズムを詳しく解説

Pythonのクラス、継承、ポリモーフィズムを詳しく解説

不言
不言オリジナル
2018-05-02 15:36:551174ブラウズ

この記事では、Python クラスの定義と使用法、継承、ポリモーフィズムを例を通して詳しく説明します。必要な場合は、

クラスの定義

を参照してください。クラスを定義したい場合は、それは 2 次元座標点を意味します:

# point.py
class Point:
  def __init__(self, x=0, y=0):
    self.x, self.y = x, y

最も基本的なメソッドは __init__ メソッドで、これは C++/Java コンストラクターに相当します。二重アンダースコア __ が付いているメソッドは、__init__ 以外にも多くのメソッドがあります (後で紹介します)。

パラメーター self は C++ のこれに相当し、すべてのメソッドにこのパラメーターがあることを示しますが、呼び出し時に指定する必要はありません。

>>> from point import *
>>> p = Point(10, 10) # __init__ 被调用
>>> type(p)
<class &#39;point.Point&#39;>
>>> p.x, p.y
(10, 10)

ほとんどすべての特別なメソッド (__init__ を含む) は暗黙的に呼び出されます (直接呼び出されるのではありません)。

すべてがオブジェクトである Python の場合、クラス自体ももちろんオブジェクトです。

>>> type(Point)
<class &#39;type&#39;>
>>> dir(Point)
[&#39;__class__&#39;, &#39;__delattr__&#39;, &#39;__dict__&#39;, ..., &#39;__init__&#39;, ...]
>>> Point.__class__
<class &#39;type&#39;>

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++ との違いは、「メンバー変数」には self. という接頭辞を付ける必要があることです。そうしないと、オブジェクトの属性ではなく、クラスの属性 (C++ の静的メンバーに相当) になります。

アクセス制御

Pythonにはpublic/protected/privateなどのアクセス制御がありませんので、どうしても「private」を表現したい場合は接頭辞にアンダースコアを2つ付けるのが一般的です。

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: &#39;Point&#39; object has no attribute &#39;__x&#39;
>>> p.__f()
...
AttributeError: &#39;Point&#39; object has no attribute &#39;__f&#39;

_repr_

Point インスタンスを印刷してみます:

>>> p = Point(10, 10)
>>> p
<point.Point object at 0x000000000272AA20>

通常、これは私たちが望んでいることではありません望ましい出力、私たちが望むものは次のとおりです:

>>> p
Point(10, 10)

特別なメソッド __repr__ を追加して達成します:

class Point:
  def __repr__(self):
    return &#39;Point({}, {})&#39;.format(self.__x, self.__y)

対話モードが実際に repr(p when print p ) を呼び出していることを確認するのは難しくありません。 :

>>> repr(p)
'Point(10, 10)'

_str_

__str__ が指定されていない場合、str() はデフォルトで repr() の結果になります。
どちらも文字列形式のオブジェクトの表現ですが、それでもいくつかの違いがあります。簡単に言うと、 repr() の結果はインタプリタ用であり、通常は Point(10, 10) などの正当な Python コードですが、 str() の結果はユーザー用であり、( 10、10)。

この原則に従って、次のように Point の __str__ の定義を提供します:

class Point:
  def __str__(self):
    return &#39;({}, {})&#39;.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 +: &#39;Point&#39; and &#39;Point&#39;

特別なメソッド __add__ を追加して実行します:

class Point:
  def __add__(self, other):
    return Point(self.__x + other.__x, self.__y + other.__y)

>>> p3 = p1 + p2
>>> p3
Point(20, 20)

これは、C++ の演算子のオーバーロードと同じです。
文字列やリストなどの 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 が独自の領域を定義すると、Shape から継承された領域が上書きされます:

>>> from shape import *
>>> Shape.area is Circle.area
False

これは、クラス辞書を通してより明確に確認できます:

>>> Shape.__dict__[&#39;area&#39;]
<function Shape.area at 0x0000000001FDB9D8>
>>> Circle.__dict__[&#39;area&#39;]
<function Circle.area at 0x0000000001FDBB70>

So, when aサブクラスは親クラスのメソッドをオーバーライドします。実際には、同じプロパティ名を別の関数オブジェクトにバインドするだけです。 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的环境配置解析


以上がPythonのクラス、継承、ポリモーフィズムを詳しく解説の詳細内容です。詳細については、PHP 中国語 Web サイトの他の関連記事を参照してください。

声明:
この記事の内容はネチズンが自主的に寄稿したものであり、著作権は原著者に帰属します。このサイトは、それに相当する法的責任を負いません。盗作または侵害の疑いのあるコンテンツを見つけた場合は、admin@php.cn までご連絡ください。