psp2

day02: vscode でプログラミング

1. 課題用のディレクトリを作る

1.1. poetry new k00

今日の課題は k00 です.

  1. エクスプローラで,適切な場所にディレクトリ k00 を作ります.
  2. Visual Studio Code を立ち上げて,今作ったフォルダ k00 を開きます.
  3. Visual Studio Code のメニューの「ターミナル」から「ターミナルを開く」
  4. Visual Studio Code のターミナルで 仮想環境の設定をします.
    1. 今開いているフォルダを,パッケージ開発用にしていきます.
       > poetry new --interactive .
      

      (最後のところ,「ドット」です.)

      いろいろ聞かれます.よく読んで答えましょう,英語ですけど.基本的にはすべて提示されたまま(ただエンターするだけ)でいいですが,Authorについては,Kotaro Sonoda <aa83988848@ms.nagasaki-u.ac.jp>などと,「自分の名前 スペース 三角カッコ メールアドレス 三角カッコ閉じ」にしましょう.

    2. Python仮想環境を作ります. アプリ作成ごとに必要なライブラリが異なってくるのが普通です. なので,ライブラリ置き場をアプリ開発専用に作ります.それが仮想環境です.
       > poetry env use py
      

      これにより,.venv/というディレクトリが新たに作られます.

      • .venv\Libs\pythonX.Y\site-packages\ がライブラリ置き場です.
    3. 仮想環境を作った後は一旦 Visual Studio Code を終了させてください.

1.2. 仮想環境に入る

仮想環境に入るときには,activate という処理が必要です.抜けるときには deactivate です.

Visual Studio Code ならば,実は .venv というフォルダを直下に持つフォルダを開いて,ターミナルを開くと自動的に activate されます.

この場合プロンプトが(k00-py3.12)となります.

Visual Studio Code のターミナルが PowerShell の場合,うまく行かない可能性があります.そのときは,

PS > Set-ExecutionPolicy RemoteSigned -Scope CurrentUser -Force

を入力してください.

2. それでは今日の課題をやっていきましょう.

2.1. k00/src/k00/k00_turtle.py

vscode を起動します.

起動直後

左端にあるアイコンは,メニューです.一番下にある帯には,現在開いているファイルに関する「状態」が表示されています.

ウィンドウのほとんどの部分は,エディタです.「ファイル名  x」という感じで,最近のウェブブラウザのように「タブ」が並びます.

初期起動時には,「Get Started」というタブが立ち上がっています.使わないので「x」を押して消してください.

一番上にあるアイコンを押してください.これは「エクスプローラ」と呼ばれています.

エクスプローラ

OPEN EDITORSには,現在開いているエディタで編集しているファイル名が並びます.先ほどタブを閉じたので何もありません.

その下にはNO FOLDER OPENEDと表示され,何やら青いボタンが並んでいます.

説明したように,「ファイルを開く」のではなく,「フォルダを開く」というボタンです.ちなみに下のボタン「Clone Repository」は「レポジトリ」と呼ばれる特殊なフォルダを開くボタンです.

さきほど作ったk00フォルダを開きます.

NO FOLDER OPENED」の表示が「k00」に変わり,

k00を開く

のように表示されていると思います.

K00」のあたりにマウスを移動させると,その帯の右側に 4 つのアイコンが並びます.それぞれのアイコンにマウスを移動させると説明が表示されますが,左から,「ファイルを追加」「フォルダを追加」「更新」「表示を縮める」です.

k00の横

なにかプログラミングしてみましょう.K00の下の src というディレクトリの下の k00というディレクトリが提出するパッケージです.まずはk00\src\k00の下にファイルを追加します.

v K00の下の> src/k00をクリックすると,k00フォルダが選択され,中身がリストアップされます.

unfold k00/k00

このフォルダで「ファイル追加」アイコンを押して,「k00_turtle.py」という名前のファイルを新しく作ってみます.

new file k00_turtle.py

OPENED EDITORS」にk00_turtle.pyが列挙され,k00_turtle.pyというタブが開きます.

もしかしたら,インタープリタを聞かれるかもしれません.そのときは,./.venv/Scripts/pythonを選びましょう.

select interpreter

k00_turtle.py に以下のようなコードを書きましょう.

# coding:utf-8

# 使うモジュールの指定
# turtleというモジュールを使う
import turtle

def main()-> None:
    """
    (このmain関数が何をするのかを書く.)
    """
    # 上の"""〜"""は,help(関数名)で表示される.

    #
    turtle.setup(width=300, height=300)

    #
    # kame1, kame2は,turtleモジュールに定義されているTurtle型
    kame1 = turtle.Turtle()
    kame2 = turtle.Turtle()

    #
    kame1.pencolor('red')
    kame2.pencolor('blue')

    #
    kame1.penup()
    kame1.goto(50,50)

    kame2.penup()
    kame2.goto(-50,50)

    kame1.pendown()
    kame2.pendown()

    #
    kame1.left(60)
    kame2.left(120)

    #
    kame1.forward(100)
    kame2.backward(200)

    #
    turtle.exitonclick()

if __name__ == '__main__':
    main()

では,このk00_turtle.pyを実行しましょう.

下に示す課題 (1) は,ここまで理解すればできるかと思います.

課題 (2) も頑張ってください.

2.2. 関数

課題 (3),(4) は 同じソースコードの中に自作関数を作ります.

例えば,k00_turtle.py

def main():
    (A)

if __name__ == '__main__':
    main()

という構造になっていますが,

if __name__ == '__main__': は単なる条件分岐で,省いてしまって単純化すると,

def main():
    (A)

main()

であり,もっと単純化すると,

(A)

と同じことをしますが,

(A) という見ただけではわからない長い処理」の全体をとりあへず「メイン関数」main() と定義したうえで,

(A)」を動かすのではなく,「メイン関数」を動かす,と主張しています.

例えば,次のソースコードは 等比数列(Geometric Sequence)の一般項を求めるものですが,

def seq_geometric(shokou, kouhi, n):
    ans = shokou
    if n == 1:
        pass
    else:
        for i in range(1,n-1):
            ans = ans * kouhi
    return ans

def main():
    a1 = int(input('初項?))
    r = int(input('公比?'))
    n = int('第何項目を求めたい?))
    an = seq_geometric(a1,r,n)
    print(an)

if __name__ == '__main__':
    main()

2.3. k00/test_k00_turtle.py:インタフェースからのパッケージ読み込み

モジュールk00_turtle.pyは,srcの下のパッケージ k00 (ディレクトリ)に含まれています.

このモジュールをインタフェースプログラムの中で呼び出して実行してみましょう.

パッケージの配置

プログラムの中でパッケージを読み出すには,一般的に,

のいずれかが必要です.

そこで,Poetryではもう1つの方法が加えられています.

これをすると,

モジュール関数の呼び出し

モジュール関数の呼び出し方は,以下のようになります.

# from (パッケージのディレクトリパス) import (モジュール名) as (別名)
from k00 import k00_turtle as my

# モジュール(別名)`my` に定義されている関数 `main()` を使うときは,
my.main()
パッケージのディレクトリパスについて

psp2@githubFIYs\reference_import.md に詳しく紹介しました.

パッケージのパス(PATH,場所)は,実行する python 環境のsys.pathに列挙されている PATH の直下に無ければいけません.

(k00-py3.12) > python
>>>> import sys
>>>> print(sys.path)
['', '/opt/homebrew/Cellar/python@3.12/3.12.3/Frameworks/Python.framework/Versions/3.12/lib/python312.zip', '/opt/homebrew/Cellar/python@3.12/3.12.3/Frameworks/Python.framework/Versions/3.12/lib/python3.12', '/opt/homebrew/Cellar/python@3.12/3.12.3/Frameworks/Python.framework/Versions/3.12/lib/python3.12/lib-dynload', '/Users/kotaro/MyRepository/MyLecture/psp2/k00/.venv/lib/python3.12/site-packages']
>>>>

あなたのk00開発環境(仮想環境.venv,(k00-py3.12))のsys.pathsite-packagesは上記のようになると思います.

唯一のsite-packagesである,psp2/k00/.venv/lib/python3.12/site-packagesを実際に見てみると

(k00-py3.12) > python
>>> import sys
>>> print(sys.path)
['', '/opt/homebrew/Cellar/python@3.12/3.12.3/Frameworks/Python.framework/Versions/3.12/lib/python312.zip', '/opt/homebrew/Cellar/python@3.12/3.12.3/Frameworks/Python.framework/Versions/3.12/lib/python3.12', '/opt/homebrew/Cellar/python@3.12/3.12.3/Frameworks/Python.framework/Versions/3.12/lib/python3.12/lib-dynload', '/Users/kotaro/MyRepository/MyLecture/psp2/k00/.venv/lib/python3.12/site-packages', '/Users/kotaro/MyRepository/MyLecture/psp2/k00']
>>>

sys.pathの中に,psp2/k00が加わりました.

また,site-packagesを見ると

install

のように,若干ファイルが増えています(だけど,k00 フォルダは無い)

sys.pathpsp2/k00フォルダが列挙されているので,(k00-py3.12)の仮想環境であれば, psp2/src/k00というフォルダをパッケージとして読み込むことができます.

たとえば,psp2/src/k00/tests/test_k00_turtle.pyという名前で上のインタフェースプログラムを書いておき,

> poetry run python tests/test_k00_turtle.py

でテストできます.

また,

> poetry run python
>>> from k00 import k00_turtle as my
>>> help(my.main)
Help on function main in module k00.k00_turtle:

main() -> None
    (このmain関数が何をするのかを書く.)

k00_turtle.pymain関数の docString が表示されます.

モジュール関数の定義を変更したとき

モジュール関数の方の関数定義を変更したのに,そのモジュール関数の挙動がおかしいことがあります.時々,

> poetry sync

を動かすのを習慣づけましょう.

2.4. k00/dists/k00-[...].whl:提出

2.4.1. poetry versionpoetry build: バージョニングと 配布用圧縮

> poetry build

で,k00/dists/k00-0.1.0-py3-none-any.whlが作られます.

これを提出してください.0.1.0は version です.

再提出の際,build の前に以下の操作を行ってください.

というか,修正を行った場合,その修正内容に応じて,その時すぐに以下の操作を行うことを勧めます.

再提出の際は,必ずバージョンを変更してください.

patcha.b.ccが増え,minorb, majoraが増えます.

2.3.2. 提出物の check

TA は,適当な環境で,

pip install k00-0.1.0-py3-none-any.whl

して,site-packagesk00をインストールします.

そこで上のtest_k00_turtle.pyを実行します.再現されるかな.

チェックが終わったら,忘れずにアンインストールして環境美化に努めます.

pip uninstall k00

@. jupyter で実行する場合

vscode やコマンドプロンプト,terminal から実行する場合はよいのですが,jupyter や GoogleColab など web 上では表示できずエラーになります.

そこで,jupyter 用の turtle ColabTurtlePlus をインストールします.

> poetry add ColabTurtlePlus

さきほどのk00_turtle.py

import turtle

のところを

from ColabTurtlePlus import Turtle as turtle

としてください.

3. 今日の課題

で,このソースコードは,どんなプログラムでしょうか?

ざっくり言うと,新しくウインドウが立ち上がって,矢印とカメがそれぞれ線を描きながら動いて停止します.ちなみに,ウインドウが立ち上がったままですが,ウインドウ内のどこかでクリックすると,ウインドウが閉じます.

というわけで,今日の課題

(1)k00_turtle.pyのそれぞれの行で行われていることを調べて,ソースコードにそれぞれの行で行われていることをコメントアウトに追記せよ.

(2)正六角形を描画するモジュールk00_hexagon.pyを作成せよ.ただし,左半分は矢印で赤,右半分はカメで青で描くこと.for文もしくはwhile文などの繰り返し処理を用いること.自分で編集したソースコードのそれぞれの行で行われていることをコメントアウトに追加せよ.

(3)k00_pentagon.pyに正 N 角形を描画する関数NsidePolygon(n)を作成し,NsidePolygon関数を利用して正五角形を描け.ただし,関数NsidePolygon(n)n匹のカメを発生させ,それぞれのカメがそれぞれ各頂点に移動して一つの辺のみを描画することとする.繰り返し処理を利用してできるだけ行数を短くせよ.

(4)k00_pentagon.pyk00_invalid.pyにコピーし,NsidePolygon関数に描く線の色名を引数colorとして指定してできるようにせよ.その上で正五角形を描け.pencolor('yellow')で黄色に,pencolor('blue')で青色の線を指定できる.ただし,適当な文字列を色名とするとエラーが出てプログラムが止まるであろう.適当な色名が指定されたときにはpencolor('black')で線を黒色に指定しなおすように,例外処理を加えよ.

(5) パッケージk00の下にさらにディレクトリk00funcsを作り,その下にk00_func.pyを作成せよ.(4)の関数NsidePolygon関数をk00_func.pyに書き,k00_octagon.pyfrom k00.k00funcs import k00_funcとして読み込んだ上で,NsidePolygon関数を利用して,正八角形を描け.

最終的に今回 poetry new k00 で作成したディレクトリの構造は以下のようになる.

k00
├── README.md
├── src/k00
│   ├── __init__.py
│   ├── k00_hexagon.py
│   ├── k00_invalid.py
│   ├── k00_octagon.py
│   ├── k00_pentagon.py
│   ├── k00_turtle.py
│   └── k00funcs
│       └── k00_func.py
├── pyproject.toml
└── tests
    └── __init__.py

課題は,

@1. 便利な vscode 用コメントアウト 「# %%

ソースコードは基本的には,python **.pyだと最初の行から最後の行まですべての工程を行います.

コードのそれぞれの行で何を行っているか調べるなら,もしくは新しく文を書いて,その動きを確かめたいのなら,python だと IPython が有名です.IPython は,Jupyter や Google Colab を動かしているプログラムです.

ターミナルやコマンドプロンプトで,ipythonと打つと,

(.venv) kotaro@calvados psp2 % ARM❯ ipython                                [04/12 21:31]
Python 3.10.0 (default, Jan 13 2022, 14:22:16) [Clang 13.0.0 (clang-1300.0.29.30)]
Type 'copyright', 'credits' or 'license' for more information
IPython 8.2.0 -- An enhanced Interactive Python. Type '?' for help.
Unable to automatically import mitosheet

In [1]:

のように,インタラクティブモードが立ち上がります.

In [1]: x = 10

In [2]: print(x)
10

のように,Jupyter や Google Colab と何ら変わりません.

ただ,問題なのは,このインタラクティブモードで書いたソースコードは保存されないということ.Jupyter や Google Colab で書いたものも,基本的には「jupyter notebook」(拡張子は .ipynb)であり,python のソースコード(.py)ではないということです.

Jupyter や Google Colab ではメニューから,JupyterNotebook(.ipynb)から python ソースコード(.py)に変換することは可能ですが,

僕らが今欲しいのは,書いている python ソースコードをブロック毎に動きを確かめたい.そこで,vscode の「# %%」です.

実は,

「Run Cell | Run Below | Debug Cell」
------------------------------------------
# %%

は,vscode 上で,python ソースコードを簡単にインタラクティブモードで動かすコメントアウトになっているのです.凄い便利!!

# %%」から次の「# %%」までが,IPython の「セル」です.

また,このように書かれた python ソースコード.pyは,Run CellRun Belowで隣に立ち上がった Jupyter みたいなタブでsaveをすることで Jupyter Notebook .ipynbに保存できます.ターミナルでjupyter notebookと打つとウェブブラウザが立ち上がり,

Jupyter Notebook

ここで,.ipynbを選択すると,jupyter Notebook を実行できます.

Jupter Notebookで開く

ちなみに,Jupyter notebook での編集は,.ipynbには反映されますが,元の.pyには反映されないので注意.

@2. 繰り返し処理

python 言語の繰り返し処理は,

処理文A
for 仮変数X in コンテナP:
    仮変数Xを利用できる処理文B
    仮変数Xを利用できる処理文C
処理文D

の形で処理文 B と処理文 C の繰り返しを実現します.

仮変数 X は繰り返しのたびに,コンテナ P の先頭から 1 つだけ要素が代入されます.コンテナ P の最後の要素が代入されたら最後の繰り返しとなり,繰り返しブロックを抜けます.

コンテナ P がリストの場合,

L = [1, 10, 'blue', 3.14]
for i in L:
    print(i)
for j in L:
    print('i')

は,どのように出力されるでしょうか?やってみましょう.

コンテナ P が文字列の場合は,どうなるでしょう?

S = 'Programing'
for i in S:
    print(i)

コンテナ P が辞書の場合は,どうなるでしょう?

D = {'a':1, 'b':10, 'c':'blue', 'd':3.14}
for i in D:
    print(i)
for j in D:
    print(D[j])

コンテナ P は,スライスでも構いません.

S = 'Programing'
for i in S[1:]:
    print(i)

コンテナ P は,その場でrangeを利用することもできます.

for i in range(5):
    print(i)

enumerateも覚えておくとよいでしょう.何が行われるか試してみてください.

S = 'Programing'
for i,j in enumerate(S):
    print(f'{i}:{j}')

@3. モジュールおよび関数

関数は,次のような書き方で宣言でき,同一のファイル内であれば,宣言したあとに呼び出せます.

def 関数名(仮引数...):
    仮引数を利用できる処理文
    return 出力変数

y = 関数名(x)

別のファイルに関数が書かれている場合,呼び出し側のファイルで import します.関数が書かれた.pyファイル,またはそのファイル名の.pyを除いた文字列をモジュールと呼びます.

import については,こちらを参考にしてください.

呼び出し側で,関数を呼び出すときは,以下のようにします.

from forder1.forder2 import module
y = module.func(x)

のように呼び出し側のファイルでは,単に関数名ではなく,モジュール.関数名で呼び出します.

import module as modのように別名を指定すれば,

y = mod.func(x)

のように別名を使うこともできます.

また,モジュールがソースコードと同じフォルダにある場合は, from を省略できます.

また,ソースコードと同じフォルダの下でなくとも,pippoetrycondaで入れたパッケージやモジュールはimportできます.

混乱を生むが許されている他のimport方法

実は他のimport方法があります.

  1. モジュールをfromにして,関数をimport

    呼び出し側のソースコードでも,単に関数名になります. 関数名が他でも使われている場合に混乱します.

    from フォルダ.モジュール import func
    
    y = func(x)
    
  2. 1 でさらにワイルドカード*で関数をimport

    importは,「,」で区切って複数のものをimportできますが, あるモジュール内に存在するすべての関数をimportする場合, 以下のように書くことができます.

    from モジュールA import *
    from モジュールB import *
    
    y = func(x)
    

    しかし,この場合,funcという関数が,どちらのモジュールに書かれているものなのか,ソースコードから読み取れません.どちらのモジュールにもfuncが存在する場合,後ろにあるモジュール B のfuncが利用され,混乱の元になります.