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 …] [, デフォルトの戻り値])

まとめ

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

ファイル情報とパーミッション

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

最近冷夏が続いているので、このまま夏が終わらないかなと思っているshadowです。
今回はLinuxパーミッションについて記事を書きたいと思います。

パーミッションとは

パーミッションとはLinuxを含むUnixで使われているファイルやディレクトリへのアクセス権の設定です。
ファイルやディレクトリは、所有者、グループ、その他のユーザの3種類のそれぞれに対して、読み込み、書き込み、実行のアクセス権を設定することができます。

確認方法

まずは適当にファイルを作成します。

$ touch sample.txt

ファイルの情報を確認するにはlsコマンドを使います。

$ ls -l sample.txt                                                            
-rw-r--r--    1 user     user             0 Jul 24 08:15 sample.txt

結果の見方

まずは見方を説明します。
この1行を詳しく分けるとこうなります。

①- ②rw-r--r-- ③1 ④user user ⑤0 ⑥Jul 24 11:15 ⑦sample.txt

①ファイルのタイプ
 ディレクトリ(d)か、リンクファイル(l)か、通常ファイル(-)か判別出来ます。

②アクセス権
 これは9個の文字で出来ています。
 最初の3つが所有者(rw-)、次の3つがグループ(r--)、最後の3つがその他のユーザ(r--)です。
 それぞれ左から順に読み込み(r)、書き込み(w)、実行(x)を表しています。
 権限のない操作は"-"で表現されます。

 つまり、以下のような権限設定になっています。

所有者 グループ その他のユーザ
読み込み
書き込み × ×
実行 × × ×

③ハードリンクの数(ファイルの場合)
 ディレクトリの場合は、ディレクトリ内のサブディレクトリの数になります。

④所有者
 左が所有しているユーザー、右が所有しているグループです。

⑤ファイルサイズ
 中身がないので今回は0です。

⑥最終更新日時
 ファイルを作成した日時になっています。

⑦ファイル名
 ファイル(ディレクトリ)の名称です。

パーミッションの指定

パーミッションの指定方法には数値で指定する方法と文字と記号で指定する方法があります。

数値で指定する方法

数字 結果
0 ---
1 --x
2 -w-
3 -wx
4 r--
5 r-x
6 rw-
7 rwx

実は上記の対応は、1(x)、2(w)、4(r)だけ覚えておけば、計算で出すことができます。
例えば6(rw-)は読み込みと書き込みの権限ですが、2(w)+4(r)で表現できます。
また、全ての権限を持った値は7ですが、これは1(x)+2(w)+4(r)の結果に一致します。

記号で指定する方法

記号 結果
u 所有者の権限
g グループの権限
o その他のユーザの権限
a 全ての権限
+ 権限の追加
- 権限の削除
= 権限の指定
r 読み込み
w 書き込み
x 実行

記号の場合は権限の追加・削除が直感的にできそうです。

設定してみよう

アクセス権を変えてみたいと思います。
アクセス権の変更にはchmodを使います。

まずは数値を使って変更します。
数値を使う場合は3桁の数字を指定し、左から順に所有者、グループ、その他ユーザーの権限に対応します。

$ chmod 766 sample.txt                                                        
$ ls -l sample.txt
-rwxrw-rw-    1 user     user             0 Jul 24 08:20 sample.txt

次に記号を使って、全員が書き込みできないようにします。

$ chmod a-w sample.txt                                                        
$ ls -l sample.txt
-r-xr--r--    1 user     user             0 Jul 24 08:20 sample.txt

最後に記号で所有者を指定し、権限を元に戻します。

$chmod u=rw- sample.txt
$ ls -l sample.txt
-rw-r--r--    1 user     user             0 Jul 24 08:20 sample.txt

オプション

chmodコマンドにはこのようなオプションがあります。

-v, --verbose
コマンド実行の診断結果を表示します。

-c, --changes
変更があった時だけ、結果を表示します。

-R, --recursive
複数ファイルに対して設定するときに使います。

まとめ

chmodはよく使うと思いますので、早くマスターしたいと思います。

Javaでクラスのインスタンス化とコンストラクについて

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

みちです。
今回は、Javaインスタンス化、コンストラクタについて勉強したいと思います。
その応用として、シングルトンパターンにも挑戦しました。

インスタンス

クラスをもとにオブジェクトを作成することを、インスタンス化と呼びます。
インスタンス化をするにはnew演算子を使います。
オブジェクトは「データ型 変数名」で宣言し、「new クラス名()」によって生成します。

構文

データ型 変数名 = new クラス名();

Data data = new Data();

オブジェクトに対してメソッドを呼び出すには以下の構文を使います。

構文

変数名.メソッド名();

data.method(2);

コンストラク

インスタンス化された時に最初に呼び出されるものです。
コンストラクタは次の2つのルールがあります。

・名前がクラス名と同じ
・戻り値を持たない

構文

class Data {
    int num;
    
    Data(int a) {
        this.num = a;
    }
}

コンストラクタを定義することで、インスタンス化と同時に生成されるオブジェクトに対して、 任意の処理を実行することができます。

コンストラクタを使わない場合

class Data {
    int num;

    void method(int a) {
        num = a;
    }
}
Data a = new Data();
a.method(2);

コンストラクタを使う場合

class Data {
    int num;

    Data(int a) {
        num = a;
    }
    void method(int a) {
        num = a;
    }
}
Data a = new Data(2);

デフォルトコンストラク

クラスにコンストラクタを明示的に定義しなかった場合、自動的にコンストラクタが追加されます。
自動的に追加されたコンストラクタの中身は引数、実装ともに空です。

コンパイル

空っぽのDataクラスを定義しました。

class Data {
}

コンパイル

コンパイル後は、"何もしない"コンストラクタが自動的に追加されていました。

class Data {
    Data() {
    }
}

デフォルトコンストラクタが生成されない例

class A {
    A(int a) {
        System.out.println("A(int a)");
    }
}

class B {
    public static void main(String[] args) throws Exception {
            A a = new A();
    }
}

インスタンス化した時に引数なしのコンストラクタを呼んでいますが、クラスAでは、引数を持ったコンストラクタしかありません。
コンストラクタを明示的に定義した場合、デフォルトコンストラクタが生成されないため、引数を持たないコンストラクタはコンパイル後に生成されません。
生成されないことによって「A a = new A();」で引数を持たないコンストラクタを呼び出せないため、上記のコードはコンパイルエラーとなります。
上記のコードを動かせるようにするには、以下のようにする必要があります。

class A {
    A() {
        System.out.println("A()");
    }
    A(int a) {
        System.out.println("A(int a)");
    }
}

class B {
    public static void main(String[] args) throws Exception {
        A a = new A();
    }
}

シングルトンクラス

プログラム中に生成されるインスタンスが、一つだけであることが保証されるクラスです。

class Singleton {
    private static Singleton a = new Singleton();
    private Singleton(){}
    public static Singleton getInstance() {
        return a;
    }
}

class Main {
    public static void main(String[] args) {
        System.out.println(Singleton.getInstance());
    }
}

Sngletonクラスをインポートした時点でクラス変数が生成されるため、 2行目のprivate static Singleton a = new Singleton();でSingletonクラスのインスタンスが生成されます。
コンストラクタの識別子をprivateにすることにより、外部からの呼び出しが出来ません。
このため、新しいインスタンスを作ることが出来なくなり、プログラム中に生成されるインスタンスが1つであることが保証されます。

このSingletonクラスを使用する際は、SingletonクラスのgetInstance()メソッドを呼び出して、インスタンスを取得します。

まとめ

今回は、インスタンス化とコンストラクタについて勉強しました。
シングルトン以外のデザインパターンについても学んでみたいと思います。

Djangoでのデータベースの扱い方②

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

どうも、夏映画を見に行きたいhashiです。

前回に続きDjango(ジャンゴ)というPythonのWebアプリケーションフレームワークでのデータベース操作についてまとめます。
今回は主にデータの挿入、更新とバリデーションチェックを扱います。

動作環境

Python 3.7.3
Django 2.2.1

テーブルの生成

Djangoでのテーブル定義は以下のように記述します。
クラス名がテーブル名、クラス変数の左辺が列の名前、右辺に型の情報やオプションを記述しています。

"""models.py"""
from django.db import models


class UserTable(models.Model):
    """ユーザー情報のテーブル"""
    user_id = models.IntegerField("ID", primary_key=True)
    name = models.CharField("ユーザー名", max_length=30)
    age = models.IntegerField("年齢", blank=True, null=True)


class PhoneTable(models.Model):
    """ユーザーの持つ電話のテーブル"""
    user = models.ForeignKey(UserTable, on_delete=models.CASCADE)
    tel = models.CharField("電話番号", max_length=15)
    email = models.EmailField("メールアドレス")
    contract_date = models.DateField("契約日", blank=True, null=True)

UserTable(表構造)

NULLを許容する 制約
user_id integer NOT NULL PRIMARY KEY
name varchar(30) NOT NULL
age integer

PhoneTable(表構造)

NULLを許容する 制約
id integer NOT NULL PRIMARY KEY
user_id integer NOT NULL REFERENCES
tel varchar(15) NOT NULL
email varchar(254) NOT NULL
contract_date date

このように、ユーザー情報と携帯電話の情報という2つのテーブルが作られます。
定義した覚えのないidという列がPhoneTableにありますが、これは primary_key=Trueを指定していないテーブルに自動的に追加される自動連番のプライマリーキーです。
これによって各テーブルにプライマリーキーが必ず1つだけ存在するようになっています。

テーブルへのデータの挿入、更新

あらかじめテーブルにいくつかデータを入れておきます。これからの操作はこのデータを用いて行います。

UserTable

user_id name age
1 tanaka 34
2 sato
3 suzuki 40
4 sasaki 24

PhoneTable

id user_id tel email contract_date
1 1 03-1234-9876 tanaka@t.com 2018年10月1日
2 1 090-1234-5678 tanaka2@t.com 2019年8月1日
3 2 0120-999-999 test@t.com 2019年7月4日
4 3 0120-202-102 temp@temp.com 2018年11月7日
5 2 0120-68-1147 phone@t.com 2019年7月23日
6 1 0120-976-543 testtest@t.com
7 1 03-1234-9876 karikari@test.com
8 4 111 poi@poi.jp

データの挿入

以下のような疑似SQL文を実行してデータを挿入したいとします。

INSERT INTO PhoneTable(user_id, tel, email, contract_date) VALUES (1, '0123-45-6789', 'insert@insert.jp', '2020-01-01');

ユーザーIDが1のユーザーの電話機の情報を新規に登録する際、Djangoでは以下のように記述します。

phone = PhoneTable()  # オブジェクトの作成
phone.user = UserTable.objects.get(user_id=1)  # 紐づくユーザー
phone.tel = '0123-45-6789'  # 電話番号
phone.email = 'insert@insert.jp'  # メールアドレス
phone.contract_date = '2020-01-01'  # 契約日
phone.save()  # データベースに反映させる

PhoneTableクラスのオブジェクトを作成、各フィールドに値を入れた後にsave()メソッドを呼ぶことで値をデータベースに挿入することができます。
また、

phone.user = UserTable.objects.get(user_id=1)

は、以下のように書き換えることもできます。

phone.user_id = 1 # (外部キーのフィールド名)_id=(参照先列の値)

挿入後のデータ

id user_id tel email contract_date
1 1 03-1234-9876 tanaka@t.com 2018年10月1日
2 1 090-1234-5678 tanaka2@t.com 2019年8月1日
3 2 0120-999-999 test@t.com 2019年7月4日
4 3 0120-202-102 temp@temp.com 2018年11月7日
5 2 0120-68-1147 phone@t.com 2019年7月23日
6 1 0120-976-543 testtest@t.com
7 1 03-1234-9876 karikari@test.com
8 4 111 poi@poi.jp
9 1 0123-45-6789 insert@insert.jp 2020年1月1日

このように、末尾にデータが挿入されました。

データの更新

以下のような疑似SQL文を実行して、データを更新したいとします。

UPDATE PhoneTable
SET user_id = 2,
    tel = '000-000-0000',
    email= 'aa@a.aaa',
    contract_date = '2019-07-07'
WHERE id = 9;

id=9の電話機の情報を更新する際、Djangoでは以下のように記述します。

phone = PhoneTable()  # オブジェクトの作成
phone.id = 9 # 主キーの指定
phone.user = UserTable.objects.get(user_id=2)  # 紐づくユーザー
phone.tel = '000-000-0000'  # 電話番号
phone.email = 'aa@a.aaa'  # メールアドレス
phone.contract_date = '2019-07-07'  # 契約日
phone.save()  # データベースに反映させる

もしくは

phone = PhoneTable.object.get(id=9)  # オブジェクトの取得
phone.user = UserTable.objects.get(user_id=2)  # 紐づくユーザー
phone.tel = '000-000-0000'  # 電話番号
phone.email = 'aa@a.aaa'  # メールアドレス
phone.contract_date = '2019-07-07'  # 契約日
phone.save()  # データベースに反映させる

なんと前者ではデータの挿入時と記述方法が同じになっています。
この時どうやってDjangoがINSERTとUPDATEを見分けるかはドキュメントによると、

  • オブジェクトのプライマリキー属性が True と評価される値にセットされている場合 (例えば None や空の文字列以外の場合)。Django は UPDATE を実行します。
  • If the object's primary key attribute is not set or if the UPDATE didn't update anything (e.g. if primary key is set to a value that doesn't exist in the database), Django executes an INSERT.

とあり、主キーの値を指定することに加えて、その値を持つ行がデータベース内に既に存在する場合、UPDATEが実行されるようです。
上に示したコードでは主キーであるidを指定しており、id=9のデータがすでに存在するためUPDATEが実行されることになります。

docs.djangoproject.com

更新後のデータ

id user_id tel email contract_date
1 1 03-1234-9876 tanaka@t.com 2018年10月1日
2 1 090-1234-5678 tanaka2@t.com 2019年8月1日
3 2 0120-999-999 test@t.com 2019年7月4日
4 3 0120-202-102 temp@temp.com 2018年11月7日
5 2 0120-68-1147 phone@t.com 2019年7月23日
6 1 0120-976-543 testtest@t.com
7 1 03-1234-9876 karikari@test.com
8 4 111 poi@poi.jp
9 2 000-000-0000 aa@a.aaa 2019年7月7日

データのバリデーションチェック

入力データが必ずしも表構造にあっているとは限らないため、入力の確認をする必要があります。
そこで使われるのがfull_clean()メソッドです。
もしこのメソッドを呼び出したオブジェクトに不正な値(型の違い、一意制約違反など…)があれば 例外(ValidationError)を投げて教えてくれます。

try:
    user.full_clean()
except ValidationError as e:# 入力に不正な値があるとき
    # エラーの出たフィールドをユーザーに教えるなどの手続きをここに書く
else: # 入力に不正な値がないとき
    user.save() # 安心してデータベースに反映することができる

ほかにもバリデーションチェックを行う方法はありますが省略します。

データの削除

以下のような疑似SQL文を実行してデータを削除したいとします。

DELETE PhoneTable
WHERE user_id = 2;

user_id = 2の電話機の情報を削除する際、Djangoでは以下のように記述します。

phone = PhoneTable.objects.filter(user_id=2)  # 削除対象のオブジェクトの取得
phone.delete()  # データベースから削除する

filter()メソッドを使用して得られたクエリセットに delete() メソッドを呼ぶことで一括削除を行えます。

削除後のデータ

id user_id tel email contract_date
1 1 03-1234-9876 tanaka@t.com 2018年10月1日
2 1 090-1234-5678 tanaka2@t.com 2019年8月1日
4 3 0120-202-102 temp@temp.com 2018年11月7日
6 1 0120-976-543 testtest@t.com
7 1 03-1234-9876 karikari@test.com
8 4 111 poi@poi.jp

まとめ

Djangoにおけるデータベースへのデータ挿入、更新と削除方法についてまとめました。
前回の記事と合わせて、これで一通りCRUD操作が扱えるようになりました。