Python初心者の備忘録 #03

# はじめに
今回私は最近はやりのchatGPTに興味を持ち、深層学習について学んでみたいと思い立ちました！  
深層学習といえばPythonということなので、最終的にはPythonを使って深層学習ができるとこまでコツコツと学習していくことにしました。  
ただ、勉強するだけではなく少しでもアウトプットをしようということで、備忘録として学習した内容をまとめていこうと思います。  
この記事が少しでも誰かの糧になることを願っております！
**※投稿主の環境はWindowsなのでMacの方は多少違う部分が出てくると思いますが、ご了承ください。**
最初の記事：[Python初心者の備忘録 #01](https://qiita.com/Yushin-Tati/items/961dc47d6163f944f7e9)
前の記事：[Python初心者の備忘録 #02](https://qiita.com/Yushin-Tati/items/52a590d5000fe7b90185)
次の記事：[Python初心者の備忘録 #04](https://qiita.com/Yushin-Tati/items/83aa101d7673e69d9279)

今回の記事はpackageやmodule、importついてまとめてあります。
## ■学習に使用している資料
Udemy：[米国AI開発者がゼロから教えるPython入門講座](https://www.udemy.com/course/python-ai/)

## ■モジュール(module)
### ▶import
別のファイルを呼び出して、そのファイル内の変数や関数を使用できるようにする方法で`import`の後に絶対パス、もしくは相対パスで呼び出したいファイルを定義する。
`from <path> import <object>`とすることで、ファイル内の特定のオブジェクトのみimportすることもできる。
基本的にimportを定義する順番は標準ライブラリ→サードパーティライブラリ→自分のライブラリ→ローカルファイルでabc順で定義する。それぞれの塊の間はスペースを開けるとよりわかりやすい。
importしたファイル内のオブジェクトを呼び出したい場合は`<path>.<object>`とする。
`import <path> as <name>`とすることで、<path>の部分を好きな<name>に置き換えることができるが、一般的なもの以外には使用しない。(例：`import numpy as np`、`import pandas as pd`など)
`*`で<path>配下のすべてをimportできるが、`*`では「_」から始まるnonpublicな関数はimportできない仕様となっている。そもそも、`*`は後述する「__init__.py」以外では非推奨となっている。
```python:mymodule.py
global_variable = "This is grlobal variable"

def myfunc():
    print("This is my function!!")


def anotherfunc():
    print("This is another function!!")


def _internal_use_only():
    print("I'm internal use only!!")

```
```python:myscript.py
# 標準ライブラリ
import sys

# ローカルファイル
import mymodule

# mymodule.xxの形でグローバル変数や関数にアクセスできる
mymodule.myfunc()
print(mymodule.global_variable)
```
### ▶サードパーティライブラリ
サードパーティライブラリを使用したい場合はコンソール画面で`pip install <library name>`とすることで、簡単に現在の環境に別の人が作ったパッケージやライブラリを取り入れることができる。
pipでインストールできるライブラリは[pyPI](https://pypi.org/)というサイトにまとめられている。
もちろんインストールしただけでは使えないので、importする必要がある。
### ▶built in module(標準ライブラリ)
下記サイトに一覧が乗っている。
https://docs.python.org/3/py-modindex.html
#### ・re(正規表現)
`re.search('regex', <str>)`という形で与えられたstr型オブジェクトに対して、あるルール(`regex`)でフィルタリングをかけることができるモジュール。
そのルールに合致していればTrue、合致しなければFalseを返す。
```python
import re

# 正規表現(Regular Expression　通称RegEx)

email = "myemail@gmail.com"
# in演算子を使うことで文字を含んでいるか確認できる
print("@" in email)

# re.searchを使うことで，より高度なマッチングが可能
matched = re.search('@\w+\.', email)
if matched:
    print(matched)
    print("Matched!!")

else:
    print("Not found!!")


# []
print(re.search('[abc]', 'a'))  # match
print(re.search('[abc]', 'apple'))  # match
print(re.search('[abc]', 'd'))  # no match
print(re.search('[a-c]', 'b'))  # match
print(re.search('[0-9]', '0'))  # match
print(re.search('[0-9]', '5'))  # match
print(re.search('[0-9]', 'a'))  # no match
print(re.search('[0-9]', 'a1'))  # match

# ^ は最初の文字
print(re.search('^[0-9]', '0test'))  # match
print(re.search('^[0-9]', 'test'))  # no match

# {n}はn回リピート(以下の例は最初は4桁の数字）
print(re.search('^[0-9]{4}', '2021/3/31'))  # match
print(re.search('^[0-9]{4}', '21/3/31'))  # no match
# {n,m}: 最低n回，最高m回リピート
print(re.search('^[0-9]{2,4}', '2021/3/31'))  # match
print(re.search('^[0-9]{2,4}', '21/3/31'))  # match

# $は最後の文字
print(re.search('[0-9]{2}$', '2021/3/31'))  # match
print(re.search('[0-9]{2}$', '2021/3/1'))  # no match

# *は左のパターンを0回以上繰り返す
print(re.search('a*b', 'b'))  # match
print(re.search('a*b', 'ab'))  # match

# +は左のパターンを1回以上繰り返す
print(re.search('a+b', 'ab'))  # match
print(re.search('a+b', 'a'))  # no match
print(re.search('a+b', 'b'))  # no match

# ?は左のパターンを0回か1回繰り返す
print(re.search('ab?c', 'abc'))  # match
print(re.search('ab?c', 'abbbc'))  # no match
print(re.search('ab?c', 'bc'))  # no match
print(re.search('abc?c', 'abc'))  # match

# |はor
print(re.search('abc|012', 'abc'))  # match
print(re.search('abc|012', '012'))  # match
print(re.search('abc|012', '01'))  # no match

# ()はグループ
print(re.search('te(s|x)t', 'test'))  # match
print(re.search('te(s|x)t', 'text'))  # match

# .は任意の一文字
print(re.search('h.t', 'hat'))  # match
print(re.search('h.t', 'hut'))  # match
print(re.search('h.t', 'hot'))  # match

# \はエスケープ
print(re.search('h\.t', 'h.t'))  # match

# \wは[a-zA-Z0-9_] つまりアルファベトおよび数字とアンダースコア
print(re.search('h\wt', 'hit'))  # match
print(re.search('h\wt', 'h0t'))  # match
print(re.search('h\wt', 'hiit'))  # no match
print(re.search('h\wt', 'h.t'))  # no match
```
#### Challenge
1.input()で入力した生年月日がフォーマット(yyyy/mm/dd)に合致しているか確かめるプログラム
2.input()で入力したEメールアドレスが正しいフォーマット(下記画像)か確かめるプログラム
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3448232/9ac98544-a8e8-aa0a-5e8c-86fd78e48619.png)
<details><summary>1の解答例</summary>

```python
# challenge1
pattern_dob = '^(20|19)[0-9]{2}/([1-9]|1[0-2])/([1-9]|1[0-9]|2[0-9]|3[01])$'
while True:
    dob = input("生年月日を入力してください(yyyy/mm/dd)")
    result = re.search(pattern_dob, dob)
    if result:
        print(f"{dob}は正しいフォーマットです")
        break
    else:
        print(f"{dob}は正しくないフォーマットです")
```
</details>

<details><summary>2の解答例</summary>

```python
# challenge2
pattern_email = '^(\w|\.|\-)+@(\w|\.|\-)+\.[a-zA-Z]{2,3}$'
print(re.search(pattern_email, 'tes_t.te-st@gmail.com'))
```
</details>

#### ・time
時間に関する関数がたくさん定義されたモジュール
```python
import time

print(time.time())  # 1970/1/1からの秒数が表示される
print(time.time()/(60*60*24*365))


# @lru_cacheをつけると，結果をキャッシュで保持し，再度実行しなくなる
# 再帰関数の実行時間の長さをある程度解消できる
@lru_cache
def fib(n):
    print(f"fibonacci with {n} is running..")
    if n < 2:
        return n
    else:
        return fib(n-1) + fib(n-2)


# 関数の実行時間を計算することができる
before = time.time()
print(fib(30))
after = time.time()
print(f"recursive fibonacci took {after - before:.2f} sec.") # floatは:.nfとすることで小数点第n位まで表示すると設定できる

# .ctime(): 今のローカル時間を文字列で返す
print(time.ctime())
# .localtime(): 今のローカル時間を構造化データで返す
localtime = time.localtime()
print(localtime)
print("今の時刻は{0.tm_year}年{0.tm_mon}月{0.tm_hour}時{0.tm_min}分です".format(localtime))

# .sleep(secs): secs秒だけプログラムが待機する
sec = 10
print(f'{sec}秒待ってください')
time.sleep(sec)
print(f'{sec}秒経ちました')
```
#### Challenge
関数の実行時間(sec)を図るtimerデコレーターの作成
<details><summary>解答例</summary>

```python
# challenge
def timer(func):
    def inner(*args, **kwargs):
        before = time.time()
        func(*args, *kwargs)
        after = time.time()
        print(f'{func.__name__} took {after-before:.2f} sec')
    return inner


@timer
def lazy_func(sec):
    print(f"I'm working so hard..")
    time.sleep(sec)
    print(f"I'm finally done!!")


lazy_func(4)
```
</details>

### ▶built in function(dir())
dir()は現在アクセス可能なオブジェクトやattributeの一覧を取得することができる。
また、引数を与えることでその引数のattributeの一覧を取得できる。
__builtins__を引数に渡すと、built inの関数やattributeの一覧を取得することもできる。
built in functionの一覧は下記サイトに載っている。
https://docs.python.org/3/library/functions.html
```python
# dir()で，今アクセス可能なオブジェクトやattributeの一覧を取得できる
print(dir())
# オブジェクトを入れると，そのオブジェクトのattributeの一覧を取得できる
print(dir("1"))
# __builtins__を入れると，built in の関数やattributeの一覧を取得できる
print(dir(__builtins__))
```
### ▶__name__と__main__
__name__は実行元のスクリプトでは「\_\_main\_\_」という文字列が入り、それ以外のimportなどで実行されるスクリプト内では、そのスクリプトのファイル名が格納されている。
実はimportされたファイルはキャッシュとしてメモリに保持するために、内部的には一度実行されている。そのため、グローバルスコープに関数の呼び出しやコードの実行処理を書いているとimportの際にそのスクリプトも実行されてしまう。
なので、importの際に余計は処理が実行されないようにする方法は存在するが、グローバルスコープに実行処理を書くことは推奨されていない。
```python:mymodule.py
global_variable = "This is grlobal variable"

def myfunc():
    print("This is my function!!")


def anotherfunc():
    print("This is another function!!")


def _internal_use_only():
    print("I'm internal use only!!")


print(f"__name__ of mymodule.py: {__name__}")
# importされた際は__name__にはファイル名が格納されているので実行されない。
if __name__ == "__main__":
    myfunc()
    anotherfunc()
    _internal_use_only()
```
```python:main.py
# import時にmymodule.pyが実行される
import mymodule
print(f"__name__ of main.py:{__name__}")
```
### ▶package(パッケージ)
packageとは複数のmoduleがまとめられたディレクトリのことで、package内のmoduleには「.」でアクセスできる。(下記のように)
`mypackage.module1`
`mypackage.module2`
通常はディレクトリは「\_\_init\_\_.py」という特殊なファイルを直下に配置することで、そのディレクトリがpackageとして認識されるようになる。
「\_\_init\_\_.py」を配置しなかった場合は「名前空間パッケージ」と認識される。
名前空間パッケージのディレクトリでは、同じパッケージ名であれば、たとえpathが違うものでも同じパッケージとして認識されるようになる。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3448232/75110d67-ddb7-5f06-e680-9a0e598a664c.png)
```:ディレクトリ構造
main.py
mypackage
  |－__init__.py
  |－mymodule1.py
  |－mymodule2.py
```
```python:mymodule1.py
def myfunc():
    print("This is myfunc() from module1")
```
```python:mymodule2.py
def myfunc():
    print("This is myfunc() from module2")
```
```python:main.py
# import方法①
import mypackage.mymodule1
import mypackage.mymodule2
mypackage.mymodule1.myfunc()
mypackage.mymodule2.myfunc()

# import方法②
from mapackage import mymodule1, mymodule2
mymodule1.myfunc()
mymodule2.myfunc()

# import方法③
# この方法では同じ名前の関数があった場合に最後にimportしたmoduleの関数しか使用できなくなるの注意
from mapackage.mymodule1 import myfunc
from mapackage.mymodule2 import myfunc
# module2のmyfunc()が実行される
myfunc()
```
### ▶\_\_init\_\_.py
\_\_init\_\_.pyには初期化用コードを記述する(import時に実行したい処理)。
また、\_\_init\_\_.pyにimport文を書くことでモジュール名をスキップして、package.のあとに関数名やクラス名にアクセスできる。
\_\_init\_\_.pyでは「*」を使用したimportが許容されており、\_\_all\_\_を定義することで、「*」を使用したとしてもimportするmodule、関数、クラスを指定することができる。
```:ディレクトリ構造
main.py
mypackage
  |－__init__.py
  |－mymodule1.py
  |－subdir
      |－__init__.py
      |－mymodule2.py
```
```python:mypackage/subdir/mymodule2.py
def myfunc():
    print("This is myfunc() from module2")


def myfunc2():
    print("This is myfunc2() from module2")
```
```python:mypackage/subdir/__init__.py
import .mymodule2
# __all__にmyfuncを定義したので、myfunc2は*ではimportされなくなる
__all__ = ["myfunc"]
```
```python:mypackage/__init__.py
from mypackage.subdir.mymodule2 import *
```
```python:main.py
import mypackage
import mypackage.module1

# module2をスキップして関数にアクセスすることができている
mypackage.myfunc()
# __init__.pyでimportしていないので普通にアクセスしている
mypackage.module1.myfunc()
# __all__に定義されていないのでアクセスできない
# mypackage.myfunc2()
```
#### 次の記事
[Python初心者の備忘録 #04](https://qiita.com/Yushin-Tati/items/83aa101d7673e69d9279)