Python の動的な動作のカスタマイズ

Beginner

This tutorial is from open-source community. Access the source code

はじめに

Python の動作のさまざまな部分は、特殊なまたはいわゆる「マジック」メソッドを通じてカスタマイズできます。このセクションでは、その考え方を紹介します。また、動的な属性アクセスとバウンド メソッドについても説明します。

はじめに

クラスは特殊メソッドを定義できます。これらは Python インタプリタにとって特殊な意味を持ちます。常に __ で前後されます。たとえば __init__ です。

class Stock(object):
    def __init__(self):
     ...
    def __repr__(self):
     ...

数十の特殊メソッドがありますが、いくつかの具体的な例を見てみましょう。

文字列変換用の特殊メソッド

オブジェクトには 2 種類の文字列表現があります。

>>> from datetime import date
>>> d = date(2012, 12, 21)
>>> print(d)
2012-12-21
>>> d
datetime.date(2012, 12, 21)
>>>

str() 関数は、見やすい表示用の出力を作成するために使用されます。

>>> str(d)
'2012-12-21'
>>>

repr() 関数は、プログラマー向けのより詳細な表現を作成するために使用されます。

>>> repr(d)
'datetime.date(2012, 12, 21)'
>>>

これらの関数 str()repr() は、クラス内の一対の特殊メソッドを使用して表示する文字列生成します。

class Date(object):
    def __init__(self, year, month, day):
        self.year = year
        self.month = month
        self.day = day

    ## `str()` と共に使用
    def __str__(self):
        return f'{self.year}-{self.month}-{self.day}'

    ## `repr()` と共に使用
    def __repr__(self):
        return f'Date({self.year},{self.month},{self.day})'

注:__repr__() の規約は、eval() に渡されたときに元のオブジェクトを再作成する文字列を返すことです。これが不可能な場合は、代わりに読みやすい表現を使用します。

数学用の特殊メソッド

数学演算子は、次のメソッドの呼び出しを伴います。

a + b       a.__add__(b)
a - b       a.__sub__(b)
a * b       a.__mul__(b)
a / b       a.__truediv__(b)
a // b      a.__floordiv__(b)
a % b       a.__mod__(b)
a << b      a.__lshift__(b)
a >> b      a.__rshift__(b)
a & b       a.__and__(b)
a | b       a.__or__(b)
a ^ b       a.__xor__(b)
a ** b      a.__pow__(b)
-a          a.__neg__()
~a          a.__invert__()
abs(a)      a.__abs__()

要素アクセス用の特殊メソッド

これらは、コンテナを実装するためのメソッドです。

len(x)      x.__len__()
x[a]        x.__getitem__(a)
x[a] = v    x.__setitem__(a,v)
del x[a]    x.__delitem__(a)

これらをクラスで使用することができます。

class Sequence:
    def __len__(self):
     ...
    def __getitem__(self,a):
     ...
    def __setitem__(self,a,v):
     ...
    def __delitem__(self,a):
     ...

メソッド呼び出し

メソッドを呼び出すには 2 段階のプロセスが必要です。

  1. 検索:. 演算子
  2. メソッド呼び出し:() 演算子
>>> s = stock.Stock('GOOG',100,490.10)
>>> c = s.cost  ## 検索
>>> c
<bound method Stock.cost of <Stock object at 0x590d0>>
>>> c()         ## メソッド呼び出し
49010.0
>>>

束縛メソッド

関数呼び出し演算子 () によってまだ呼び出されていないメソッドは、「束縛メソッド」と呼ばれます。それは、その元となったインスタンスで動作します。

>>> s = stock.Stock('GOOG', 100, 490.10)
>>> s
<Stock object at 0x590d0>
>>> c = s.cost
>>> c
<bound method Stock.cost of <Stock object at 0x590d0>>
>>> c()
49010.0
>>>

束縛メソッドは、しばしば不注意で明確でないエラーの原因になります。たとえば:

>>> s = stock.Stock('GOOG', 100, 490.10)
>>> print('Cost : %0.2f' % s.cost)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: float argument required
>>>

または、デバッグが難しい巧妙な動作もあります。

f = open(filename, 'w')
...
f.close     ## あっとう!何もしませんでした。`f` はまだ開いたままです。

これらの場合の両方で、エラーの原因は、末尾の丸括弧を忘れてしまったことによります。たとえば、s.cost() または f.close() です。

属性アクセス

属性にアクセスし、操作し、管理するための代替方法があります。

getattr(obj, 'name')          ## obj.name と同じ
setattr(obj, 'name', value)   ## obj.name = value と同じ
delattr(obj, 'name')          ## del obj.name と同じ
hasattr(obj, 'name')          ## 属性が存在するかどうかをテストする

例:

if hasattr(obj, 'x'):
    x = getattr(obj, 'x'):
else:
    x = None

*注:getattr() には便利なデフォルト値 *arg* もあります。

x = getattr(obj, 'x', None)

演習 4.9:オブジェクトの印刷時のより良い出力

stock.py で定義した Stock オブジェクトを変更して、__repr__() メソッドがより有用な出力を生成するようにします。たとえば:

>>> goog = stock.Stock('GOOG', 100, 490.1)
>>> goog
Stock('GOOG', 100, 490.1)
>>>

これらの変更を行った後、株式のポートフォリオを読み込み、結果のリストを表示したときに何が起こるか見てみましょう。たとえば:

>>> import report
>>> portfolio = report.read_portfolio('portfolio.csv')
>>> portfolio
... 出力がどのようになるか見る...
>>>

演習 4.10:getattr() の使用例

getattr() は属性を読み取るための代替メカニズムです。非常に柔軟なコードを書くために使用できます。まずは、この例を試してみましょう:

>>> import stock
>>> s = stock.Stock('GOOG', 100, 490.1)
>>> columns = ['name','shares']
>>> for colname in columns:
        print(colname, '=', getattr(s, colname))

name = GOOG
shares = 100
>>>

出力データが、columns 変数にリストされた属性名に完全によって決まることに注意深く観察してください。

tableformat.py ファイルで、この考えを取り上げて、任意のオブジェクトのリストのユーザ指定属性を表示するテーブルを印刷する汎用関数 print_table() に拡張します。以前の print_report() 関数と同様に、print_table() も出力形式を制御するために TableFormatter インスタンスを受け取る必要があります。以下がその動作方法です:

>>> import report
>>> portfolio = report.read_portfolio('portfolio.csv')
>>> from tableformat import create_formatter, print_table
>>> formatter = create_formatter('txt')
>>> print_table(portfolio, ['name','shares'], formatter)
      name     shares
---------- ----------
        AA        100
       IBM         50
       CAT        150
      MSFT        200
        GE         95
      MSFT         50
       IBM        100

>>> print_table(portfolio, ['name','shares', 'price'], formatter)
      name     shares      price
---------- ---------- ----------
        AA        100       32.2
       IBM         50       91.1
       CAT        150      83.44
      MSFT        200      51.23
        GE         95      40.37
      MSFT         50       65.1
       IBM        100      70.44
>>>

まとめ

おめでとうございます!あなたは特別メソッドの実験を完了しました。あなたのスキルを向上させるために、LabEx でさらに多くの実験を行って練習することができます。