Python:リストとNumpy配列の違い

f:id:monozukuri-bu:20191007172946j:plain

ふっちーです。
今回は戻ってPythonです。

研修で触ったNumpyについてまとめたいと思います。

目次

・Numpyの用途
・Numpyのリスト
・リストと配列の変換
・スライシング
・データ型は統一せよ
・四則演算

環境

今回はGoogle Colabolatory(Colab)を使用しました。
colab.research.google.com 本来Numpyはインストールが必要なモジュールです。
しかし、colabは機械学習を目的に作られているので、インポートするだけで使用できます。

Numpyとは

そもそもNumpyとは何でしょうか。
Wikipedia先生に頼ってみます。

Pythonは動的型付け言語であるため、プログラムを柔軟に記述できる一方で、純粋にPythonのみを使って数値計算を行うと、ほとんどの場合C言語Javaなどの静的型付き言語で書いたコードに比べて大幅に計算時間がかかる。そこでNumPyは、Pythonに対して型付きの多次元配列オブジェクト (numpy.ndarray) と、その配列に対する多数の演算関数や操作関数を提供することにより、この問題を解決しようとしている。NumPyの内部はC言語 (およびFortran)によって実装されているため非常に高速に動作する。したがって、目的の処理を、大きな多次元配列(ベクトル・行列など)に対する演算として記述できれば(ベクトル化できれば)、計算時間の大半はPythonではなくC言語によるネイティブコードで実行されるようになり大幅に高速化する。さらに、NumPyは BLAS APIを実装した行列演算ライブラリ (OpenBLAS, ATLAS, Intel Math Kernel Library など)を使用して線形代数演算を行うため、C言語で単純に書いた線形代数演算よりも高速に動作しうる

NumPy - Wikipedia

な、なるほど!
つまり、普通にPythonで書くと遅いけど、Numpyを使えば内部でC言語Fortranが動作して高速に演算ができる、ということですね。

NumpyはPythonのモジュールとだけあってPythonと似たように動かすことができます。
しかし、異なる部分も多くありPythonに似てる分ややこしいこともあります。
今回はこのNumpyをPythonとどう違うのかを見ていきましょう。

Numpyのリスト

早速Numpyに触ってみましょう。
NumpyはPythonで言うところのリストによく似ています。
それもそのはずでNumpyはリストと同じ構造の配列を基本としています。

しかし、リストとは異なり、Numpyには数学の行列の概念が存在します。
Numpyでは行を「次元数」、列を「要素数」など表記する場合があります。
また、Pythonの組み込み関数で使えるものを「リスト」、Numpyで使われるものを「配列」または「多次元配列」と呼びます。
今回は上記の書き方で統一します。

では、早速Numpyに触れていきましょう。

import Numpy as np

x = np.array([[1, 2, 3], [4, 5, 6]])#arrayで具体的に配列を作る
print(x)
# [[1 2 3]
#  [4 5 6]]

これでNumpyの配列が作成できました。
Numpyではこれをndarrayというデータ型として扱います。
list型とは異なるので、間違えないようにしましょう。

type(x)
# Numpy.ndarray

配列の作り方は他にもあります。

arange関数

y = np.arange(0, 12).reshape(2,6)#reshape(n,m)でn×mの行列に変換
print(y)
# [[ 0  1  2  3  4  5]
#  [ 6  7  8  9 10 11]]

0から11の配列ができました。
arange関数はrange関数のように配列を作ることができます。

linspace関数

z = np.linspace(1, 25, 12).reshape(3,4)
print(z)
# [[ 1.          3.18181818  5.36363636  7.54545455]
#  [ 9.72727273 11.90909091 14.09090909 16.27272727]
#  [18.45454545 20.63636364 22.81818182 25.        ]]

1から25の間を12等分した値の配列ができました。
linspace関数は始点と終点を決めて、その間を好きな数で等分した作ることができます。
また、linspace関数はarange関数とは違って、終点を含める点に注意しましょう。

リストと配列の変換

PythonのリストとNumpyの配列は互いに変換することができます。
最初に作った多次元配列のxをリストにしてみましょう。

多次元配列からリストへ

x_list=x.tolist()
print(x_list)
print(type(x_list))
# [[1, 2, 3], [4, 5, 6]]
# <class 'list'>

to_list()を使うことでこのようにリストに変換できます。

今度はx_listを多次元配列に変換して、元に戻るか試してみましょう。

リストから多次元配列へ

x_ndarray = np.array(x_list)
print(x_ndarray)
print(type(x_ndarray))
# [[1 2 3]
#  [4 5 6]]
# <class 'Numpy.ndarray'>

多次元配列を作ったarray関数で変換することができました。
最初に作ったxと同じ結果になっているのが分かります。

スライシング

Numpyでもスライシングは可能です。

print(x[1][:2])
# [4 5]

また、このような書き方も認可されています。

print(x[1, :2])
# [4 5]

行と列の順番は最初の例と変わりありません。

また、Numpyでは配列という特性から行と列という概念を持っていますと説明しました。
そのため列でスライシングすることができます。

print(x[:, 2])
# [3 6]

それぞれの行のインデックス値が2の要素を抜き出しました。
このようにNumpyでは縦横ともにスライシングできます。

当然ですが、リストはできません。

print(x_list[:, 1])
# TypeError: list indices must be integers or slices, not tuple

リストではそもそも[n, m]でスライシングできないため、列の要素を抜き出すといったことはできません。
どうしてもというなら以下の方法があります。

a = [row[2] for row in x_list]
print(a)
# [3, 6]

データ型は統一せよ

Numpyの多次元配列ではPythonのリストと違って要素のデータ型をすべて統一する必要があります。
xに文字列を入れてみましょう。

x[0][1]='g'
# ValueError: invalid literal for int() with base 10: 'g'

intに変換できないとエラーが出てきました。
これは多次元配列xのデータ型がintで固定されているからです。
配列のデータ型はdtypeで調べることができます。

print(x.dtype)
# int64

zでもやってみましょう。

z[0]='g'
# ValueError: could not convert string to float: 'g'

こちらもstrはfloatにできませんとエラーが出てきました。
このように配列のデータ型は統一する必要があります。

一方リストにはそのような制約はありません。

x_list[0][1]='g'
print(x_list)
# [[1, 'g', 3], [4, 5, 6]]

四則演算

Numpy配列の四則演算は各要素ごとにその計算が行われます。
また、Numpyでは配列の形が異なっていても、一定の条件で四則演算することができます。

print(x*3)
# [[ 3  6  9]
#  [12 15 18]]

このように配列ではない相手でもすべての要素に対して計算を行います。
これはスカラー倍と呼ばれています。

次に配列同士で計算してみましょう。

b = np.array([1,2,3])
print(x+b)
# [[2 4 6]
#  [5 7 9]]

列数は両方とも3ですが、bは次元数が1で、xは次元数が2ですが計算できました。
このように次元数が異なる場合は、次元数が少ないほうの配列が多いほうに次元数を合わせます。
つまり、aは計算する瞬間だけ
[[1,2,3]
[1,2,3]]
となり、それぞれの要素の積を計算できたということです。

最後に列数が異なる場合の計算も見てみましょう。

c = np.array([[5], [7]])
print(x-c)
# [[-4 -3 -2] 
#  [-3 -2 -1]]

こちらもcが
[[5,5,5]
[7,7,7]]
のようになり、計算することができました。

このように計算するために配列の形を合わせるのをブロードキャスト機能といいます。

ちなみに、このような場合は計算できません。
+ (n,m)と(n,l)

print(x-y)#(2,3)と(2,6)
# ValueError: operands could not be broadcast together with shapes (2,3) (2,6) 

これはブロードキャスト機能が適用されるのは条件があるからです。
それは計算に使える配列は次元数か要素数が1か最大値と同じ場合のみと限られているからです。
要するに次元数が2において計算できる配列の形は
・(n, m)と(n, m)
・(n, m)と(n, 1)
・(n, m)と(1, m)
・(n, 1)と(1, m)
・(n, m)とk(実数)
だけになります。

また、これは行列との計算とは異なるということに注意しましょう。
例えば、(a, b) 行列と (c, d) 行列の乗算を行う場合、b=cでないと計算できません。
Numpy配列で行列の計算を行う場合は、dot関数を使いましょう。

d = np.array([[3,3],[3,3],[3,3]])
print(np.dot(x,d))
# [[18 18]
#  [45 45]]

リストに対して同じようにすると以下のようになります。

print(x_list*3)
print(x_list+3)
# [[1, 'g', 3], [4, 5, 6], [1, 'g', 3], [4, 5, 6], [1, 'g', 3], [4, 5, 6]]
# TypeError: can only concatenate list (not "int") to list

最後に、Numpyの計算速度が速いことを確かめてみましょう。

d = np.arange(1,1000) 

%timeit sum(d)
%timeit np.sum(d)
# 10000 loops, best of 3: 93.9 ?s per loop
# The slowest run took 21.44 times longer than the fastest. This could mean that an intermediate result is being cached.
# 100000 loops, best of 3: 4.3 ?s per loop

上がPythonのsumで計算した場合の時間で、下がNumpyのnp.sumで計算した場合の時間です。
このようにNumpyの方が速いとわかります。

まとめ

Numpyの配列とPythonのリストの違いをまとめました。
Pythonに似ているからこそ、覚えやすい面もあれば、混乱する面もあります。
しっかり細かな違いを把握していきましょう。

NULLール〜SQLの基本とNULLについて〜

f:id:monozukuri-bu:20190827200842j:plain

ふっちーです。
SQLのブログは初投稿です。

Oracle MASTER Bronze 12c SQLの資格取得を目指して勉強しております。
今回は基礎の部分とNULLを中心に取り上げていきます。

目次

・用語
・NULLについて
・記述の順番

テーブル

テーブル名:cope

| number | name | Salary | perpay |
|--------|------|--------|--------|
|      1 |佐々木| 180000 | (null) |
|      2 | 富田 | 200000 | (null) |
|      3 | 桑原 | 730000 | (null) |
|      4 | 衛藤 | 360000 |  70000 |
|      5 | 田中 | 310000 |  40000 |
|      6 |  林  | 270000 | (null) |
|      7 | 高木 | 190000 |  50000 |
|      8 | 藤原 | 450000 |  90000 |

以下のSQL文ではこのテーブルを使用しています。

用語

select文の機能とSQL文の分類

機能名 役割 具体例
射影 特定の列を取り出す select
選択 特定の行を取り出す where, having等
結合 表と表を横に繋げる join等

SQLと言ったら外せないselect文についてです。

個人的な経験ですが、試験において意外とつまづきやすいのがこのような名称の問題だと思っています。
他にもSQL文の分類である

DML(Data Manipulation Language)
DDL(Data Definition Language)
・DCL(Data Control Language)
トランザクション制御

SQLにはselect文の様なデータ表を表示する機能だけでなく、内容を書き換えたり、データに接続できる権限の制御まで行えます。
上の4つはその種類分けにつけられている名称です。

select文はDMLに当てはまります。

関数のタイプ

SQLには様々な関数があります。
これはSQLには数値や日付、文字データなどのデータが使用可能です。

SQLの関数には2種類あります。

・単一行関数
・グループ関数

単一行関数

単一行関数は表に対して、一行の結果を出力します。
引数には数値だけではなく、列名や式を入力できるものもあります。

いくつか例を挙げてみましょう。

trunc関数

trunc関数は数値の切り捨てを行います。

select trunc(m, n) from cope;

mを小数点n桁以下を切り捨てします。
nは任意で入力でき、入力しない場合は小数点以下を切り捨てします。
-(マイナス)で入力した場合は小数点の左側を数える。

concat関数

concat関数は文字列の結合を行います。

 select concat('m', 'n') as test from cope;
| TEST |
|------|
|   mn |

2つまでしか結合できないので、3つ以上結合したい場合は(||)を使いましょう。

グループ関数

グループ関数は列に対して、一行の結果を出力します。

こちらもいくつか例を見てみましょう。

avg関数

指定した列の平均値を求めます。
そのため、数値のみの行にしか適用できません。

 select avg("Salary") as test from cope;
|   TEST |
|--------|
| 336250 |

count関数

指定した列内のNULL以外の行数を出力します。

select count("perpay") as test from cope;
| TEST |
|------|
|    4 |

ただし、引数に*(アスタリスク)を入れるとNULLを含めて数えます。

select count(*) as test from cope;
| TEST |
|------|
|    8 |

max関数

指定した列内の最大値を出力します。

select max("Salary") as test from cope;
|   TEST |
|--------|
| 730000 |

ここでいう最大値は数値だけではなく、日付や文字列も含めます。
日付の場合は一番新しい日付が出力されます。
文字列の場合は文字コードが最も大きい文字列が出力されます。

NULLについて

SQLにおけるNULLがややこしかったのでここでまとめてみたいと思います。

四則演算子

NULLは四則演算(+, -, *, /)のどれを適用してもNULLを返します。

select 100*NULL from cope;
| 100*NULL |
|----------|
|   (null) |

NULLを0として扱いたい場合はNVL関数が使えます。
NVL関数については後述しています。

比較演算子

比較を行った場合、基本的にTrueかFalseが返ってくるのが普通ですが、ここでもNULLはNULLを返します。

select * from cope where "perpay" = NULL;

上記も四則演算同様にエラーは発生しませんが、該当なしと出力されます。

しかし、比較自体が行えないわけではありません。
where句でisを使うとNULLかどうかを判断することができます。

select * from cope where "perpay" is NULL;
| number | name | Salary | perpay |
|--------|------|--------|--------|
|      1 |佐々木| 180000 | (null) |
|      2 | 富田 | 200000 | (null) |
|      3 | 桑原 | 730000 | (null) |
|      6 |  林  | 270000 | (null) |

ORDER BYでNULLはどこに来る?

order byは表のソートを行う機能です。

select * from table order by 列名;

この時列にNULLが含まれている場合どうなるでしょうか。
まず、昇順で並べてみましょう。

select "perpay" from cope order by "perpay";
| perpay |
|--------|
|  40000 |
|  50000 |
|  70000 |
|  90000 |
| (null) |
| (null) |
| (null) |
| (null) |

次に、降順で並べてみましょう。

select "perpay" from cope order by "perpay" desc;
| perpay |
|--------|
| (null) |
| (null) |
| (null) |
| (null) |
|  90000 |
|  70000 |
|  50000 |
|  40000 |

このように、ソートでは1番大きいものとして扱われます。

NULLを使った関数

前述した汎用関数はNULLを使用できます。

NVL関数

select nvl("perpay", 0) as test from cope 
|  TEST |
|-------|
|     0 |
|     0 |
|     0 |
| 70000 |
| 40000 |
|     0 |
| 50000 |
| 90000 |

式がNULLの場合、設定された値を戻します。
例の場合はNULLの時0を返します。

NVL2関数

select nvl2("perpay", 1, 0) as test from cope 
| TEST |
|------|
|    0 |
|    0 |
|    0 |
|    1 |
|    1 |
|    0 |
|    1 |
|    1 |

式がNULLでないなら1つ目の値を、NULLなら2つ目の値を戻します。 例の場合はNULL以外で1、NULLで0を出力します。

NULLIF関数

select nullif("Salary", 180000) as test from cope 
|   TEST |
|--------|
| (null) |
| 200000 |
| 730000 |
| 360000 |
| 310000 |
| 270000 |
| 190000 |
| 450000 |

2つの式を比較し、等しい場合はNULLを返す。異なる場合は1つ目の式を戻します。
例の場合はSalaryが180000のときNULLを返します。

まとめ

今回はSQLの用語とNULLについてまとめました。

特にNULLは先月まで勉強していたPythonでは見られない動作をするので、なんとかして慣れたいです。
また、個人的に今一番の壁が相関副問合せなので、これも克服していきたいです。

OracleDatabase SQL VIEW、順序について

f:id:monozukuri-bu:20190827194855j:plain

みちです。
Oracle DatabaseのVIEWと順序の作成・削除についての勉強していきたいと思います。

VIEW

 表や他のVIEWをもとに作成されるオブジェクトです。VIEWはデータを持っていません。
VIEWのもととなる表を「実表」、VIEWを「仮想表」といいます。

VIEWの種類

単一VIEW

 一つの表から構成され、関数やGROUP BY句を含まないVIEWのことです。
VIEWから実表の操作をすることが可能です。(条件による)

複合VIEW

 二つ以上の表から構成されているVIEWもしくは、1つの表から構成され関数やGROUP BY句を含むVIEWのことです。
VIEWから実表の操作をすることは特定の条件を満たす場合可能です。

VIEWの作成

 CREATE VIEW権限を持っていればVIEWの作成が可能となります。

CREATE [OR REPLACE] [FORCE | NOFORCE] VIEW VIEW名[(別名 [,別名 …])]
AS
副問合せ
[WITH CHECK OPTION [CONSTRAINT 制約名]]
[WITH READ ONLY [CONSTRAINT 制約名]];

CREATE VIEW文のオプション

OR REPLACE

 同名のVIEWが存在した場合そのVIEWに上書きします。
同名のVIEWが存在しているのにも関わらずOR REPLACEを省略するとエラーとなります。
同名のVIEWが存在しない場合新たにVIEWが作成されます。

FORCE

 実表が存在しない場合エラーとなるがVIEWは強制的に作成されます。

NOFORCE

 実表がある場合VIEWが作成されます。実表が存在しない場合はエラーとなります。

WITH CHECK OPTION

 VIEWを通じてアクセスできる行のみ追加、更新できます。

WITH READ ONLY

 VIEWが読み取り専用となります。そのためデータの追加、更新、削除が出来なくなります。

VIEWの削除

DROP ANY VIEW権限を持つユーザーがVIEWを削除することが出来ます。

DROP VIEW VIEW名;

VIEWを使用したデータの変更

データの削除の場合(DELETE)

 GROUP BY句、ROWNUM疑似列、グループ関数、DISTINCTキーワードの要素が含まれていない場合削除することが出来ます。

データの更新の場合(UPDATE)

 GROUP BY句、ROWNUM疑似列、グループ関数、DISTINCTキーワード、式によって定義された列が要素が含まれていない場合変更することが出来ます。
副問合せのSELECT句で計算式や関数を使用している場合、列別名を指定する必要があります。

データの追加(INSERT INTO)

 GROUP BY句、ROWNUM疑似列、グループ関数、DISTINCTキーワード、式によって定義された列、VIEWに含まれていない実表の列にNOT NULL制約が定義されている要素が含まれていない場合変更することが出来ます。

順序の作成・削除

順序の作成

 順序は一意な番号を自動で生成するオブジェクトです。
CREATE SEQUENCE権限を持つことでCREATE SEQUENCEを使用することが出来ます。

INCREMENT BY

 デフォルト値は1です。指定した数だけ増幅します。

START WITH

 デフォルト値は1です。初期値を設定することが出来ます。

MAXVALUE / NOMAXVALUE

 最大値を設定することが出来ます。デフォルトはNOMAXVALUEです。

MINVALUE / NOMINVALUE

 最小値を設定することが出来ます。デフォルトはNOMINVALUEです。

CYCLE / NOCYCLE

 順序値が最大値または最小値に達した場合、初期値に戻り繰り返し順序値を生成します。
デフォルトはNO CYCLEです。

CASHE / NOCASHE

 指定された数順序値をメモリに割り当てます。デフォルト値は20です。

順序の使用方法

 主問い合わせのSELECT句のリスト、INSERTの副問合せのSELECT句のリスト、INSERTのVALUES句、UPDATEのSET句で参照することが出来ます。

NEXTVAL疑似列

 新しい順序値が戻されます。

CURRVAL疑似列

 最後に取得された順序値が戻されます。

順序値の欠番の発生

ロールバックが発生した場合、順序が複数の表で指定されていると、システムがクラッシュした場合に欠番が発生することがあります。

順序の定義の削除

DROP SEQUENCEを使うことによって順序の削除をすることが出来ます。
DROP ANY SEQUENCE権限をもつユーザーは削除を行うことが出来ます。

DROP SEQUENCE 順序名;

順序の定義の変更

 ALTER SEQUENCEを使うことによって順序の定義を変更することが出来ます。
START WITH以外のオプションを指定することが出来ます。
初期値を指定する場合は順序を削除してから再度作成する必要があります。

まとめ

 今回はVIEWの作成と順序について勉強しました。

ハードリンクとシンボリックリンク

f:id:monozukuri-bu:20190816184235j:plain

shadowです。
今回はWindouwsを使っている人ならお馴染みのショートカットについて、
Linuxにはそのショートカットが2種類あるので、詳しく説明したいと思います。

ハードリンクとシンボリックリンクの違い

Linuxにはショートカットの種類として、ハードリンクとシンボリックリンクが存在します。
この2つのリンクの違いについて説明する前にまずは、ファイルのinode番号について書いていきます。

inode番号

Linuxでファイルを作成するとinode番号という一意の番号が割り当てられます。
また、inodeにはファイルの属性情報が格納されています。
・ファイル種別
・ファイルサイズ
・アクセス権
・所有者情報
・リンク
・ブロック番号

$ ls -li sample.txt
7454 -rw-r--r--    1 user     user             0 Aug  9  2019 sample.txt

一番左の7454がinode番号です。

ハードリンク

ハードリンクはファイルに別名をつけるようなものです。

ファイル名A -> ファイルの実体 <- ファイル名B

このようにファイル名は違えど同じファイルの実体(inode)を指しています。
また、ファイルのinodeにアクセスしているため、ファイルが移動しても問題ありません。
そして、異なるファイルシステム上や、ディレクトリにはリンクを作成することが出来ません。

シンボリックリンク

シンボリックリンクWindowsのショートカットに近い概念です。
シンボリックリンクはハードリンクと違い、ファイルの実体ではなくファイルパスを指します。

シンボリックリンクファイル -> ファイル名 -> ファイルの実体

シンボリックリンクはinodeに依存せず、パスで参照しているので、
ハードリンクと違い、異なるファイルシステム上のファイルや、ディレクトリに対してもリンクを作成することができます。
ただし、パスでアクセスしているため、ファイルが移動するとアクセス不能なデットリンクになってしまいます。

lnコマンド

リンクを作成するにはlnコマンドを使います。
オプションを指定せずにlnコマンドを実行した場合、ハードリンクが作成されます。
-sオプションをつけることで、シンボリックリンクが作成されます。

動かしてみる

ハードリンク

実際に動かしてみます。

$ mkdir -p testdir/sampledir

$ touch testdir/sampledir/file.txt

$ echo Test > testdir/sampledir/file.txt

$ cd sampledir

$ ln sampledir/file.txt ./sample_hard

$ cat sample_hard
Test

sample_hardの中を見てみるとfile.txtと同じ内容になっています。

次はinode番号をチェックしてみます。

$ ls -li sample_hard sampledir/file.txt                               
   7461 -rw-r--r--    1 root     root             5 Aug  9  2019 sample_hard    
   7461 -rw-r--r--    1 user     user             5 Aug  9  2019 sampledir/file.
txt

どちらも同じになっています。
ファイルの移動もしてみたいと思います。

$ mv sampledir/file.txt .

$ cat sample_hard
Test

移動しても同じ内容を表示しました。

シンボリックリンク

$ mkdir sampledir2

$ touch sampledir2/file2.txt

$ echo Test2 > sampledir2/file2.txt

$ cat sampledir2/file2.txt                            
Test2

$ ln -s sampledir2/file2.txt ./sample_symbolic

$ cat sample_symbolic
Test2

シンボリックリンクの中身も同じでした。
こちらもファイルの移動をします。

$ mv sampledir2/file2.txt .

$ cat sample_symbolic
cat: sample_symbolic: そのようなファイルやディレクトリはありません

こちらはファイルが移動すると参照出来ませんでした。

まとめ

最後にハードリンクとシンボリックリンクの特徴を、ざっくりまとめると以下のようになります。

特徴 ハードリンク シンボリックリンク
参照 inode パス
異なるファイルシステム できない できる
元ファイルの移動 移動しても消えない 移動すると参照できない
元ファイルの削除 削除しても消えない 削除すると消える

以上です。お疲れ様でした。

Pythonのクラスに関する備忘録

f:id:monozukuri-bu:20190816165831j:plain

ふっちーです。
今回はクラスについての備忘録を残します。
今までクラスに触れてきた中で、理解するのに時間が掛かったものや印象に残ったものを、自分の言葉にしてみたいと思います。
クラスの基本的な部分や用語の説明は省略しています。

4つの疑問

・そもそもクラスはどういうときに使えばいいの?
・クラスの関数にあるselfって?
・継承とは?superってどうやって使うの?
・クラスを含んだモジュールは関数だけのモジュールと違う部分はあるの?

今回は上記4つについて書いてみたいと思います。

そもそもクラスはどういうときに使えばいいの?

クラスはデータ(属性)や関数(メソッド)を1つにまとめて、データの保持と処理を一括で管理できます。
そして、同じクラスでも異なるインスタンスを設定すれば、異なるデータをインスタンスごとに別々で処理できます。
確かに便利そうな機能だとは思うのですが、実際にクラスはどんな時に使われるのでしょうか。

結論から言うと、
テンプレートを作成し、似たようなものを手軽に生産できる
という点があげられます。

例を挙げると、ゲーム制作が分かりやすいと思います。
キャラクターには(ゲームにもよりますが)名前や性別など必ず持っているデータ(属性)がいくつか存在します。
レーシングゲームだったら車体の名前や色だったり、アクセルやブレーキなどのデータ処理(メソッド)が該当します。

そして、ゲームではこれらのデータを持つオブジェクト(キャラクター、車など)をたくさん作成する場合があります。
普通に変数1つ1つでこれを作るとなるととんでもない労力がかかります。
この時、クラスの存在がこのような作業をできるだけ少ない労力でコードを書く手助けをしてくれるのです。

試しに名前、年齢、性別が設定されたキャラを作るために、実際にクラスを書いてみましょう。

class Chara:
  def __init__(self, name, age, sex):
    self.name = name
    self.age = age
    self.sex = sex
  
  def status(self):
    print("名前:"+self.name)
    print("年齢:"+str(self.age)+"歳")
    print("性別:"+self.sex)

このようにキャラの基本情報を設定することで、これを基にしたキャラの情報を多く作ることができます。

a = Chara("John", 14, "male")
b = Chara("Alice", 8, "female")
c = Chara("Robin", 40, "male")

これだけで、3人分のキャラを設定できました。
もしこれを変数だけで作ろうとすると、9個の変数が必要になることを考えると、クラスがいかにデータを楽に管理できるかが分かります。
statusメソッドでデータを確認してみましょう。

a.status()
出力結果:
名前:John
年齢:14歳
性別:male

このようにクラスで基となるデータやメソッドを用意することで、大量のデータを簡単に管理することができます。

また、キャラをPCキャラとNPCキャラにわけて、PC側には職業や体力を、NPC側にはセリフを設定することもできます。
この場合は継承を使うときれいに設定できます。
継承については後述します。

クラスの関数にあるselfって?

クラスで関数を使うときに疑問に思った方もいるかと思います。
中途半端に理解してしまい、混乱することが多かったので、まとめることにしました。

まず、selfの正体は何なのでしょうか。
selfはその名の通り自分自身を指します。
ここで言う自分自身とは、インスタンス化したクラスを示しています。
さっきの例で言うとa, b, cがそれに当てはまります。

a = Chara("John", 14, "male")
b = Chara("Alice", 8, "female")
c = Chara("Robin", 40, "male")

インスタンス化したクラスを格納するための変数名(a, b, c)は自分で勝手に決められますが、
それをクラス内部で使用する場合はselfで統一しています。
クラスは各インスタンス毎に異なる情報を持つため、selfでどのインスタンスなのか判断する必要があるのです。
つまりこの引数selfは、インスタンス化されたクラスを入れて識別する為の引数と言えます。

実際に書いてみましょう。

class Chara:
  def __init__(self, name, age, sex):
    self.name = name
    self.age = age
    self.sex = sex
    
  def get_old(self):
    self.age += 1
    print(self.age)  

先ほどのクラスに歳をとるget_oldメソッドを新しく追加しました。
このget_oldメソッドを使って以下のコードを実行してみましょう。

a.get_old()     #インスタンス関数を使う場合はこれが一般的
Chara.get_old(b)  #実はこれでも動く
出力結果:
15
9

上の例にあるように、変数名.get_old()は、クラス名.get_old(x)と同じと言えます。
2つ目の例が本来の書き方といった感じですが、毎回クラス名を書く必要がない1つ目の方が効率的ですね。

継承とは? superってどうやって使うの?

継承はクラスの機能拡張として使われます。
継承を使うことでクラスに新しい属性やメソッドを追加したり、別のものに変更することができます。
これにより新しい機能を実装する際に一からクラスを作り直す手間を省くことができます。

実際に継承を使ったクラスを見てみましょう。

import random
class Sleep(Chara):
  def __init__(self, name, age, sex, sleep):
    super().__init__(name, age, sex)
    self.sleep = sleep
  
  def status(self):
    if self.sleep == 0:
      print("ZZZZZ...")
    
    else:
      print("Good morning!")
  
  def n_status(self):
    super().status()
      
  def wake_up(self, name, sleep):
    if self.sleep == 0:
      print("Wake up! "+self.name+"!")
      r = random.randint(0, 1)
      if r == 0:
        print("Oh, come on!")
      
      else:
        self.sleep = 1
        print("Good morning, "+self.name+".")
        
    else:
      print("Oh, sorry.")

最初のクラスを継承したSleepクラスを作りました。
早速キャラを設定し、以下のコードを実行します。

a = Sleep("John", 14, "male", 0)
a.status()
a.get_old()
出力結果:
ZZZZZ...
15

ここで注目してほしいのは2点です。
・Sleepクラスのstatusメソッドが実行されている
・Sleepクラスにないget_oldメソッドが使用できている

基にしたクラスにあるメソッドは継承したクラスでも使用することができます。
継承しなければ、当然使用することはできません。

b.wake_up()
AttributeError: 'Chara' object has no attribute 'wake_up'

継承の大まかな内容を掴んだところで、superに触れていきましょう。
先ほどstatusメソッドがSleepクラスのものに上書きされていました。
この場合Charaクラスのstatusメソッドは使えないのでしょうか。

そこでsuperが役に立ちます。
n_statusメソッドがsuperを使っているので、実行してみましょう。

a.n_status()
出力結果:
名前:John
年齢:15歳
性別:male

Charaクラスのstatusメソッドを使用することができました。

また、Sleepクラスのinitにもsuperは使われています。
これは継承がデータも引き継がれることを利用して、superで基のクラスに既にあるデータを保持するためです。

最後にwake_upメソッドで、Johnを起こしましょう。

a.wake_up()  #起きるまで何回もやろう!
出力結果:
Wake up! John!
Good morning, John.

何回でJohnを起こせましたか?
私は2回でした。

クラスを含んだモジュールは関数だけのモジュールと違う部分はあるの?

モジュールでよく使うのは関数の集まりなので、あまり気にした人はいないかもしれません。
しかし、私は関数を囲むクラスの姿を見て、これをモジュールとして使用する場合どんな動作になるだろうと思いました。

最初のCharaクラスをモジュール(character.py)としてみます。

class Chara:
  def __init__(self, name, age, sex):
    self.name = name
    self.age = age
    self.sex = sex
  
  def status(self):
    print("名前:"+self.name)
    print("年齢:"+str(self.age)+"歳")
    print("性別:"+self.sex)

次に以下のコードを記入します。

import character

d = character.Chara("Asbel", 18, "male")
d.status()
出力結果:
名前:Asbel
年齢:18歳
性別:male

これでモジュールのクラスが使えるようになりました。
関数モジュールを使うときのようにモジュール名.クラスとしてインスタンス化すれば、後は通常通りクラスを使用できるようです。

今回はここまでです。
お疲れさまでした。

まとめ

今回はクラスで疑問に思ったところをまとめてみました。
・クラスは似たようなものを大量に用意する場合に便利だと分かりました
・selfはどのインスタンスなのか見分けるために、変数の名前を受け取る引数です
・継承は元のクラスを拡張して多くのデータやメソッドを追加してバリエーションを持たせることができます
・superで親クラスのデータを記述せずに使用することができます
・クラスを含んだモジュールでもインスタンス化の際にモジュール名を付けるだけで大きな差はないです

今回の勉強でクラスについての理解が深まったように思えます。
しかし、クラスを活かすには個人だと限界があるので、何か面白いアイデアはないか探してみたいと思っています。

Oracle Database 表の作成

f:id:monozukuri-bu:20190816152328j:plain

みちです。
Oracle Databaseの表の作成について、勉強していきたいと思います。

スキーマ

 データの論理構造の集合で、ユーザーごとに1つのスキーマを持っています。
各ユーザーが作成したオブジェクトは、作成したユーザーのスキーマに格納されます。

別のユーザーが所有しているオブジェクトを参照する方法

スキーマ名.オブジェクト名

オブジェクトの命名規則

・使用できるの文字は英数字、漢字、ひらがな、カタカナ、記号「_」、「#」、「$」
・先頭の文字は数字、記号以外
・アルファベットの大文字・小文字は区別されない
・長さは30バイト以下
・同一のスキーマ内で重複する名前は指定できない
予約語は使用できない

表の作成

CREATE TABLE [スキーマ名.]表名
(
    列名 データ型
    [, 列名 データ型]
);

DEFAULTオプション

 表の作成したときにデフォルト値を指定することが出来ます。

列名 データ型 [DEFAULT 式]

表の削除

 表を削除すると表内のすべてのデータと定義されている制約、索引が削除されます。しかし、ビューとシノニムは削除されません。
DROP TABLE文を実行すると、表はゴミ箱へ移動します。完全に削除したい場合は、PURGE句を指定します。
DROP ANY TABLE権限を持っているユーザーか、表の所有者でないと削除することが出来ません。

DROP TABLE 表名 [PURGE];

データ型

分類 データ型の例
文字型 CHAR
VARCHAR2
LONG
CLOB
数値型 NUMBER
日付型 DATE
バイナリ型 RAW
LONG RAW
BLOB
BFILE
ROWID ROWID

文字型

 CHAR型は固定長であり、VARCHAR2型は可変長です。
そのため、CHAR(5)に「abc」という文字列を指定した場合、5バイトの扱いなります。
また、VARCHAR(5)に「abc」という文字列を指定した場合、3バイトの扱いとなります。

数値型

列名 NUMBER[(最大精度[,位取り])]

NUMBER(6, 3)に定義されている列に123.4567を指定した場合、
全体で6桁、整数部分は三桁、小数点以下が第3位までであるため、
小数点第4位の7が四捨五入で繰り上がり、123.457となります。

日付型

 日付型のデータのサイズは固定長であり、7バイトです。

列名 DATE

バイナリデータ型

 RAW型、LONG RAW型、BLOB型、BFILE型の四種類です。

列名 RAW(最大サイズ)
列名 LONG RAW
列名 BLOB
列名 BFILE

ROWID型

 各行に割り当てられている一意なアドレスです。
ROWIDをSELECTで取得する場合は*では取れないため、明記する必要があります。

列名 ROWID

TIMESTAMP型

 DATE型を拡張したデータ型です。年月日時分秒と秒の小数点以下の値を格納することが出来ます。
小数点以下の桁数を指定することができ、デフォルト値は6です。

列名 TIMESTAMP[(小数点以下の桁数)]

INTERVAL YEAR TO MONTH型

 2つの日付と時刻の間隔を年及び月の単位で格納します。

INTERVAL DAY TO SECOND型

 2つの日付と時刻の間隔を日時分秒の単位で格納します。

制約の定義

 制約は列または表レベルで定義することが出来ます。
制約名を省略すると、制約名は「SYS_Cn」となります。

制約の種類

種類
PRIMARY KEY制約(主キー制約)
FOREIGN KEY制約(外部キー制約)
CHECK制約
NOT NULL制約
UNIQUE制約(一意制約)

PRIMARY KEY制約

 表内の各行を一意に識別できる値のみ許可されています。重複値とNULL値は許可されていません。
この制約は表に一つしか定義することが出来ません。

FOREIGN KEY制約

 参照先の列にある値またはNULL値は許可されています。
子表内に親表を参照する行が存在する場合、親表の参照されている行を削除することが出来ません。
また、FOREIGN KEY制約を定義すると親表は削除することが出来ません。
削除する場合は子表から削除する必要があります。

CHECK 制約

 条件に合う値のみ許可されています。条件に合わない値の場合はエラーとなります。

NOT NULL制約

 NULL値は許可されません。列レベルで定義することが出来ます。

UNIQUE制約

 重複した値は許可されません。NULL値は許可されています。
列の組み合わせにUNIQUE制約を定義する場合、表レベルの制約を使う必要があります。
その場合、組み合わせで一意であれば良いため、1つの列で同じ値でも他の列で異なる値が入っていればデータを登録することが出来ます。

副問合せを使用した表の作成

 列名の指定は省略することが出来ます。
その場合、副問合せでSELECT句で指定された列名と同じ名前の列が定義されます。

列名を指定する場合、副問合せのSELECT句と同じ個数の列名を指定する必要があります。
NOT NULL以外の制約はコピーされません。

暗黙的にNOT NULL制約がつくPRIMARY KEY制約もNOT NULL制約が付与されません。
副問合せのSELECT句で計算式や関数を使用している場合、列別名を指定する必要があります。

ALTER TABLE

ALTER TABLE 表名 {READ WRITE | READ ONRY};

READ WRITE

 表のデータを変更することが出来ます。読み取り、書き込みモード。

READ ONRY

 表のデータを変更することが出来ません。表自体の削除だけ可能です。読み取りモード。

まとめ

 今回は表の作成について勉強しました。 細かいところも含めてしっかりと覚えておく必要があると感じました。

Python 便利なリスト演算まとめ

f:id:monozukuri-bu:20190816145258j:plain

最近やっと暑くなってきたので海に行きたいhashiです。
現在、Python3エンジニア認定基礎試験の勉強に励んでいます。
資格勉強のためチュートリアルを見返すとリストへの操作には、融通の利くいろいろな演算があると気づかされました。
分かりにくい所もあるので、実行結果とともにまとめてみます。

そもそもリストとは

ドキュメントによると、

リストはミュータブルなシーケンスで、一般的に同種の項目の集まりを格納するために使われます (厳密な類似の度合いはアプリケーションによって異なる場合があります)。

「同種の項目の集まりを格納する」はわかりやすいですが、「ミュータブル」とは……?
用語集に頼ることにします。

docs.python.org

  • mutable(ミュータブル)
    ミュータブルなオブジェクトは、 id() を変えることなく値を変更できます。イミュータブル (immutable) も参照してください。

うーん……。イミュータブルの解説を見てみます。

  • immutable(イミュータブル)
    固定の値を持ったオブジェクトです。イミュータブルなオブジェクトには、数値、文字列、およびタプルなどがあります。これらのオブジェクトは値を変えられません。別の値を記憶させる際には、新たなオブジェクトを作成しなければなりません。イミュータブルなオブジェクトは、固定のハッシュ値が必要となる状況で重要な役割を果たします。辞書のキーがその例です。

数値aに対してa = a + 1という操作をするときには、aという箱の中身を変えているわけではなく、
新しいオブジェクトにaという名前を付けているという感じでしょうか。
それに対してリストは外観を変えずに箱の中身(が見ているオブジェクト)を変えられるという理解でよさそうです。

演算

特定の値がリストに含まれているかの判定

x in s
リストsの要素の中にxと等しいものがあればTrue、なければFalseを返す。

x not in s
リストsの要素の中にxと等しいものがなければTrue、あればFalseを返す。

わざわざfor文を回すことなく要素の存在を確認できるのは、コードの分量も減りうれしいですね。

sample_list = [1, 2, 3, 100, 1, 7, 9]
print("sample_list:" + str(sample_list))

"""
もうこんな行数いらない!
for i in sample_list:
    if i == 0:
        print(True)
        break
else:
    print(False)
"""

# 特定の値が含まれているかの判定
print("0 in sample_list")
print(0 in sample_list)

# 特定の値が含まれていないかの判定
print("0 not in sample_list")
print(0 not in sample_list)

実行結果

sample_list:[1, 2, 3, 100, 1, 7, 9]
0 in sample_list
False
0 not in sample_list
True

結合

s + t
リストsの後ろにリストtが続くリストを返す。

s*x
リストsをx(整数)個つなげたリストを返す。x<=0の時は空のリストを返す。

リスト同士の足し算、リストと整数の掛け算によってリストの結合が可能です。

sample_list = [1, 2, 3, 100, 1, 7, 9]
print("sample_list:" + str(sample_list))

print(sample_list + [7])    # 2つのリストの結合
print(sample_list * 3)       # 同じリストを3つ結合
print(sample_list * (0))    # 同じリストを0つ結合=空のリスト
print(sample_list * (-6))  # 0未満の値は0と扱われる
print(sample_list)  # 元のリストに影響はない

実行結果

sample_list:[1, 2, 3, 100, 1, 7, 9]
[1, 2, 3, 100, 1, 7, 9, 7]
[1, 2, 3, 100, 1, 7, 9, 1, 2, 3, 100, 1, 7, 9, 1, 2, 3, 100, 1, 7, 9]
[]
[]
[1, 2, 3, 100, 1, 7, 9]

要素の抜き出し

s[i]
i番目の要素を抜き出す。

s[i:j]
i番目からj-1番目まで抜き出す。

s[i:j:k]
i番目からj-1番目までの範囲の、k個ごとに抜き出す。

sample_list = [1, 2, 3, 100, 1, 7, 9]
print("sample_list:" + str(sample_list))
i, j, k = 1, 7, 2
print(sample_list[i])  # i番目の値を抜き出す
print(sample_list[i:j])  # i番目からj-1番目までのリストを抜き出す
print(sample_list[i:j:k])  # i+nk (i<=i+nk<j, nは非負の整数)番目の値のリストを抜き出す

実行結果

sample_list:[1, 2, 3, 100, 1, 7, 9]
2
[2, 3, 100, 1, 7, 9]
[2, 100, 7]

また、i,jに負数を指定するとリストの後ろからの距離でインデックスを指定できたり、
スライスのi,jを省略しても良きに計らってくれるのが便利です。

sample_list = [1, 2, 3, 100, 1, 7, 9, 10]
print("sample_list:" + str(sample_list))
i, j, k = 1, 7, 2

print(sample_list[-i])  # (最後尾を1として)最後からi番目の値を抜き出す
print(sample_list[i:])  # i番目から(リストのサイズ-1番目まで)のリストを抜き出す
print(sample_list[-5:])  # リストの後ろから5つの要素を抜き出す
print(sample_list[:j])  # (0番目から)j-1番目までのリストを抜き出す
print(sample_list[:-5])  # (0番目から)リストの後ろから5つを除いた要素を抜き出す
print(sample_list[::-1])  # (0番目からリストのサイズ-1番目までの)リストを-1刻みで抜き取ることで得られるリストを抜き出す

実行結果

sample_list:[1, 2, 3, 100, 1, 7, 9, 10]
10
[2, 3, 100, 1, 7, 9, 10]
[100, 1, 7, 9, 10]
[1, 2, 3, 100, 1, 7, 9]
[1, 2, 3]
[10, 9, 7, 1, 100, 3, 2, 1]

print(sample_list[::-1])はリストを逆順で表示するという意味になるのでlist(reversed(sample_list))とも書けますが、
スライスを使ったほうが簡単ですね。

リストの編集

s[i]=x
i番目の要素をxに入れ替える。

s[i:j]=l
i番目からj-1番目までをリストlに入れ替える。

s.append=x
リストの最後尾にxを追加する。

s.reverse()
リストの中身を逆順にする

ここまでの演算では元のリストの中身に影響がありませんでしたが、これ以降はリストの中身を編集します。

sample_list = [1, 2, 3, 100, 1, 7, 9]
print("sample_list:" + str(sample_list))
sample_list[0] = 2  # 指定したインデックスの要素を変える
print(sample_list)

sample_list = [1, 2, 3, 100, 1, 7, 9]
sample_list[1:4] = [0, 0, 0, 0, 0]  # 指定した範囲を入れ替える(長さは同じでなくてもよい)
print(sample_list)

sample_list = [1, 2, 3, 100, 1, 7, 9]
sample_list.append(9)  # 最後尾に要素を追加
print(sample_list)  # 最後尾に9が追加されている

sample_list = [1, 2, 3, 100, 1, 7, 9]
sample_list.reverse()  # 逆順にする
print(sample_list)

実行結果

sample_list:[1, 2, 3, 100, 1, 7, 9]
[2, 2, 3, 100, 1, 7, 9]
[1, 0, 0, 0, 0, 0, 1, 7, 9]
[1, 2, 3, 100, 1, 7, 9, 9]
[9, 7, 1, 100, 3, 2, 1]

リストのソート

s.sort()
リストsを昇順でソートする(実行前後でsの中身が変わる)
引数にkey=...,reverse=...をとることができ、ソートの条件を指定できる

 - key=(1引数をとる関数)
 リストの要素を関数に渡したときの戻り値を使ってソートを行うようにする

 - reverse=(真偽値)
Trueならば降順、Falseなら昇順にソートを行う

keyの指定方法がちょっとわかりにくいですが、リスト中の要素でソートしたい時などに便利です。
また、ソート前のリストを保持しておきたい場合はsorted()を使うと元のリストをいじらずにソート済みリストを取得できます。

sample_list = [1, 2, -3, 100, -1, 7, -9]
print("sample_list:" + str(sample_list))

sample_list.sort()  # ソート(デフォルトは昇順)
print(sample_list)

sample_list.sort(reverse=True)  # 逆順ソート
print(sample_list)

sample_list.sort(key=abs)  # 値の絶対値でソート
print(sample_list)

listlist = [[100, 1], [0, 3], [9, 2]]
print("listlist:"+str(listlist))

listlist.sort(key=lambda x: x[1])  # リストの1番目の値をソートに使う
print(listlist)

実行結果

sample_list:[1, 2, -3, 100, -1, 7, -9]
[-9, -3, -1, 1, 2, 7, 100]
[100, 7, 2, 1, -1, -3, -9]
[1, -1, 2, -3, 7, -9, 100]
listlist:[[100, 1], [0, 3], [9, 2]]
[[100, 1], [9, 2], [0, 3]]

まとめ

Python3でのリストの仕様や便利なメソッドなどをまとめました。
活用して短く読みやすいコードを書きたいと思います。