データと振る舞いをひとまとめに
これまで、ポジションはタプルや辞書で表し、別々の関数がそれを処理していました。クラスは、データとそれに属する関数をひとつの新しい型にまとめます。class Position: が設計図を定義し、それを呼び出す Position("AAPL", 100, 180.0) が、その設計図からオブジェクト(インスタンスとも呼ばれます)を作ります。各オブジェクトは自分のデータを持ち、クラスの中で定義された関数、つまりメソッドがそのオブジェクトに対して働きます。
class Position:
def __init__(self, symbol, quantity=0):
self.symbol = symbol # attributes belong to this object
self.quantity = quantity
def describe(self):
return f"{self.quantity} x {self.symbol}"
apple = Position("AAPL", 100)
tesla = Position("TSLA")
print(apple.describe()) # 100 x AAPL
print(tesla.describe()) # 0 x TSLA
print(apple.quantity, tesla.quantity) # 100 0__init__ と self
__init__ はコンストラクターです。オブジェクトを作るときに Python が自動で実行し、初期値を保存します。すべてのメソッドは第1引数としてオブジェクト自身を受け取り、慣習で self と呼びます。自分で渡す必要はなく、Python が渡します(apple.describe() は Position.describe(apple) を呼びます)。self.name = value で保存したものはそのオブジェクトの属性となり、メソッド呼び出しをまたいで残ります。__init__ は準備をするだけで、何も返しません。
class Account:
def __init__(self, cash):
self.cash = cash
def deposit(self, amount):
self.cash += amount # changes this object's attribute
return self.cash
first = Account(1000)
second = Account(50)
first.deposit(500)
print(first.cash, second.cash) # 1500 50 (separate objects, separate data)
same = first # a second name for the same object
same.deposit(1)
print(first.cash) # 1501状態を変えるメソッドと、問いに答えるメソッド
オブジェクトを変えずに問いに答えるメソッド(market_value(price))は、結果を返すだけで属性には触れないようにします。オブジェクトを変更するメソッド(apply_fill)は、属性を更新し、実現損益のような有用な値を返すことがよくあります。メソッドは self.other_method(...) で別のメソッドを呼べるため、ルールを1か所にまとめられます。unrealized_pnl が時価を必要とするなら、式を繰り返さず self.market_value(price) に尋ねます。
class Position:
def __init__(self, symbol, quantity=0, avg_price=0.0):
self.symbol = symbol
self.quantity = quantity
self.avg_price = avg_price
def market_value(self, price):
return self.quantity * price
def unrealized_pnl(self, price):
return self.market_value(price) - self.quantity * self.avg_price
pos = Position("AAPL", 100, 180.0)
print(pos.market_value(185.0)) # 18500.0
print(pos.unrealized_pnl(185.0)) # 500.0落とし穴と、クラスを使うべきとき
self. を忘れるのは典型的なミスで、メソッド内の quantity = 5 は属性ではなく使い捨てのローカル変数を作ります。クラス名は CapWords(Position)、属性とメソッドは小文字とアンダースコアで書き、先頭のアンダースコア(_cache)は内部用の印です。def __init__(self, fills=[]) のような変更可能なデフォルト値は使わないでください。すべてのオブジェクトがその1つのリストを共有してしまいます。fills=None として、内部で [] を作りましょう。クラスは、データとそれに関するルールが一緒に動くときに作ります。単純な関数や辞書のほうが簡単なことも多いです。
class Wrong:
def __init__(self, quantity):
quantity = quantity # forgot self.: nothing is stored
class Shared:
def __init__(self, fills=[]): # one list shared by every object
self.fills = fills
class Right:
def __init__(self, fills=None):
self.fills = [] if fills is None else list(fills)
a, b = Shared(), Shared()
a.fills.append(100)
print(b.fills) # [100] (surprise!)
c, d = Right(), Right()
c.fills.append(100)
print(d.fills) # []
print(hasattr(Wrong(5), "quantity")) # Falseclass Name:で型を定義し、Name(args)でオブジェクトを作って__init__を実行するself.attr = valueでオブジェクトにデータを保存する。メソッドは最初にselfを受け取る- 問い合わせメソッドは答えを返し、更新メソッドは属性を変える。変更可能なデフォルト値は避ける
次は __repr__ や __eq__ などの特殊メソッドです。オブジェクトをきれいに表示し、期待どおりに比較できるようにします。
ラボ:採点される内容
__init__でポジションを作る25点market_valueでポジションの時価を求める25点unrealized_pnlで含み損益を計算する25点apply_fillで約定後にポジションを更新する25点