mizzsugar’s blog

日々感じていることや学んだことを書きます。エンジニアリング以外にも書くかもしれません。

【ポエム】ヘルプデスクの仕事を振り返る

プログラマに転職する前に、1年ほどやっていたヘルプデスクの仕事の所感をまとめようと思います。

仕事内容は会社によりますし、個人の感想(思っていることは変えられない)なので ご配慮いただけると幸いです。

また、具体的なことはかけないので、結構ふんわりしていると思います(^^;) まあ、ポエムなので、そこはご愛嬌ということで・・・笑


●最初は嫌で嫌で仕方がなかったヘルプデスク

実は新入社員の研修が終わって最初に配属されたのは、コンサルでした。 しかし、1ヶ月ほどで戦力外通知的なものを受け、HRシステムのヘルプデスクに異動となりました。

コンサル時代の上司には「ヘルプデスクはマニュアルに沿って答えるだけ。」みたいなことを言われていたので 「落ちこぼれがやる、誰でもできる最底辺の仕事をするキャリアになるのか・・・」と悲観しました。

(今思えば、本当に失礼な新卒だ笑)

後々、その偏見は解消されることになります。


●私がやったこと

最初の1-2ヶ月は、ひたすら回答していました。

お客さんから「トラブルがあった」「設定の仕方が分からない」などの問い合わせが 問い合わせ専用の掲示板みたいなものに挙げられ、 分かりそうな問い合わせのの担当に入って順々に回答していました。

最初は解決能力が低いのでマニュアル・FAQ・過去の回答を検索して 案内して解決するものを中心に担当していました。

それだけでも、 文章構成やお客様の気持ちに寄り添った回答をすることも求められたため、回答業務を身に着けるのに苦戦しました。

マニュアルの内容で解決する問い合わせに慣れたら 今度はマニュアル外の問い合わせです。

前職のヘルプデスクでは、データ状況を確認するために データーベースやログを確認しました。

ログも、システムが吐き出すログだけでなく サーバが吐き出すログも読んだので 意外と技術的な知見が求められました。

また、処理手順に沿ってテーブルを順に追って調査するため データベースの設計を学べたのは良かったです。

ただ調査するだけではありません。 お客様が自己解決できるように FAQを作成したり、より分かりやすい説明に書き直したりもしました。

「これは手作業や運用カバーでは無理すぎる」というものには 開発に修正案を出したりもしました。

また、これはめぐまれているなと思うのは、 ソースコード読むことが出来たことです。

たしかに、最低限のことだけするならば 「マニュアルに沿って回答するだけ」になりますが お客様とコミュニケーションを取って運用アドバイスしたり、DB設計学んだりと幅広くスキルを身につけられる職でもありました。

●やってみて見方が変わった

「マニュアルに沿って回答するだけ」という受動的なイメージはなくなりました。

例えば、原因を報告するにも オラクルやJavaの内容を噛み砕いてお客様に説明する必要があるため コード書けなくても知識は必須でした。

また、よくも悪くもシステムに関するお客様の不満や困りごとが集まる場所なので ここで集まった知見をほっといて開発するのは損だなと、今なら自信をもって言えます笑 もちろん、「マニュアルに沿って回答するだけだろ?」とか「どうせコードかけないんでしょ?」と卑下するのはもってのほか。(すみません、かつての自分です←)

ヘルプデスクは、お客様の関心ごとが集まる最先端の場所です。


●それでもヘルプデスクをやめてプログラマになりたい理由

もともと手を動かして作るのが好きというのがあって ずっとプログラマを志望していましたが 運用側の立場になってみて、作る側になりたい理由が増えました。

システムで何か困ったことがあった際に 作る立場と運用する立場の壁は大きいと感じたからです。

運用側から意見する場合、技術的な理由で出来ない、もしくは時間がかかる となるとぐうの音も出ないので 作る側になって根本解決したいと思いました。

また、そもそも業務にフィットするものを最初から作りたいという気持ちもあります。

まあ、業務進めるために運用でカバーの案内はしましたが やはり、運用でカバーは悔しいです。

時間ないなかで手間な手作業させるのも 間違いがあったらどうしようという不安を抱えながらデータ扱わせるのも システムあるのに残念なことです。


●これから

プログラマになる道が吉と出るか凶と出るか分かりませんが ヘルプデスクで学んだ、 作ったシステムには使っているお客様がいるということと 困り事を解決するためにシステムがあるということは 忘れずに物作りしていこうと思います。

DjangoのフォームのChoiceFieldに画像を挿入する

苦労したので、備忘録に・・


環境

※今回投稿する方法は、Django1系では利用できません。 1系はこちらをご参照ください。 なお、下記の記事の中にあるRadioFieldRendererは2系では存在しません・・・

moqada.hatenablog.com

注文アプリを作成している時に、モデルから選択肢を作成し、モデルに格納しているimgを挿入したいという件がありました。


完成図

f:id:mizzsugar:20181205223208p:plain

models.py

class Curry(models.Model):
    name = models.CharField(max_length=30)
    price = models.IntegerField()
    image = models.FilePathField() 


class Order(models.Model):
    user_name = models.CharField(max_length=20, unique=True)
    curry = models.ForeignKey(Curry, on_delete=models.CASCADE)
    amount = models.IntegerField()

forms.py

def generate_curry_choice() -> Iterable[Tuple[int, str]]:
    @dataclasses.dataclass
    class MyLabel:
        name: str
        image: str
        price: int

        def __str__(self):
            return self.name

    return (
        (curry.id, MyLabel(curry.name, curry.image, curry.price))
        for curry in Curry.objects.all()
    )


class OrderForm(forms.Form):
    user_name = forms.CharField(label='名前', max_length=20)
    curry = forms.ChoiceField(
        label='カレー',
        choices=generate_curry_choice,
        widget=RadioSelect
    )

order.html

<form action="/order_form/{{ group.url_uuid }}" enctype="multipart/form-data" method="POST">
        {% csrf_token %}
        <div class="form-group">
          <label>{{ form.user_name.label }}</label>
          {{ form.user_name }}
          {{ form.user_name.errors }}
        </div>
        <div class="form-group">

            {% for radio in form.curry %}
            <div class="curry-select">
            {{ radio.tag }}
            </div>
            {% endfor %}
            {{ form.curry.errors }}
        </div>
        <button type="submit" class="btn btn-primary btn-lg btn-block">登録</button>
    </form>



画像をいれるスキがない!!!

widgetのRadioSelectについて公式ドキュメントで調べてみると・・・

class RadioSelect(ChoiceWidget):
    input_type = 'radio'
    template_name = 'django/forms/widgets/radio.html'
    option_template_name = 'django/forms/widgets/radio_option.html'

Djangoにあるhtmlを利用してフォームを生成しているっぽい。

ウィジェット | Django documentation | Django


ソースをみると・・・

radio_option.html

{% include "django/forms/widgets/input_option.html" %}


radio_option.htmlが継承している、input_option.htmlの中身は・・・

input_option.html

{% if widget.wrap_label %}
<label
        {% if widget.attrs.id %} for="{{ widget.attrs.id }}"{% endif %}>{% endif %}
    {% include "django/forms/widgets/input.html" %}{% if widget.wrap_label %} {{ widget.label }}
</label>
{% endif %}


input_option.htmlが継承している、input.htmlの中身は・・・

input.html

<input 
        type="{{ widget.type }}"
        name="{{ widget.name }}"
        {% if widget.value != None %} value="{{ widget.value|stringformat:'s' }}"{% endif %}
        {% include "django/forms/widgets/attrs.html" %}
>


どうやら、input.htmlにて ラジオボタンを生成しているっぽいです。

django/django/forms/templates/django/forms/widgets at master · django/django · GitHub


input_option.htmlのlabel内の

inputの上辺りに画像を挿入したらいけるかも・・・?


しかし、RadioSelectでは実現できない。


カスタマイズしたwidgetを生成しよう。


htmlファイルが入った、templateディレクトリの中に、 widgetというディレクトリを生成し、 curry_radio_option.htmlというwidget用のhtmlファイルを作成します。

curry_radio_option.html

{% if widget.wrap_label %}
<label
{% if widget.attrs.id %} for="{{ widget.attrs.id }}"{% endif %}>{% endif %}

<img src="{{ widget.label.image }}">
    <div class="block">
    <input
    type="{{ widget.type }}"
    name="{{ widget.name }}"
    {% if widget.value != None %} value="{{ widget.value|stringformat:'s' }}"{% endif %}
    {% include "django/forms/widgets/attrs.html" %}
>
    {{ widget.label.name }}
        </div>
{% if widget.wrap_label %}
</label>
{% endif %}


cssはこんな感じです。

.block{
    display:block;
    font-size: 20px;
    font-weight: bold;
    align: center;
}

.curry-select{
    display: inline-block;
    padding: 20px;
    margin: 20px;
}


forms.pyの中に、ImageSelectというカスタマイズしたwidgetクラスを作ります。

forms.py

class ImageSelect(forms.widgets.RadioSelect):
    template_name = 'widgets/curry_radio.html'
    option_template_name = 'widgets/curry_radio_option.html'


OrderFormの中のwidget

RadioSelectからImageSelectに変更します。

forms.py

class OrderForm(forms.Form):
    user_name = forms.CharField(label='名前', max_length=20)
    curry = forms.ChoiceField(
        label='カレー',
        choices=generate_curry_choice,
        widget=ImageSelect
    )


無事、画像が入りました〜。


htmlファイルをいじる方法、他のwidgetでも応用できそうです。

他にもっと良い方法があったらぜひ教えてください(>_<)

20181103 ミニTDDBC振り返りその2

mizzsugar.hatenablog.com

の続きです。


今回学んだことは

1. きれいなコミットメッセージの書き方

2. 学習テスト

3. DDDなリファクタリング


この記事では、「2. 学習テスト」について書きます。

今回の題材はこちら

お題: セマンティック・バージョニング · GitHub


我々のGitHub

github.com


2. 学習テスト

問2で、等しいバージョンナンバーから成るSemVerオブジェクトが等しく、異なるバージョンナンバーから成るSemBerオブジェクトが異なるといった実装をしました。

テストコード↓

class TestSemVer(unittest.TestCase):

    def test_同じバージョンを持つ2つのオブジェクトが等しいことを確認(self):
        self.assertTrue(SemVer(1, 4, 2) == SemVer(1, 4, 2))

    def test_違うバージョンを持つ2つのオブジェクトは等しくないことを確認(self):
        self.assertFalse(SemVer(1, 4, 2) == SemVer(2, 30, 400))

このテストを書いた時点のプロダクトコード↓

class SemVer:
     def get_notation(self) -> str:
        return str(self.major) + "." + str(self.minor) + "." + str(self.patch)
     def __eq__(self, other: SemVer) -> bool:
        return True

eqを実装しよう、ということでまず最初の変更↓

    def __eq__(self, other) -> bool:
        return self.get_notation() == other.get_notation() 

しかしこれだと、別クラスのインスタンスであっても、文字列が等しければ等しいとなってしまいます。

ということで、isinstanceを利用しようという話になりました。

しかし、私はisinstanceを利用するのが初めてで使い方がわかりませんでした(^^;


ここで、学習テストの出番です。

学習テストとは、APIや言語の仕様が期待通りに動くかを確認するためのテストです。

ここでは、 isinstanceの引数を色々いれてみて、どのように動くか確かめました。

学習テストのテストコード↓

    def test_isinstanceの勉強(self): # 学習テスト
        self.assertFalse(isinstance('hoge', SemVer)) # 1
        self.assertTrue(isinstance('hoge', str)) # 2

1つ目のテストでは、strオブジェクト'hoge'がSemVerオブジェクトでないかを判定しています。

2つ目のテストでは、strオブジェクト'hoge'がstrオブジェクトであるかを判定しています。

2つともPassしました。


2つのテストから、

1. isinstanceでは、第一引数は判定対象となるオブジェクトであり、第二引数はクラス名を入れる仕様である

2. isinstaceは、第一引数のオブジェクトが第二引数のクラスのインスタンスかを判定するメソッドである

ということが分かりました。


実際に1と2の通りに動くか、確認します。

        self.assertTrue(isinstance(SemVer(1, 4, 2), SemVer))

Passしました。


仕様が分かったところで、プロダクトコードを変更します。

    def __eq__(self, other) -> bool:
        return self.get_notation() == other.get_notation() \
               and isinstance(other, SemVer)

これで、同じバージョンナンバーであり、かつSemVerオブジェクトであれば等しいという実装ができました。


学習テストは、その場ですぐに仕様を確認することが出来るのでとても便利だと思いました!

ドキュメント読んでも理解が曖昧な時に積極的に利用していこうと思います(^^)

isinstanceについてのドキュメント↓

2. 組み込み関数 — Python 3.6.5 ドキュメント

20181103 ミニTDDBC振り返りその1

都内某所でミニTDDBCみたいなものに参加しました。

今回学んだことは

1. きれいなコミットメッセージの書き方

2. 学習テスト

3. DDDなリファクタリング


この記事では、「1. きれいなコミットメッセージの書き方」について書きます。

今回の題材はこちら

お題: セマンティック・バージョニング · GitHub


我々のGitHub

github.com


1. きれいなコミットメッセージの書き方

問1で最初下記のような仮実装をしていました。

import unittest


class SemVer:
    pass
    
    def get_notation(self) -> str:
        return "1.4.2"


class TestSemVer(unittest.TestCase):
    def test_major_minor_patchにそれぞれ142を与えてバージョンオブジェクトを作成(self):
        semver = SemVer(1, 4, 2)
        actual = semver.get_notation()

        expected = "1.4.2"
        self.assertEqual(expected, actual)


if __name__ == "__main__":
    unittest.main()

他の数字でも通るか不安なので、「1.4.2」以外のテストケースを加えました。

    def test_major_minor_patchにそれぞれ230400を与えてバージョンオブジェクトを作成(self):
        self.assertEqual("2.30.400", SemVer(2, 30, 400).get_notation())

テストケースを加えた後、「コミットすっぞ!」ということで、最初下記のようなコミットメッセージを書きました。


「major=2, minor=3, patch=40でSemVerを作るためのテストを追加した」


これに対して、モブメンバーの方から、こんなふうなご指摘が


「コードに書いてること文字に書いてるだけなので、コミットメッセージにする意味ない」


なんと、意味のないコミットメッセージを書いてしまったようです笑


「コミットメッセージは、なぜその変更をしたのか書くもの」


とのこと。

ここで、t-wadaさんの名言を教えてもらいました。

そして、コミットメッセージのリファクタリングが始まりました。

コミットメッセージも仮実装から始めるスタイル?で、

最初とにかく書く→音読する→書いたことを解釈する→コミットメッセージのリファクタリング

という形で実装。


最終的に、

「入力値が異なると通るか不安なので、テストケースを追加した。」

というコミットメッセージになりました!!

これで、パンセン方もニッコリだそうです!!

(2. 学習テスト につづく・・・)

PythonのWEBスクレイピング超絶入門に利用した文法など

PythonでWEBスクレイピングに初挑戦しました!

今回は、下記のnote教材にお世話になりました!

note.mu

超絶入門ということもあり、 さらっと出来てすごいなあと感動しました! (本当にありがたい・・・!)

しかし、私自身、「パーサとは」といったことから分かっていませんでした・・・

このまま次のステップへ進むのは危険なので、

備忘録として、「WEBスクレイピングとは」といったところから

使用したライブラリの簡単な文法まで

書き残したいと思います。

前提

  • Python 3.7.0
  • BeautifulSoup 4.4.0
  • pandas 0.23.4

パーサとは

「パーサとは、構文解析を行うためのプログラムの総称である。」

「パーサの中には、特定の解析対象を明示する形で、HTMLパーサやXMLパーサといった具合に呼称される場合がある」

www.weblio.jp

構文解析とは

「単語や字句で構成される文を、定義された文法に従って解釈し、文の構造を明確にすることである。」

www.weblio.jp

htmlパーサは、解析対象がhtmlとなります。

パーサは、それを読み込むアプリケーションにとって利用しやすい形に変換する機能を持っています。

なお、Pythonの標準ライブラリにhtmlパーサは含まれており、

今回利用するBeautifulSoupはそれをサポートしています。

また、BeautifulSoupは他のサードパーティーのライブラリもサポートしています。

BeautifulSoupで、とあるテキストに記載されているurlを取得してみる

教材にて、hmtl_docという変数に格納されたドキュメントにあるURLを取得して表示させるというものがありました。

 html_doc = """
<html><head><title>The Dormouse's story</title></head>
<body>
<p class="title"><b>The Dormouse's story</b></p>
<p class="story">Once upon a time there were three little sisters; and their names were
<a href="http://example.com/elsie" class="sister" id="link1">Elsie</a>,
<a href="http://example.com/lacie" class="sister" id="link2">Lacie</a> and
<a href="http://example.com/tillie" class="sister" id="link3">Tillie</a>;
and they lived at the bottom of a well.</p>
<p class="story">...</p>
"""

処理の流れとしてはこんな感じかな、と解釈しました。

①対象となるドキュメントのBeautifulSoupインスタンスの生成

②①で生成したBeautifulSoupインスタンスから、'a'タグの要素を抽出

①対象となるドキュメントのBeautifulSoupオブジェクトの生成

soup = BeautifulSoup(html_doc, 'html.parser') 

BeautifulSoup(対象となるドキュメント, パーサ)

という構文でBeautifulSoupオブジェクトを生成しています。

URLの取得に利用するのは、 BeautifulSoup型の変数soupになります。

また、prettify()という、対象となるBeautifulSoupインスタンスのパーサの型に応じて きれいにフォーマットを整えた文字列に変換して返してくれるメソッドの紹介もありました。

文法は、

BeautifulSoupインスタンス.prettify()

です。

soup = BeautifulSoup(html_doc, 'html.parser')
print(soup) # タグによってインデントがない見にくいドキュメントが表示されます
print(soup.prettify()) # インデントが整えられた、見えやすいドキュメントが表示されます。

aタグの要素を取得して表示してみます。

print(soup.a)
# BeautifulSoupインスタンス.a といったところですかね。

一つの要素しか表示されないかと存じます。

aタグのすべての要素を取得するために、find_all()を利用します。

print(soup.find_all('a'))
# BeautifulSoupインスタンス.find_all('タグ名') 
tags = soup.find_all('a')
for tag in tags:
    print(tag.get('href')

get('href')で、urlのみを取得します。

get(要素)で、その要素の値のみを取得します。

aタグでクラスが"class_name"の要素を抽出するなら↓

soup.find_all("a", {"class", "class_name})
soup.find_all("タグ名", {"class", "クラス名"}) # といったところでしょうか

第一引数がタグ名

第二引数が{str, str}のdict

となります。

requestsで指定されたURLのWEBページのhtmlを取得する

import requests
response = requests.get("https://review-of-my-life.blogspot.com/")
print(response.text)

requestsは、PythonのHTTPを扱うためのライブラリです。

get(url)で、urlにgetリクエストを送った際のレスポンスを取得します。

また、requests.post(url)やrequests.put(url)など、様々なhttpリクエストを送った際のレスポンスも簡単に取得できます。

textでレスポンスの内容をドキュメント化します。

printすると、ドキュメント化されたレスポンスの内容が表示されます。

クイックスタート — requests-docs-ja 1.0.4 documentation

soup = BeautifulSoup(response, 'html.parser')
tags = soup.find_all('a')

for tag in tags:
    print(tag.get('href'))

pandasで分析結果の表を作成する

pandasとは

強力なPython製のデータ分析ツールです。 CSVやhtmlなど様々なファイルからデータを読み込み、DataFrame(表のような形式のオブジェクト)に変換し、様々な形式で出力することができます。

https://docs.pyq.jp/python/pydata/pandas/index.html

Cookbook — pandas 0.23.4 documentation

今回は、表を作成をします。

列を指定してヘッダを作成する

import pandas as pd
columns = ["Name", "Url"]
df = pd.DataFrame(columns=columns) # 列名を指定する
print(df)

DataFrame(column = ["Name", "Url"]) columnはDataFrameのキーワード引数です。

DataFrameオブジェクトに行を追加してボディを作成する

se = pd.Series(['LINEから送った画像を文字起こししてくれるアプリを作るときのメモ①', 'https://review-of-my-life.blogspot.com/2018/03/moji-okosi-1.html'], columns) # 行を作成
df = df.append(se, columns) # データフレームに行を追加
df

Series(シリーズ)は、 1次元の配列のオブジェクト(リストのような形式)となります。

DataFrame(データフレーム)は、 二次元の配列のオブジェクト(表のような形式)となります。

DataFrameオブジェクトにSeriesオブジェクトを追加するのは、

表に行を追加するイメージです。

Intro to Data Structures — pandas 0.23.4 documentation

google.colabでCSVをダウンロードする

from google.colab import files
df1.to_csv("df1.csv")
files.download('df1.csv')

to_csvcsvファイルを作成します。

download(file)でファイルをダウンロードします。

Djangoで画像をクリックしたらモーダルで表示するには

【前提】

方法、見つけました。コピペしたら動きました。

が、コピペ丸にはなりたくないので、 自学のためにこのコードの動きを書くことにしました。

方法、見つけました。コピペしたら動きました。

が、コピペ丸にはなりたくないので、 自学のためにこのコードの動きを書くことにしました。

torina.top

なお、上記サイトではlazyload.jsを読み込んでいますが、 読み込まなくてもうごきました。

lazyloadは、遅延読み込み(不必要な画像の読み込みを後回しにして、画像以外のCSSやJSファイルの読み込みが先に行うためのライブラリ。そうすることで、表示速度を速くすることができる)ためのオプションみたいな感じだから 読み込まなくても動くのでしょうか。

今回は、上記URLとは異なるコードを用いて説明します。

画像の投稿一覧画面にて、

画像をクリックしたらモーダルで大きく表示されるという設定です。

postモデルにfile_pathアトリビュートがあり、各画像のパスを管理しています。

photos.html

    <!-- モーダルウィンドウの中身 -->
    <div class="modal fade" id="imagemodal" tabindex="-1" role="dialog" aria-labelledby="myModalLabel" aria-hidden="true">
      <div class="modal-dialog modal-lg">
        <div class="modal-content">
          <div class="modal-header">
              <h4 class="modal-title" id="myModalLabel">プレビュー</h4>
              <button type="button" class="close" data-dismiss="modal"><span aria-hidden="true">&times;</span><span class="sr-only">Close</span></button>
          </div>
          <div class="modal-body">
            <img src="" id="imagepreview" class="img-responsive" >
          </div>
          <div class="modal-footer">
            <button type="button" class="btn btn-default" data-dismiss="modal">Close</button>
          </div>
        </div>
      </div>
    </div>


    <!-- 画像一覧 -->
    <div style="padding-bottom:200px">
        <ul class="one-post">
          {% load static %}
            {% for post in posts %}
            <li><div class="user-page-img modal-open"><a href="#"><img src="{% static post.file_path %}" class="center" onclick="pop(this)"></a></div></li> <!--  file_pathはpostモデルで管理している、画像のパス-->
            {% endfor %}
        </ul>
    </div>

onclick="pop(this)

onclick=""で、その要素をクリックした際の関数呼び出しをしています。

pop(this)がどのような処理を行うかは、jsに記載されています。

function pop(self) {
    $('#imagepreview').attr('src', $(self).attr('src'));
    $('#imagemodal').modal('show');
}

selfが引数となっています。

html上では、実際に利用されるオブジェクトとして thisが利用されています。

JSでは、カレントオブジェクト()を参照するためにthisが使われます。

ここでは、imgにonclickが登録されているので、imgになりますね。

複数のimgが表示されていますが、クリックしたimgを参照します。

JavaPythonでは、this(もしくはself)といえば、インスタンス自身を指すので、やはりJSは難しいですね)

developer.mozilla.org

①でidがimagepreviewである要素にattrを実行しています。

attr(a, b)でaという要素の属性をbに設定しています。

attr(a)で、aという要素を取得しています。

imagepreviewのsrcの値を、クリックしたimgのsrcの値に設定しています。

①が終わったら、②でモーダルを表示しています。

Python3で再帰処理を書いたよ

再帰処理を1日中学んだ翌日、夢の中で再帰処理を書いてしまうほどになってしまったので

これはお告げだと思い、ブログに書くことにしました。

【前提】

再帰処理とは

「プログラムのある関数の中から自分自身の関数を呼び出す」処理のことらしい

(「呼び出し」と書いてあるけど、やっていることは同じなので「処理」置き換えてもいいかな、と判断しました。あと、色々読んだ中でこの説明が一番しっくりきた)

www.weblio.jp

と言ってもイメージが沸かないので、実際に再帰処理を用いた処理を書きます。

リストに格納されている文字列の登場回数を数える関数です。 (collectionsのCounterで間に合うやつです汗)

count_name.py

from typing import Dict, List


l = ['a', 'b', 'a']



def count_name(name: str, list: List[str], num: int) -> int:
    if not list: # ①listが空の場合(listが[ ] の場合は、falseを返す)
        return num
    if list[0] == name:  # ②listの先頭がnameと一致する場合
        num += 1
    return count_name(name, list[1:], num) # ③listは先頭を1つずらしたものにし、次の再帰処理へ

print(count_name('a', l, 0)) # ④

こんな感じで処理が行われています。

1.list = ['a', 'b', 'a'], name = 'a'

①list == [ ] ではないのでスキップ

②list[0] == 'a'なので

num += 1

③return count_name('a' , list['b', 'a'])

2.list = ['b', 'a'], name = 'a'

①list == [ ] ではないのでスキップ

②list[0] = 'b'なのでスキップ

③return count_name('a' , list['a'])

3.list = ['a'], name = 'a'

①list == [ ] ではないのでスキップ

②list[0] == 'a'なので

num += 1

③return count_name('a' , list[ ])

4.list = [ ], name = 'a'

①list == [ ] なので retun num

③のcount_nameを繰り返した結果、2が返ってきています。 ④でcount_nameをprintすると、count_name内で呼び出しているcount_nameの返り値が2なので、 2がプリントされます。

ちなみに、③は「count_name(list[1:], name 」では駄目なのか? と私は思いました。 駄目です。

理由は、return がないと何も実行されず、処理が終わらないからです。

関数では、returnの箇所で処理が終了するのですが、それがないと

ifで当てはまらなかった場合に永遠に何も起こらないことになってしまいます。

おまけ。後から提案されたやつ↓

def count_name(name: str, list: List[str]) -> int:
    if not list:
        return 0
    
    head, *tail = list
    n = 1 if head == name else 0
    return n + count_name(name, tail)

print(count_name('a', l))

head, *tail = list 以下はこんな感じです。

head = list[0]
tail = list[1:]

if head == name:
    n = 1
else:
    n = 0
return n + count_name(name, tail)

それにしても、自分が書いたやつ、もっさりしてる(涙)

私のがもっさりスパゲッティなら、後者のコードはそうめんなので

私もそうめん書けるようにがんばります。