このページでは、自動販売機をクラスとして設計しながら、クラスの扱い方についてまとめました。
使用した環境
Windows10
WSL2 - Ubuntu 20.04 LTS
Python 3.10.0
インスタンス作成
Pythonにはあらかじめ組み込まれている型(int, str, list等)がありますが、クラスはこの型を新しく定義する機能です。
文型は以下のようになります。
class クラス名(基底クラス):
内容
()のなかには継承するクラスを記述します。
ない場合は省略可能です。その際はobjectクラス(すべてのクラスの基底となるもの)が継承されます。
まずは自動販売機クラスの名前だけ定義しましょう。
Python 3.10.0 (default, Oct 12 2021, 16:02:08) [GCC 9.3.0] on linux
Type "help", "copyright", "credits" or "license" for more information.
>>> class VendingMachine:
... pass
...
クラス名の一文字めは、大文字のアルファベットとするのが一般的です。
単語をつなげる場合、つなげた単語の頭文字も大文字にします(キャメルケース)。
クラス名を決めただけで中身は何もありませんが、これだけでもクラスからインスタンス(実体)を作成することができます。
インスタンスとは、クラスが実体のないもの――設計図だとすると、その設計図を元に作り出された具体的なものです。
自動販売機の図面や仕様などをまとめたのがクラス、街中に実際に置いてある自動販売機がインスタンスだと言えるでしょうか。
インスタンスを作成するには、クラス名に()を付けます。
>>> tokyo = VendingMachine()
変数「tokyo」にはVendingMachineクラスのインスタンスが入っています。
東京に自動販売機を1つ設置したイメージです。(中身のない筐体ですが)
インスタンス変数
実際には自動販売機には色々な飲みものが入っていますよね。しかも個体ごとに商品のラインナップは異なります。
独自の商品ラインナップを保持させるために、インスタンスに変数を持たせます。
やり方は、まず、インスタンス作成時に暗黙的に呼ばれる初期化メソッド「__init__」を定義します。
def __init__(self):
必ず第一引数を受け取る必要があり、その引数は慣例的に自分自身を表すselfが使われます。
そのメソッドの中で変数を定義します。
self.変数名
インスタンス自身に変数を取り込ませるイメージです。
初期化メソッドの中で、変数を定義すると、インスタンスごとに独自の値を持つようになります。
別のクラスを作って例示します。
>>> class Name: ... def __init__(self, last, first): ... self.last_name = last ... self.first_name = first ... >>> sister = Name('フグ田', 'サザエ') >>> sister.last_name 'フグ田' >>> sister.first_name 'サザエ'
上記Nameは、インスタンスが苗字と名前を保持するクラスです。
インスタンス変数の値の参照は、インスタンスにドットを付けます。
インスタンス名.変数名
sister.last_name
このようにドットをつけて参照、実行できるものを属性と言います。
「last_name」、「first_name」がそうです。
後に紹介するクラス内で定義した関数――メソッドも属性のひとつです。
インスタンス変数の値は、インスタンスごとに異なります(同じであっても構いません)。
>>> sister = Name('フグ田', 'サザエ') >>> sister.last_name 'フグ田' >>> sister.first_name 'サザエ' >>> me = Name('磯野', 'カツオ') >>> me.last_name + me.first_name '磯野カツオ'
では、VendingMachineクラスに戻り、初期化メソッドを定義しましょう。
自動販売機の商品と在庫を再現します。
>>> class VendingMachine: ... def __init__(self, items: tuple): ... self.stock = {items[i] : 10 for i in range(3)} ... >>> tokyo = VendingMachine(('Water', 'Coffee', 'Green-Tea')) >>> osaka = VendingMachine(('Cola', 'Tea', 'Oshiruko')) >>> tokyo.lineup {''Water': 10, 'Coffee': 10, 'Green-Tea': 10} >>> osaka.stock {'Cola': 10, 'Tea': 10, 'Oshiruko': 10}
インスタンス変数「stock」は辞書で、タプルで渡された3つの飲み物を10個ずつ持てるようにしています。
東京に置いた自販機は、ミネラルウォーター・コーヒー・お茶がそれぞれ10個ずつ入っている、
大阪のは、コーラ・紅茶・おしるこ、が10個ずつ入っている、というイメージですね。
インスタンスメソッド
クラスのなかで定義した関数はメソッドと呼ばれ、インスタンスのみを介して呼び出せるものをインスタンスメソッドと言います。
自動販売機の一番の機能は飲み物を売ることです。
インスタンスメソッドとして定義してみましょう。
>>> class VendingMachine:
... def __init__(self, items: tuple):
... self.stock = {items[i] : 10 for i in range(3)}
... self._count = 0
...
... def sell(self, name):
... self.stock[name] -= 1
... self._count += 1
... print(f'ガコンッ\n/{name}/')
...
>>>
>>> tokyo = VendingMachine(('Water', 'Coffee', 'Green-Tea'))
>>> tokyo.sell('Coffee')
ガコンッ
/Coffee/
>>> tokyo.stock
{'Water': 10, 'Coffee': 9, 'Green-Tea': 10}
sellメソッドが実行されると、商品名の在庫が一つ減り、売り上げとして追加した変数_countに加算されます。
printは雰囲気を出したくて付けました。
_countのように、アンダースコアのついた変数は、外部からの利用を想定していないプライベート変数であることを表現します。
クラス変数
インスタンス変数はインスタンスごとに固有の値を持ちました。
インスタンスではなく、そのクラスに変数を持たせることもできます。
やり方は、クラス外での変数定義と同じです。
商品のラインナップ数を保持する変数を「lineup」、商品の値段を「price」、製造メーカーを「manufacturer」として定義します。
>>> class VendingMachine: ... lineup = 3 ... price = 150 ... manufacturer = 'FU-JI-NO' ...
クラス変数は、インスタンスがなくても参照できます。
>>> VendingMachine.lineup 3 >>> VendingMachine.manufacturer 'FU-JI-NO' >>> VendingMachine.price 150
インスタンスを介しての参照もできます。
>>> tokyo = VendingMachine(('Water', 'Coffee', 'Green-Tea')) >>> tokyo.lineup 3
クラス変数を利用した初期化メソッドに変更。
class VendingMachine:
lineup = 3
price = 150
manufacturer = 'FU-JI-NO'
def __init__(self, items: tuple):
self.stock = {items[i] : 10 for i in range(self.lineup)}
self._count = 0
クラスメソッド
クラスメソッドは、インスタンスがなくても実行できるメソッドです。
やり方は「@classmethod」を付けたあとでメソッドを定義します。
@classmethod
def メソッド名(cls):
この場合も第一引数を渡す必要があり、慣例的にclsが使われます。
>>> class VendingMachine: ... lineup = 3 ... price = 150 ... manufacturer = 'FU-JI-NO' ... @classmethod ... def print_manufacture(cls): ... print('--------------------') ... print(f'|{cls.manufacturer.center(18)}|') ... print('--------------------') ... >>> VendingMachine.print_manufacture() -------------------- | FU-JI-NO | --------------------
上記のクラスメソッドでは、製造メーカー名を表示させるようにしました。
スタティックメソッド
クラスやインスタンスを介して行うメソッドには、スタティックメソッドというものもあります。
自動販売機クラスでは使わないので、簡単にいきます。
やり方は、メソッド定義の前に「@staticmethod」をつけます。
>>> class Greeting:
... @staticmethod
... def hello(name):
... print(f'Hello {name}.')
...
>>> Greeting.hello('fuji')
Hello fuji.
「@classmethod」や「@staticmethod」はデコレータというものの一種です。
メソッドごとの使い分け
これまで3つのメソッド――インスタンス、クラス、スタティックメソッドと紹介してきました。
ちょっと違いがわかりにくかったかもしれません。
いったん自動販売機クラスから離れてまとめると、
- そもそもメソッドに引数が必要ない場合、クラス外で関数を定義する時とまったく一緒。
>>> class Greeting: ... def hello(): ... print('Hello') ... >>> Greeting.hello() Hello
- メソッドに引数は使うけど、クラス内部のものが必要ない場合は、スタティックメソッド。
@staticmethodをつける。
>>> class Greeting: ... @staticmethod ... def hello(name): ... print(f'Hello {name}.') ... >>> Greeting.hello('fuji') Hello fuji.
上記の二つのメソッドは、定義をクラス外に取り出してもそのまま機能します。
>>> def hello1(): ... print('Hello') ... >>> def hello2(name): ... print(f'Hello {name.}') ... >>> hello1() Hello >>> hello2('fuji') Hello fuji.
なのでクラス内で定義する必要はないかもしれません。
- クラス変数を利用したものが、クラスメソッド。
引数にcls。
>>> class Greeting: ... name = 'Human' ... @classmethod ... def hello(cls): ... print(f'Hello {cls.name}.') ... >>> Greeting.hello() Hello Human.
- インスタンス変数を使う場合、インスタンスメソッド。
引数にself。
>>> class Greeting: ... def __init__(self, name): ... self.name = name ... def hello(self): ... print(f'{self.name} "Hello."') ... >>> kei = Greeting('Kei') >>> yui = Greeting('Yui') >>> kei.hello() Kei "Hello." >>> yui.hello() Yui "Hello."
プロパティ
メソッドに「@property」をつけると、そのメソッドはクラス変数・インスタンス変数と同じように使えるようになります。
売り上げを計算するsalesメソッドを@propertyをつけて定義します。
>>> class VendingMachine: ... lineup = 3 ... price = 150 ... def __init__(self, items: tuple): ... self.stock = {items[i] : 10 for i in range(self.lineup)} ... self._count = 0 ... ... def sell(self, name): ... self.stock[name] -= 1 ... self._count += 1 ... print(f'ガコンッ\n/{name}/') ... ... @property ... def sales(self): ... return self._count * self.price ... >>> tokyo = VendingMachine(('Water', 'Coffee', 'Green-Tea')) >>> tokyo.sell('Coffee') ガコンッ /Coffee/ >>> tokyo.sell('Green-Tea') ガコンッ /Green-Tea/ >>> tokyo.sales 300
salesを利用するときを見てほしいのですが、()は必要ありません。
tokyo.sales
インスタンス変数と同じように使っていますね。
ただ上記はクラス・インスタンス変数と違い、読み取り専用です。
>>> tokyo.price = 200 >>> tokyo.price 200 >>> tokyo.sales = 10000 Traceback (most recent call last): File "<stdin>", line 1, in <module> AttributeError: can't set attribute 'sales'
自動販売機クラスの実現
せっかくなので、もう少し自動販売機クラスに手を加えて、終わりたいと思います。
- 自動販売機を利用するユーザークラスの作成。
class User: def __init__(self, wallet): self.wallet = wallet
Userクラスはwalletという属性を持ちます。
財布ですね。
- VendingMachineクラスのメソッドの変更と追加。
# 上部省略 def show(self): self.print_manufacture() for item in self.stock: print(f'|{item.center(18)}|') print(f'|{str(self.price).center(18)}|') print('--------------------') print('| | | |') print('--------------------') def sell(self, user: User, name): if user.wallet - self.price < 0: print('お金が足りません...') elif self.stock[name] == 0: print('売り切れ!') else: user.wallet -= self.price self.stock[name] -= 1 self._count += 1 print(f'ガコンッ\n/{name}/') def restock(self): for name in self.stock: self.stock[name] = 10
自動販売機の商品リストを表示させる、showメソッド。
Userのwallet、VendingMachineのstockを確認し、OKなら販売するsellメソッド。
在庫を補充するrestockメソッドを定義しました。
長くなったのでモジュールとしてまとめます。
class User:
def __init__(self, wallet):
self.wallet = wallet
class VendingMachine:
lineup = 3
price = 150
manufacturer = 'FU-JI-NO'
def __init__(self, items: tuple):
self.stock = {items[i] : 10 for i in range(self.lineup)}
self._count = 0
@classmethod
def print_manufacture(cls):
print('--------------------')
print(f'|{cls.manufacturer.center(18)}|')
print('--------------------')
def show(self):
self.print_manufacture()
for item in self.stock:
print(f'|{item.center(18)}|')
print(f'|{str(self.price).center(18)}|')
print('--------------------')
print('| | | |')
print('--------------------')
def sell(self, user: User, name):
if user.wallet - self.price < 0:
print('お金が足りません...')
elif self.stock[name] == 0:
print('売り切れ!')
else:
user.wallet -= self.price
self.stock[name] -= 1
self._count += 1
print(f'ガコンッ\n/{name}/')
def restock(self):
for name in self.stock:
self.stock[name] = 10
@property
def sales(self):
return self._count * self.price
インポートして使ってみます。
$ python Python 3.10.0 (default, Oct 12 2021, 16:02:08) [GCC 9.3.0] on linux Type "help", "copyright", "credits" or "license" for more information. >>> from vmachine import User, VendingMachine >>> tokyo = VendingMachine(('Cola', 'Milk', 'Tea')) >>> kids = User(100) >>> user1 = User(1000) >>> tokyo.show() -------------------- | FU-JI-NO | -------------------- | Cola | | 150 | -------------------- | Milk | | 150 | -------------------- | Tea | | 150 | -------------------- | | | | -------------------- >>> tokyo.sell(kids, 'Cola') お金が足りません... >>> tokyo.sell(user1, 'Tea') ガコンッ /Tea/ >>> tokyo.sell(user1, 'Milk') ガコンッ /Milk/ >>> tokyo.sell(user1, 'Cola') ガコンッ /Cola/ >>> print(f'user1:{user1.wallet}円, kids:{kids.wallet}円') user1:550円, kids:100円 >>> tokyo.stock {'Cola': 9, 'Milk': 9, 'Tea': 9} >>> tokyo.stock['Milk'] = 0 >>> tokyo.sell(user1, 'Milk') 売り切れ! >>> tokyo.restock() >>> tokyo.stock {'Cola': 10, 'Milk': 10, 'Tea': 10} >>> print(f'現在の売り上げは{tokyo.sales}円です。') 現在の売り上げは450円です。
簡略化はしましたが、これで自動販売機を再現できました。
このページが少しでもお役に立てたのなら幸いです。