乙Py先生のプログラミング教室
初学者のためのプログラミング学習サイト

プログラミング

                 37.例外処理

今までの講義では何度かエラーの表示が出ていたかと思います。

例えば

In [1]:
# リスト型を定義
aqq = [1,2,3]
# インデックスで値を抽出
print(aqq[31])
---------------------------------------------------------------------------
IndexError                                Traceback (most recent call last)
<ipython-input-1-4d1059cfba83> in <module>()
      2 aqq = [1,2,3]
      3 # インデックスで値を抽出
----> 4 print(aqq[31])

IndexError: list index out of range

リスト型のインデックスの範囲外の値を指定すると

list index out of range

エラーが発生します、リストの範囲外というエラー出力です。

こういったエラーが発生すると、
そこでプログラムは終了してしまいます。
なのでエラーを発生させないようにするか、
エラーが発生した場合の対処をする必要があります。

エラーの発生を抑えるのは困難なので、
エラーを回避する記述をすることで
プログラムを終了させないようにします。

それが例外処理になります。

pythonの例外処理の書き方

try:
    処理
except:
    例外時の処理

pythonでの例外処理は、
エラーが発生しそうな箇所を
try - exceptという文言で囲みます。

exception というのは例外という意味の英語です。
ぜひ覚えておいてください。

In [2]:
try:
    aqq = [1,2,3]
    # ここでエラーが発生する
    print(aqq[31])
except:
    # エラー発生時にはこちらが呼ばれる
    print('error')
error

エラーが発生した場合の処理を
exceptのブロックに書きます。

エラーが発生しても何も処理をしない場合は
何も書かないと構文エラーになってしまうので
pass と書いておきます。

In [3]:
try:
    aqq = [1,2,3]
    print(aqq[31])
except:
    # 何も処理を書か無いとエラーになる
  File "<ipython-input-3-c85aeb4f8051>", line 5
    # 何も処理を書か無いとエラーになる
                      ^
SyntaxError: unexpected EOF while parsing
In [4]:
try:
    aqq = [1,2,3]
    print(aqq[31])
except:
    # 処理が定まらない時はpassと書いておく
    pass

上記ではエラーが発生したことは確認できますが
何が起こっているのかは分からないです。

なのであらかじめエラーが想定できる場合は
エラーが発生した場合の処理を複数書くことができます。

try:
    処理
except エラー名:
    例外時の処理

In [5]:
try:
    aqq = [1,2,3]
    # ここでIndexエラーが発生する
    print(aqq[31])
except IndexError:
    # ここでIndexエラーをキャッチする
    print('index error')
except Exception:
    print('exception')
index error

except の後にエラー名を書くことで、
該当するエラーが発生した場合の処理を
書き分けることができます。

Indexエラーが発生すると分かっていれば
IndexErrorの部分の処理が実行されることになります。
Exception はすべてのエラーを拾います。

pythonのエラーの種類

ZeroDivisionError

数値を0で割ると発生

In [6]:
1/0
---------------------------------------------------------------------------
ZeroDivisionError                         Traceback (most recent call last)
<ipython-input-6-05c9758a9c21> in <module>()
----> 1 1/0

ZeroDivisionError: division by zero

NameError

存在しない変数やメソッドを参照しようとすると発生

In [7]:
print(asokokoks)
---------------------------------------------------------------------------
NameError                                 Traceback (most recent call last)
<ipython-input-7-ed0c4fbe8f44> in <module>()
----> 1 print(asokokoks)

NameError: name 'asokokoks' is not defined

そもそもこれは文法間違いであるので、
try-exceptでは
あまり用いられない使い方です。

KeyError

辞書型のキーがない場合などに発生

In [8]:
d ={1:2,3:4}
print(d[5])
---------------------------------------------------------------------------
KeyError                                  Traceback (most recent call last)
<ipython-input-8-bfd75dce33c0> in <module>()
      1 d ={1:2,3:4}
----> 2 print(d[5])

KeyError: 5

他にもたくさんのエラーが存在します。

pythonの組み込みエラーを表示します。

In [9]:
[i for i in dir(__builtins__) if 'Error' in i]
Out[9]:
['ArithmeticError',
 'AssertionError',
 'AttributeError',
 'BlockingIOError',
 'BrokenPipeError',
 'BufferError',
 'ChildProcessError',
 'ConnectionAbortedError',
 'ConnectionError',
 'ConnectionRefusedError',
 'ConnectionResetError',
 'EOFError',
 'EnvironmentError',
 'FileExistsError',
 'FileNotFoundError',
 'FloatingPointError',
 'IOError',
 'ImportError',
 'IndentationError',
 'IndexError',
 'InterruptedError',
 'IsADirectoryError',
 'KeyError',
 'LookupError',
 'MemoryError',
 'NameError',
 'NotADirectoryError',
 'NotImplementedError',
 'OSError',
 'OverflowError',
 'PermissionError',
 'ProcessLookupError',
 'RecursionError',
 'ReferenceError',
 'RuntimeError',
 'SyntaxError',
 'SystemError',
 'TabError',
 'TimeoutError',
 'TypeError',
 'UnboundLocalError',
 'UnicodeDecodeError',
 'UnicodeEncodeError',
 'UnicodeError',
 'UnicodeTranslateError',
 'ValueError',
 'ZeroDivisionError']

特に発生するエラーがわからない場合は
とりあえずtry - exceptで囲んでおいて、のちに
エラー内容に合わせて処理を追加してゆく、
というのが良いでしょう。

エラーの開始、例外処理はプログラムでは必須になるテクニックなので
必ず覚えておいてください。

                 36.クラスについて

前回はオブジェクト指向についての概念を説明しました。

オブジェクト指向の概念を学んだら、次は実際にpythonで
そのオブジェクトとなるクラスを作って見ましょう。

クラスの作り方:

class クラス名():
    def __init__(self,):
        処理

クラスの呼び出し方

変数名 = クラス名()

簡単なクラスを作ってみましょう。

In [1]:
class class_a():
    def __init__(self,):
        print('初期化')
        self.ac = 'aaa'

インスタンスとインスタンス化

インスタンスとは、クラスを実体化したものです。
クラスのデータやメソッドを使うためには
「インスタンス」と呼ばれるものを生成する必要があります。

クラスを呼び出して変数に格納します。

この事を「インスタンス化」
作った変数のことを「インスタンス」とも言います。

In [2]:
a = class_a()
初期化

ここでinitという関数の部分は
「コンストラクタ」と呼ばれるものになり、
クラスを生成する際に
一番初めに呼ばれる処理になります。

クラス呼び出して変数に格納した際に
init が呼ばれてその中の処理が実行されます。

このinitの処理の中では
引数に self という変数名がありますが
selfという変数は自分自身を指すもので、
クラスではこの引数は必須で
書かないとエラーになってしまいます。

initの処理の中ではself.ac という変数に値として 
aaa を代入しています。

これでこのクラスの中にacという変数を確保して、
値を代入したことになります。

クラスを生成した後に呼び出す部分で
この変数が使えるようになります。

クラスの変数やメソッドの呼び出し方

クラス変数名.メソッド名

In [3]:
aa = class_a()
print(aa.ac)
初期化
aaa

クラスの変数を追加して見ましょう。

In [4]:
aa.b = 2
print(aa.b)
2

クラスの中に変数 b が確保され、2という値が代入され
この変数を使いまわすことができるようになります。

このクラスに定義した変数のことを「アトリビュート」と呼んでいます。

クラスではコンストラクタとは別に関数も定義でき、
このクラス内に定義した関数のことをメソッドと呼んでいます。

メソッドを追加して見ましょう。

In [5]:
class class_a():
    def __init__(self,):
        print('初期化')
        self.ac = 'aaa'

    def anaa(self, ):
        print(1)
In [6]:
# クラスを呼び出して変数に格納
aa = class_a()
# クラスのメソッドを呼び出す。
aa.anaa()
初期化
1

このようにクラスとは複数のメソッドや変数を持つもの
オブジェクトということになります。

メソッドやアトリビュートは
そのクラスの中からしか呼び出すことができません。
クラスで定義した ac という変数はそのままでは使用できません。

In [7]:
print(ac)
---------------------------------------------------------------------------
NameError                                 Traceback (most recent call last)
<ipython-input-7-2faad1a56ed6> in <module>()
----> 1 print(ac)

NameError: name 'ac' is not defined

必ずクラス変数とともに使う事が必要になります。

In [8]:
print(aa.ac)
aaa

クラスはどのような時に使うのでしょうか?
それはプログラムの中で大きな処理をひとまとめにしたり
別のプログラムで使いまわしたいときなどに役立ちます。

一旦一つのプログラムとしてクラス化しておいて
別のプログラムでそれを使いまわすという使い方が一般的です。

クラスの継承

またクラスには継承という概念もあります。

まずは継承するものとされるものとして
親子のクラスを作り、その中にメソッドを作っておきます。

In [9]:
class oya():
    def oyano_method(self):
        print('oyano')

# 継承するクラスを引数に指定する
class ko(oya):
    def kono_method(self):
        print('kono')

この場合 子のクラスの方は引数として
親クラスを指定しておきます。

そうすることで子のクラスを生成すると、
親クラスのメソッドが使えるようになります。

In [10]:
# 子クラスを呼び出す。
k = ko()

# 子クラスで継承した親クラスが呼べる
k.kono_method()
k.oyano_method()
kono
oyano

このように子のクラスには無かった
親クラスのメソッドが呼び出せるようになります。

これは今までにあるクラスを派生させて、
新しいクラスを作りたい場合などに
用いることができる便利な機能です。

クラスの概念や、仕組みは覚えるまでは大変ですが、
覚えればプログラミングの幅が大きく広がるものとなりますので
ゆっくりと確実に覚えるようにしていってください

                 35.オブジェクト指向の話

python言語はオブジェクト指向のプログラミング言語となります。

それではオブジェクト指向とはなんなのでしょうか?

pythonでは取り扱うものは全部「オブジェクト」
という概念となります。

例えば変数 a に 文字列 abc を代入すると、
この変数 a は 文字列型のオブジェクトになります。

In [1]:
# 文字列型のオブジェクト
a = 'abc'
print(a)
abc

関数とメソッド

次にリスト型の変数 l と要素を宣言して
そうするとこの変数 l はリスト型のオブジェクトになり、
同時にリストオブジェクトが持つ関数(メソッド)が使えるようになります。

In [2]:
# リスト型を定義
lis1 = [1,2,3]
# リスト型のメソッド
lis1.append(4)
print(lis1)
[1, 2, 3, 4]

厳密には関数とメソッドは違うものですが
分かりやすくいうと、リスト型でデータを追加するappend
などはリストオブジェクトそのものに対する操作になるので
メソッド、という取り扱いになります。

オブジェクト指向ではオブジェクトで定義された
関数と同じような書き方を
したものをメソッドと呼んでいます。

それ以外を関数と呼んでいます。

文字列型のオブジェクトを作れば、
そのオブジェクトが持つ関数はメソッドと言うことになります。

オブジェクト思考の概念

オブジェクト指向とは、何かしらの物(オブジェクト)で構成された
プログラミング言語のことを言い、
オブジェクトとは変数、データ型、関数、そしてクラスといったものが挙げられます。

プログラム上で何かしらのモノを作ってそれを取り扱ってゆくという考えが
オブジェクト指向の基本的な考え方になっていきます。

ここら辺は概念的なものなので
今すぐわからなくても良いし、わからなくても簡単なプログラムであれば
なんとかなるものです。ゆくゆくは覚えていってください。

この次の回ではクラスについて詳しくやっていきたいと思います。

                34.無名関数

さてこれまでは関数についての授業を行ってきましたが、
今回は無名関数というものについてのお話です。

ソートの回で少しだけ触れた
lambda ラムダ というものについてです。
ラムダ式とも呼び、名前のない関数を作ることができます。

ラムダ式の書き方:

lambda 引数 : 処理

普通の関数の書き方だと

In [1]:
def add (a,b):
    return a+b
print(add(2,3))
5

次にラムダを使った無名関数だと

In [2]:
add = lambda a,b : a+b
print(add(2,3))
5

ラムダの後に書いた変数を受け取って : の後の処理が実行されます。

ラムダ式で変数addに関数を代入し
その変数を用いると関数が実行される仕組みです

通常の関数との違いはほとんどなく
関数を定義したほうが無難です。

ラムダ式が効力を発揮するのはsortなどの時です。

辞書型の値でソートの際にこのラムダ式が出てきました。

In [3]:
dct ={ 3:7 , 5:6 ,1:3 }
# 辞書型を値で昇順ソートする。
print(sorted(dct.items(), key=lambda x:x[1]))
[(1, 3), (5, 6), (3, 7)]

key= 以降がラムダ式です。
sorted 関数の引数 key が関数を受け取れます。

複雑なソートをする場合にラムダ式が役立ちます。

In [4]:
lis1 = [2,3,4,5,7]
# 要素を3で割った余りでソートするような場合
print(sorted(lis1 , key=lambda x:x%3))
[3, 4, 7, 2, 5]

辞書型の際はキーと値をタプル型で返してきます。

In [5]:
dct ={ 3:7 , 5:6 ,1:3 }
print(sorted(dct.items(), key=lambda x:x[1]))
[(1, 3), (5, 6), (3, 7)]

key=lambda x:x[1] のインデックス部分で
1つ目 [0] がキー、二つ目 [1] が値となります。

値でソートしたいのなら、
ラムダ式ではインデックスが二つ目
すなわち 1 となるので key=lambda x:x[1]
と指定することになるのです。

これは複合したデータの並び替えなどで役立つことになります。

In [6]:
# 多重のリストを定義
complex_list = [[1,2,3]   ,  [3,5,2]    ,   [2,6,8]]
print(complex_list)
[[1, 2, 3], [3, 5, 2], [2, 6, 8]]
In [7]:
# リストの最初の値で並び替え
print(sorted(complex_list,key=lambda x : x[0]))

# リストの2番目の値で並び替え
print(sorted(complex_list,key=lambda x : x[1]))

# リストの3番目の値で並び替え
print(sorted(complex_list,key=lambda x : x[2]))
[[1, 2, 3], [2, 6, 8], [3, 5, 2]]
[[1, 2, 3], [3, 5, 2], [2, 6, 8]]
[[3, 5, 2], [1, 2, 3], [2, 6, 8]]

大きなリストの要素として存在するリストの並べ替えのキーを
リストの各項目で定義することができます。

このようにすればどのようなデータの形でも対応できるようになるので
ラムダ式が大活躍します。

関数を定義するまでもないような小さな処理に向いていますので
関数やソートでの利用の仕方を覚えておきましょう。

                 33.global変数とスコープ

関数などを使う際に必ず付いてくるのが、スコープという概念です。
これは宣言した変数が使える範囲を示す概念のことです。

スコープには
グローバルとローカルの2種類があります。

ざっくり説明すると、一番外にあるものがグローバル
関数のブロックの中がローカルということになります。

ではその違いについて見て見ましょう。
まずはグローバルで使える変数を用意します。

In [1]:
global_var = 'global'

次に関数を定義して、その中で使える変数を用意します。

In [2]:
def local_def():
    # ローカル用の変数
    local_var = 'local'
    return local_var

グローバルの変数の呼び出しは

In [3]:
print(global_var)
global

ローカル変数の呼び出しは

In [4]:
print(local_var)
---------------------------------------------------------------------------
NameError                                 Traceback (most recent call last)
<ipython-input-4-38c61bb47a8e> in <module>()
----> 1 print(local_var)

NameError: name 'local_var' is not defined

変数が定義されていないと、エラーが出てしまいます。

ローカルで定義したものは、
そのローカルのブロックの中でしか使えないからです。

なのでこのlocal変数が使えるのは、
定義した関数の中だけになります。

次に関数をいじって、グローバル変数を関数の中で読んでみましょう。

In [5]:
def local_def():
    # グローバル変数の参照
    print(global_var)

local_def()
global

グローバルで宣言した変数はどこでも使用することができます。

ローカルで宣言したものを
グローバルで使うにはどうすればいいでしょうか?

関数であれば戻り値として
グローバルの方に戻すことで使い回すことができます。

In [6]:
def local_def():
    local_var = 'local'
    # ローカルで定義した変数を戻す
    return local_var

g2 = local_def()
print(g2)
local

for文の方も見て見ましょう。

In [7]:
global_var = 'g'

for i in range(5):
    j = global_var*i
    
# ローカルの変数をそのままプリントする
print(i,j)
4 gggg

結果は最終的に代入された結果のみが反映されることになります。

for文のブロックで使用していた変数などを
気づかずに再利用してしまうケースなどがありえると思いますが、
変数に格納した値によってはプログラムの結果に大きく影響が出てきます。

最後に、グローバルとローカルで変数名がかぶってしまった場合どうなるか

In [8]:
sp = 'global'

def local_def():
    sp = 'local'
    print(sp)

local_def()
print(sp)
local
global

一旦ローカルで同じ変数名で定義していますが
関数を呼び出した際はその関数で定義した変数名で
上書きされる形になるのでローカルで代入した値が表示されます。

その後グローバルの変数を呼び出す際には
元の代入された値が表示されるという仕組みです。

global という予約語を用いると、
このグローバルの変数をローカル側で操作することできます。

In [9]:
sp = 'global'

def local_def():
    # ローカルでグローバル変数として定義
    global sp 
    sp= 'local'
    print(sp)

local_def()
print(sp)
local
local

一旦ローカルで変数の前にglobalをつけて宣言し、
その後に変数に代入するとグローバルの変数の操作をすることができます。

グローバルと、ローカルで同じ変数を用いると、
不慮のバグの元になりやすいです。

なので慣れないうちは変数名はなるべく被らないような
名称にすることをお勧めします。

このページのトップヘ