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

Python 参照先アドレスについて

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

Python3エンジニア認定基礎試験に合格したふっちーです。
合格ラインギリギリでしたがなんとか1発で受かりました。
ありがとうございます。

というわけで今回もPythonをやっていきます。
勝って兜の緒を締めよ、です。

今回は参照先アドレス特集です。
前回でも少し触れた内容ですが、これがきっかけでPythonの学習が捗りました。
ちょっとしたもやもやが晴れるかもしれません。
さあ、一緒に学んでいきましょう。

環境

今回もGoogle Colabolatory(Colab)を使用しました。
colab.research.google.com

参照先アドレスとは

参照先アドレスとは変数の情報(数値や文字列など)を保管している住所の様なものです。
変数が持っている情報をコンピュータが知りたい場合に、この参照先アドレスでどこに保管してあるかを調べるのに使います。
参照先アドレスは組み込み関数id()で確認できます。
実際に見てみましょう。

n = 3
print(id(n))
出力結果:
10968864

変数(n)が持っている情報(3)は10968864という場所に保管されていることが分かりました。
つまり、コンピュータがnの情報を知りたい場合は、10968864という場所に行けば、3を見つけられるという事です。

次に変数nの情報を変更してみましょう。

n = 9
print(id(n))
出力結果:
10969056

このとき、3は10968864に残ったままです。あくまでnの参照先アドレスが9の10969056に変わっただけです。
nに9という情報が設定されたので、コンピュータがその分新しく参照先アドレスを割り振ったにすぎません。
このように参照先アドレスは変数に情報を設定した瞬間にコンピュータが自動的に決定します。

変更不能体と変更可能体

前回にも説明しましたが、Pythonには変更不能体と変更可能体が存在します。

変更可能体 変更不能
リスト、ディクショナリ タプル、文字列、数値

変更不能体とは要素の一部が変更不可であり、変更可能体は要素の一部が変更可能であるという規則があります。
そして、この区別は参照先アドレスについて考えると一気に理解が深まります。

リストが要素の変更をするときの参照先アドレスを見てみましょう。

v_to_z = ['v', 'w', 'x', 'd', 'z']
print(id(v_to_z))
v_to_z[2] = 'k'
print(id(v_to_z))
出力結果:
140656074921096
140656074921096

このように変化がありません。

タプルと文字列は変更不能体なので、上と同じことをしようとするとエラーが発生します。
また、数値を変化させると参照先アドレスが変化しましたが、これはタプルや文字列でも同じです。

w = 'double'
print(id(w))
w = 'world'
print(id(w))
出力結果:
140103509005120
140103134067488

つまり、以下のようにまとめられます。

変更不能体:同じ参照先アドレスでは要素を変更できない
      →変数の再設定は可能だが、要素の変更は不可である
変更可能体:同じ参照先アドレスで要素を変更できる
      →変数の再設定も要素の変更も可能である

要素の変更と変数の再設定

「要素の変更」と「変数の再設定」で変更不能体と変更可能体を区別できることを説明しました。
では、具体的にどのような場合で「要素の変更」と「変数の再設定」は起こるのでしょうか。

実際に確認してみましょう。

a = [6]
print(id(a))

a[0] = 8
print(id(a))

a = [7]
print(id(a))
出力結果:
140094475436616
140094475436616
140094475304712

3行目はaの0番目を8に「要素の変更」をしているのに対し、5行目ではaそのものを[7]に「変数の再設定」をしています。
確かに、参照先アドレスが変化していますね。

続いては以下の例です。

b = [2]
print(id(b))

b.append(5)
print(id(b))
b = [2]
print(id(b))

b = b + [5]
print(id(b))

どちらも[2, 5]が出力されますが、参照先アドレスはどうなっているでしょうか。

出力結果:
140094475437320
140094475437320
出力結果:
140094475304392
140094475437512

このように出力だけ見ても変化がないものでも、参照先アドレスが異なる場合があるのです。
結論としては、「変数名 = 」が再設定で、それ以外は要素の変更である、というのが一番覚えやすいと思います。

スコープ

スコープとは、変数が適用される範囲の事を指します。
スコープにはローカルスコープやグローバルスコープなどがあります。
他にもスコープはありますが、今回取り扱うのはこの2つです。

例とともに説明していきます。

x = 1

def local1():
  x = 2
  print(x)
  
local1()
print(x)

まず、ローカルスコープは関数の内側が範囲となります。
defから1つ目のprint(x)までの部分です。

def local1():
  x = 2
  print(x)

次に、グローバルスコープはモジュール全体が範囲となります。

x=1グローバル変数x=2はローカル変数と呼ばれています。

このスコープによって変数を参照できる範囲が変化します。
ローカルスコープは、その外(今回はグローバルスコープのみ)のスコープにある変数を参照できます。
反対にグローバルスコープは、ローカルスコープにある変数を参照できません。

言葉だけでは理解しにくいと思うので、上の例を動かしてみましょう。

出力結果:
2
1

まず初めに、x=1が読み込まれ、グローバル変数xに1が設定されます。
次に関数(local1)が読み込まれ、ローカル変数xに2が設定されます。
そして、local1を呼び出し、関数内のprintが動きます。→出力:2
関数の処理が終わると、最後のprintが動いて全体の処理が終わります。→出力:1

つまり、順番ではx=2の方が新しく設定されていますが、グローバルスコープのx=1に影響がありません。
これが上の書いてあるグローバルスコープはローカルスコープにある変数を参照できないの意味です。
ローカルスコープでグローバルスコープにある同じ変数名の情報が変化しても、グローバルスコープ側には関係ないのです。

同じxでも参照先アドレスはグローバルスコープ版とローカルスコープ版で分かれていると考えられます。

グローバル変数とローカル変数の例外

ローカルスコープ側で変数が変化しても、グローバルスコープ側には関係ないと説明しました。
しかし、例外が存在します。
それが今回解説した「要素の変更」と「変数の再設定」に関係しています。

もしかしたら、気づいた方もいるかもしれませんね。
結論から言うと「要素の変更」はグローバル変数にも影響を与えます。

例とともに解説します。

x = [90]

def local2():
  x[0] = 50

print(x)

local2()
print(x)
出力結果:
[90]
[50]

printはグローバルスコープにしかありませんが、要素の変更でxが変化しました。

この謎を解くカギは参照先アドレスとローカル変数はグローバル変数を参照できるという点です。

まず、グローバルスコープでx=[90]が設定されました。
次に関数local2の定義でxを参照していますが、ローカルスコープには変数xの定義が存在しません。
この場合は、一つ外側のスコープに変数を探しにいくため、グローバルスコープの変数xを参照することになります。 つまり、同じ参照先アドレスに「要素の変更」をしているという事です。
よって、最後のprintはxに設定されている参照先アドレスの情報を出力するため、変更されたものが出力されました。

まとめ

今回は参照先アドレスについてまとめました。
・参照先アドレスとは変数の所有物(数値や文字列など)を保管している場所を表した住所の様なものである。
・参照先アドレスの中身が変更できるかできないかで変更可能体と変更不能体でわかれる。
・「要素の変更」が参照先アドレスに影響を与えず、「変数の再設定」は参照先アドレスを変更することになる。
・スコープでもこの法則は適用される。

私はスコープや変更不能体などが理解しづらかったのですが、参照先アドレスのおかげでスムーズに理解することが出来ました。
拙い説明でしたが皆さんの理解に役立てられたら光栄です。

Oracle Database SQL基礎 用語まとめ

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

みちです。
Oracle DatabaseのSQLにおいての用語の勉強していきたいと思います。

リレーショナルデータベースの用語

表(テーブル)

 データが格納されているもの。
 データベースの中に複数の表が存在し、表の中にデータが格納される。

レコード

 表のデータの行。

カラム

 表のデータの列。

フィールド

 行と列が交わる部分のこと。

NULL値

 値の入っていない状態のこと。

主キー

 一意な値である(同じ値が入らない)ことを表現する制約。

外部キー

 他の表に存在する値しか入れられないようにする制約。
 依存関係にある2つの表を結びつけるときに使用する。

SQL(Structured Query Langage)

 リレーショナルデータベースを操作するための言語。
 四種類に分類される。

DML(Data Manipulation Language)…データ操作言語

 表に対する操作で、データの追加や削除、参照を行うことができる。

コマンド 動作
INSERT 行の新規挿入
DELETE 行の削除
MARGE 行のマージ
SELECT データの検索
UPDATE 値の更新

DDL(Data Definition Language)…データ定義言語

 DBや表自体の操作で、データベースの新規作成、削除、変更などができる。

コマンド 動作
CREATE オブジェクトの作成
DROP オブジェクトの削除
ALTER オブジェクトの変更
RENAME オブジェクト名の変更
COMMENT コメント定義
TRUNCATE 表の切り捨て

DCL(Data Control Language)…データ制御言語

 の設定で、アクセス権限を付与、削除できる。

コマンド 動作
GRANT 権限の付与
REVOKE 権限の削除

トランザクション制御

 一連の操作の中で処理に失敗したら、操作する前の状態に戻すことができる。

コマンド 動作
COMMIT 更新の確定
ROLLBACK 更新の取り消し
SAVEPOINT セーブポイントの設定

SQLの記述の仕方

SQLを記述するにあたって規則がある。
  ・大文字/小文字の区別はしない
  ・句を書く際に行をまたぐことは出来ない
  ・複数行に書くことが出来る

select 列名のリスト 
from 表名のリスト 
[where 検索条件]
[order by 並べ替え条件]

 複数の列名を指定する場合は、「,」で区切る。

まとめ

 今回はSQLの用語ついて勉強しました。

Python3エンジニア認定基礎試験で覚えにくかった内容まとめ

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

ふっちーです。
Python 3 エンジニア認定基礎試験目前なので、基礎を固める内容にしました。
個人的にPythonチュートリアルで忘れやすい、覚えにくいものを挙げてみました。

www.oreilly.co.jp

環境

今回もGoogle Colabolatory(Colab)を使用しました。
colab.research.google.com

スライシング

基礎的な内容ですが意外と「あれこれどうするんだっけ?」となりやすかったので、まとめておきます。
スライシングはインデックス値を範囲指定する場合に使われます。
例えば、

v_to_z = ['v', 'w', 'x', 'y', 'z']

というリストが存在し、'x', 'y', 'z'だけが欲しくなった場合を想定します。
この場合は以下のように指定できます。

v_to_z[2:5]
出力結果:
['x', 'y', 'z']

ポイントとして、終点の値は欲しい要素のインデックス値に+1した数字を指定することです。
'z'のインデックス値は4ですのでその+1である5が指定されています。
また、今回の例では'z'はリストの終点なので以下の様にも記述できます。

v_to_z[2:]

このように始点だけ決めて最後までといった範囲の決め方も可能です。
さらにもう一つ方法があります。

v_to_z[-3:]

-(マイナス)は後ろから数えた場合のインデックス値を示しています。
0から始まる正のインデックス値とは異なり、最後の要素は-1から数えるという点には注意しましょう。

最後にインデックス値が偶数番の要素だけを取り出してみましょう。

v_to_z[0:10:2]
出力結果:
['v', 'x', 'z']

始点と終点を書いた後にもう1つ数字が書かれています。
この値は始点から○個先の要素を選ぶようにしています。

例の場合は2個先なので、始点の'v'から2個先の'x'が選ばれ、さらに2個先の'z'が選ばれました。

補足として、スライシングで存在しないインデックス値を指定しても、空の結果を返すだけでエラーにはなりません。
上の例も存在しないインデックス値10まで指定してますが、動作に問題はありません。

変更可能体と変更不能

pythonには要素を変更できる変更可能体(mutable)と変更できない変更不能体(immutable)が存在します。
pythonでは以下の様に区分されています。

変更可能体 変更不能
リスト、ディクショナリ タプル、文字列、数値

先ほどの例を使って確かめてみましょう。

v_to_z[3] = 'd'
print(vtoz)
出力結果:
['v', 'w', 'x', 'd', 'z']

'y'が'd'に変更できました。
次にこのリストがタプルだった場合の動作を見てみましょう。

tuple = ('v', 'w', 'x', 'y', 'z')
tuple[3] = 'd'
TypeError: 'tuple' object does not support item assignment

「タプルは変更できません」というエラーが出てしまいました。
これによりリストは要素の変更ができて、タプルはできないという事が分かります。

しかし、数値はどうでしょう。

n = 3
print(n)
n = 8
print(n)
出力結果:
3
8

簡単に変更できてしまいました。
なぜ数値は変更不能体に分類されているのでしょうか。

この謎を解くカギは参照先アドレス(id)にあります。
参照先アドレスとは変数がどこにあるのかコンピュータに知らせるために設定される数値の事です。
変数が定義されるたびにその時に合った参照先アドレスをコンピュータが自動で決定し、その変数に割り当てます。
そして、コード等から変数が呼び出されたときは参照先アドレスを参考にその変数を見つけ、使用します。

この参照先アドレスは、id関数で見ることが可能なので上の例を使って見てみましょう。

n = 3
print(id(n))
n = 8
print(id(n))
出力結果:
10968864
10969024

同じ変数名(n)ですが、参照先アドレスが違うことが分かります。
これはn=3がn=8に変更されたのではなく、n=3とは別に新しくn=8を作ったことを意味します。
このことから、n=8はn=3に変更を加えたわけではないので、変更不能体であってもエラーにならないのです。
変更不能体とは要するに「同じ参照先アドレスの数値や要素を変更できない」と言い換えることができます。

リストで確かめてみましょう。

v_to_z = ['v', 'w', 'x', 'd', 'z']
print(id(v_to_z))
v_to_z[2] = 'k'
print(id(v_to_z))
出力結果:
140656074921096
140656074921096

まったく変わりませんね。
これで変更可能体は「同じ参照先アドレスの数値や要素を変更できる」と言いかえることができます。

引数の順番

ここでは関数...特に引数についてまとめます。

引数にはデフォルト値を設定することができます。
デフォルト値を用いることで関数を使用する際、無理に引数を入力しなくてよくなります。

しかし、デフォルト値を使って関数を定義する場合、その順番に気を付ける必要があります。

def a(b=9, c):
  pass
SyntaxError: non-default argument follows default argument

このようにデフォルト値を設定する場合はデフォルト値が設定されていない引数より前に配置することはできません。

これは関数を使用する場合も同様です。

def a(b, c=9, d=5):
  return b + c - d
  
a(c=5, 7)
SyntaxError: positional argument follows keyword argument

上の例にあるbの様な引数を位置引数、cの様な引数をキーワード引数と言います。
このキーワード引数同士には先ほどのような順番は関係ありません。

a(6, d=1, c=7)
出力結果:
12

このようにdを先に書いても大丈夫です。

おまけ 引数リストのアンパックについて

これはリストの要素1つ1つを引数の1つ1つに入れることが出来る事を意味しています。

a(*m[0])
出力結果:
7

m[0]は[1, 9, 3]なので、a(b=1, c=9, d=3)で関数を使用したことになります。

演算子の優先順位

ここでは個人的に覚えることに苦労した演算子の優先順位を取り上げます。

以下の様な関係であると語られています。

() >>> 数値演算子 >>> 比較演算子 >>> 短絡演算子

なお、各演算子には次のようなものがあります。
短絡演算子の中にも強弱があります。

名称 演算子
数値演算子 +, -, *, /, %
比較演算子 <, >, ==
短絡演算子 not > and > or

例を挙げてみましょう。

9-8 > 7
出力結果:
False

上の比較を見ると数値演算子の方が強いため、先に左辺(9-8)が評価されます。
これで左辺が1になったため1>7となり、これは間違った式なのでFalseが出力されました。

今度はこれに()を加えてみましょう。

9 - (8>7)
出力結果:
8

これは予想していませんでした...

比較では()が最も優先度が高いため8>7が先に評価されます。
この関係は正しいので、結果はTrueとなることがわかります。

すると、残りは9-Trueなので、このままでは計算できません。
しかし、ここで現れたTrueは1としても扱う仕様になっています。

if True == 1:
  print('Trueは1ニャ')
else:
  print('Trueは1じゃないワン')
出力結果:
Trueは1ニャ

このようにif文でも適用されます。

よって、9-1となり結果として8が出力されたというわけです。

printができること

print()は初心者にとって一番なじみのある関数でしょう。
ここでは普通に表示する以外にどんな機能があるかを再確認します。

end

print('hello', end=' ')のように用います。
これは表示した文字の後に続けて入力される文字などを設定することができる引数です。
デフォルトは\n(改行)が設定されています。

for i in range(8):
  print(i)
出力結果:
0
1
2
3
4
5
6
7

これがデフォルトの動きです。

for i in range(8):
  print(i, end=' ')
出力結果:
0 1 2 3 4 5 6 7 

出力ごとに' '(スペース)が入力されていることがわかります。

文字列フォーマットと組み合わせてみる

文字列のformat関数を利用することで、print文がより便利に扱えます。

print('Dirty {1} Done Dirt {0}'.format('Cheap', 'Deeds'))
出力結果:
Dirty Deeds Done Dirt Cheap

このように{}の中に入る文字列を後付けの様に設定できます。
{}に数字を入れない場合はformatの引数順に入ります。

数字だけではなくキーワード引数も入れることができます。

print('{0} love {name}'.format('I', name='you'))
出力結果:
I love you

このキーワード引数は関数と同様に位置引数よりも前に書くことはできません。
また、表示桁数も決められます。

import math
print('πは{0:.10f}です。'.format(math.pi))
出力結果:
πは3.1415926536です。

例では、小数点以下が10桁になるように表示しています。

同様のことをround関数でも行うことができます。

print('πは'+str(round(math.pi,10))+'です。')

出力結果は同じです。
%でもできます。

print('πは%.10fです。'%math.pi)

まとめ

今回は試験に向けて不安があった部分を自分の言葉でまとめました。
・スライシング
・変更可能体と変更不能
・引数の順番
・記号の優先順位
・printができること

Oracle Database SQL文法のややこしい部分まとめ

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

Oracle Database SQL文法のややこしい部分まとめ

どうも、小さいころ見た映画のリメイクを見て懐かしい気持ちになったhashiです。
先日、Oracle Master 12c Bronze SQL基礎に合格しました!
そこで、今回は勉強時のメモを記事にまとめて、今勉強中の方の助けになるようにしたいと思います。

引用符の使い分け

一重引用符

文字リテラル'moziretu'や日時リテラル'2010-04-01'を示す際に使います。

二重引用符

テーブル名や列名などオブジェクト名として使われます。
オブジェクト名は、英数字のみで構成され、かつ大文字と小文字の区別がされないのが通常ですが、二重引用符を使うと、記号を含めたり、大文字と小文字を区別したりできます。

create table test-1(id integer); --これはエラー
create table "test-1"(id integer); --"test-1"というテーブルが作られる
create table "Test-1"(val text); --"Test-1"というテーブルが作られる
select * from Test-1; --これもエラー
select * from "Test-1"; --こうしないと指定できない

結合

表接頭辞をつけるかどうかがややこしいのでまとめました。

NATURAL JOIN(自然結合)

・同名同型の列の存在を前提とした結合
・どの列を使うかの指定は不可能→USINGやONは使えない
・結合列の指定に表接頭辞を使ってはいけない

JOIN(USING句を使用した結合)

・同名同型の列の存在を前提とした結合
・どの列を使うかの指定が可能
・結合列の指定に表接頭辞を使ってはいけない

JOIN(ON句を使用した結合)

・どの列を使うかの指定が可能
・結合列に表接頭辞を指定する必要がある

oracle独自の結合

・結合する表名は,(カンマ)で区切ってFROM句に指定
・結合条件はWHERE句に指定
・結合条件以外の条件は結合条件の後にAND演算子で指定する

関数

LISTAGG

LISTAGG(連結して表示する列名 [, 'デリミタ'])
WITHIN GROUP(ORDER BY ソートする項目 [ASC | DESC])

複数行の列の値を連結して1行で表示する関数です。
WITHIN GROUP(ORDER BY …)の順番は覚えるほかないですね…

ROUND

数値や日付に対して四捨五入を行う関数です。
どの桁に丸めるかの指定が直感的にわかりにくいのでまとめます。

数値の場合

ROUND(x [, 'y'])

・数値xの四捨五入をして小数点第y位「に」丸める
・yは省略可能。省略するとy=0となり、1の位に丸める
・yには負数も指定可能で、y=-1なら1の位で四捨五入して10の位に丸める

日付の場合

ROUND(x [, 'y'])

・日付xの四捨五入をして書式yに丸める
・yは省略可能で、デフォルトは'DD'

書式 説明
YEAR 6月30日以前ならその年の1月1日、7月1日以後なら次の年の1月1日を返す
MONTH 15日以前ならその月の1日、16日以後なら次の月の1日を返す
DD 午前ならその日の午前0時、午後なら次の日の午前0時を返す

TRUNC

TRUNC(x [, 'y'])

ROUNDと同様の形ですが四捨五入ではなく切り捨てを行う関数です。

MOD

MOD(x,y)

xをyで割った余りを返す関数です。

POWER

POWER(m, n)

mをn乗した値を返す関数です。

TO_DATE

TO_DATE(文字列 [, 'format'] [, NLSパラメータ])

第一引数の文字列を第二引数で指定したフォーマットor既定のフォーマットで日付データに変える関数です。
第一引数にdate型を入れることも可能で、以下の文はエラーになりません。
暗黙の型変換によって前もって文字列に変換されるためです。

TO_DATE(SYSDATE,'YYYY/MM/DD')

TO_CHAR

数値や日付、NCHAR、NVARCHAR2、CLOBまたはNCLOBデータを十分な長さのVARCHAR2に変換する関数です。
日付データには少し注意が必要で、以下の文はエラーとなります。
文字列を暗黙的に日付に変換してから文字列に変換はしてくれないためです。

TO_CHAR('2000-01-01', 'YYYY"年"MM"月"DD"日"')

TO_NUMBER

TO_NUMBER(文字列 [, '数値書式'] [, NLSパラメータ])

文字列を指定された書式に従って数値に変換する関数です。
数値書式は第1引数で指定した文字列を数値に変換する際のフォーマットで、以下に示す文字を使って表します。

書式 説明
9 任意の数字を示す
0 0埋めに使用。先頭なら先頭に0の数だけ0を追加。末尾も同様
D (1個以下限定)小数点の位置を指定する
S (先頭or末尾限定)この文字を置いた位置に符号を追加する
MI (末尾限定)負の数の末尾に-を追加する
G (小数点より左限定) 桁区切りの記号を挿入
, (小数点より左限定) ,を挿入
L (小数点より左限定) 通貨の記号を挿入
$ (小数点より左限定) $を挿入

NLSパラメータは数値に使う記号(通貨記号など)を指定するものです。

複合問合せのORDER BY句

ORDER BY句は文の最後に置き、指定する列は一番左の表にある列名にする必要があります。
そのため、以下の複合問い合わせ結果のソートを行う際、ORDER BY sample21ORDER BY x2は無効となります。

 SELECT sample11 x1, sample12 y1 FROM table1
  INTERSECT
 SELECT sample21 x2, sample22 y2 FROM table2

副問い合わせによる表の作成

引き継ぐもの
・列のデータ型
・NOT NULL制約(制約はこれのみ。PRIMARY KEYなどは引き継がれない)

個々に指定できるもの
・制約

指定できないもの
・データ型

CREATE TABLE employees2 (id PRIMARY KEY)
AS
SELECT employee_id FROM employees;

デフォルト値

CREATE TABLE employees2 (id, name, hiredate DEFAULT SYSDATE)
AS
SELECT employee_id, employee_name, hiredate FROM employees;

条件式

CASE

単純CASE式 ・式の値と条件の値を比較し、一致したら対応する戻り値を返す
NULL値の評価が不可能
・最後まで一致しない場合、ELSE句のデフォルトの戻り値が返る(ELSE句がなければNULLが返る)

CASEWHEN 条件1 THEN 戻り値1 [WHEN 条件2 THEN 戻り値2 ...] [ELSE デフォルトの戻り値] END

検索CASE式

・条件を判定し、真と判定されたら対応する戻り値を返す
・最後まで真と判定されない場合、ELSE句のデフォルトの戻り値が返る(ELSE句がなければNULLが返る)

CASE WHEN 条件1 THEN 戻り値1 [WHEN 条件2 THEN 戻り値2 ...] [ELSE デフォルトの戻り値] END

DECODE関数

・式の値と条件の値を比較し、一致したら対応する戻り値を返す
・DECODE関数では、単純CASE式と違ってNULL値の評価が可能
・最後まで真と判定されない場合、デフォルトの戻り値が存在すればそれが返る(なければNULLが返る)

DECODE(式, 条件1, 戻り値1 [, 条件2, 戻り値2 …] [, デフォルトの戻り値])

まとめ

練習問題を解く中で自分の混乱したことや、分かりにくいと感じたことを整理しました。
わからなかったことをまとめておいて、のちに復習する方法はどんなことにも有効なので続けていきたいです。