Pythonの入門者向け動画教材 2023年改訂版

JMOOCで2020年に公開していたPythonの入門者向けの動画教材ですが、昨年の11月に内容を更新したものを公開していました。

まだ記事にしていなかったので、ここでも書いておきます。

Python入門2023改訂版 - Python3.11対応版

Pythonによるプログラミング入門の動画教材です。

GoogleアカウントでPlatJaMというシステムにログインすると、無料で受講できます。

Windowsパソコンの操作ができ、プログラミングを初めて勉強する人を想定した内容となっています。

2020年公開版との違い

  • スライド資料をアップデートしました。内容はほとんど同じですが、Python3.11を前提とした説明に変更しています。
  • 動画をすべて再撮影しました。動画コンテンツとしては完全に別物で新しくなりました。
  • 実際にコマンドを実行したり、Pythonの対話インターフェースで動かすデモ、VisualStudioCodeでコードを入力するデモを追加しました。
    • 2020年のバージョンではスライドと音声による説明だけでした。

2020年公開版についての記事

Pyodideを試す

Pyodideは、CPythonをWebAssembly(WASM)/Emscriptenにポーティングしたソフトウェア。

PythonがWASMとして動作するので、ブラウザ上でPythonを動かせる。

pyodide.org

ドキュメントには実際に動作するREPLのリンクがある。

https://pyodide.org/en/stable/console.html

Pyodideをウェブサイト上で動かす

PyodideはWASMなので、JavaScriptから呼び出して利用可能。

また、CDNでホストされたバージョンもあるため、少し組み込んで使うくらいであれば、少量のコードでできる。

https://pyodide.org/en/stable/usage/quickstart.html

サンプルコード(ドキュメントより抜粋):

<!doctype html>
<html>
  <head>
      <script src="https://cdn.jsdelivr.net/pyodide/v0.24.1/full/pyodide.js"></script>
  </head>
  <body>
    Pyodide test page <br>
    Open your browser console to see Pyodide output
    <script type="text/javascript">
      async function main(){
        let pyodide = await loadPyodide();
        console.log(pyodide.runPython(`
            import sys
            sys.version
        `));
        pyodide.runPython("print(1 + 2)");
      }
      main();
    </script>
  </body>
</html>

CDNからスクリプトをロードし、 loadPyodide() でWASMをロード、初期化。その後は runPython()Pythonコードを実行できる。

サードパーティ製パッケージを動かす

Pyodideでは、micropipというAPIを使って、外部のPythonパッケージを動かすことができる。

Pyodideが標準モジュールをある程度サポートしていることもあり、Pure Pythonで書かれたパッケージであれば、動かすハードルは低め。

標準モジュールの互換性についてもドキュメントに記載がある。

pyodide.org

JS側、Python側どちらからでもmicropipを使える。柔軟性は高いように見える。

<script type="text/javascript">
  async function main(){
    const pyodide = await loadPyodide();
    await pyodide.loadPackage("micropip");
    const micropip = pyodide.pyimport("micropip");
    await micropip.install("regex")
  }
</script>

DjangoのIt works画面をうごかしてみる

では、Djangoを無理矢理うごかしてみる。

<!doctype html>
<html>
  <head>
    <script src="https://cdn.jsdelivr.net/pyodide/v0.24.1/full/pyodide.js"></script>
  </head>
  <body>
    <script type="text/javascript">
      async function main(){
        // setup
        const pyodide = await loadPyodide();
        await pyodide.FS.writeFile("/home/pyodide/urls.py", "urlpatterns=[]");
        await pyodide.loadPackage("micropip");
        const micropip = pyodide.pyimport("micropip");
        await micropip.install("Django")
        // run django app
        const output = pyodide.runPython(`
            import io
            import sys
            import django
            from wsgiref.handlers import BaseCGIHandler
            from django.conf import settings
            from django.core.handlers.wsgi import WSGIHandler

            settings.configure(
                ROOT_URLCONF="urls",
                SECRET_KEY="dummy",
                DEBUG=True,
            )
            django.setup()
            app = WSGIHandler()
            output = io.BytesIO()
            env = {
                "REQUEST_METHOD": "GET",
                "SERVER_NAME": "pyodide",
                "SERVER_PORT": "8000",
            }
            BaseCGIHandler(
                sys.stdin.buffer,
                output,
                sys.stderr,
                env,
                multithread=False
            ).run(app)
            response = output.getvalue().decode("utf-8")
            "".join(response.splitlines()[2:])
        `);
        document.open();
        document.write(output);
        document.close();
      }
      main();
    </script>
  </body>
</html>

Pyodideを初期化してからDjangoをインストール、その後WSGIハンドラを実行している。

実行すると、ブラウザ上でDjangoを実行して、It worksの画面が表示される。

Djangoのシグナル機能(django.dispatch.Signal)

Djangoには、フレームワーク内での各種アクションの発生を他の機能へ通知する、シグナル(Signals)という機能があります。

Signals | Django ドキュメント | Django

シグナルの機能を使うと、Djangoアプリケーション間のモジュールの依存関係を緩くできたり、拡張性のあるアプリを作れたりします。

試した環境は Python 3.11, Django 5.0

シグナルの使い方

シグナルの使い方は以下の通りです。

  1. シグナルを定義する ... my_signal = Signal()
  2. (受信側)シグナルにレシーバーを接続する ... Signal.connect()
  3. (送信側)シグナルを送信する ... Signal.send()
  4. (受信側)レシーバーの処理が実行される

自分で定義したシグナルを使う場合は、1のシグナル定義から。既定のシグナルを利用する場合は、2のレシーバーの接続からになります。

シグナルを使わない例

base、app1、app2 のように、3つのDjangoアプリケーションがある例です。

baseアプリは最初に作成され、後から追加されたapp1とapp2は、baseアプリから処理が呼び出される、といったモジュール構成を想定します。

base/spam.py:

from app1.egg import receiver as app1_receiver
from app2.egg import receiver as app2_receiver

def main():
    print("base/spam")
    app1_receiver("by base")
    app2_receiver("by base")

app1/egg.py:

def receiver(param):
    print(f"app1 {param}")

app2/egg.py:

def receiver(param):
    print(f"app2 {param}")

この場合、Django shellから base/spam.py:main() を実行すると、以下のようになります。

>>> from base.spam import main
>>> main()
base/spam
app1 by base
app2 by base

このとき、base、app1、app2の依存関係について考えてみます。

  • baseアプリからは、app1とapp2の関数をimportしているので、Pythonモジュールとして直接依存している
  • app1とapp2の間には依存関係はない

また、「app1とapp2は後から追加される」という想定だと、app1やapp2を実装するときに、呼び出し元のbaseのアプリも編集が必要となります。

base/spam.py:main() の処理は、密結合のapp1とapp2のreceiverの処理を含むため、このような構成だと main()ユニットテストは複雑になっていきます。

シグナルを使うと、このような密結合のモジュールの依存を変更し、緩い結合にできます。

モジュール、関数の依存と呼び出しの関係は、以下の図のようになります。

シグナルを使わない構成

シグナルを利用する形に書き換えた例

base/signals.py:

from django.dispatch import Signal

spam_main = Signal()

base/spam.py:

from .signals import spam_main

def main():
    print("base/spam")
    spam_main.send(main, "by base")

app1/egg.py:

def receiver(sender, **kwargs):
    print(f"app1 {kwargs['param']}")

app1/apps.py:

from django.apps import AppConfig
from base.signals import spam_main
from .egg import receiver


class App1Config(AppConfig):
    default_auto_field = 'django.db.models.BigAutoField'
    name = 'app1'

    def ready(self):
        spam_main.connect(receiver)

app2/egg.py:

def receiver(sender, **kwargs):
    print(f"app2 {kwargs['param']}")

app2/apps.py:

from django.apps import AppConfig
from base.signals import spam_main
from .egg import receiver


class App2Config(AppConfig):
    default_auto_field = 'django.db.models.BigAutoField'
    name = 'app2'

    def ready(self):
        spam_main.connect(receiver)

実行結果は、シグナルを使わない場合と同じです。

ポイントは以下の通りです。

  • app1とapp2を後から追加するときに、baseを変更しなくてよい
  • base側からapp1とapp2へのimportをしなくてよい
  • 記述するコード量は増える
  • シグナルへの接続は、AppConfig.readyでやっている

モジュール、関数の依存と呼び出しの関係は、以下の図のようになります。

シグナルを利用した構成

図ではAppConfig部分を省略していますが、シグナルを使わない例よりも複雑になっています。

base/main.py、app1/egg.py、app2/egg.pyでそれぞれのモジュールが独立している構成になるため、ユニットテストを書いたりするときに楽になります。

まとめ

  • Djangoのシグナル機能は機能間でアクションを通知できる
  • シグナルを使うとアプリ間の依存を緩くできる

検証に使ったコードはGitHub上にあります。

https://github.com/tokibito/sample_nullpobug/tree/main/django/django-signal

畑 2023

この記事は pyspa Advent Calendar 2023 の3日目の記事です。昨日は tokoroten の「2023年活動報告」でした。

今年も畑の成果報告です。

市内の民間の農園で4m×5mの小さい畑を借りています。以前は2016年~2018年に3年間借りていて、その後、去年から借りているところです。

以前の畑についての記事

2023年の成果

2022年の9月から使用している畑なので、去年のアドベントカレンダーの記事では、あまり多くの収穫がまだなかったのですが、今年は1年通して野菜を作れたので、たくさん成果がありました。

年始は1月にキャベツを収穫しました。

ブロッコリーは12月~4月ころまで収穫できました。長い。

きぬさや、スナップエンドウ、そら豆、ナス

食用ほおずき

食用ほおずきは、ミニトマトに似た食味で結構甘いです。7月~9月ごろにたくさん収穫できました。

ナス、ししとう、食用ほおずき、ミニトマト、とうがらし、ピーマン、錦糸瓜

糸瓜は前年に実を買って食べたあと、残しておいた種から育てたのですが、うまくいきました。

すいか、赤紫蘇、きゅうり

ピーマン

とうがらし

さつまいも

うまくいった・良かった点

  • 接ぎ木タイプの苗
    • ナスやトマトは接ぎ木した苗がホームセンターに売っていて、それを使った。過去にタネから育てたときよりも生育状態は比較的良かった。
  • 雑草除去
    • 春、夏ごろは、雑草除去を過去の畑よりも比較的うまくできていた。
  • マルチング
    • マルチで畝を覆うのは、ほとんどの野菜で有効だった。土が乾きづらくなるので水やりの頻度を下げれたし、土に潜って根を食べる害虫への対策にもなる。
  • 敷き藁
    • 夏場の乾燥対策に使用したが効果があった。畝の間に敷くと土が乾きにくくなる&雑草対策になる。マルチと違って空気を通すため、完全に乾かないわけではないので、根腐りを防ぎつつ乾燥対策ができる資材。
    • さつまいものツルを伸ばす時には、土への接地を避けれてツルが根を張る対策になるのも良かった。
  • 収穫量増加
    • 乾燥対策、枝の伸ばし方により、実の収穫量は過去最高だった。過去4年の畑とは、かなり違いがあった。地植えの利点を活かす栽培が重要。
  • おすそ分け
    • 収穫量増加で消費しきれない分を家族、友人、近所の人などにおすそ分けできた。消費しきれない場合は今後もやっていきたいと思う。
  • 収穫体験
    • 友人の子たちに、野菜の収穫体験をしてもらうことができた。機会が作れれば、またやりたい。
  • ししとう・ピーマン
    • 5月に植えて、6月から12月まで収穫できた。長くてよかった。
  • ナス
    • 5月に植えて、6月から11月まで収穫できた。長くてよかった。消費しきれないので株は少し減らしてよいかも。

失敗した・悪かった点

  • 蝶・蛾対策
    • 蝶や蛾などが葉物野菜に卵を産み付けて、孵化した幼虫(イモムシ)が葉や実を食い荒らす。卵を見つけたら除去する対処が必要。農薬の使用はなるべく避けたいが、ひどい場合はオルトランなどの農薬使用もやむなし。
  • ウリハムシ対策
    • イカ、キュウリ、錦糸瓜などのウリ科の葉はウリハムシが大量に発生して食い荒らされた。木酢液や、捕獲用の道具などで対処を考えたい。
  • カメムシ対策
  • 蟻対策
    • 食用ほおずきは蟻が実を食い荒らす。アリの巣を見つけたら苦土石灰で対処を考えたい。
  • ネズミ対策
    • イカ、さつまいもはネズミ被害があった。金網、木酢液などで対処を考えたい。
  • イカ
    • ネットで上に伸ばしてみたところ、実の重さでつるが切れてしまった。
    • 植え付けが遅くて実が大きくならなかった。
  • 行者にんにく
    • ホームセンターに苗が売っていたので植えてみたが、生育環境が合ってなさそうだった。気温が高すぎて枯れてしまう。
  • にんにく
    • 球根が大きくならなかった。肥料不足か土が合ってないかのどちらか。
  • 玉ねぎ
    • 球根が大きくならなかった。肥料不足か土が合ってないかのどちらか。
  • ブロッコリー
    • 長期間収穫できていたが、2月にムクドリに葉を全部食べられてしまった。ネットで鳥よけするなどの対策を考えたい。
  • キャベツ
    • ムクドリの被害あり。蝶や蛾の幼虫の食害あり。
  • ビーリー
    • 雑草対策がうまくできてなくて、イモムシに食い荒らされたりして失敗。
  • さつまいものツルの伸ばし方
    • さつまいもはある程度ツルを伸ばさないと根が大きくならないので伸ばしたのだが、取り回しに失敗して他の野菜の株の生育を妨げることになった部分があった。範囲を抑制するために囲いのネットを増やすなどの工夫をしたい。
  • 基本的にF1種のタネを使うべき
    • 糸瓜とスイカは、前年に実を買って食べたあとにタネを残しておいて、そこから栽培したのですが、発芽時期や病害虫対策などの点で弱い可能性があるので、できるだけ早めに市販のF1種のタネを用意しておいて栽培したほうがよい。

おわりに

2023年は収穫量が多かったのがとてもよかったです。良い点、悪い点どちらもありましたが、来年に活かそうと思います。

1年通して栽培して土が疲弊しているので、来年の春に向けて肥料を足したりして、調整するのが必要そうだなと考えてるところです。

来年も1年通して畑を利用できるので、引き続きやっていきます。

明日は、 id:shiumachi の記事です。

Co-KoNPILe東青梅合宿

2023/11/23~11/25の2泊3日でCo-KoNPILeの合宿をやっている

cokonpile.connpass.com

東青梅駅近くの古民家の宿を貸し切り。

自分のアウトプットは、合宿飯を作ること。

そこそこ予定していたものを作れました。

DjangoのフォームをDjango shellでデバッグする

Djangoフレームワークには、ウェブ用のFormを作る機能があります。

フォームを使う | Django ドキュメント | Django

フォームを定義して実際にブラウザ上で表示するためには、ビューやテンプレートを用意する必要があるのですが、少しフォームを試したいだけであれば、毎回すべてを用意するのは少し手間です。

フォームクラスの動作を確認するだけであれば、Djangoのrequestオブジェクトには異存しないので、Django shell(manage.py shell)からでも手軽に試せます。

フォームを定義する

適当にmyappというDjangoアプリケーションを作成し、プロジェクトのsettings.LANGUAGEを 'ja' に変更してからフォームを作ってみます。

myapp/forms.py:

from django import forms

class ContactForm(forms.Form):
    name = forms.CharField(label="お名前", max_length=10)
    email = forms.EmailField(label="メールアドレス")
    content = forms.CharField(widget=forms.Textarea)

Django shellから試す

manage.py shell を起動して、定義しておいた ContactForm クラスをインポートします。

>>> from myapp.forms import ContactForm

これでContactFormクラスを試す準備ができました。

フォームのインスタンスを作る、メソッドを呼び出す

Djangoのフォームは引数を省略した場合、空のフォーム(新規入力相当)を生成します。

>>> form = ContactForm()
>>> form
<ContactForm bound=False, valid=Unknown, fields=(name;email;content)>
>>> form.as_p()  # pタグでフィールドをレンダリングしたHTMLを生成
'<p>\n    <label for="id_name">お名前:</label>\n    <input type="text" name="name" maxlength="10" required id="id_name">\n    \n    \n  </p>\n\n  \n  <p>\n    <label for="id_email">メールアドレス:</label>\n    <input type="email" name="email" maxlength="320" required id="id_email">\n    \n    \n  </p>\n\n  \n  <p>\n    <label for="id_content">Content:</label>\n    <textarea name="content" cols="40" rows="10" required id="id_content">\n</textarea>\n    \n    \n      \n    \n  </p>'

form.as_p() のようにして、formの各メソッドを試すことができます。

この例だと、pタグでフォームのHTMLを出力するメソッドを試しています。定義したフィールドに対応するHTMLの文字列が生成されていることを確認できます。

入力バリデーションを試す

Djangoのフォームでよく試したいのは、入力バリデーションだと思います。試していきましょう。

Djangoのフォームを使う際に、ビューでは request.POST のようなオブジェクトを引数で渡していることが多いですが、これは辞書ライクなオブジェクト(QueryDict)です。

実際、Djangoのフォームは入力値としてPythonの辞書を渡せば動かすことができる、シンプルな作りになっています。

formには空の辞書を渡した場合、bound=Trueの状態のフォームインスタンスが作られます。Unbound Form, Bound Formについては、ドキュメントを参照してください。

フォーム API | Django ドキュメント | Django

Bound Formの場合、errorsを参照すると、バリデーションが実行されるので、必須入力のチェックが実行されていることを確認できます。

>>> form = ContactForm({})
>>> form
<ContactForm bound=True, valid=Unknown, fields=(name;email;content)>
>>> form.errors
{'name': ['このフィールドは必須です。'], 'email': ['このフィールドは必須です。'], 'content': ['このフィールドは必須です。']}

nameフィールドの初期値だけを与えてテストしてみましょう。

>>> form = ContactForm({'name': 'おなまえおなまえおなまえ'})
>>> form.is_valid()
False
>>> form.errors
{'name': ['この値は 10 文字以下でなければなりません( 12 文字になっています)。'], 'email': ['このフィールドは必須です。'], 'content': ['このフィールドは必須です。']}

このように、Django shellで、フォームのバリデーションを試すことができます。

cleand_dataを試す

フォームの is_valid() がTrueを返す状態であれば、 cleaned_data プロパティでクリーニングしたフィールド毎のデータも取得できます。

フォームフィールドやウィジェットでデータ変換が発生するフォームの場合は、これもDjango shell上で試せるとデバッグがはかどると思います。

>>> form = ContactForm({'name': 'モンティ・パイソン', 'email': 'foo@example.com', 'content': 'お問い合わせ内容'})
>>> form.is_valid()
True
>>> form.cleaned_data
{'name': 'モンティ・パイソン', 'email': 'foo@example.com', 'content': 'お問い合わせ内容'}

IPythonのautoreload

フォームを頻繁に書き換えて試す場合、毎回shellを起動しなおして、インポートからやりなおすのは手間です。

IPythonのshellであれば、autoreloadという拡張を使って少し楽をできるかもしれません。

autoreload — IPython 3.2.1 documentation

IPythonをインストールした状態で、 manage.py shell を起動すると、IPythonのシェルになります。

%load_ext autoreload で自動リロードの拡張をロードし、 %autoreload 2 で自動リロードのモードを変更します。

モード2は、「Reload all modules」となっていて、コード実行前に毎回すべてのモジュールがリロードされます。

In [1]: %load_ext autoreload

In [2]: %autoreload 2

In [3]: from myapp.forms import ContactForm

In [4]: form = ContactForm({})

In [5]: form
Out[5]: <ContactForm bound=True, valid=Unknown, fields=(name;email;content)>

# ここで、ソースコード内のcontentの行をコメントアウト

In [6]: form = ContactForm({})

In [7]: form
Out[7]: <ContactForm bound=True, valid=Unknown, fields=(name;email)>  # 自動でリロードされたクラスが使われている

プロジェクトが大きいと、毎回全てのモジュールをリロードするのは遅い可能性があるので、その場合はドキュメントにあるように %aimport を使うなどして、部分的にリロードするだけでもいいかもしれません。

まとめ

Django shellでフォームをインポートして試せることを紹介しました。

最終的にはユニットテストのコードを書いて、保守しやすい状態にするのがよいですが、試行錯誤する段階では、このようにshell上でデバッグする方法も手軽なので、知っておくとよいかなと思います。

PyCon APAC 2023へのポスターセッション参加とDjango利用状況アンケート結果

2023/10/27~10/28の2日間、PyCon APAC 2023に参加してきました。

今回はdjango-jaのポスターセッションをしました。

ポスターセッションは、所定の展示スペースに収まるサイズのポスターを作って、展示しておき、Breakの時間帯にはポスターの前に待機して、来訪者に説明する、というものです。

Djangoの利用状況のアンケートを先日行っていたので、結果のまとめと、django-jaの活動についてを記載したA0サイズのポスターを作成しました。

私は2日間、ポスターの前に長くいましたが、多くの人が来てくれて話すことができました。ありがとうございました。

ポスターの準備や当日の対応で、結構疲れてしまいましたが、やってよかったです。

過去にDjangoCongressJPで、海外からオンライン登壇してくれた方にも挨拶できました。

英語の対応が全然できないので、もっと頑張る必要があるなと思いました。

Django利用状況アンケート結果

ポスターのデータそのままですが、以下から閲覧できます。

https://djangoproject.jp/whouses/pyconapac2023-django-ja.pdf (PDFファイルです)