ひとり勉強ログ

ITエンジニアの勉強したことメモ

【ゼロから作るDeepLearning】2章パーセプトロン

2.1 パーセプトロンとは

def AND(x1, x2):
  w1, w2, theta = 0.5, 0.5, 0.7
  tmp = x1*w1 + x2*w2
  if tmp <= theta:
    return 0
  elif tmp > theta:
    return 1

print(AND(0, 0))
print(AND(1, 0))
print(AND(0, 1))
print(AND(1, 1))

パラメータのw1、w2、thetaは関数内で初期化し、重み付き入力の総和が閾値を超えると1を返し、それ以外は0を返します。

0
0
0
1

2.3.2 重みとバイアスの導入

>>> import numpy as np
>>> x = np.array([0, 1]) # 入力
>>> w = np.array([0.5, 0.5]) # 重み
>>> b = -0.7 # バイアス
>>> w*x
array([0. , 0.5])
>>> np.sum(w*x)
0.5
>>> np.sum(w*x) + b
-0.19999999999999996

2.3.3 重みとバイアスによる実装

import numpy as np

# ANDゲート
def AND(x1, x2):
  x = np.array([x1, x2])
  w = np.array([0.5, 0.5])
  b = -0.7
  tmp = np.sum(w*x) + b
  if tmp <= 0:
    return 0
  else:
    return 1
# NANDゲート
def NAND(x1, x2):
  x = np.array([x1, x2])
  w = np.array([-0.5, -0.5])
  b = 0.7
  tmp = np.sum(w*x) + b
  if tmp <= 0:
    return 0
  else:
    return 1
# ORゲート
def OR(x1, x2):
  x = np.array([x1, x2])
  w = np.array([0.5, 0.5])
  b = -0.2
  tmp = np.sum(w*x) + b
  if tmp <= 0:
    return 0
  else:
    return 1

AND、NAND、ORは同じ構造のパーセプトロンであり、違いは重みパラーメータの値だけでした。NANDとORゲートの実装においても、ANDとは異なる箇所は重みとバイアスの値を設定する箇所だけになります。

2.5.2 XORゲートの実装

def XOR(x1, x2):
  s1 = NAND(x1, x2)
  s2 = OR(x1, x2)
  y = AND(s1, s2)
  return y

print (XOR(0, 0))
print (XOR(1, 0))
print (XOR(0, 1))
print (XOR(1, 1))
0
1
1
0

【ゼロから作るDeepLearning】1章 python入門

1.5 Numpy

1.5.5 ブロードキャスト

>>> import numpy as np
>>> A = np.array([[1,2], [3,4]])
>>> B = np.array([10,20])
>>> A*B
array([[10, 40],
       [30, 80]])

1.6 Matplotlib

1.6.1 単純なグラフの描画

>>> import numpy as np
>>> import matplotlib.pyplot as plt
>>> x = np.arange(0, 6, 0.1)
>>> y = np.sin(x)
>>> plt.plot(x, y)
[<matplotlib.lines.Line2D object at 0x7fdf41037e50>]
>>> plt.show()

1.6.2 pyplotの機能

>>> import numpy as np
>>> import matplotlib.pyplot as plt
>>> x = np.arange(0, 6, 0.1)
>>> y1 = np.sin(x)
>>> y2 = np.cos(x)
>>> plt.plot(x, y1, label="sin")
[<matplotlib.lines.Line2D object at 0x7fdf410f9e80>]
>>> plt.plot(x, y2, linestyle = "--", label="cos")
[<matplotlib.lines.Line2D object at 0x7fdf41107610>]
>>> plt.xlabel("x")
Text(0.5, 0, 'x')
>>> plt.ylabel("y")
Text(0, 0.5, 'y')
>>> plt.title('sin & cos')
Text(0.5, 1.0, 'sin & cos')
>>> plt.legend()
<matplotlib.legend.Legend object at 0x7fdf410e0af0>
>>> plt.show()

第1章 人工知能(AI)とは【G検定公式テキスト】

1-1.人工知能(AI)とは

1.人工知能の定義

1.1 人工知能とは何か

人工知能」が、推論、認識、判断など、人間と同じ知的な処理能力を持つ機械(情報処理システム)であるという点については、大多数の研究者の意見は一致しているといってよいでしょう。

京都大学名誉教授の長尾真は「人間の頭脳活動を極限までシミュレートするシステム」と定義し、東京大学教授の松尾豊は「人工的につくられた人間にような知能、ないしはそれをつくる技術」と定義しています。

1.2 人工知能の大まかな分類

レベル1:シンプルな制御プログラム エアコンの温度調整、洗濯機の水量調整、電気シェーバーの深剃り調整など、あらかじめ単純な振る舞いがハードウェアやソフトウェアで決まっている製品が、このカテゴリに分類されます。これらの製品では、すべての振る舞いがあらかじめ決められており、その通りに動くだけです。

レベル2:古典的な人工知能 掃除ロボットや診断プログラムなど、探索・推論、知識データを利用することで、状況に応じて極めて複雑な振る舞いをする製品がこのカテゴリに属します。

レベル3:機械学習を取り入れた人工知能 検索エンジンや交通渋滞予測など、非常に多くのサンプルデータをもとに入力と出力の関係を学習した製品がこのカテゴリに属します。

レベル4:ディープラーニングを取り入れた人工知能 機械学習では、学習対象となるデータの、どのような特徴が学習結果に大きく影響するかを知ることはとても重要です(これを特徴量)と呼びます。

この特徴量と呼ばれる変数を、自動的に学習するサービスや製品がこのカテゴリに属します。

1.3 AI効果

人工知能で何か新しいことが実現され、その原理がわかってしまうと、「それは単純な自動化であって知能とは関係ない」と結論づける人間の心理的な効果をAI効果と呼びます。

1.4 人工知能とロボットの違い

ロボットの脳に当たる部分が人工知能になります。

1-2.人工知能研究の歴史

1.人工知能研究の歴史

1.1 世界初の汎用コンピュータの誕生

1946年、アメリカのペンシルベニア大学エニアック(ENIACという17、468本もの真空管を使った巨大な電算機が開発されました。

1.2 ダートマス会議

人工知能という言葉は、エニアックの誕生からちょうど10年後の1956年にアメリカで開催されたダートマス会議において初めて使われました。

特にニューウェルとサイモンは、世界初の人工知能プログラムといわれるロジック・セオリストをデモンストレーションし、コンピュータを利用して数学の定理を自動的に証明することが実現可能であることを示しました。

1.3 人工知能研究のブームと冬の時代

第1次AIブーム(推論・探索の時代:1950年代後半〜1960年代) 迷路や数学の定理の証明のような簡単な問題(「トイ・プロブレム(おもちゃの問題)」)は解けても、複雑な現実の問題は解けないことが明らかになった結果、ブームは急速に冷め、1970年代には人工知能研究は冬の時代を迎えます。

第2次AIブーム(知識の時代:1980年代) コンピュータに「知識」を入れると賢くなるというアプローチが全盛を迎え、データベースに大量の専門知識を溜め込んだエキスパート・システムと呼ばれる実用的なシステムがたくさん作られました。日本では、政府によって「第五世代コンピュータ」と名付けられた大型プロジェクトが推進されました。

第3次AIブーム(機械学習・特徴表現学習の時代:2010年〜) ビッグデータと呼ばれる大量のデータを用いることで、人工知能が自ら知識を獲得する機械学習が実用化されました。また、知識を定義する要素特徴量と呼ばれる対象を認識する際に注目すべき特徴を定量的に表したもの)人工知能が自ら習得するディープラーニング(深層学習)が登場したことが、ブームの背景にあります。

8章 ファイルの読み書き【退屈なことはPythonにやらせよう】

8.1 ファイルとファイルパス

8.1.1 Windowsのバックスラッシュ、MacLinuxのスラッシュ

>>> import os
>>> os.path.join('user', 'bin', 'spam')
'user/bin/spam'

os.path.join()関数は、ファイル操作関数に渡して用いるような、フルパスのファイル名を作るのに便利。

my_files = ['account.txt', 'details.csv', 'invite.docx']
for filename in my_files:
    print(os.path.join('C:\\Users\\baba', filename))


C:\Users\baba\account.txt
C:\Users\baba\details.csv
C:\Users\baba\invite.docx

8.1.2 カレントディレクト

カレントディレクトリはos.getcwd()関数で文字列として取得でき、os.chdir()関数で変更することができる。

import os
os.getcwd()
'C:\\Users\\baba\\AppData\\Local\\Programs\\Python\\Python310'

os.chdir()関数でカレントディレクトリを変更することができる。

>>> import os
>>> os.getcwd()
'/Users/xxx/develop/python/automate'
>>> os.chdir('/Users/makoto/develop')
>>> os.getcwd()
'/Users/xxx/develop'

8.1.3 絶対パス相対パス

os.makedirs()関数を使って新しいフォルダを作る。

>>> import os
>>> os.makedirs('/Users/xxx/waffles')

os.makedirs()は、フルパスで指定したフォルダに必要な中間フォルダも作成してくれる。

8.2 os.pathモジュール

8.2.1 絶対パス相対パスを操作する

os.path.abspath(path) 引数に渡したパスの絶対パスを文字列として返す。

os.path.isabs(path) 引数が絶対パスならTrue、相対パスならFalseを返す。

os.path.relpath(path, start) startからpathへの相対パスを文字列として返す。

>>> os.path.abspath('.')
'/Users/makoto/develop/python/automate'
>>> os.path.isabs('.')
False
>>> os.path.isabs(os.path.abspath('.'))
True
>>> os.path.relpath('/Users/xxx/develop', '/Users/xxx')
'develop'
>>> os.path.relpath('/Users/xxx/develop', '/Users/share')
'../xxx/develop'
>>> os.getcwd()
'/Users/xxx/develop/python/automate'

os.path.dirname(path)は引数pathの最後のパス区切り記号までの文字列を返す。一方、os.path.basename(path)は最後の区切り記号より後ろを返す。

>>> path = '/Users/xxx/develop/automate/example.csv'
>>> os.path.basename(path)
'example.csv'
>>> os.path.dirname(path)
'/Users/xxx/develop/automate'

パスのdirnameとbasenameを同時に取得したいときには、os.path.split()を呼び出せば、2つの文字列のタプルを取得できる。

>>> calc_file_path = '/Users/xxx/develop/example.csv'
>>> os.path.split(calc_file_path)
('/Users/xxx/develop', 'example.csv') 

os.path.split()は個々のファイルまで分割しない。そうしたいなら、os.sepを用いて文字列のsplit()メソッドを呼び出す。os.sepにはプログラムを実行しているOSにおけるパス区切り文字が格納されている。

8.2.2 ファイルサイズとフォルダ内容を調べる

>>> os.path.getsize('/Users/xxx/develop/python/automate/example.csv')
183
>>> os.listdir('/Users/xxx/')
['.config', 'Music', '.condarc', '.docker', '.nodebrew', 'go', '.DS_Store', 'develop', '.yarnrc', '.CFUserTextEncoding', '.stCommitMsg', '.hgignore_global', '.xonshrc', '.zshrc', 'Pictures', '.zprofile', '.zsh_history', '.ipython', 'Desktop', 'Library', '.matplotlib', '.oracle_jre_usage', '.gitignore_global', '.cups', 'Public', '.tcshrc', '.anaconda', '.ssh', 'Movies', 'Applications', 'opt', '.Trash', '.gitflow_export', '.ipynb_checkpoints', '.jupyter', '.npm', 'Documents', '.BrowserLock', '.vscode', '.bash_profile', 'Downloads', '.python_history', 'first_notebook', '.continuum', '.aws', '.gitconfig', '.putty', '.viminfo', '.zsh_sessions', '.conda', '.composer']

ディレクトリに含まれるファイルの合計サイズを求める

>>> total_size = 0
>>> for filename in os.listdir('/Users/xxx/develop/python/automate'):
...   total_size = total_size + os.path.getsize(os.path.join('/Users/xxx/develop/python/automate', filename))
... 
>>> print(total_size)
255375

os.path.getsize()を呼び出すときに、os.path.join()を呼び出して、フォルダ名と現在のファイル名を連結している点に注意する。

8.2.3 パスを検査する

os.path.exists(path) 引数に指定したファイルやフォルダが存在すればTrue、存在しなければFalseを返す。

os.path.isfile(path) 引数に指定した先が存在し、それがファイルであればTrue、そうでなければFalseを返す。

os.path.isdir(path) 引数に指定した先が存在し、それがフォルダであればTrue、そうでなければFalseを返す。

>>> os.path.exists('/Users/xxx')
True
>>> os.path.exists('/Users/some_made_up_folder')
False
>>> os.path.isdir('/Users/xxx/develop')
True
>>> os.path.isfile('/Users/xxx/develop')
False
>>> os.path.isdir('/Users/xxx/develop/python/automate/chapter1.py')
False
>>> os.path.isfile('/Users/xxx/develop/python/automate/chapter1.py')
True

8.3 ファイルの読み書きの方法

Pythonでファイルの読み書きをするには3つのステップがある。

  1. open()関数を呼び出し、Fileオブジェクトを取得する。
  2. Fileオブジェクトのread()やwrite()メソッドを呼び出して読み書きする。
  3. Fileオブジェクトのclose()オブジェクトを呼び出してファイルを閉じる。

8.3.1 open()関数を用いてファイルを開く

開きたいファイルのパスを文字列として渡す。絶対パスでも相対パスでも構わない。

hello_file = open('/Users/xxx/develop/python/automate/hello.txt')

このようにopen()を呼び出すと、「テキストファイルを読む」モード、つまり読み込みモードでファイルを開く。

8.3.2 ファイルの内容を読み込む

>>> hello_content = hello_file.read()
>>> hello_content
'Hello world!!'

ファイルの中身をひとつの巨大な文字列の値だとみなせば、read()メソッドは、ファイルに格納された文字列を返すことになる。

別の方法として、readlines()というメソッドを用いれば、1行ずつの文字列のリストとしてファイルを読み込むこともできる。

例として、hello.txtと同じフォルダに、sonnet29.txtというファイルを作り、以下のように入力する。

When, in disgrace with fortune and men's eyes,
I all alone beweep my outcast state,
And trouble deaf heaven with my bootless cries,
And look upon myself and curse my face,
>>> sonnet_file = open('sonnet29.txt')
>>> sonnet_file.readlines()
["When, in disgrace with fortune and men's eyes,\n", 'I all alone beweep my outcast state,\n', 'And trouble deaf heaven with my bootless cries,\n', 'And look upon myself and curse my face,']

8.3.3 ファイルを書き込む

ファイルに書き込むには、書き込みモード、もしくは、追記モードでファイルを開く必要がある。 書き込みモードは、既存のファイルを上書きして最初から書き直すことになる。 書き込みモードでも追記モードでも、もしopen()の第1引数に渡したファイルが存在しなければ、新たに空のファイルが作成される。ファイルを読み書きした後にはclose()メソッドを呼び出して閉じる。書き込みや追記モードのときにファイルを閉じ忘れたままプログラムが異常終了すると、ファイル内容が壊れることがある。

>>> bacon_file = open('bacon.txt', 'w') // 書き込みモードで開く
>>> bacon_file.write('Hello world!\n') // 文字列をファイルに書き込む
13
>>> bacon_file.close() // ファイルを閉じる
>>> bacon_file = open('bacon.txt', 'a') // 追記モードでファイルを開く
>>> bacon_file.write('Bacon is not a vegetable.') // 文字列をファイルに書き込む
25
>>> bacon_file.close()
>>> bacon_file = open('bacon.txt')
>>> content = bacon_file.read() // ファイル内容をcontentに読み込む
>>> bacon_file.close()
>>> print(content) // cotentを画面に表示する
Hello world!
Bacon is not a vegetable.

8.4 shelveモジュールを用いて変数を保存する

shelveモジュールを用いると、Pythonプログラム中の変数を、シェルフ(棚)というバイナリファイルとして保存することができる。ハードディスクに保存したデータは、後で変数に復元することができる。

shelveモジュールを使えば、プログラムに「保存」と「開く」機能を追加することができる。

>>> import shelve // インポート
>>> shelf_file = shelve.open('mydata')
>>> cats = ['Zophie', 'Pooka', 'Simon']
>>> shelf_file['cats'] = cats // 'cats'というキーに対応した値としてリストcatsの内容を保存
>>> shelf_file.close()

shelve.openにファイル名を渡して呼び出し、戻り値のシェルフオブジェクトを変数に格納しておく。シェルフの値は辞書のように変更することができる。 Windowsで前記のコードを実行すると、カレントディレクトリに、mydata.bak、mydata.data、mydata.dirの3つのファイルが作られる。これらのバイナリファイルには、シェルフに保存したデータが格納されている。 シェルフファイルを開いてデータを取り出すのにもshelveモジュールを用いる。 シェルフファイルは、書き込みモードと読み込みモードで開き直す必要がなく、一度開けば読み書きできる。

>>> shelf_file = shelve.open('mydata')
>>> type(shelf_file)
<class 'shelve.DbfilenameShelf'>
>>> shelf_file['cats']
['Zophie', 'Pooka', 'Simon']
>>> shelf_file.close()```

辞書と同様に、シェルフにもkeys()とvalues()メソッドがあり、シェルフに含まれるキーと値の並びを返す。ただし、これは真のリストではなくリスト風のオブジェクトなので、真のリストが必要ならlist()関数に渡す必要がある。

shelf_file = shelve.open('mydata') list(shelf_file.keys()) ['cats'] list(shelf_file.values()) 'Zophie', 'Pooka', 'Simon' shelf_file.close()

プレーンテキストはメモ帳やTextEditで読めるファイルを作るのに便利だが、Pythonプログラムからデータを保存したいときにはshelveモジュールを使うのが便利。


### pprint.pformat()関数を用いて変数を保存する

pprint.pformat()関数は、画面に表示せずに整形した文字列を返す。整形された文字列は読みやすいだけではなく、Pythonコードとして文法的に正しいものになっている。

import pprint # モジュールをインポート cats = [{'name': 'Zophie', 'desc':'chubby'},{'name':'Pooka', 'desc': 'fluffy'}] pprint.pformat(cats) "[{'desc': 'chubby', 'name': 'Zophie'}, {'desc': 'fluffy', 'name': 'Pooka'}]" file_obj = open('myCats.py', 'w') file_obj.write('cats = ' + pprint.pformat(cats)+ '\n') 83 file_obj.close()

Pythonのスクリプト自体が.pyという拡張子を持ったテキストファイルなので、Pythonプログラムが他のPythonプログラムを生成することができる。生成したファイルをインポートしてみる。

import pprint cats = [{'name':'Zophie', 'desc':'chubby'},{'name':'Pooka', 'desc': 'fluffy'}] # 辞書リストを作成、変数catsに格納 pprint.pformat(cats) # pprint.pformat()を呼び出して文字列を取得 "[{'desc': 'chubby', 'name': 'Zophie'}, {'desc': 'fluffy', 'name': 'Pooka'}]" file_obj = open('myCats.py', 'w') file_obj.write('cats= ' + pprint.pformat(cats) + '\n') 82 file_obj.close()

import文によってインポートするモジュールも、Pythonスクリプトで記述する。pprint.pformat()から得られた文字列を,pyファイルに保存したなら、そのファイルは他でもインポート可能なモジュールになる。

Pythonのスクリプト自体が.pyという拡張子を持ったテキストファイルなので、Pythonプログラムが他のPythonプログラムを生成することができるわけである。

生成したファイルをインポートしてみる。

import myCats myCats.cats [{'desc': 'chubby', 'name': 'Zophie'}, {'desc': 'fluffy', 'name': 'Pooka'}] myCats.cats[0] {'desc': 'chubby', 'name': 'Zophie'} myCats.cats[0]['name'] 'Zophie'

### プロジェクト:ランダムな問題集ファイルを作成する

* 35通りの問題集を作成する。
* 問題集は都道府県を網羅する47問の4択問題とし、問題の順番はランダムとする。
* 各問題の選択肢は、正解1つと、ランダムな誤答3つがあり、順番はランダムとする。
* 問題集は35個のテキストファイルに書き出す。
* 解答集も35個のテキストファイルに書き出す。 

! python3

randomQuizeGenerator.py

import random

問題のデータ。キーが都道府県で、値が県庁所在地

capitals = {'北海道': '札幌市', '青森県': '青森市', '岩手県': '盛岡市', '宮城県': '仙台市', '秋田県': '秋田市', '山形県': '山形市', '福島県': '福島市', '茨城県': '水戸市', '栃木県': '宇都宮市', '群馬県': '前橋市', '埼玉県': 'さいたま市', '千葉県': '千葉市', '東京都': '東京', '神奈川県': '横浜市', '新潟県': '新潟市', '富山県': '富山市', '石川県': '金沢市', '福井県': '福井市', '山梨県': '甲府市', '長野県': '長野市', '岐阜県': '岐阜市', '静岡県': '静岡市', '愛知県': '名古屋市', '三重県': '津市', '滋賀県': '大津市', '京都府': '京都市', '大阪府': '大阪市', '兵庫県': '神戸市', '奈良県': '奈良市', '和歌山県': '和歌山市', '鳥取県': '鳥取市', '島根県': '松江市', '岡山県': '岡山市', '広島県': '広島市', '山口県': '山口市', '徳島県': '徳島市', '香川県': '高松市', '愛媛県': '松山市', '高知県': '高知市', '福岡県': '福岡市', '佐賀県': '佐賀市', '長崎県': '長崎市', '熊本県': '熊本市', '大分県': '大分市', '宮崎県': '宮崎市', '鹿児島県': '鹿児島市', '沖縄県': '那覇市'}

35個の問題集を作成する

for quiz_num in range(35): # 問題集と解凍集のファイルを作成する quiz_file = open('capitalsquiz{}.txt'.format(quiz_num + 1), 'w') # answer_key_file = open('capitalsquiz_answers{}.txt'.format(quiz_num + 1), 'w') #

# 問題集のヘッダーを書く quiz_file.write('名前:\n\n日付:\n\n学期:\n\n') # 生徒が記入する問題集のヘッダーを作成 quiz_file.write*1 quiz_file.write('\n\n')

# 都道府県の順番をシャッフルする prefectures = list(capitals.keys()) random.shuffle(prefectures) # ランダムに並び替えた都道府県リストを作成する

# 47都道府県をループして、それぞれ問題を作成する for question_num in range(len(prefectures)): # 正解と誤答を取得する correct_answer = capitals[prefectures[question_num]] # 各都道府県ごとに都道府県庁所在地をcorrect_answerに保存 wrong_answers = list(capitals.values()) # capitalsの辞書のすべての値を複製 del wrong_answers[wrong_answers.index(correct_answer)] # 正解を削除 wrong_answers = random.sample(wrong_answers, 3) # ランダムに3つを選ぶ answer_options = wrong_answers + [correct_answer] # 3つの誤答と正解をまとめて選択肢リストとする random.shuffle(answer_options) # 選択肢リストをシャッフルする

# 問題文と回答選択肢を問題ファイルに書く quiz_file.write('{}. {}の都道府県庁所在地は?\n'.format(question_num + 1, prefectures[question_num])) for i in range(4): # answer_optionsリストの選択肢を0〜3まで順番に表示する quiz_file.write('{}. {}\n'.format('ABCD'[i], answer_options[i])) # 'ABCD'という文字列を配列とみなし

quiz_file.write('\n')

# 答えの選択肢をファイルに書く answer_key_file.write('{}. {}\n'.format(question_num + 1, 'ABCD'[ answer_options.index(correct_answer)])) # シャッフルされた選択肢の中から正解の番号を見つける

quiz_file.close() answer_key_file.close()

*1:' ' * 20) + '都道府県庁所在地クイズ(問題番号{})'.format(quiz_num + 1

14章 CSVファイルとJSONデータ【退屈なことはPythonにやらせよう】

CSVモジュール

Readerオブジェクト

CSVモジュールを使ってCSVファイルからデータを読み込むには、Readerオブジェクトを生成する。

>>> import csv # 別途インストール不要
>>> example_file = open('example.csv')
>>> example_reader = csv.reader(example_file)
>>> example_data = list(example_reader) # Readerオブジェクトをlist()に渡すと、リストが返ってくる
>>> example_data
[['4/5/2014 13:34', 'Apples', '73'], ['4/5/2014 3:41', 'Cherries', '85'], ['4/6/2014 12:46', 'Pears', '14'], ['4/8/2014 8:59', 'Oranges', '52'], ['4/10/2014 2:07', 'Apples', '152'], ['4/10/2014 18:10', 'Bananas', '23'], ['4/10/2014 2:40', 'Strawberries', '98']]
>>> example_data[0][0]
'4/5/2014 13:34'
>>> example_data[0][1]
'Apples'
>>> example_data[0][2]
'73'
>>> example_data[1][1]
'Cherries'
>>> example_data[6][1]
'Strawberries'
forループでReaderオブジェクトからデータを読み出す

巨大なCSVファイルの場合は、forループの中でReaderオブジェクトを使うほうがよい。そうすれば、ファイルを一度にメモリーに読み込むことを避けられる。

>>> import csv
>>> example_file = open('example.csv')
>>> example_reader = csv.reader(example_file)
>>> for row in example_reader:
...     print('Row #' + str(example_reader.line_num) + ' ' + str(row))
... 
Row #1 ['4/5/2014 13:34', 'Apples', '73']
Row #2 ['4/5/2014 3:41', 'Cherries', '85']
Row #3 ['4/6/2014 12:46', 'Pears', '14']
Row #4 ['4/8/2014 8:59', 'Oranges', '52']
Row #5 ['4/10/2014 2:07', 'Apples', '152']
Row #6 ['4/10/2014 18:10', 'Bananas', '23']
Row #7 ['4/10/2014 2:40', 'Strawberries', '98']
Writerオブジェクト

Writerオブジェクトを用いると、データをCSVファイルに書き込むことができる。

>>> import csv
>>> output_file = open('output.csv', 'w', newline='') # 書き込みモードでファイルを開く
>>> output_writer = csv.writer(output_file) # Writerオブジェクトを生成
>>> output_writer.writerow(['spam', 'eggs', 'bacon', 'ham']) # writerowメソッドはリストを引数にとる
21
>>> output_writer.writerow(['Hello, world!', 'eggs', 'bacon', 'ham'])
29
>>> output_writer.writerow([1, 2, 3.141592, 4])
16
>>> output_file.close()
キーワード引数delimiterとlineterminator

カンマの代わりにタブ文字でセルを区切り、1行空きにする。

>>> import csv
>>> csv_file = open('example.tsv', 'w', newline='')
>>> csv_writer = csv.writer(csv_file, delimiter='\t', lineterminator='\n\n') # セル間の文字がタブ文字、行間の文字は2つの改行文字
>>> csv_writer.writerow(['apples', 'oranges', 'grapes'])
23
>>> csv_writer.writerow(['eggs', 'bacon', 'ham'])
16
>>> csv_writer.writerow(['spam', 'spam', 'spam', 'spam', 'spam', 'spam'])
31
>>> csv_file.close()

プロジェクト:CSVファイルから見出しを削除する

#! python3

import csv, os

# 見出しを削除したCSVファイルを書き込むheaderRemovedというフォルダを作る
os.makedirs('headerRemoved', exist_ok=True)

# カレントディレクトリの全ファイルをループする
for csv_filename in os.listdir('.'):
  if not csv_filename.endswith('.csv'):
    continue # CSVファイルでなければスキップ
  print('見出し削除中 ' + csv_filename + '...')

# CSVファイルを書き込む
# 最初の行を削除するのではなく、最初の行を除いたCSVファイルの複製を作る。
# プログラムには、最初の行をループ中かどうかを追跡する手段が必要。

# CSVファイルを読み込む(最初の行をスキップする)
csv_rows = []
csv_file_obj = open(csv_filename)
reader_obj = csv.reader(csv_file_obj)
for row in reader_obj:
  if reader_obj.line_num == 1: # line_num属性を使えば、CSVファイルから現在読み込んでいる行番号がわかる
    continue
  csv_rows.append(row)
csv_file_obj.close()

# カレントディレクトリの全ファイルをループする
for csv_filename in os.listdir('.'):
  if not csv_filename.endswith('.csv'):
    continue # CSVファイルでなければスキップ
  # CSVファイルを書き出す
  csv_file_obj = open(os.path.join('headerRemoved', csv_filename), 'w', newline='')
  csv_writer = csv.writer(csv_file_obj) # WriterオブジェクトはheaderRemovedフォルダの中にReaderと同じcsv_filenameという名前のCSVファイルとしてcsv_rowsを書き込む。
  for row in csv_rows:
    csv_writer.writerow(row) # csv_rowsをループして行をファイルに書き出す
  csv_file_obj.close()

JSONAPI

loads()関数を用いてJSONを読み込む
>>> string_of_json_data = '{"name": "Zophie", "isCat": true, "miceCaught": 0, "felineIQ": null}' # JSON文字列は常にダブルクオートで囲む
>>> import json
>>> json_data_as_python_value = json.loads(string_of_json_data)
>>> json_data_as_python_value
{'name': 'Zophie', 'isCat': True, 'miceCaught': 0, 'felineIQ': None}
dumps()関数を用いてJSONを書き出す
>>> python_value = '{"name": "Zophie", "isCat": true, "miceCaught": 0, "felineIQ": null}'
>>> import json
>>> string_of_data = json.dumps(python_value)
>>> string_of_data
'"{\\"name\\": \\"Zophie\\", \\"isCat\\": true, \\"miceCaught\\": 0, \\"felineIQ\\": null}"'
天気予報データを取得する

プログラムは次の処理を行う。

  • コマンドラインから地名を読み込む。
  • OpenWeatherMap.orgからJSON形式で天気予報データをダウンロードする。
  • JSONデータ文字列をPythonのデータ構造に変換する。
  • 今日と明日、明後日の天気予報を表示する。

そのため、次のコードを実装する必要がある。

  • sys.argvの文字列を結合して地名を取得する。
  • requests.get()を呼び出して天気予報データをダウンロードする。
  • json.loads()を呼び出し、JSONデータをPythonのデータ構造に変換する。
  • 天気予報を表示する。

7章 正規表現によるパターンマッチング【退屈なことはPythonにやらせよう】

# 正規表現を使わないテキストパターン検索
from pyexpat.errors import messages


def is_phone_number(text):
  if (len(text) != 12): # 文字列長がぴったり12文字かどうか調べる
    return False
  for i in range(0,3):
    if not text[i].isdecimal():
      return False
  if text[3] != '-': # 市外局番の後がハイフンになっていること
    return False
  for i in range(4,7):
    if not text[i].isdecimal(): # 3桁の数字が続くこと
      return False
  if text[7] != '-': # もう一度ハイフンがくること
    return False
  for i in range(8, 12):
    if not text[i].isdecimal(): # 最後に4桁の数字であること
      return False
  return True # すべて一致すればTrueを返す

print('415-555-4242 は電話番号:')
print(is_phone_number('415-555-4242'))
print('Moshi moshi は電話番号:')
print(is_phone_number('Moshi moshi'))
# 415-555-4242 は電話番号:
# True
# Moshi moshi は電話番号:
# False
message = '明日415-555-1011に電話してください。オフィスは415-555-9999です。'
for i in range(len(message)):
  chunk = message[i:i+12] # messageから12文字のまとまりを切り出して変数chunkに格納する。
  if is_phone_number(chunk): # 電話番号パターンに一致するか調べ、もしそうならchunkを返す。
    print('電話番号が見つかりました: ' + chunk)
print('完了')
# 電話番号が見つかりました: 415-555-1011
# 電話番号が見つかりました: 415-555-9999
# 完了
import re

phone_num_regex = re.compile(r'\d\d\d-\d\d\d-\d\d\d\d')
mo = phone_num_regex.search('私の電話番号は415-555-4242です。') # moという変数名はMatchオブジェクトに用いられる汎用名。
print('電話番号が見つかりました: ' + mo.group())
# 電話番号が見つかりました: 415-555-4242
# 電話番号を、市外局番とそれ以外に分けて取得したい。そうするには(\d\d\d)-(\d\d\d-\d\d\d\d)のように正規表現に丸カッコを追加して「グループ」を作成する。
# 最初の丸カッコで囲まれたグループは、グループ1となる。2番めはグループ2である。group()メソッドに整数1や2を渡すと、マッチした文字列の異なる部分を取得することができる。
phone_num_regex = re.compile(r'(\d\d\d)-(\d\d\d-\d\d\d\d)')
mo = phone_num_regex.search('私の電話番号は415-555-4242です。')
print(mo.group(1))
# 415
print(mo.group(2))
# 555-4242
print(mo.group(0))
# 415-555-4242
print(mo.group())
# 415-555-4242
# すべてのグループを一度に取得したいときには、groups()という複数形の名前に鳴ったメソッドを用いる。
mo.groups()
# 415-555-4242
area_code, main_number = mo.groups() # 複数代入の方法を使って、別々の変数にそれぞれの値を代入する。
print(area_code)
# 415
print(main_number)
# 555-4242
# 正規表現では丸カッコは特別な意味を持つが、丸カッコを検索したいときはバックスラッシュを使って(と)をエスケープする必要がある。
phone_num_regex = re.compile(r'(\(\d\d\d\)) (\d\d\d-\d\d\d\d)')
mo = phone_num_regex.search('私の電話番号は (415) 555-4242です。')
print(mo.group(1))
# (415)
print(mo.group(2))
# 555-4242
# 縦線を使って複数のグループとマッチする
# | 文字は「縦線」と呼ばれている。複数のパターンの内ひとつとマッチすることができる。
import re

hero_regex = re.compile(r'Batman|Tina Fey')
mo1 = hero_regex.search('Batman and Tina Fey')
print(mo1.group())
# Batman
mo2 = hero_regex.search('Tina Fey and Batman')
print(mo2.group())
# Tina Fey

bat_regex = re.compile(r'Bat(man|mobile|copter|bat)')
mo = bat_regex.search('Batmobile lost a wheel')
print(mo.group())
# Batmobile
print(mo.group(1))
# mobile

# メソッドmo.group()を引数を付けずに呼び出すと、マッチした文字列全体’Batmobile'を取得でき、mo.group(1)のようび呼び出すと、1番目の松カッコのグループにマッチした文字列だけを取得することができる。
# 疑問符を用いた任意のマッチ
import re
bat_regex = re.compile(r'Bat(wo)?man')
mo1 = bat_regex.search('The Adventures of Batman')
print(mo1.group())
# Batman
mo2 = bat_regex.search('The Adventures of Batwoman')
print(mo2.group())
# Batwoman

# 正規表現の(wo)?の部分は、woというパターンが任意のグループであることを意味する。
# 正規表現はwoが0回もしくは1回現れるテキストにマッチする。そのため、'Batman'にも'Batwoman'にもマッチする。
# 電話番号の例で、市外局番の有無にかかわらず電話番号を検索したい。
import re
phone_regex = re.compile(r'(\d\d\d-)?\d\d\d-\d\d\d\d')
mo1 = phone_regex.search('私の電話番号は415-555-4242です。')
print(mo1.group())
# 415-555-4242
mo2 = phone_regex.search('私の電話番号は555-4242です。')
print(mo2.group())
# 555-4242

# ?は「直前のグループに0回か1回マッチする」と考えることができる。
# 疑問符の文字そのものにマッチさせたいときには、バックスラッシュを使って\?のようにエスケープする。
# アスタリスクを用いた0回以上のマッチ
# アスタリスク*は、「0回以上にマッチする」という意味である。すなわち、アスタリスクの直前のグループが任意の回数出現してもよいということ。まったく出現しなくてもよいし、繰り返し出現しても構わない。

import re
bat_regex = re.compile(r'Bat(wo)*man')
mo1 = bat_regex.search('The adventure of Batman')
print(mo1.group())
# Batman
mo2 = bat_regex.search('The adventure of Batwoman')
print(mo2.group())
# Batwoman
mo3 = bat_regex.search('The adventure of Batwowowowoman')
print(mo3.group())
# Batwowowowoman

# 'Batman'に対しては(wo)*の正規表現のwoが0回出現する場合としてマッチする。'Batman'に対してはwoが1回、'Batwowowowoman'に対しては4回、それぞれ出現する場合としてマッチする。
# プラスを用いた1回以上のマッチ
# プラス + は「1回上マッチする」ことを意味する。アスタリスクは直前のグループで出現しなくても良いが、プラスは少なくとも1回は出現する必要がある。

import re
bat_regex = re.compile(r'Bat(wo)+man')
mo1 = bat_regex.search('The Adventures of Batwoman')
print(mo1.group())
# Batwoman

mo2 = bat_regex.search('The Adventures of Batwowowowoman')
print(mo2.group())
# Batwowowowoman

mo3 = bat_regex.search('The Adventures of Batman')
print(mo3 == None)
# True
# 'Bat(wo)+man'は、woが少なくとも1回出現する必要があるので、'Batman'にはマッチせず、mo3はNoneになる。
# 波カッコを用いて繰り返し回数を指定する。
# グループの繰り返し回数を指定したいときには、波カッコの中に回数を指定する。
# 繰り返し回数はひとつの文字だけではなく、カンマで区切って最小値と最大値を指定することもできる。
# 次の2つの正規表現は同じパターンにマッチする
# (Ha){3}
# (HaHaHa)

# 次の2つの表現も同じパターンにマッチする
# (Ha){5}
# (HaHaHa|HaHaHaHa|HaHaHaHaHa)

import re

ha_regex = re.compile(r'(Ha){3,5}')
mo1 = ha_regex.search('HaHaHaHaHa')
print(mo1.group)
# HaHaHaHaHa
mo2 = ha_regex.search('Ha')
print(mo2 == None)
# True

# (Ha){3}は'HaHaHa'にマッチするが、'Ha'にはまっちしない
# 貪欲マッチと非貪欲マッチ
# Pythonの正規表現は、デフォルトでは「貪欲(greedy)」にマッチする。つまり、複数の可能性がある場合には最も長いものにマッチする。一方、閉じカッコの後に疑問符を付けると「非貪欲」なマッチを意味し、最も短いものにマッチするようになる。

import re

greedy_Ha_regex = re.compile(r'(Ha){3,5}')
mo1 = greedy_Ha_regex.search('HaHaHaHaHa')
print(mo1.group)
# HaHaHaHaHa
nongreedy_Ha_regex = re.compile(r'(Ha){3,5}?')
mo2 = nongreedy_Ha_regex.search('HaHaHaHaHa')
print(mo2.group)
# HaHaHa
# findallメソッド
# Regexオブジェクトには、search()メソッドの他にfindall()メソッドがある。search() が最初に見つかった文字列のMatchオブジェクトを返すのに対し、findall()はすべての文字列を返す。

import re

phone_num_regex = re.compile(r'\d\d\d-\d\d\d-\d\d\d\d')
mo = phone_num_regex.search('Cell: 415-555-9999 Work: 212-555-0000')
print(mo.group())
# 415-555-9999

# これに対しfindall()はMatchオブジェクトではなく文字列のリストを返す。リストの各要素は、正規表現にマッチした文字列。

phone_num_regex = re.compile(r'\d\d\d-\d\d\d-\d\d\d\d')
print(phone_num_regex.findall('Cell: 415-555-9999 Work: 212-555-0000'))
# ['415-555-9999', '212-555-0000']

# 正規表現にグループが含まれていると、findall()はタプルのリストを返す。各タプルの要素は、正規表現のグループに対応してマッチした文字列。
phone_num_regex = re.compile(r'(\d\d\d)-(\d\d\d)-(\d\d\d\d)')
print(phone_num_regex.findall('Cell: 415-555-9999 Work: 212-555-0000'))
# [('415', '555', '9999'), ('212', '555', '0000')]

文字集合

一般的な文字集合を表す短縮形

aa

|短縮形|意味| |\d|0〜9の数字| |\D|0〜9の数字以外| |\w|文字、数字、下線| |\W|文字、数字、下線以外| |\s|スペース、タブ、改行(空白spaceのs)| |\S|スペース、タブ、改行以外|

import re

xmas_regex = re.compile(r'\d+\s\w+')
print(xmas_regex.findall('12 drummers, 11 pipers, 10 lords, 9 ladies, 8 maids, 7 swans, 6 geese, 5 rings, 4 birds, 3 hens, 2 doves, 1 partridge'))
# ['12 drummers', '11 pipers', '10 lords', '9 ladies', '8 maids', '7 swans', '6 geese', '5 rings', '4 birds', '3 hens', '2 doves', '1 partridge']
# 独自に文字集合を定義する
# \d、\w、\sのような短縮形では、文字の集合が広すぎる場合には、角カッコを使って独自の文字集合を定義することができる。
# [aeiouAEIOU]は、大文字と小文字の母音にマッチする。

import re

vowel_regex = re.compile(r'[aeiouAEIOU]')
print(vowel_regex.findall('RoboCop eats baby food. BABY FOOD.'))
# ['o', 'o', 'o', 'e', 'a', 'a', 'o', 'o', 'A', 'O', 'O']

# ハイフンを使って文字や数字の範囲を指定することもできる。例えば、[a-zA-Z0-9]という文字集合は、小文字と大文字と数字にマッチする。
# [の直後にキャレット記号(^)を付けると、文字の「補集合」となる。補修号とは、定義した文字集合以外とマッチするという意味である。

consonant_regex = re.compile(r'[^aeiouAEIOU]')
print(consonant_regex.findall('RoboCop eats baby food. BABY FOOD.'))
# ['R', 'b', 'C', 'p', ' ', 't', 's', ' ', 'b', 'b', 'y', ' ', 'f', 'd', '.', ' ', 'B', 'B', 'Y', ' ', 'F', 'D', '.']

# 母音ではなく、母音以外の文字とマッチするようになった
# キャレットとドル記号
# キャレット(^)には別の使い方があり、検索対象の文字列の先頭にマッチすることを指定するときにも使う。同様に、ドル記号($)は文字列の末尾にマッチすることを表す。^と$を同時に使うと、文字列全体が正規表現とマッチすることを表す。つまり、文字列の一部がマッチするだけでは不十分になるのである。
import re

begins_with_hello = re.compile(r'^Hello')
print(begins_with_hello.search('Hello world!'))
# <re.Match object; span=(0, 5), match='Hello'>
print(begins_with_hello.search('He said Hello') == None)
# True

# \d$という表現は、0〜9の数字で終わる文字列にマッチする。
ends_with_number = re.compile(r'\d$')
print(ends_with_number.search('Your number is 42'))
# <re.Match object; span=(16, 17), match='2'>
print(ends_with_number.search('You are 42 years old.') == None)
# True

# ^\d+$という正規表現は、全体が1文字以上の数字である文字列とマッチする。
whole_string_is_num = re.compile(r'^\d+$')
print(whole_string_is_num.search('1234567890'))
# <re.Match object; span=(0, 10), match='1234567890'>
print(whole_string_is_num.search('12345xyz67890') == None)
# True
print(whole_string_is_num.search('12 34567890') == None)
# True
# ワイルドカード文字
# 正規表現ではドット(.)は「ワイルドカード」といい、改行以外の任意の文字列とマッチする。
import re

at_regex = re.compile(r'.at')
print(at_regex.findall('The cat in the hat sat on the flat mat.'))
# ['cat', 'hat', 'sat', 'lat', 'mat']

# ドットとアスタリスクであらゆる文字列とマッチする
# どんな文字列でもマッチしたいことがある。例えば、'First Name:'(名)にマッチした後に続く文字列や、'Last Name:'(姓)にマッチした後に続く文字列など。このような「なんでも」に相当する正規表現は、.*と書く。ドット¥は「改行以外の任意の1文字」であり、アスタリスクは「直前のパターンの0回以上の繰り返し」を意味する。

name_regex = re.compile(r'First Name:(.*) Last Name:(.*)')
mo = name_regex.search('First Name: Al Last Name: Sweigart')
print(mo.group(1))
# Al
print(mo.group(2))
# Sweigart

# .*は貪欲モードなので、できるだけ長い文字列にマッチする。非貪欲モードにしたいときは、.*?と疑問符を付ける。前記の波カッコ{}の場合と同様に、疑問符?は非貪欲モードにすることを意味する。

nongreedy_regex = re.compile(r'<.*?>') # 非貪欲
mo = nongreedy_regex.search('<To serve man> for dinner.>')
print(mo.group())
# <To serve man>
greedy_regex = re.compile(r'<.*>') # 貪欲
mo = greedy_regex.search('<To serve man> for dinner.>')
print(mo.group())
# <To serve man> for dinner.>

# ドットを改行とマッチさせる
# .*は改行以外のあらゆる文字列とマッチする。re.compile()の第2引数として、re.DOALLを渡すと、ドット文字が改行を含むすべての文字とマッチするようになる。
no_new_line_regex = re.compile(r'.*')
print(no_new_line_regex.search('Serve the public trust.\nProtect the innocent.\nUphold the low.').group())
# Serve the public trust.
new_line_regex = re.compile(r'.*', re.DOTALL)
print(new_line_regex.search('Serve the public trust.\nProtect the innocent.\nUphold the low.').group())
# Serve the public trust.
# Protect the innocent.
# Uphold the low.

正規表現に用いる記号のまとめ

  • ?は、直前のグループの0回か1回の出現にマッチする。
  • *は、直前のグループの0回以上の出現にマッチする。
  • +は、直前のグループの1回以上の出現にマッチする。
  • {n}は、直前のグループのn回の出現にマッチする。
  • {n,}は、直前のグループのn回以上の出現にマッチする。
  • {,m}は、直前のグループの0〜m回の出現にマッチする。
  • {n,m}は、直前のグループのn〜m回の出現にマッチする。
  • {n,m}?、{*?}、{+?}は、直前のグループの非貪欲マッチを行う。
  • ^spam$は、「spam」から始まる文字列とマッチする。
  • spam$は、「spam」で終わる文字列とマッチする。
  • .は、改行文字以外の任意の1文字とマッチする。
  • \d、\w、\sは、それぞれ、数字、単語を構成する文字、空白文字にマッチする。
  • \D、\W、\Sは、それぞれ、数字、単語を構成する文字、空白文字以外の文字にマッチする。
  • [abc]は、角カッコの中の任意の1文字にマッチする。
  • [^abc]は、角カッコの中の文字以外の任意の1文字にマッチする。
# 大文字・小文字を無視したマッチ
# 通常は、正規表現は大文字と小文字を区別してマッチする。例えば、次の正規表現はまったく異なる文字列とマッチする。
import re

regex1 = re.compile('Robocop')
regex1 = re.compile('ROBOCOP')
regex1 = re.compile('robOcop')
regex1 = re.compile('RobocOp')

# けれども、大文字と小文字を区別せずにマッチしたい場合、re.compile()に、re.IGNORECASEもしくはre.Iオプションを渡す。
robocop = re.compile(r'robocop', re.I)
print(robocop.search('RoboCop is part of man, part machine, all cop.').group())
# RoboCop
print(robocop.search('ROBOCOP protects the innocent.').group())
# ROBOCOP
print(robocop.search('Al, why does your programming book talk robocop so much?.').group())
# robocop
# sub()メソッドを用いて文字列を置換する
# 正規表現は文字列のパターンを検索するだけでなく、文字列を置換するのにも使える。Regexオブジェクトのsub()メソッドは引数を2つとる。第1引数は置き換える文字列で、第2引数は検索置換対象の文字列である。sub()メソッドは、置換後の文字列を返す。
from os import name
import re

names_regex = re.compile(r'Agent \w+')
print(names_regex.sub('CENSORED', 'Agent Alice gave the secret docments to Agent Bob.'))
# CENSORED gave the secret docments to CENSORED.

# マッチした文字列を、置き換えの一部として使いたいときもある。そうするには、sub()の第1引数に、\1、\2、\3のように、グループ番号を使って記述する。
# 例えば、秘密諜報員の名前を検閲し、頭文字だけで表示したいとする。そうするには、(\w)\w*という正規表現を用いて、sub()の第1引数に、\1****を渡します。\1はグループ1にマッチした文字列、この場合は正規表現の(\w)に置き換わる。

agent_names_regex = re.compile(r'Agent (\w)\w*')
print(agent_names_regex.sub(r'\1****', 'Agent Alice told Agent Carol that Agent Eve knew Agent Bob was a double agent.'))
# A**** told C**** that E**** knew B**** was a double agent.
# 複雑な正規表現を管理する
# 正規表現は、マッチしようとするパターンが単純なうちはよいが、複雑になってくると、長くてややこしい記述が必要になる。
import re

phonr_regex = re.compile(r'((\d{3}|\(\d{3}\))?(\s|-|\.)?\d{3}(\s|-|\.)\d{4}(\s*(ext|x|ext.)\s*\d{2,5})?)')

# 以上のような解読困難な1行の正規表現を、次のように、複数行に分けてコメントを付けて分かりやすく記述することができる。

phonr_regex = re.compile(r'''(
  (\d{3}|\(\d{3}\))? # 3桁の市外局番(()がついていても良い)
  (\s|-|\.)? # 区切り(スペースかハイフンかドット)
  \d{3} # 3桁の市外局番
  (\s|-|\.) # 区切り
  \d{4} # 4桁の番号
  (\s*(ext|x|ext.)\s*\d{2,5})? # 2〜5桁の内線番号
)''', re.VERBOSE)
# 電話番号とメールアドレスの抽出

# 1.電話番号の正規表現を作る
import pyperclip, re

phone_regex = re.compile(r'''(
  (\d{3}|\(\d{3}\))? # 市外局番
  (\s|-|\.)? # 区切り
  (\d{3}) # 3桁の番号
  (\s|-|\.) # 区切り
  (\d{4}) # 4桁の番号
  (\s*(ext|x|ext.)\s*(\d{2,5}))? # 内線番号
)''')

# 電子メールの正規表現を作る
email_regex = re.compile(r'''(
  [a-zA-Z0-9._%+-]+ # ユーザー名
  @ # @記号
  [a-zA-Z0-9.-]+ # ドメイン名
  (\.[a-zA-Z]{2,4}) # ドットなんとか
)''', re.VERBOSE)

# クリップボードのテキストを検索
text = str((pyperclip.paste()))
matches = []
for groups in phone_regex.findall(text):
  phone_num = '-'.join(groups[1], groups[3], groups[5])
  if groups[8] != '':
    phone_num += ' x' + groups[8]
  matches.append(phone_num)
for groups in email_regex.findall(text):
  matches.append(groups[0])

# 検索結果をクリップボードに貼り付ける
if len(matches) > 0:
  pyperclip.copy('\n'.join(matches))
  print('クリップボードにコピーしました')
  print('\n'.join(matches))
else:
  print('電話番号やメールアドレスは見つかりませんでした。')

6章 文字列操作【退屈なことはPythonにやらせよう】

# エスケープ文字
spam = 'Say hi to Bob\'s mother.'
print(spam)
# Say hi to Bob's mother.

print("Hello there!\nHow are you?\nI\'m doing fine.")
# Hello there!
# How are you?
# I'm doing fine.

# raw文字列
print(r'That is Carol\'s cat.')
# That is Carol\'s cat.

# 三連クォートによる複数行文字列
print('''Dear Alice

Eve's cat has been arrested for catnapping, cat burglary, and extortion.

Sincerely,
Bob''')
# Dear Alice

# Eve's cat has been arrested for catnapping, cat burglary, and extortion.

# Sincerely,
# Bob
# 文字列のインデックスとスライス
spam = 'Hello world!'
print(spam[0])
# H
print(spam[4])
# o
print(spam[-1])
# !
print(spam[0:5])
# Hello
print(spam[6:])
# world!

# ひとつの変数からスライスを取得して別の変数に格納しておく
spam = 'Hello world!'
fizz = spam[0:5]
print(fizz)
# Hello
print(spam)
# Hello world!
# 文字列に対するinとnot in演算子
print('Hello' in 'Hello World!')
# True
print('Hello' in 'Hello')
# True
print('HELLO' in 'Hello World!')
# False
print('cats' not in 'cats and dogs')
# False
# 便利な文字列メソッド
# upper(),lower(),isupper(),islower()
spam = 'Hello World!'
spam = spam.upper()
print(spam)
# HELLO WORLD!
spam = spam.lower()
print(spam)
# hello world!

print('How are you?')
feeling = input()
if feeling.lower() == 'great':
  print('I feel great too.')
else:
  print('I hope the rest of your day is good.')

# greatの揺らぎとしてGReatと入力しても、I feel great too.と表示される。
# 大文字と小文字の不統一のような、ユーザー入力の揺らぎや間違いを適切に扱うコードを補うことによって、プログラムが使いやすくなり失敗も減る。

spam = 'Hello world!'
print(spam.islower())
# False
print(spam.isupper())
# False
print('Hello'.isupper())
# True
print('abc12345'.islower())
# True
print('12345'.islower())
# False
print('12345'.isupper())
# False
# upper()とlower()は文字列を返す
print('Hello'.upper())
# HELLO
print('Hello'.upper().lower())
# hello
print('Hello'.upper().lower().upper())
# HELLO
print('HELLO'.lower())
# hello
print('HELLO'.lower().islower())
# True
# isXというメソッド

# 1文字以上の英文字だけから文字列が構成されている場合にTrueを返す
print('hello'.isalpha())
# True
print('hello123'.isalpha())
# False

# 1文字以上の英文字化数字から構成されている場合にTrueを返す
print('hello123'.isalnum())
# True
print('hello'.isalnum())
# True

# 1文字以上の英数字か数字から構成されている場合にTrueを返す
print('123'.isdecimal())
# True

# 文字列がスペースかタブか改行だけで構成されている場合にTrueを返す
print(' '.isspace())
# True

# 大文字から始まり残りがすべて小文字の英単語から構成されている場合にTrueを返す
print('This Is Title Case'.istitle)
# True
print('This Is Title Case 123'.istitle)
# True
print('This Is not Title Case'.istitle)
# False
print('This Is NOT Title Case Either'.istitle)
# False
# isX系の文字列メソッドは、ユーザー入力を検証する場合に便利である。
# 例えば、次のプログラムは、正しい入力が得られるまで繰り返しユーザーに年齢とパスワードを入力してもらうものである。
while True:
  print('年齢を入力してください')
  age = input()
  if age.isdecimal():
    break
  print('年齢は数字で入力してください')

while True:
  print('新しいパスワードを入力してください')
  password = input()
  if password.isalnum():
    break
  print('パスワードは英数字で入力してください')
# startswith()メソッドとendswith()メソッド
# 対象の文字列がメソッドに渡された文字列から始まるか、または終わる場合にTrueを返し、そうでなければFalseを返す。
print('Hello world!'.startswith('Hello'))
# True
print('Hello world!'.endswith('world!'))
# True
print('abc123'.startswith('abcdef'))
# False
print('abc123'.endswith('12'))
# False
print('Hello world!'.startswith('Hello world!'))
# True
print('Hello world!'.endswith('Hello world!'))
# True
# join()とsplit()メソッド
print(', '.join(['cats', 'rats', 'bats']))
# cats, rats, bats
print(' '.join(['My', 'name', 'is', 'Simon']))
# My name is Simon
print('ABC'.join(['My', 'name', 'is', 'Simon']))
# MyABCnameABCisABCSimon

print('My name is Simon'.split())
# ['My', 'name', 'is', 'Simon']
print('MyABCnameABCisABCSimon'.split('ABC'))
# ['My', 'name', 'is', 'Simon']
print('My name is Simon'.split('m'))
# ['My na', 'e is Si', 'on']

spam = '''Dear Alice,
How are you been? I am fine.
There is a container in the fridge
that is labeled "Milk Experiment".
Please do not drink it.
Sincerely,
Bob'''
print(spam.split('\n'))
# ['Dear Alice,', 'How are you been? I am fine.', 'There is a container in the fridge', 'that is labeled "Milk Experiment".', 'Please do not drink it.', 'Sincerely,', 'Bob']
# rjust(),ljust(),center()メソッドを用いてテキストを揃える
from turtle import left


print('Hello'.rjust(10))
#      Hello
print('Hello'.rjust(20))
#                Hello
print('Hello World!'.rjust(20))
#         Hello World!
print('Hello'.ljust(10))
# Hello

print('Hello'.rjust(20, '*'))
# ***************Hello
print('Hello'.ljust(20, '-'))
# Hello---------------
print('Hello'.center(20, '='))
# =======Hello========
def print_picnic(items_dict, left_width, right_width):
  print('PICNIC ITEMS'.center(left_width + right_width, '-'))
  for k, v in items_dict.items():
    print(k.ljust(left_width, '.') + str(v).rjust(right_width))

picnic_items={'sandwiches': 4, 'apples': 12, 'cups': 4, 'cookies': 8000}
print_picnic(picnic_items, 12, 5)
print_picnic(picnic_items, 20, 6)

# ---PICNIC ITEMS--
# sandwiches..    4
# apples......   12
# cups........    4
# cookies..... 8000
# -------PICNIC ITEMS-------
# sandwiches..........     4
# apples..............    12
# cups................     4
# cookies.............  8000
# strir(),rstrip(),lstrip()メソッドを用いて空白文字を除去する
spam='        Hello World        '
print(spam.strip())
# 'Hello World'
print(spam.lstrip())
# 'Hello World        '
print(spam.rstrip())
# '        Hello World'
# 除去すべき文字を文字列の引数として渡すことができる。
spam='SpamSpamBaconSpamEggsSpamSpam'
print(spam.strip('ampS'))
# BaconSpamEggs
# strip()に'ampS'という引数を渡すと、a,m,p,大文字のSを、文字列の両端から除去する。
# pyperclipモジュールを用いて文字列をコピー&ペーストする
# コンピュータのクリップボードとテキストを授受することができる。
import pyperclip
pyperclip.copy('Hello world!')
pyperclip.paste()
# Hello world!
# ModuleNotFoundError: No module named 'pyperclip'と表示されたらpyperclipをインストールする。
# pip install pyperclip