ファイル操作やクラス、Pandasなど

1.ファイル操作

・pythonでは、ログファイルを見て検索したり、ファイルから何かの処理をすることがある。こういう使い方が多いかなー。
・概要として、以下
 ・openでファイルを開く
 ・readでファイルを読む
 ・writeでファイルに書き込む
 ・closeでファイルを閉じる

1.ファイルオープンとクローズ

❶基本
・file1=open('file1.txt') #ファイルを開く。通常は読み込みなので、これでいいでしょう。または、モードをつけることもできる。
w 読み(read)  ⇒w+でよみ書き
r 書き(write)
a 追記(add)

file1 = open('file1.txt', 'r')
print(type(file1))
file1.close

❷with
withを使うと、close()が不要になるので便利。closeを使うより、こちらの方がお勧めかつ一般的。

with open('file1.txt') as file1:
    print(type(file1))

ただし、きちんとインデントをつけよう。
❸文字コードを指定して開く
・以下は一般的なUTF-8の場合

with open('/root/file1.txt','r', encoding='utf-8') as file1:
(2)ファイルを読み込む1 基本

メソッドとして、以下がある

file1.read() #すべてのファイルを読み込む  ※ファイルを全て読み込む
file1.readline() #1行を読み込む
file1.readlines() #すべての行を配列で読み込む ※ファイルを全て読み込む

❶全てのファイルを読み込む read

with open('file1.txt') as file1:
    print(file1.read())

結果は、file1.txtの内容がそのまま出力される

Hello everyone!
That's All.

【注意】1回しか読んでくれない?
以下のように、一度、print(f.read())で読んだあと、もう一度f.read()で読んだとしても、次のsは表示してくれなかった。1度読むと、読み切ってしまうのかも。
with open('sample.txt', 'r') as f:
print(f.read())
s=f.read()
print(s)

❷1行ずつ読み込む
お薦めは以下。メソッドを使わずに、1行ずつデータとして読み込む。※dataは単なる変数。end=''で改行を無しにしている。

with open('file1.txt') as file1:
    for data in file1:
        print(data,end='')

readline()を使うと、1行ずつ読めるが、1行しか出力しない。なので、2行あればコマンドを2回実行する必要がある。
with open('file1.txt') as file1:
print(file1.readline())
print(file1.readline())

❸参考
・Linuxで実際に成功したコード

#!/usr/bin/python3.7
# -*- coding: utf-8 -*-
with open('/root/file1.txt','r') as file1:
    print(file1.read())

・エラー処理
パスが間違っていないか、ファイルが存在するかの確認は、以下で実施可能。Trueと出れば問題ない。
import os.path
print(os.path.isfile('/root/file1.txt'))

(3)ファイルを読み込む2 CSVファイル

以下のb.csvというファイルがある。

# cat b.csv
11,12,13,14
21,22,23,24
31,32,33,34

❶CSVというモジュールを使って、配列に入れる

import csv

code='utf-8'        #文字コードを指定 
csvfile='./b.csv'   #ファイル名とパスを指定
with open(csvfile, encoding=code) as file1:  #ファイルを開く
    x = list(csv.reader(file1))              #csvモジュールでリストに入れる
print(x)   #==>[['11', '12', '13', '14'], ['21', '22', '23', '24'], ['31', '32', '33', '34']]

・イテレータになっている(と思われる)ので、順に表示するなどは簡単にできる。

import csv
with open(csvfile, encoding=code) as file1:  #ファイルを開く
    x = csv.reader(file1)    
        for row in x:
        print(row)  #配列ではないが、順番に表示される。

❷モジュールを使わずに書く。

#!/bin/python3
code='utf-8'        #文字コードを指定
csvfile='./b.csv'   #ファイル名とパスを指定
with open(csvfile, encoding=code) as file1:
    array=[]
    for data in file1:
      s=data.rstrip('\n')   #改行コードが入っているので、これで消す。
      x=s.split(',') #配列に入れる
      array.append(x)
print(array)

※ rstripのところは、 s=data.replace('\n','') でも動いた。このあたりは動く方で。

(4)CSVファイルをGoogle Colabで開く方法

まず、GoogleDriveのマイドライブにファイルを置き、以下のように実行する。GoogleDrive上では、パスがカタカナで「マイドライブ」になっているが、以下のように英語で対応できる。
❶CSVで取り込む

import csv
csvfile = open('/content/drive/My Drive/Colab Notebooks/access_log.txt')
reader = csv.DictReader(csvfile)
for row in reader:
 print(row)

❷pandasのデータフレーム

from google.colab import drive
import pandas as pd 
data= pd.read_csv('/content/drive/My Drive/Colab Notebooks/access_log.txt')
print(data.head(5))
print(data.shape) 
(5)ファイルに書き込む

モードにはwやr+があるが、追記ではなく、今あるファイルを消して書くので注意。
なので、以下のようにモードwで開いた瞬間に、ファイルの中身が全て消える。

with open('/root/file1.txt','w') as file1:

・追記の場合はaを使う
❶aを使って、printでファイルに追記 ※あまり主流ではないと思う。次のwirteメソッドが主流だろう。
ファイルの最後に追記される。

with open('/root/file1.txt','a') as file1:
    print('123',file=file1)

❷aを使って、write メソッドでファイルに追記

with open('/root/file1.txt','a') as file1:
    file1.write('abcde\n')

最後に改行コードを入れたが、入れないと改行コードが入らない。(状況によって使い分け)

❸追記して、その内容を表示する場合
もう一度、rのモードでファイルを開きなおさなければいけないと思う。(要調査)

❹ファイルの置換
読んで、そのファイルの最後に文字を足すのは簡単である。
だが、ファイルを開いてそれを読み込み、別のファイルに書き込むとなると、ちょっと工夫が必要。
with openでは1つのファイルを開いていたからだ。
以下は、file1.txtを開いて、文字を置換し、file2.txtに書き込むというやり方をしている。openを2回使うしかない。

with open('/root/file1.txt', 'r') as f1:
    data=f1.read()
    data=data.replace('、',',')        #dataという変数に上書きしている。
    data=data.replace('。','.')
with open('/root/file2.txt', 'w') as f2:
    f2.write(data)

※改行コードを消す場合は、以下でいいだろう。
data=data.replace( '\n' , '' )

・もう一つ。
正直、あまり美しくない書き方だと思う。でも、1行ずつ関数で処理させる場合、上のやり方ではできなかったので、苦肉の策という感じ。
a.txtファイルを開き、同時にb.txtファイルを開き、そして、1行ずつ置換してかb.txtに書き込んでいく。

infile='a.txt'
outfile='b.txt'
with open(infile) as file1:
    with open(outfile, mode='w') as file2:
        for line in file1:
            line2 = ' '.join(t.tokenize(line, wakati=True))
            file2.write(line2 + '\n')

❺ファイル書込みの応用
・以下のように、元のファイルをorgとして開き、copy先をwモードで開いて、writeで書ければ、ファイルのコピーができる。
with open('./file1.txt') as org,open('./file1.txt.bk','w') as copy:
copy.write(org.read())

(6)ファイルの操作 (コピー)

#!/usr/bin/python3.7
# -*- coding: utf-8 -*-
import shutil
shutil.copy2('/root/file1.txt','/root/file2.txt')

2.pandas

・pandas(パンダス)は、データ分析に便利なモジュール。
・データをシリーズまたはデータフレームのオブジェクトとして保持する。シリーズは1次元、データフレームは2次元のデータ構造。

(1)初期設定とモジュールのインポート

Linuxの場合、モジュールをインストール

pip3 install pandas #もしかすると pip install pandas

インポートする場合は、pdと別名をつけることが慣例

import pandas as pd
(2)シリーズとデータオブジェクト

❶シリーズ(Series)
データをシリーズとして保持する場合。

x=pd.Series([0,1,2])
print(x)

実行結果は以下

0    0
1    1
2    2
dtype: int64

❷データフレーム (DataFrame)
データフレーム化しておくと、そのあと、色々なメソッドで処理がしやすい。
・データフレーム (DataFrame) として保持する場合で、以下は行と列にタイトルをつけない場合

import pandas as pd
x=pd.DataFrame([[1,2,3],[21,22,23],[31,32,33],[41,42,43]])
print(x)

実行結果は以下

    0   1   2
0   1   2   3
1  21  22  23
2  31  32  33
3  41  42  43

・行と列にタイトルを付けた場合

import pandas as pd
x=pd.DataFrame([[1,2,3],[21,22,23],[31,32,33],[41,42,43]],columns=['no.1', 'no.2', 'no.3'],index=['row1', 'row2', 'row3','row4'])
print(x)

実行結果は以下

      no.1  no.2  no.3
row1     1     2     3
row2    21    22    23
row3    31    32    33
row4    41    42    43

・列にタイトルを付けるだけなら、以下のように書いてもOK

import pandas as pd
x=pd.DataFrame({'no.1':[1,2,3],'no.2':[21,22,23],'no.3':[31,32,33],'no.4':[41,42,43]})
print(x)

実行結果は以下

   no.1  no.2  no.3  no.4
0     1    21    31    41
1     2    22    32    42
2     3    23    33    43
(3)CSVファイルのインポート1

最初に、ファイル形式とその読み込み、書き込みのメソッドを整理しておく。

ファイル形式 読み込み 書き込み(出力)
csv read_csv() to_csv
excel read_excel() to_excel

❶単にファイルを開く

with open('list.csv') as file1:
    print(file1.read())

実行結果は以下。カンマ区切りでデータが列挙してある。

ito,24,aichi
sato,23,aichi
asai,19,gifu
kato,29,osaka

❷pandasを使ってデータフレームオブジェクトにする
read_csv() 関数を使ってCSVファイルを読み込む。

import pandas as pd
x = pd.read_csv('./list.csv')
print(x)

出力結果は以下。自動で番号が付与される。0から始まっているが、変更可能。

   name  age    ken
0   ito   24  aichi
1  sato   23  aichi
2  asai   19   gifu
3  kato   29  osaka

❸URLを指定やZIPファイルも取得可能
以下は、URLを指定して郵便番号を取得、また、先頭から3行を表示。

y = pd.read_csv('http://www.post.japanpost.jp/zipcode/dl/oogaki/zip/13tokyo.zip',header=None, encoding='shift_jis')
print(y.head(3))

便利だ!

(4)CSVファイルのインポート2 →read_csv()のオプションについて

❶ヘッダというか各列のタイトル
・1行目はヘッダとして認識される。1行目はヘッダではなくデータの場合、header=Noneをつける
x = pd.read_csv('./list.csv', header=None)
・または、names=を使って、ヘッダの名前の付与も可能。
・あまり使うことはないと思うが、ヘッダの行番号を指定する場合、header=1などと指定。デフォルトはheader=0、つまり先頭行。
❷行番号
・何も指定しないと、自動で0から番号が割り振られる。
・すでにCSVにあるデータを使いたい場合、行を示す番号として、index_col=0などで指定できる。普通は0が多いだろう。
❸列の指定
読み込む列を指定するには、usecols=[1, 3]などと指定する。

(5)メソッド

データフレームにしてしまえば、いろいろなメソッドが使える。以下はその例。
❶データフレームを以下で定義
以下は、小数点以下の桁数をprecisionで指定している。

import pandas as pd
x=pd.DataFrame([[1,2,3],[21,22,23],[31,32,33],[41,42,43]])
pd.options.display.precision = 1 #小数点以下の桁数を1に指定。

❷head データフレームの先頭の3行を表示。()で数字を指定しないと、5行を表示

print(x.head(3))  

❸shape 行数と列数を表示する

print(x.shape) # ==> (4, 3)

❹index インデックス(恐らく何行目かという情報)

print(x.index) # ==> RangeIndex(start=0, stop=4, step=1) 0から始まり、1つずつ増やして4行

❺age列の平均を表示

print("平均値:", x.age.mean())   ==> 平均値: 23.75

❻age列の最小値を表示 

print("最小値:", x.age.min())  ==> 最小値: 19

❼describeで概要をを表示 

print(x.describe)  

かなりいろいろできそうだ。
データ分析で頻出のPandas基本操作 - Qiita

(6)検索条件を指定

もちろん、ファイルをread_csvで読みこんだ上で、条件を指定
・複数条件は()で囲った上で、andは&、orは| で条件をつなぐ。

#age列の値が23以下という場合
print(x[x['age'] < 23])
#検索条件を複数指定する場合
print(x[(x['age'] < 25) & (x['ken']=='aichi')])
(7)特定の行や列を抜き出す

・x[列番号]で列を表示可能。
・locを使うといい。タイトルというか、ラベルが無い場合は、数字で指定する。
・ilocもあり、こちらは行や列の番号で指定。locはラベル番号なのだが、locは両方に対応していると思う。(要確認)

import pandas as pd
x=pd.DataFrame([[11,12,13],[21,22,23],[31,32,33],[41,42,43]])
print(x[1])   # ==> 1列目を表示する。
print(x.loc[0:1])     #0行目と1行目を抜き出す
print(x.loc[:, [0,2]])   # :は全ての行を表す。よって、0列と2列を全て抜き出す。
print(x.iloc[1,2]) # 特定の値を抜き出す。このように、行番号と列番号を指定する 

・タイトルがある場合はタイトルを指定する。

import pandas as pd
x=pd.DataFrame([[11,12,13],[21,22,23],[31,32,33],[41,42,43]],columns=['no.1', 'no.2', 'no.3'],index=['row1', 'row2', 'row3','row4'])
print(x.loc[:, ['no.1','no.3']])

実行結果は以下。

      no.1  no.3
row1    11    13
row2    21    23
row3    31    33
row4    41    43
(8)行や列を追加する

❶列を追加
・x['sum']という感じで、データセットに列名を入れて、=でつないで値を入れる
・以下は、3つの列の合計をsumという列名で追加。
・x[2] = 'one'とすると、列のラべルが無い場合に、2列目の全てに’one'という値を入れる。

import pandas as pd
x=pd.DataFrame([[1,2,3],[21,22,23],[31,32,33],[41,42,43]],columns=['no.1', 'no.2', 'no.3'],index=['row1', 'row2', 'row3','row4'])
print(x)
x['sum']=x['no.1']+x['no.2']+x['no.3']
print(x)

出力結果は以下。上が列を追加する前、下が追加した後。

      no.1  no.2  no.3
row1     1     2     3
row2    21    22    23
row3    31    32    33
row4    41    42    43
      no.1  no.2  no.3  sum
row1     1     2     3    6
row2    21    22    23   66
row3    31    32    33   96
row4    41    42    43  126

❷行を追加
append() メソッド

(9)行や列を削除

❶列を削除
del x['sum'] という感じで削除できる。
❷行を削除
dropを使う。

(10)データの並び替え

・sort_index() はインデックスに基づくソート。→もともとインデックス順に並んでいるので、あまり使わないのではないか。強いて言うなら降順に並べるとか。
・sort_values()は、任意の列によるソート。複数列の指定も可能。ascending=Falseで、降順に

(11)データの連結

・concat()を使って、単純にデータを連結する方法がある。
・DBでキーを基に結合する場合のようにもできる。その場合はmerge() 関数を使う。
❶concatで2つのデータセットを単純結合
・以下は、乱数で2つの整数の配列を作り、それをconcat()で結合してdfに入れている。

import numpy as np
x=np.random.randint(0, 10, (3, 2)) 
y=np.random.randint(10, 20, (3, 2)) 
import pandas as pd
x_pd=pd.DataFrame(x)
y_pd=pd.DataFrame(y)
df= pd.concat([x_pd,y_pd])
print(df)

結果はこんな感じ

    0   1
0   5   4
1   6   7
2   1   5
0  15  13
1  17  13
2  11  11

もし、行のインデックスを振りなおしたい場合は、以下を入れる。

df = df.reset_index() 
(11)CSVファイルへ出力

以下のようにして出力する。

df.to_csv("file.csv")

オプション

項目 解説
sep 区切り文字、デフォルトは,(カンマ) sep=" "などと指定する
index 行名の出力。デフォルトは有効(True)。 index=False などと指定する。

2.クラス

1.概要

普通に変数を作っていってもいいのだが、ユーザ情報の場合、ユーザの属性(性別、年齢、所属)など、一つ一つを変数にするのは面倒で、わかりにくい。
たとえば、40人の生徒で、名前、性別、5教科の点数を変数に入れてみよう。

user1name='yamada'
user1sex='male'
user1english='90'
user1math='70'
・・・
user2name='aoki'
user2sex='male'
・・・
user3name='ito'
user3sex='female'

こうなると、40人×7属性で、280の変数を作る必要がある。これは面倒だ。
そこで、クラスを使う。

class User: #クラス
    pass

user1 = User() # インスタンス
user1.name = 'yamada'
user1.sex='male'
user1.english='90'
user1.math='70'

user2 = User() # インスタンス
user2.name='aoki'
user2.sex='male'

user3 = User() # インスタンス
user3.name='ito'
user3.sex='female'

print(user1.sex)  # → male が出力される

こうすれば、40人のユーザと5つの属性を管理すればいいので、管理がとても楽である。

2.クラスを作ってみよう

1.言葉や決め事など

❶クラス
・オブジェクトに共通する性質を定義したものがクラス。
・言い方を変えてみると、同一のデータ構造と同一の手続をもつオブジェクトをまとめて表現する。こうしてできたものがクラス。
・違う言い方をしてみると、オブジェクトは、データと手続きをまとめたもの。
❷インスタンス
・クラスの定義に基づいてインスタンスが生成される
・一つのクラスに対して,複数のインスタンスが対応する。たとえば、生徒(student)というクラスのインスタンスとして、user1,user2などのインスタンスを定義できます。
→じゃあ、name,sex,engishなどは何?
・「オブジェクト」と「インスタンス」を同じものようにして説明されることがある。たまに、「オブジェクト」と「クラス」も同じもののように説明されることもある。
❸メソッド

❺決め事
・単位クラス名の先頭は大文字。c.f.変数は小文字
・インスタンスを作るには、インスタンス名=クラス名()とする。

2.基本構文

基本構文は以下である。

class クラス名:
    def メソッド名(self,引数1,引数2,・・・):
        処理
インスタンス=クラス名()
インスタンス.メソッド名(引数1,引数2,・・・)
2.単純なクラスを作る

❶ユーザのクラス
先ほど書いたものと同じである。Userというクラスを作り、その中にuser1 user2・・・というインスタンスを作る。少し簡略化している。

class User: #クラス
    pass

user1 = User() # インスタンス
user1.name = 'yamada'
user1.sex='male'
user1.english='90'
user1.math='70'

user2 = User() # インスタンス
user2.name='aoki'
user2.sex='male'

print(user1.sex)  # → male が出力される

❷関数として
クラスは、単に関数の置き換えにもなる。関数であることが分かりやすいように、クラスの名前の先頭を小文字にした。

class func1:
    def __init__(self, x):
        print("input=" + x)
func1("5")

これは、func1という関数を、クラスで定義している。自動で実行される__init__によって、結果は以下

input=5

3.メソッド

(1)言葉や決め事など

・まあ、メソッド=関数と思っていいだろう(想定)
・メソッドの定義は、関数と同じくdefで行う。

(2)メソッドを作ってみよう。

あまりわかりやすい例ではないが、単にgoukeiというメソッドを作っただけ、と考えてほしい。
まずは普通の関数。

def goukei(x,y):   #普通の関数
    return x+y
print(goukei(10,20))
print(goukei(20,30))

次が、クラスに作成した関数(メソッド)。
何をやっているかというと、引数であるxとyを足すだけ。

class User: #クラス
    def goukei(self,x,y):    #このクラスにおけるメソッド
        return x + y

user1 = User() # インスタンス
print(user1.goukei(10,20))  # →sumいう関数(メソッド)により30
user2 = User() # インスタンス
print(user2.goukei(20,30)) # →sumいう関数(メソッド)により50

ここで、もちろん、クラスの中で定義したメソッドは、そのクラスにおいてだけ有効である。なので、以下のように実行しても、「name 'goukei' is not defined」とエラーがでる。

class User: #クラス
    def goukei(self,x,y):
        return x + y

print(goukei(20,30))
(3)selfについて

さて、goukei(self,x,y)のselfは何であろうか。goukei(x,y)ではいけないのか。→エラーになる。渡している側と受け取る側で引数の数が異なるからだ。
引数を渡す側としては、以下のようにしているので、10と20を渡している。
user1.goukei(10,20)
加えて、クラスのメソッドには、このuser1というオブジェクトというか、インスタンスの情報を渡している。それをselfという値にセットしているだけだ。
だから、3つを渡されたら、3つを受け取る箱がいる。そのために作ったのがselfである。
具体的にはどんな内容か、Printしてみよう。

class User: #クラス
    def goukei(self,x,y):
        print(self)  #==> <__main__.User object at 0x7fcea2beffd0> 
        print(x)  #==> 10
        print(y)  #==> 20
        return x + y

user1 = User() # インスタンス
user1.goukei(10,20) 

このように、goukei(self,x,y):では、引数を受け取っているだけなので、selfという文字ではなくてxでもaaaaでもなんでもいい。単に、慣習としてselfが使われているだけである。
また、インスタンスのメソッドの場合は必ずselfをつける。ちなみに、クラスにメソッドを作るときは、selfをつけない。

では、インスタンス変数につけるselfについて、具体的に実行してみよう。
Userクラスを定義し、yamadaというインスタンスを作る。着目すべき点は、コンストラクタ内のself.nameである。このように、受け取った引数を、self.nameで再定義している。これはselfは必要なのか。また、再定義しないと使えないのか、試してみる。
❶一般的な書き方

class User:
    def __init__(self, name):    #コンストラクタ
        self.name = name  #関数内で変数を使うには、self.を付与する。selfは別の名前でも可能。
    def msg1(self):   #メソッド
        print(self.name)
yamada = User("yamada")
yamada.msg1() # ==> yamada

❷コンストラクタ内でself.を付与しないで変数を定義
以下のように書くことができる。コンストラクタ内では問題なく利用できる。

class User:
    def __init__(self, name):    #コンストラクタ
        self.name = name  
        x=name         #self.を付与しない変数も定義できるが、このコンストラクタ内でしか使えない。
        print(x)
    def msg1(self):   #メソッド
        pass
yamada = User("yamada")
yamada.msg1() # ==> yamada

❸メソッドでself.を付与しないで変数を使ってもエラーになる

class User:
    def __init__(self, name):    #コンストラクタ
        x=name
    def msg1(self):   #メソッド
        print(x)  # ==> エラーになる。NameError: name 'x' is not defined
yamada = User("yamada")
yamada.msg1()

❹コンストラクタ内でself.で定義しない場合
Userオブジェクトでnameが定義されていないとエラーになる。

class User:
    def __init__(self, name):    #コンストラクタ
        pass
    def msg1(self):   #メソッド
        print(self.name)  # ==> エラーになる。'User' object has no attribute 'name'
yamada = User("yamada")
yamada.msg1()

★このように、オブジェクトの中ではself.name = nameのようにself.をつけて変数を定義し、self.を付けて変数を使う。面倒だと思うし、わざわざself.を付けない方が簡単ではないか、と思うこともある。一方で、オブジェクトの中だけの変数であることが明確だし、長いプログラムを書いたとしても、他の変数に影響を及ぼさない。そいう観点から考えると、こういう仕組みであることは利点があるとも言える。

(4)メソッドの復習

ココから先はコンストラクタの内容が含まれているので、先にコンストラクタを読まれることをお勧めする。
では、もう一度メソッドを作ってみよう。

class User:
    def __init__(self, name,age,sex):    #コンストラクタ
        self.name = name  
        self.age = age   
        self.sex = sex     
    def msg1(self):   #メソッド
        print('Hello')

yamada = User("yamada",10,"male")
aoki = User("aoki",12,"femal")

#メソッドmsg1を実行
yamada.msg1()
aoki.msg1()

結果は、以下のように、単にHelloをプリントするmsg1という関数を実行しているだけ。インスタンスがyamadaだろうがaokiだろうがどちらも同じ

Hello
Hello

こだだと面白くないので、インスタンスの属性を含めてメソッドを実行させる。
このとき、引数の値だけでなく、テクスト文字も含めて表記したい場合がある。その場合、引数は先頭から{0} {1} {2}・・・と表し、ドットに続けて、format(self.引数の名)を指定する。

class User:
    def __init__(self, name,age,sex):    #コンストラクタ
        self.name = name  
        self.age = age   
        self.sex = sex     
    def msg2(self):   #メソッド
        print(self.name)  #値だけであればこの書き方
        print("Hello {0}".format(self.name))  #テキスト文字と引数の値を組み合わせる場合はこの書き方。

yamada = User("yamada",10,"male")
aoki = User("aoki",12,"femal")

#メソッドmsg2を実行
yamada.msg2()
aoki.msg2()

結果は、以下のように、インスタンスの属性を付与できている。

yamada
Hello yamada
aoki
Hello aoki

複数の引数を付与する場合は、以下のように {0},{1}などと加えていって、formatの中に利用する引数を追加する。

print("Hello {0},your age={1}".format(self.name,self.age))

4.コンストラクタ

・コンストラクタは、インスタンスを作成したときに一度だけ実行されるメソッド(関数)
・Classの中にインストラクタのメソッドを書く。具体的には、__init__で作成する。
・使い方の例として、コンストラクタを使って、データの初期値を渡そう。
・また、インスタンスの型を決めることにも役立つ。
・インスタンスの内部で持つ変数であるインスタンス変数は、コンストラクタにて定義される。
・インスタンスを作成したときに1度実行されるので、なにか、処理をしたいことがあれば、ここでさせるとよい。

(2)コンストラクタはなぜ便利か

クラスを作ることのメリットはなんとなく感じてもらったでしょう。40人の生徒で、それぞれ属性が7つあれば、管理が大変。
しかし、以下のように40人にそれぞれ7属性を1つずつ入れていっては、合計280回、変数の値を入れる必要があり、280個変数を作るのと変わらない。

class User: #クラス
    pass

user1 = User() # インスタンス
user1.name = 'yamada'
user1.age = 10
user1.sex = male

user2 = User() # インスタンス
user2.name='aoki'
user2.age = 12
user2.sex = female

そこで、コンストラクタを使う。コンストラクタといっても、普通に関数を定義するだけである。たとえば、以下は「__init__」という名前の関数を定義している。コンストラクタの場合は「__init__」という名前を付けることが決っている。そして、yamada = User("yamada",10,"male") のようにインスタンスに対して値が付与されると、このコンストラクタの関数が実行されるのである。

class User:
    def __init__(self, name,age,sex):    #コンストラクタ
        self.name = name   #コンストラクタ変数
        self.age = age          #コンストラクタ変数
        self.sex = sex             #コンストラクタ変数
yamada = User("yamada",10,"male")
aoki = User("aoki",12,"femal")

もう少し解説すると、コンストラクタを使ってyamada = User("yamada",10,"male") のように、name='yamada'、age=10、sex='male'という値を渡す。そして、self.nameという変数に受け取ったyamadaや10、maleなどのデータをセットしてくれる。
では、値が入っているか、確認してみよう。

print(yamada.name)
print(aoki.age)

結果は、以下のようになる。このようにすれば、40人に対して7属性を付与する場合でも、280行の変数定義は不要である。40行で済むから便利だ。

yamada
12
(3)初期値を入れるだけがコンストラクタではない

コンストラクタは、インスタンスを作ったときに1回だけ実行されるものである。なので、初期値を入れるには便利で。ただ、初期値を入れるだけにコンストラクタが使われるわけではない。
たとえば、以下はユーザのリストをコンストラクタで作った。何をしているかというと、yamada、aokiなどのインスタンスが作成される都度、namelistというリストに、引数として受け取ったnameを追加している。

class User:
    namelist=[]
    def __init__(self, name,age,sex):
        self.namelist.append(name)
yamada = User("yamada",10,"male")
aoki = User("aoki",12,'female')
print(User.namelist)

結果はこんな感じ

['yamada', 'aoki']

5.インスタンスとクラスの応用

(1)インスタンス変数とクラス変数

復習を兼ねて・・・
❶インスタンス変数
・インスタンスの内部で持つ変数がインスタンス変数である。また、クラス変数もある。
・「self.変数」の形で書く。
・クラスで変数を定義してしまうと、すべてのインスタンスで共通の変数になります。
・一方、インスタンス変数であれば、インスタンスごとに独立した変数を持てる。(というか、独立している。)
・インスタンス変数はコンストラクタで定義する。つまり、__init__ のところで記載する。
❷クラス変数
・インスタンス変数と違って、クラスで共通の変数。あまり使うことは多くないかもしれないが、大文字で初めてクラス共通の定数として使う場合が多いのではないか。
・書き方はごく普通、Classに直接変数を書く
・呼び出すときは、クラス名.クラス変数

class User():             #User()クラスを定義
    U_city='Tokyo'    #クラス変数
    
    def __init__(self,name,age):  #コンストラクタ
        self.name=name   #コンストラクタ変数
        self.age=age    #コンストラクタ変数

print(User.U_city)   #==> Tokyo
(2)メソッドやクラスのアクセス制限

外部から参照されないようにしたい場合は、_をつける。
たとえば、関数であれば、

def _func1()

変数であれば、

def __init__(self,data)
    self._data=data

ただ、これはルールとしているだけなので、実際には呼び出せる。
厳密にするには__と、アンダーラインを2つつける。こうすると、_クラス名をつけないとアクセスできないので、
より厳密になりやすい。

(3)クラスメソッドとインスタンスメソッド

・通常、メソッドはインスタンスを作成して、インスタンス.メソッド名 という形で呼ばれることが一般的
・クラスメソッドは、インスタンスを作らなくても使えるメソッド。インスタンスを作らないので、結果的に、普通の関数みたいなイメージだと思っている(が、使い方は少し特殊だ)
・クラスメソッドは@classmethodで宣言し、selfの代わりにclsを使う。
❶使い方の例
ハッキリ言って、関数と同じような使い方。

class User():
    @classmethod
    def cm1(cls):
        return "Hello"
print(User.cm1())  # ==> Hello

❷スタティックメソッド
・クラスメソッドに似たものに、スタティックメソッドがある。
・クラスには関係ない普通の関数と考えてた方がいい。クラスメソッドなどは、クラス変数などを使ったりできるが、スタティックメソッドは、それが使えない。だったら、クラスの入れなくてもいいはず。
それはその通り。だけど、なんとなく座りがいいから置きたい場合に使う。
・表記としては、@staticmethod として宣言する。

6.クラスの継承

(1)概要

・親クラスのメソッドなどを子クラスに継承する。
・親クラスのメソッドを使う場合にはsuper()を使う
書き方はこんな感じ。

class Parent: #親クラスの定義
・・・
class Child(Parent): #親クラスParentを継承
・・・
(2)実際に書いてみよう。

・次は、User()という親クラスに、SuperUser()という子クラスを作る。
・イメージとしては、Userが一般ユーザ、SuperUserは、一般ユーザの属性や権限に加えて、プラスアルファの情報がある。なので、親クラスの基本情報や関数は引き継ぐが、それに加えて変数や関数を持つ。
・以下は、Userは名前と年齢を持ち、SuperUserはそれに加えて性別を持つ。
・コンスタクタも、関数func1()も、親クラスを継承している。

class User():     #親クラス、スーパークラス
    def __init__(self,name,age):  #コンストラクタ
        self.name=name   #コンストラクタ変数
        self.age=age    #コンストラクタ変数
    def func1(self):
        print('name is ' + self.name)

class SuperUser(User):    #子クラス
    def __init__(self,name,age,sex):
        super().__init__(name,age)     #親のコンストラクタを利用
        self.sex=sex
    def func1(self):   #親と同じ関数名にできる
        super().func1()
        print('sex is ' + self.sex)    

yamada = User("yamada",10)
aoki = User("aoki",12)
ito=SuperUser("ito",13,"female")
yamada.func1()   # ==> name is yamada
ito.func1()      # ==> name is ito  sex is female

実行結果を見てもらうと、同じfunc1()という関数を使っていても、Userクラスのyamadaさんと、SuperUserクラスのitoさんでは、結果が違う。

(3)クラスの多重継承

クラスCがクラスAとBを継承する場合、書き方のイメージは以下。
class A:
class B:
class C(A,B)

6.特殊メソッド

__iter__ と __next__
以下に解説あり。
utokyo-ipp.github.io