SHOEISHA iD

※旧SEメンバーシップ会員の方は、同じ登録情報(メールアドレス&パスワード)でログインいただけます

DeveloperZine(デベロッパージン)- エンジニアの意思決定を支える技術情報メディア ProductZine

CodeZine編集部では、現場で活躍するデベロッパーをスターにするためのカンファレンス「Developers Summit」や、エンジニアの生きざまをブーストするためのイベント「Developers Boost」など、さまざまなカンファレンスを企画・運営しています。

japan.internet.com翻訳記事

Python 3.0開発者ガイド:コア言語の変更点

Python 3.0の主要な言語機能

新しい型システムの探究

 Python 3.0では、古いクラスが削除されています。Python 2.2で、組み込み型とユーザー定義型を統合する新しい形式のクラスが導入されましたが、後方互換性のために旧形式のクラスも残されました。Python 2.x(ただし2.2以上)では、クラスを次のように定義していました。

class OldStyleClass:
    pass

class NewStyleClass(object):
    pass

 Python 3.0では、オブジェクトを継承する必要がなくなりました。旧形式のクラスではPythonの能力を十分に発揮できず、旧形式のクラスに適用できない新機能が数多くありました。これ以降の各セクションで、デコレータ、関数アノテーション、新しいメタクラス、および抽象基底クラスを詳しく見ていきます。

抽象基底クラス

 抽象基底クラス(Abstract Base Class:ABC)機能は、Pythonのオブジェクトモデルを拡充し、クラスのメタデータを提供するという継続的な流れの一環です。抽象基底クラスを使うと、オブジェクトの機能(関数呼び出しのパラメータなど)を確実にテストできます。次に短い例を示します。

def foo(arg):
   assert isinstance(arg, dict)
   print arg['name']

 foo()関数はarg引数に辞書としてアクセスする必要があるため、argdictのインスタンスかどうかをチェックしています。しかし多くの場合、これには限界があります。次のように、__getitem__メソッドを定義しているクラスには、すべて辞書としてアクセスできるからです。

class A(object):
    def __getitem__(key):
        return len(key) * 'A'


>>> a = A()
>>> print a['123']
AAA

 使う予定のすべての属性が存在するかどうかをテストすることも可能ですが、面倒で間違いが起きやすい上に、それでも正しいオブジェクトを扱っているという確信が得られないことがあります。

 抽象基底クラスを使うと、この問題が見事に解決します。扱っているオブジェクトがABCに準拠していれば、クラスの構文上および(強制されない)セマンティック上のコントラクトである抽象クラスを割り当て、後からisinstance()issubclass()を使ってオブジェクトをテストできます。

 次に簡単な例を示します。例えば、宇宙船、惑星、小惑星、ミサイル、レーザー、その他いろいろなものが登場するスペースシューティングゲームを作成するとします。当然ながら、さまざまなオブジェクト同士の衝突を検出する必要があります。そのためには、コードでオブジェクトの位置や速度などのいくつもの属性にアクセスし、衝突後にオブジェクトがまだ「生きている」かどうかを確認する必要があります。そこで、必要なすべての属性とメソッドをオブジェクトがサポートしているかどうかをdetect_collision()関数で確認します。この関数を次に示します(メインロジックは省略します)。

def detect_collision(obj_1, obj_2):
  assert isinstance(obj_1, MovingObject)
  assert isinstance(obj_2, MovingObject)
  # Collision detection logic follows...
  ...

 この関数ではisinstance()を使って、obj_1obj_2の両方がMovingObjectのインスタンスであることを確認しています。MovingObjectは、detect_collision()に必要なメソッドと属性を定義しているABCです。MovingObjectの定義を次に示します。

from abc import ABCMeta, abstractmethod, abstractproperty

class MovingObject(metaclass=ABCMeta):
  @abstractmethod
  def is_alive(self):
    return self._lifeCount > 0

  @abstractmethod
  def move(self, x, y):
    pass

  @abstractproperty
  def speed(self):
    pass

  def get_position(self):
    return self._position

  def set_position(self, position):
    self._position = position

  position = abstractproperty(get_position, set_position)

 最初に、新しいモジュールであるabcをインポートしています。このモジュールには、ABCMetaメタクラスと、ABCの抽象メソッドおよび抽象プロパティを示すために使われる@abstractmethodおよび@abstractpropertyデコレータが含まれています。抽象メソッドと抽象プロパティは、たとえABCが実装を提供している場合でも、ABCコントラクトを実装するクラスで実装しなければなりません。ただし、実装クラスはABCの実装をそのまま呼び出すことができます。これは、実装されたC++の純粋仮想関数に似ています(多くの人が、C++の純粋仮想関数は実装を持つことができないと誤解しています)。

 MovingObjectクラスは、ABCMetaをメタクラスとして持っています(Python 3.0のメタクラスの新しい構文については後で説明します)。ABCMetaメタクラスはABCに必須ではありませんが、ABCの主要なメソッドである__instancecheck__()および__subclasscheck__()のデフォルトの実装を持っているので、これがあると非常に便利です。speedなどの読み取り専用プロパティは@abstractproperty「デコレータ」を使って宣言できますが、positionなどの読み書き可能プロパティはabstractproperty「クラス」を使って宣言しなければならないことに注意してください。

 さて、MovingObject ABCを定義しましたが、このABCをインスタンス化しようとすると、次のエラーが表示されます。

>>> MovingObject()
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: Can't instantiate abstract class MovingObject 
   with abstract methods is_alive, move, position, speed

 エラーが起きるのは、これが抽象クラスだからです。MovingObjectのインスタンスを作成することはできません。しかし、MovingObjectは基底クラスの役割を果たします。例えば、MovingObjectを継承するSpaceshipクラスでは、次のようにis_alive()move()speedpositionの各抽象メソッドと抽象プロパティをすべて実装しなければなりません。

class Spaceship(MovingObject):
  def __init__(self):
    self._lifeCount = 1
    self._speed = 5
    self.position = (100, 100)

  def speed(self):
    return self._speed

  def get_life_count(self):
    return self._lifeCount

  def is_alive (self):
    return MovingObject.is_alive(self)

  def move(self, x, y):
    self.position = (self.position[0] + x, self.position[1] + y)

  position = property(MovingObject.get_position, MovingObject.set_position)

 サブクラスですべての抽象メソッドと抽象プロパティを実装しないと、そのサブクラスも抽象クラスと見なされます。このため、サブクラスをインスタンス化しようとすると、再びエラーメッセージが表示され、実装していない抽象メソッドや抽象プロパティの名前(次の例ではis_aliveposition)が列挙されます。

class StillAbstractMovingObject(MovingObject):
  def move(self):
    pass

  def speed(self):
    pass

>>> StillAbstractMovingObject()
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: Can't instantiate abstract class StillAbstractMovingObject 
   with abstract methods is_alive, position

 1つ問題となり得るのは、実装される抽象メソッドがABCの元のメソッドと同じシグネチャを持つ必要がないことです。Pythonではメソッド名しか確認されません。このため、ABCフレームワークはサブクラスが抽象コントラクトを完全に実装しているという保証にはなりません。

 Python 3.0には、コンテナ、イテレータ、および数値用の一連のABCがあらかじめ組み込まれています。新しい数値型の階層とABCについては、後で詳しく説明します。collectionsモジュールにはコンテナおよびイテレータ用のABCの定義が含まれており、numbersモジュールでは数値用のABCが定義されています。都合のいいことに、どちらのモジュールでも、あるオブジェクトが必要な機能をサポートしているかどうかを確認できる抽象クラスの階層が定義されています。例えばcollectionsモジュールには、すべてのマッピング(辞書風)オブジェクトが従う必要があるMapping抽象クラスがあります。Mappingは、抽象メソッドの__len____iter____contains__を定義するSizedIterable、およびContainer抽象クラスを継承します。Mappingクラス自体(リスト1を参照)は__getitem__という別の抽象メソッドを定義しており、この抽象メソッドで__contains__抽象メソッドを実装します。従って、Mappingには__getitem____iter____len__という未実装の抽象メソッド(__abstractmethods__属性に格納される)があることになります。インスタンス化できる具象マッピングオブジェクトでは、この3つのメソッドをすべて実装しなければなりません。

リスト1 抽象マッピングクラス
class Mapping(Sized, Iterable, Container)
  Method resolution order:
      Mapping
      Sized
      Iterable
      Container
      builtins.object

  Methods defined here:

  __contains__(self, key)

  __eq__(self, other)

  __getitem__(self, key)

  __ne__(self, other)

  get(self, key, default=None)

  items(self)

  keys(self)

   values(self)

  ----------------------------------------------------------------------
  Data and other attributes defined here:

  __abstractmethods__ = frozenset(['__getitem__', '__iter__', '__len__']...

  __hash__ = None

  ----------------------------------------------------------------------
  Methods inherited from Sized:

  __len__(self)

  ----------------------------------------------------------------------
  Class methods inherited from Sized:

  __subclasshook__(cls, C) from abc.ABCMeta

  ----------------------------------------------------------------------
  Data descriptors inherited from Sized:

  __dict__
      dictionary for instance variables (if defined)

  __weakref__
      list of weak references to the object (if defined)

  ----------------------------------------------------------------------
  Methods inherited from Iterable:

  __iter__(self)

 クラスは、この要件を満たし、isinstance()およびissubclass()呼び出しに正しく応答するために、必ずしもABCを継承する必要はありません。クラスと組み込み型を登録することもできます。例えば、組み込みのdictクラスはオブジェクトしか継承しませんが、次の照会に対してTrueを返します。

dictクラスがTrueを返す例
>>> issubclass(dict, collections.Mapping)
True
>>> issubclass(dict, collections.MutableMapping)
True
>>> isinstance({}, collections.Sized)
True

 クラスの基底クラスのリストをいじりたくない場合は、登録が便利です。クラスをABCに登録する場合は注意が必要です。登録したクラスは、たとえ実際には必要な抽象メソッドや抽象プロパティを実装していなくても、issubclass()およびisinstance()のチェックに対して常にTrueを返すからです。例えば、必要な__len__および__getitem__抽象メソッドを実装していなくても、次のようにSpaceshipクラスはSequenceとして登録されます。

SpaceshipクラスはSequenceとして登録される
s = Spaceship()
assert isinstance(s, collections.Sequence) == False
collections.Sequence.register(Spaceship)
assert isinstance(s, collections.Sequence) == True
assert hasattr(s, '__len__') == False
assert hasattr(s, '__getitem__') == False
※著者注

 詳細については、「PEP-3119」を参照ください。

次のページ
クラスデコレータ

この記事は参考になりましたか?

japan.internet.com翻訳記事連載記事一覧

もっと読む

この記事の著者

japan.internet.com(ジャパンインターネットコム)

japan.internet.com は、1999年9月にオープンした、日本初のネットビジネス専門ニュースサイト。月間2億以上のページビューを誇る米国 Jupitermedia Corporation (Nasdaq: JUPM) のニュースサイト internet.comEarthWeb.com からの最新記事を日本語に翻訳して掲載するとともに、日本独自のネットビジネス関連記事やレポートを配信。

※プロフィールは、執筆時点、または直近の記事の寄稿時点での内容です

Gigi Sayfan(Gigi Sayfan)

 主に大規模分散システムを対象とした、C/C++/C#/Python/Javaによるクロスプラットフォームのオブジェクト指向プログラミングに精通。現在は、Numenta社で脳の働きを模した人工知能システムの開発に取り組んでいる。

※プロフィールは、執筆時点、または直近の記事の寄稿時点での内容です

この記事は参考になりましたか?

この記事をシェア

CodeZine(コードジン)
https://codezine.jp/article/detail/4051 2009/06/25 14:00

イベント

CodeZine編集部では、現場で活躍するデベロッパーをスターにするためのカンファレンス「Developers Summit」や、エンジニアの生きざまをブーストするためのイベント「Developers Boost」など、さまざまなカンファレンスを企画・運営しています。

新規会員登録無料のご案内

  • ・全ての過去記事が閲覧できます
  • ・会員限定メルマガを受信できます

メールバックナンバー