<?xml version="1.0" encoding="UTF-8"?><rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom" version="2.0" xmlns:cc="http://cyber.law.harvard.edu/rss/creativeCommonsRssModule.html">
    <channel>
        <title><![CDATA[Stories by 貓橘毛 aka Lanfon on Medium]]></title>
        <description><![CDATA[Stories by 貓橘毛 aka Lanfon on Medium]]></description>
        <link>https://medium.com/@lanf0n?source=rss-36352b1a93d4------2</link>
        <image>
            <url>https://cdn-images-1.medium.com/fit/c/150/150/1*erl1rHMx9bq65paCG-9NMw.jpeg</url>
            <title>Stories by 貓橘毛 aka Lanfon on Medium</title>
            <link>https://medium.com/@lanf0n?source=rss-36352b1a93d4------2</link>
        </image>
        <generator>Medium</generator>
        <lastBuildDate>Wed, 20 May 2026 17:41:13 GMT</lastBuildDate>
        <atom:link href="https://medium.com/@lanf0n/feed" rel="self" type="application/rss+xml"/>
        <webMaster><![CDATA[yourfriends@medium.com]]></webMaster>
        <atom:link href="http://medium.superfeedr.com" rel="hub"/>
        <item>
            <title><![CDATA[鐘擺]]></title>
            <link>https://lanf0n.medium.com/%E9%90%98%E6%93%BA-a01b606d2ffd?source=rss-36352b1a93d4------2</link>
            <guid isPermaLink="false">https://medium.com/p/a01b606d2ffd</guid>
            <dc:creator><![CDATA[貓橘毛 aka Lanfon]]></dc:creator>
            <pubDate>Mon, 31 Jul 2023 19:14:57 GMT</pubDate>
            <atom:updated>2023-07-31T19:23:16.683Z</atom:updated>
            <content:encoded><![CDATA[<p>想著還是紀錄點什麼，思緒來來回回的。</p><h3>結婚</h3><p>結論上來說沒有要結了。總之疫情改變了很多事，18 個月後的現在才開始能夠比較平靜的去體會這結果。這陣子一直都會想，這四年我不努力嗎？還是忽略了什麼？最後的結論只能變成：「別想了，吃點逍遙散吧。」</p><p>從現在回頭看六年前，總覺得自己也沒什麼成長。從沒想過結婚、不考慮結婚、思考結婚的必要性、考慮結婚到決定要結婚⋯最後就是等待到 timed out 後斷線。到現在也已經覺得無所謂了吧，像是又擺盪回頭一樣 – 不考慮結婚。</p><p>不想再去思考去適應家庭關係什麼的，但也許某層面來說這 18 個月也還不夠長，還沒消化完。但我也不知道要怎麼樣才算消化好了，像是某人結婚時我毫無波瀾的只想問會不會跌倒一樣嗎？</p><p>感覺日子還長。</p><h3>房子</h3><p>工作後就一直在思考房子的問題，來來回回的。與其說是什麼目標，反而比較像是讓自己能夠舒適的過日子，免得換個洗衣機要問、買傢俱要考慮搬走後方不方便、格局不能改之類的瑣事。</p><p>從一開始覺得收入再高一點再看看、存看看再決定、能買哪裡、要住哪裡到要不要買透天一起住⋯然後就，嗯還是不買透天了吧，好像也不用考慮住哪裡不好找工作了，但這樣好像不買也沒什麼關係。</p><p>買了沒人繼承、買太大打掃麻煩、買了出國不方便⋯考慮到最後還是算了。</p><p>想法擺盪了一圈又回到起點，想想也是吧，畢竟又不養小孩， TCP 跟 UDP 壽終之後也沒什麼好要求的⋯</p><p>五年一下子就結束了，當初追求的目標幾乎都做完了，才發現這幾年的目標好像都不只是和自己有關，所以突然就不重要也不要了。</p><p>去年是真的沒辦法再思考什麼了，今年則是不知道該思考什麼。短期目標還是寫了，一陣一陣地前進；中長期目標反而不見了、寫不出來，想著想著就一片空白。</p><p>以前總是可以規劃長遠的 ABC plan ，這陣子連往 A 的路都不通了，來來回回的突然就一片空白。然後變成：明天要做什麼、這週要做什麼。</p><p>腦袋塞住了，不知道什麼時候會通。不過算了，慢慢推吧，今年 domain 買了， blog 打算從 medium 移到 GitHub 上但不知道什麼時候會好，然後就是其他再看看了。</p><p>願你一切都好。</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=a01b606d2ffd" width="1" height="1" alt="">]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Social media]]></title>
            <link>https://lanf0n.medium.com/social-media-c7d3ec6dac9d?source=rss-36352b1a93d4------2</link>
            <guid isPermaLink="false">https://medium.com/p/c7d3ec6dac9d</guid>
            <dc:creator><![CDATA[貓橘毛 aka Lanfon]]></dc:creator>
            <pubDate>Fri, 07 Jul 2023 18:37:22 GMT</pubDate>
            <atom:updated>2023-07-07T18:37:22.087Z</atom:updated>
            <content:encoded><![CDATA[<p>毫無長進。</p><p>這一年多 code 寫得少，學習的東西也是…零碎得可以。</p><p>總想誠實的說，心裡病了、累了，日子變得異常地無所謂；沒什麼強烈動機的情況下，時間真的很容易就被各種 social media 消耗光。</p><p>覺得年紀越長，願意說的東西反而越來越少。好多想法在腦裡轉了幾圈後就淡掉了，像是越來越社會化之後，好多東西似乎就其實也無所謂了。</p><p>嗯。</p><p>今年就別努力了吧，明年再說。</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=c7d3ec6dac9d" width="1" height="1" alt="">]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[年前年後]]></title>
            <link>https://lanf0n.medium.com/%E5%B9%B4%E5%89%8D%E5%B9%B4%E5%BE%8C-a3b4f2e3c337?source=rss-36352b1a93d4------2</link>
            <guid isPermaLink="false">https://medium.com/p/a3b4f2e3c337</guid>
            <dc:creator><![CDATA[貓橘毛 aka Lanfon]]></dc:creator>
            <pubDate>Tue, 11 Jan 2022 12:30:07 GMT</pubDate>
            <atom:updated>2022-01-11T12:30:07.321Z</atom:updated>
            <content:encoded><![CDATA[<p>雜記的一篇，今年的進度應該是遠離網路。</p><p>Todoist 用了快兩年了吧，當日常變成一種習慣…但我的英文看來仍舊沒什麼起色。(訂了雜誌到最後變成一天捕魚六天曬網…)</p><p>2020、2021 懶散的日子太長，打開 Youtube 變成了一種習慣；年後的 2022 終究該把那些時間省下來做點別的事情。畢竟焦慮不會因為多看了幾部影片就被解決的。</p><p>新工作很好，但我也不知道我這樣好不好。曾經殷殷期盼疫情早點結束，現在好像也沒那麼有所謂了。</p><p>剩下殷殷期盼著結束這糞 Game 的日子早日來臨。</p><p>也許已經老了。但我的心仍然不願意離開 18 歲剛感受到自由的心情，人生如此漫長，在那之後的日子我又做了些什麼？</p><p>這輩子也許最後就這樣了，我無法同理的事情太多，將近七十的你是否曾經自我思考過，這輩子做對了什麼？又或者高傲的人根本不明白什麼是反省？又或者數十年的酒精早就讓你無法思考未來是什麼？</p><p>我不知道我難不難過，難過的事情太多，也許終究麻木無法明白無法思考，日子怎麼那麼長呢？</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=a3b4f2e3c337" width="1" height="1" alt="">]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[contextvars & async REPL]]></title>
            <link>https://lanf0n.medium.com/contextvars-async-repl-5121352195fd?source=rss-36352b1a93d4------2</link>
            <guid isPermaLink="false">https://medium.com/p/5121352195fd</guid>
            <category><![CDATA[contextvars]]></category>
            <category><![CDATA[python]]></category>
            <category><![CDATA[asyncio]]></category>
            <dc:creator><![CDATA[貓橘毛 aka Lanfon]]></dc:creator>
            <pubDate>Sun, 07 Mar 2021 14:53:43 GMT</pubDate>
            <atom:updated>2021-03-07T15:29:11.373Z</atom:updated>
            <content:encoded><![CDATA[<p>踩雷王是我…QQ<br>總之想在 python -m asyncio 測一下 <a href="https://github.com/aiogram/aiogram">aiogram</a> 的各種 API 就踩到 contextvars 在 async REPL 的問題…用 <a href="https://github.com/vxgmichel/aioconsole">aioconsole</a> 測了一下發現是 asyncio 提供的 REPL 本身的問題，總之發了個 <a href="https://github.com/python/cpython/pull/24773">PR</a> ，但修過之後在某些 case 下還是有問題 XDDD ((但這真的不是我沒修好是就現行的 API 我修不到…總之等等解釋!!</p><p>先把 REPL 的問題放一邊，先討論一下 <a href="https://docs.python.org/3/library/contextvars.html">contextvars</a> (new in 3.7 via <a href="https://www.python.org/dev/peps/pep-0567/">PEP567</a>)，stackoverflow 上面有<a href="https://stackoverflow.com/questions/63105799/understanding-python-contextvars">一篇</a>給的 code 還蠻有趣的，可以參考一下。(如果是找中文的…看了幾篇之後我寧可跑去看 source code 了ㄅ欠)</p><p>btw ，會摸到 threading State 所以 library 是直接實作在 C 那層，Python 版的可以參考 <a href="https://github.com/MagicStack/contextvars/blob/master/contextvars/__init__.py">MagicStack/contextvars</a>，CPython 的 PR 在 <a href="https://github.com/python/cpython/pull/5027">#5027</a>。</p><p>好的，那接下來建議參考 python 版的 code 比較容易理解(?)</p><p>可用的 APIs 什麼的就不說了(文件有寫稍微瞄一下吧)， <em>ContextVar</em> 在同步的情況下，除了 API 不太一樣外，基本上和 T<em>hread-Local Storage</em> (threading.local()) 沒什麼太大的差別，就 threading 目前支援的情況，如果要在 threads 之間「<strong>共用</strong>」，只能把 target function 外再包一層context.run ，像是 PEP567 <a href="https://www.python.org/dev/peps/pep-0567/#offloading-execution-to-other-threads">Offloading execution to other threads</a> 的範例：</p><pre>executor = ThreadPoolExecutor()<br>current_context = contextvars.copy_context()<br><br>executor.submit(current_context.run, some_function)</pre><p>好的，回過頭來說說所謂「<strong>共用</strong>」的部份， <em>contextvars</em> 和 <em>thread-local</em> 比較大的差別是你沒辦法摸到<strong>目前的</strong> Context A，只能拿到複本 B。</p><p>在 MainThread 的情況下，所有的 <em>ContextVar</em> 預設是寫到 Context A ，以上面的例子「<strong>共用</strong>」的情況， write 的行為只會發生在複本 B，也就是說 contextvar.set() 的行為都不會影響到 Context A 。(目前的 API 沒辦法拿到 ContextA)</p><p>當然有幾個常見的「<strong>例外</strong>」就是 immutability 的問題，這個在 thread-local 也一樣就沒啥好解釋的了…(然後請不要想著把它用在 <em>multiprocessing</em> 上好嗎..它本來就限制是 thread scope 的東西了…)</p><p>接下來就是 <em>contextvars</em> 主要影響到的部份 — asyncio ，在 3.7 的 what’s new 是這麼說的：</p><blockquote>asyncio gained support for <a href="https://docs.python.org/3.7/library/contextvars.html#module-contextvars">contextvars</a>. <a href="https://docs.python.org/3.7/library/asyncio-eventloop.html#asyncio.loop.call_soon">loop.call_soon()</a>, <a href="https://docs.python.org/3.7/library/asyncio-eventloop.html#asyncio.loop.call_soon_threadsafe">loop.call_soon_threadsafe()</a>, <a href="https://docs.python.org/3.7/library/asyncio-eventloop.html#asyncio.loop.call_later">loop.call_later()</a>, <a href="https://docs.python.org/3.7/library/asyncio-eventloop.html#asyncio.loop.call_at">loop.call_at()</a>, and <a href="https://docs.python.org/3.7/library/asyncio-future.html#asyncio.Future.add_done_callback">Future.add_done_callback()</a> have a new optional keyword-only <em>context</em> parameter. <a href="https://docs.python.org/3.7/library/asyncio-task.html#asyncio.Task">Tasks</a> now track their context automatically. See <a href="https://www.python.org/dev/peps/pep-0567"><strong>PEP 567</strong></a> for more details. (Contributed by Yury Selivanov in <a href="https://bugs.python.org/issue32436">bpo-32436</a>.)</blockquote><p>主要其實就一件事： copy_context() will be called automatically (if no context propagated).</p><p>白話點說就是，如果你用 .call_{at|soon|later|call_soon_threadsafe} 或是 .add_done_callback 的時候沒有指定 <em>context</em> 的話，default 會用 copy_context() 拿複本B 來執行；<strong><em>Tasks</em></strong> 的部份目前沒有辦法指定 <em>context</em> ，所以<strong>全部</strong>會產生 Task instance 的行為都不會影響到外層的 <em>context</em> 。</p><p>會產生 Tasks 的行為包括像 asyncio 下的 <em>ensure_future</em>, <em>gather</em>, <em>wait_for</em>, <em>shield</em>…etc ，基本上要丟 <em>coroutine</em> or <em>future</em> 當參數的，實作上都會用到 <em>ensure_future</em> (或是 loop.create_task, 基本上一樣…)。</p><p>只能說這用在 library 上其實蠻容易會有因為設計而產生的 bug ，例如 encode/databases 就有個 <a href="https://github.com/encode/databases/issues/230">issue</a> 在講這個 XD</p><p>這也是為什麼上面那個 stackoverflow 裡面的範例，拿到的 id 跟傳進去的一樣。完整一點的 example 像下面 <a href="https://gist.github.com/lanfon72/3236e41b198e8f6ad405601ab517e2e1">gist</a> 的例子，也可以直接開 <a href="https://replit.com/@lanfon72/contextvarsexmaple#main.py">repl.it</a> 來玩看看。</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/96d8d6f3db81d810cfdda70f70a176bb/href">https://medium.com/media/96d8d6f3db81d810cfdda70f70a176bb/href</a></iframe><p>簡單解釋一下幾個部份：</p><h4>Context in Executor</h4><p>Executor 的實作是開 <em>n</em> 個 workers 用 queue.get 的方式去跑傳進去的 functions ，所以每個 Thread 內的 Context 都是獨立<strong>但</strong>被上一個 function 用過之後的狀態。(突然覺得有點可憐XD)</p><h4>Context in Main()</h4><p>asyncio.run 等同於 loop.run_until_complete ，傳進去的 <em>coroutine</em> 會被 <em>ensure_future</em> 包成 future 執行，所以在 main() 內的 <em>Context</em> 就已經是MainThread 的複本B 了。</p><h4>Context in some_outer_{…}()</h4><p>兩個 async functions 被 scheduled 的方式是透過 <em>ensure_future</em> ，所以目前的 Context 已經是複本B 的複本C 了。</p><h4>Context in inner_coroutine()</h4><p>這邊就比較有趣了，inner_coroutine() 在 outer_with_await() 的情況會是共用 Context (複本C)；在 outer_coroutine() 的情況下，因為先被 <em>ensure_future</em> wrapped 過，所以內部的 Context 會是複本D 。</p><p>就目前來說，task instance 沒辦法傳 Context 進去，所以 inner_coroutine() 內的情況其實蠻容易踩到的(畢竟沒辦法限制 caller 不要先 schedule 再 <em>await</em>..)</p><p>但老實說就算 tasks 可以傳 <em>context</em> 了，就目前 <em>contextvars</em> 的 API 並沒有辦法拿到 <em>current context</em> ，所以到頭來還是得用複本(copy_context())….</p><p>至於為什麼 await coro 跟 await task 為什麼會是不同的 context ，我是覺得設計上其實蠻合理的， task 在被 scheduled 的想法上就是開另一個 mini-thread 同步在執行，就 thread-local 的概念上，會有 context 的副本還蠻正常的(?)</p><p>但這就又有另一個問題是，就問前 contextvars 在設計上，每個 thread 是獨立的而且不會用 parent thread 的複本(雖然有一個 <a href="https://github.com/python/cpython/pull/24074">PR</a> 在改這個)，那 schedule sub-task 的時候到底….???</p><p>回過頭來說說我發的那個 <a href="https://github.com/python/cpython/pull/24773">PR</a> 為什麼沒完整修好 async REPL 的細節…</p><p>async REPL 的實作是開一個 <em>REPLThread</em> 來讀 input ，然後 MainThread 負責跑 event loop ，但對 user 來說兩個應該要是同一個 thread 才對，所以 context 需要 sync 。</p><p>解決方案就是弄一個 repl_context 讓執行的 code 都跑在這個 context 下就行了！！！</p><p>但實際上修不好的部份(真的不是我的問題啊大大)…如果 input 是用 await ... 的方式餵進來的話， REPLThread 會拿到一個 coroutine ，用 loop.create_task 讓 event loop 跑它(<a href="https://github.com/python/cpython/blob/8d00462850b32da4649c3403692ed5515e6a96d1/Lib/asyncio/__main__.py#L50">ref</a>)，在 task instance 會拿到 context 複本的前提下，REPL 的環境不會有像上面 outer &amp; inner 共用 context 的情況出現。(但實際上，如果直接 await coro 的 context 應該是要 share 的…)</p><p><a href="https://github.com/vxgmichel/aioconsole">aioconsole</a> 反而沒有這個問題，稍微瞄了一下好像是因為本質上就是在同一個 thread 的關係…</p><p>雜七雜八， contextvars 雖然有 backport 的版本，但等同於沒有….不管是 MagicStack 的 pure python 版，或是另一個 <a href="https://github.com/fantix/aiocontextvars">aiocontextvars</a> ，基本上都沒有辦法 patch 到跟 3.7 之後的版本一樣 — — 因為 loop 的 behavior 不一樣。</p><p>混上 uvloop for 3.6 的話就更 WTF 了，不過 3.6 也快 EOL 了，還好還好。</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=5121352195fd" width="1" height="1" alt="">]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[async API test]]></title>
            <link>https://lanf0n.medium.com/async-api-test-d1abd5510371?source=rss-36352b1a93d4------2</link>
            <guid isPermaLink="false">https://medium.com/p/d1abd5510371</guid>
            <category><![CDATA[asyncio]]></category>
            <category><![CDATA[aiotodoist]]></category>
            <category><![CDATA[python]]></category>
            <dc:creator><![CDATA[貓橘毛 aka Lanfon]]></dc:creator>
            <pubDate>Fri, 05 Mar 2021 15:47:29 GMT</pubDate>
            <atom:updated>2021-03-05T15:47:29.543Z</atom:updated>
            <content:encoded><![CDATA[<p>aio-todoist 測完 api.py 之後包了一版上 PYPI，稍微記錄下 aiohttp 的 client 測試跟用 pytest 的 WTF 。</p><p>先講講 pytest ，之前寫 unittest 的時候微稍看過一下，覺得 configuration 太多了就沒詳細用(畢竟 builtin 的 unittest 在小測試的情況就很夠用了)。</p><p>這次在寫 test cases 的時候想說有時間就來看一下，順便裝個 <a href="https://github.com/joeyespo/pytest-watch">pytest-watch</a> continuous test 一下，省得 terminal 跟 source code 切來切去的各種麻煩。<br>(btw, sublime 可以裝 <a href="https://github.com/randy3k/Terminus">Terminus</a> 就可以直接在 editor 裡面開 console 了， vscode 有富爸爸我記得原本就有整合了…)</p><p>一裝下光 execution 不一致的部份就研究了老半天，簡單來說用 python -m pytest 跟直接下 command pytest 兩個方式拿到的 sys.path 會不一樣，官方<a href="https://docs.pytest.org/en/latest/pythonpath.html#invoking-pytest-versus-python-m-pytest">文件</a>是這麼說的：</p><blockquote>Running pytest with pytest [...] instead of python -m pytest [...] yields nearly equivalent behaviour, except that the latter will add the current directory to sys.path, which is standard python behavior.</blockquote><p>好的好的，但不是說好直接 adopt builtin 的 unittest 嗎? unittest 的 <strong>behaviour</strong> 不管你怎麼執行都是後者呢…</p><p>簡單來說，如果文件結構像這樣：</p><pre>root_dir<br>+-- tests<br>|   +-- conftest.py<br>|   +-- tests_abc.py   # from THE_PROJECT import abc<br>+-- THE_PROJECT<br>|   +-- miscellaneous modules</pre><p>執行 python -m unittest 或 python -m pytest 可以正常跑測試，但直接執行 pytest 會跳 ImportError 。</p><p>好的好的，然後我就花了一堆時間研究 document 跟看了一下 stackoverflow 的 <a href="https://stackoverflow.com/a/43003192"><strong>解法</strong></a><strong> </strong>…很好，直接把 parent dir 塞進 sys.path 裡面，簡單直接又暴力 — 但我不喜歡。</p><p>花了一堆時間測了各種 command line arguments ，最後測到直接在 tests 裡面塞個空的 __init__.py 就可以了… (((??????? MTFK???</p><p>至於為什麼不乾脆放棄 pytest 直接用 python -m pytest 來執行呢…當然是因為我主要的需求是 <a href="https://github.com/joeyespo/pytest-watch">pytest-watch</a> ，嗯是的我還跑去瞄了一下 code 。(但想想不對側室是無辜的…)</p><p>好的，接下來還是 pytest 的時間，這次是 log 的部份。</p><p>因為一直用 <a href="https://github.com/joeyespo/pytest-watch">ptw</a> 勾著跑測試就沒特別再試 unittest 跑出來的 output (想想畢竟都相容了…)，直到東西丟上 pypi 之後想說用 unittest 跑看看結果怎麼樣…</p><p>一試不得了，到底哪來的 unhandled callback Exception 勒??為什麼 pytest 都沒東西?? 不是都吐到 log.error 了嗎??</p><p>好的，所以 pytest default 的 root log handlers 是：</p><pre>(Pdb) asyncio.log.logger.root.handlers</pre><pre>[&lt;_LiveLoggingNullHandler (NOTSET)&gt;, &lt;_FileHandler /dev/null (NOTSET)&gt;, &lt;LogCaptureHandler (NOTSET)&gt;, &lt;LogCaptureHandler (NOTSET)&gt;]</pre><pre>(Pdb) asyncio.log.logger.handlers</pre><pre>[]</pre><p>(所以我說到底為什麼一個測試用的 libary 要預設把 root log 吃掉???)</p><p>研究了好幾下 cli arguemtns 發現 --show-capture 是不行的呢， --debug, — verbosity, -v 或是 --log-level 都沒有，你得要用 --log-cli-level 才會有東西….<br>(所以我說到底為什麼預設的 root log handlers 要設計被黑洞吃掉呢到底???)</p><p>老實說比較直接開箱即用的 builtin unittest ， pytest 的各種 configurations 跟 decorate fixtures 的設計對於新的使用者來說真的不太友善。<br>(只是寫幾個小測試卻得要先看滿滿的設定、API，那比起單一頁面找一下 self.assertXXX 也沒有好到哪去…)</p><p>回過頭來說說 aiohttp 測試的部份，library 內有提供 test_utils 也有<a href="https://docs.aiohttp.org/en/stable/testing.html#unittest">文件</a> for unittest &amp; pytest ，不過我個人建議是直接看 <a href="https://github.com/aio-libs/aiohttp/blob/25dfe50e5203dd77778a668fd005250dceaccf05/aiohttp/test_utils.py#L397">source code</a> 比較好懂…畢竟文件寫的東西有點…不太齊? (pytest 用的 fixture <a href="https://github.com/aio-libs/aiohttp/blob/master/aiohttp/pytest_plugin.py">在這</a>)</p><p>unittest 的部份， class 繼承 AioHTTPTestCase 、實作 get_application ，async 的測試要先 @unittest_run_loop ，大致上…就這樣。(???)</p><p>aio-todoist 在測 api.py 的部份主要是測 async 的兼容性(compatibility?)，所以跟 server 之間往來的資料還是用 mock 比較多…</p><p>測 _get dispatch to _get_async 的部份用了一下 AsyncMock ，不過 AsyncMock 拿到的 <em>coroutine</em> 沒辦法判斷 coro.close() 有沒有被呼叫，只能測 not awaited …</p><p>可能一般來說 coroutine spawn 之後就是直接被 schedule ( <em>await</em> or <em>ensure_future()</em>) 了才沒有實作(看了一下 mock 要弄也是蠻複雜的QQ)，但實際上如果 coroutine spawn 之後沒有被 schedule 是會噴 log 的 (用不到的話記得用 .close() 關掉啊)</p><p>第二個比較有趣的部份是 future.add_done_callback(cb)，callback function 在 future <strong>完成 </strong>(set_result/set_exception/cancel) 之後都會被呼叫，但如果在 callback 內發生 exception ，外部 (caller) 是抓不到的…一般來說會被 loop 的 exception handler 處理(預設是吐 traceback 給 log.error)，相關的 API 可以參考<a href="https://docs.python.org/3/library/asyncio-eventloop.html#error-handling-api">文件</a>。(((((所以說 pytest 把 log 直接吃掉真的不好!!!</p><p>api.commit() 在寫測試的時候才發現有 bug …因為是透過 api.sync() 來作，在 async scenario 下拿到的應該是 future 而不是一開始寫的 coroutine ，總之在 sync function 裡面拿到 future 還要再做 callback 真的有點賽(完整的部份可以看 <a href="https://github.com/LFLab/aio-todoist/blob/b0ad64dbfe57143f5d12960c020693ea903efca4/aiotodoist/api.py#L120">github</a>)：</p><pre>if self.queue:<br>    queue = self.queue[:]<br>    ret = self.sync(commands=queue)<br>    self.queue[:] = []<br>    if isfuture(ret):<br>        src_fut = ret<br>        ret = ensure_future(_helper(src_fut))<br>        ret.add_done_callback(_check_cancel)<br>    else:<br>        _callback(ret)</pre><p>_helper 是 async-function，當然也是可以用 sync-function 來實作兩個 <em>futures</em> 的 chaining 啦，但這樣的話勢必得利用 src_fut.add_done_callback() 再用 partial 把要 return 的 <em>future</em> 塞在一起，真心搞死自己XD</p><p>_helper 的實作方式就比較單純一點，在裡面 <em>await</em> 再處理 exceptions 的 self.queue restore ，這樣就可以先轉成 <em>coroutine</em> 再轉成 <em>future</em> 傳出去，比較需要注意的是 <em>future</em> 是可以取消的，在這樣的情況下變成要透過 add_done_callback 去連著取消前一個 <em>future </em>。</p><p>至於到底 network I/O 的 cancellation 到底有沒有用就是另一回事了…但我有稍微測過 todoist API 在 duplicate self.queue 資料操作的時候是沒有問題的(XD)，所以就當作有用吧，發出去失敗的 <em>commands</em> 還是要塞回 self.queue 才對!!</p><p>另一個用 async _helper 的原因是 _callback 裡面會 raise Exception ，用 callback + partial 的方式還要再處理 exception ，寫出來的 code 真的醜….</p><p>雜七雜八時間！</p><p>pypi 有上傳專用的 <a href="https://pypi.org/help/#apitoken">api token</a> 了!! 不知道什麼時候加的，但至少 Github Action 裡面不用再塞密碼了 XDD</p><p>唯一缺點是你要先上傳第一版，才可以申請那個 project 專用的 token ，然後目前好像用設定的 scope 還有限就是…</p><p>然後上一篇本來想要寫 async-generator (todoist 裡面 archive manager 的實作)，但後來忘了…可能也許大概下次 ?</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=d1abd5510371" width="1" height="1" alt="">]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[aio Todoist]]></title>
            <link>https://lanf0n.medium.com/aio-todoist-c166f991e5dc?source=rss-36352b1a93d4------2</link>
            <guid isPermaLink="false">https://medium.com/p/c166f991e5dc</guid>
            <category><![CDATA[python]]></category>
            <category><![CDATA[todoist]]></category>
            <category><![CDATA[asyncio]]></category>
            <dc:creator><![CDATA[貓橘毛 aka Lanfon]]></dc:creator>
            <pubDate>Tue, 02 Mar 2021 08:39:19 GMT</pubDate>
            <atom:updated>2021-03-02T08:39:19.251Z</atom:updated>
            <content:encoded><![CDATA[<p>前陣子打算拿 Todoist 來當 backend (?!) 用，發些通知之類的，總之大概記錄一下有趣的東西 &amp; Link 之類的雜七雜八。</p><p>Repo <a href="https://github.com/LFLab/aio-todoist">在這</a>，原本是打算直接發 PR 進<a href="https://github.com/Doist/todoist-python">官方版的API </a>，後來稍微改了 <em>api.py</em> 試了一下才發現事情要是拿摸簡單，還需要發廢文嗎QQ</p><p>詳細改到的 methods 都列在 README 裡面了，還是來說說實作的部份8</p><h4>Coroutine</h4><p>一般的情況就是直接轉成 async def ... 的 async function ，這樣在呼叫的時候就會直接回傳 <strong>coroutine</strong> ，但以最小改動 &amp; 向前相容為原則來說，咱們還是能用的就加減用…</p><p>官方原本的 API 是直接 rely on requests.session ，也就是說實際上 user 本來就可以直接傳 aiohttp.ClientSession 用來轉成 async 的方式操作，但可惜就是(官方)沒有實作而已，所以我們來弄一個像這樣：</p><pre>def _get(self, call, url=None, **kwargs):<br>    url = url or self.get_api_url()<br>    resp = self.session.get(url + call, **kwargs)</pre><pre>    if iscoroutine(resp):<br>        resp.close()<br>        return self._get_async(call, url, **kwargs)</pre><pre>    # ...後略，詳細見 AsyncTodoistAPI._get</pre><p>簡單解，原本的 sync function 在拿到 response 之後檢查是不是摸到 <strong>coroutine</strong> ，如果是 <strong>coroutine</strong> 的話改由 <em>async function</em> 處理，這樣原本的 function 就可以直接升格(?)成支援 async-call (depends on your session)。</p><p>** resp.close() 是為了避免 coroutine never awaited.</p><h4>Future</h4><p>另一種作法是回傳 Future type (also awaitable)，以下用 api.commit 來當例子：</p><pre>def commit(self, raise_on_error=True):<br>    def _callback(fut=None, ret=None):<br>        try:<br>            ret = ret or fut.result()<br>        except Exception as e:<br>            self.queue[:] = queue + self.queue[:]<br>            raise e<br>        # ...(後略)<br> <br>    if self.queue:<br>        queue = self.queue[:]<br>        ret = self.sync(commands=queue)<br>        self.queue[:] = []<br>        if iscoroutine(ret):<br>            ret = ensure_future(ret)<br>            ret.add_done_callback(_callback)<br>        else:<br>            _callback(ret=ret)</pre><pre>    return ret</pre><p>回傳的 asyncio.Future 在被 await 之後會直接拿到 fut.result() ，使用上和 <em>coroutine</em> (from async-function) 沒啥太大的差別。(雖然和 concurrent.futures 同名為 <em>Future</em> ，但使用的場合不太一樣)</p><p>api.commit 比較有趣的地方在於 self.queue 的刪除操作，在 async 的情況下得考量到拿到 exception 的時間比其他操作來得晚的問題。(that’s why _callback 裡面會把 queue 接回去)</p><h4>實作考量</h4><p>簡單原則(?)，如果原本的設計就綁在同步(or maybe multi-threaded?)操作的話就不用這麼麻煩，寫新的 async-function (but maybe with similar signatures?) 就好，以官方的 API 來說，原本就沒有限定 session 的傳遞，所以才考慮直接堆在原本的 methods 上面。</p><p>第二個部份是 return Types，雖然 Python 是 duck typing ，但對使用者來說，新增後出現「新」的型別就會是一種阻礙(但把 async 堆上去，會多 awaitable 算是無法避免的)。</p><p><strong>Coroutine</strong> 和 <strong>Future</strong> 的選擇倒是蠻隨意的(?)，我自己的考量主要是以 function 最後回傳的東西為主，如果是來自 async-calls (例如 api.commit)的話，直接 wrap Future 會是比較簡單的作法(return 前的操作可以用 callback 解決)；以 api._get 的例子來說，最後回傳的東西是 resp.json() or resp.text 兩種，直接 wrap Future 是不行的。(aiohttp 的這兩個都是 async methods)</p><p>但就算不是 async methods ，我自己應該還是會 prefer 另外實作 async-function ，add_done_callback 早期在 Future 還是以 Python 實作的時候是可以直接把 fut._result 替換成別的值，但 3.7 之後原則上都是 C 實作的版本了，還是別考慮從拿到的 future 來下手ㄅ…</p><h4>雜七雜八</h4><p>用 Mac commit 的時候發現 GPG sign 不過(Windows 就沒這問題哭哭)，稍微研究了一下…把找到的東西就堆在這惹。</p><p><a href="https://github.com/pstadler/keybase-gpg-github">Create a GPG key on keybase.io</a><em><br>&gt; </em>感覺很潮但完全沒用到的 Tutorial… maybe 改天會心血來潮註冊一下 <a href="https://www.wikiwand.com/zh-tw/Keybase">keybase.io</a> (?)</p><p><a href="https://stackoverflow.com/a/55646482">git — gpg onto mac osx: error: gpg failed to sign the data</a><br>&gt; 最後沒有裝 <em>gpg2</em> (我就已經有 gpg 了為什麼還要我移掉再裝 gpg2 !!!)<br>&gt; 但裝了 <em>pinentry-mac</em> (雖然覺得好像沒用到)<br>&gt; 最後的能 sign 的作法大概是：</p><pre>$ echo &quot;GPG_TTY=$(tty)&quot;<br># 原本沒這個第二步不會動 QQ, 後來 echo 進 bash_profile 惹</pre><pre>$ echo &quot;test&quot; | gpg --clearsign<br># 確定 gpg 正常能動，這步會要輸入密碼(terminal prompt, but pinentry-mac didn&#39;t installed)</pre><pre>$ gpg --list-secret-keys --keyid-format LONG<br># 這步會拿到一大串像是 <a href="https://stackoverflow.com/a/65232996">https://stackoverflow.com/a/65232996</a> 的圖</pre><pre>$ git config --global user.signing.key &lt;上面看到的<strong>完整的</strong> hash code&gt;</pre><p>** P.S. mac 裡面已經有能用的 key 了</p><p>其他什麼雜七雜八的像是寫進 gpg.conf 之類的根本不影響R…，pinentry-mac 裝起來<strong>可能</strong>有點影響吧，畢竟 commit 的時候跳的是 GUI prompt (輸入密碼)，但感覺就算沒裝也可以跳 terminal prompt …</p><p>btw, git 可以用 git config --global commit.gpgsign true 來設定每個 commits 都要 gpgsign ，這樣就不用每次 commit 的時候在那邊多打一個 -S 惹，讚啦。</p><p>aio-todoist 可能這幾天稍微測一下之後補個 setup.py 就傳到 pypi 上面去惹，應該可能也許會再寫點 test cases 吧大概(?)</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=c166f991e5dc" width="1" height="1" alt="">]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[離開了 HPE 然後呢]]></title>
            <link>https://lanf0n.medium.com/%E9%9B%A2%E9%96%8B%E4%BA%86-hpe-%E7%84%B6%E5%BE%8C%E5%91%A2-345b806aec30?source=rss-36352b1a93d4------2</link>
            <guid isPermaLink="false">https://medium.com/p/345b806aec30</guid>
            <dc:creator><![CDATA[貓橘毛 aka Lanfon]]></dc:creator>
            <pubDate>Thu, 11 Feb 2021 00:55:34 GMT</pubDate>
            <atom:updated>2021-02-11T00:55:34.291Z</atom:updated>
            <content:encoded><![CDATA[<p>話先說在前頭，簡單來說公司本身很好(制度/環境/資源)，但主管只能說慎選。</p><p>這篇應該算是 career 類的，跟 programming 無關。主要也就是記錄一下，順便看看能不能放下都已經離職一年了還會覺得憤怒的情緒，讓 2021 農曆年後有個好的開始。</p><p>先講故事，時間細節就不提了。</p><p>原本的 team 的定位算是 automate testing ，主要的任務是協助 tester 把現有的 manual tests 自動化；這部份做完之後剛好公司 re-organize ，原有的 tester 被切去負責其他產品線，原有的機器交接給我們，然後開了幾個 tester 的缺。(時間上我就不多寫了)</p><p>主管D 找了一個 test lead A， onboard 的前兩週就要開始幫忙跑既有的測試，然後負責生報表、每週匯報。報告的時候 D 會特別問各個測試的細節跟進度，A 在有些測項的說法是「…auto 這邊沒辦法測，所以這部份是 fail 」，連續兩週被我直接指正「…是原本 auto 就 cover 不到需要手動測，另一張 x 是本來就不用測」。</p><p>第二次週報完的晚上， A 發了 mail 補上當週的進度，信上寫的大概是：「…這部份 auto 都沒辦法測所以 fail ，目前的進度 oo % ， schedule 上會 delay … 」；當晚剛好還在忙就第一時間看到 mail ，我直接回了一封咆哮信「上次 meeting 的時候就有說 y 卡本來就是手動測，不是 auto 有沒有 cover 的問題； x 卡的部份我早上也重複過兩次它本來就不用測。就我看來時間上不會有什麼問題…」</p><p>主管 D 過沒幾天就找我私下進會議室，表示「我覺得你在霸凌A」，當下我回了像是「他掛 leader 我怎麼霸凌他？我一個人是怎麼霸凌他？你確定？你懂霸凌什麼意思？」；第二次找我進會議室的時候改口說「A 覺得你在霸凌他」，我回了我們可以直接三個人談談這個問題。</p><p>三人在會議室的時候我直接問了 A 關於「他覺得我在霸凌他」的意思，A 解釋說他手頭上有很多工作做不完，和主管求救希望可以有 training 的時候被拒絕要他自己解決，沒有 resource 可以幫他 training ，加上咆哮信讓他有被霸凌的感覺blahblah 。</p><p>當下我直接再重複了 mail 上的問題、解釋了霸凌的範圍以及在他到職之前就有跟主管D 說過會要安排時間幫他 training ，到職後也有再問過主管D 幫他 training 的時間安排。</p><p>主管D 最後的結論是希望同事A 在下週開會的時候跟所有同事道歉，這件事這樣就算結束。</p><p>以上的故事是最後一根稻草，當然在事件(前)後也是有各種草我就不多寫了，還是說說我個人的立場。</p><p>先講最不能接受的部份：「霸凌」。我小六的時候因為年紀/轉學生/家庭因素的關係被同班霸凌了兩個學期，國二的時候被同班比較要好的同學因為小圈圈跟其他因素霸凌了一個學期(下學期比較沒事畢竟我都躲起來去圖書館或其他地方 &amp; 暑輔完升國三前就轉學了)。</p><p>就我的立場，我不覺得指正問題有達到霸凌的標準。(雖然咆哮信的確蠻情緒的，但當天早上跟前幾次正常的對話很顯然同事都沒聽進去，加上把問題都推出去都是 they 的錯，我也真的是很美送)</p><p>且在主管D 說是「同事A」覺得的時候，在會議室裡他解釋的那些壓力來源跟後續要他跟其他同事道歉(老實說眾同事當天可是一臉 ????? )的總總行為，這是職權壓迫吧。<br>(就時間長度來看它還沒到霸凌的要件，就霸凌的標準來說會是連續且持續性的不合理、帶有情緒的壓迫，相關的部份可參考<a href="https://csrc.edu.tw/bully/about.html">校園霸凌</a>或是你認識的社工)</p><p>單就被一個慣性利用主管職權做權力壓迫的主管<strong>指控</strong>是霸凌者，我也沒什麼好忍的了。(是的，在這之前主管D 對於其他意見跟他有抵觸的同事，常做的就是那我們<strong>私下聊聊</strong>的方式來<strong>討論</strong>你這樣的意見跟你的考績…)</p><p>只能說主管慎選，老子不幹了。</p><p>說是這樣說，但就「霸凌」這件事，當時還是發了封 mail 幫大家知識+一下所謂的霸凌到底是什麼。(我是覺得這應該是主管的工作la)</p><p>也私下跟同事A 聊了幾回，解解他的心結和他跟其他同事之間作事方式的不滿。(我是覺得這應該是主管的工作la)</p><p>後來幾個月，主管先是各種私聊說我自己有問題，然後說出要我月底離職或是他辭退我選一個blahblah (啊這段我剛好有錄音呢)，之後變成問我什麼時候離職(畢竟發現 lay 人要寫報告呢)，到我開始整理各種交接的東之後的態度變成「我其實沒有要趕你走的意思，希望你還是可以留下來幫忙blabla」</p><p>老實說我覺得也沒什麼好講的，被說是霸凌加害者後氣到發抖，就算跑去看了幾個月的中醫到現在，想到還是一把火就上來。</p><p>就一個字，渣。</p><p>所以離職了然後呢？</p><p>然後就一整年都沒工作到現在了呢…</p><p>剛離職的前幾個月真的是完全沒有心想工作，畢竟人生真的是第一次遇到為了往上爬不擇手段的<strong>機會主義者</strong>，不懂先裝懂把各種 buzzwords 拿出來刷、有問題都甩出鍋(我只是讓他們自由發揮不知道怎麼會這樣)，有功勞都出來沾(唉呀我也是很努力幫忙找資源)，真的是受教了。(機會主義者還是他自己很自豪的事呢)</p><p>到四、五月比較有點動力看職缺，但畢竟疫情高峰期都只剩下各種 SI 外包公司、博彩中資…結論上來說，佛系找工作到 10 月開始才真的有在投履歷。(不過也是到這時間才有其他缺就是…)</p><p>不過會那麼無作為畢竟也是因為四月的時候 C 家(現在變 I 家的孩紙惹)認識的說他們有要開缺，phone interview 完說 headcounts 要 first day 後才有開，那時候再面一次….嗯然後就等了幾個月，進辦公室二面後，說能力不錯但覺得會待不久，後面再說覺得前一份工作的 package 太高….然後就沒有然後了。</p><p>只能說譴責無聲卡，謝謝不連絡。<br>(N家跟 U 家好歹投的時候還回個感謝信你年資不夠喔ㄅ欠)</p><p>但算了反正我就廢菜，只能說那些會跟你說不看年資學歷的 100% 是騙人的好嗎，你有看過哪個賺大錢的出來大聲嚷嚷、講大賺的進場沒變9菜的？</p><p>醒醒好ㄇ。</p><p>新年新希望la，比起賺大錢，比較希望下個主管有點腦正常點。</p><p>阿麵。</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=345b806aec30" width="1" height="1" alt="">]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[數位搬家]]></title>
            <link>https://lanf0n.medium.com/%E6%95%B8%E4%BD%8D%E6%90%AC%E5%AE%B6-c880597104dc?source=rss-36352b1a93d4------2</link>
            <guid isPermaLink="false">https://medium.com/p/c880597104dc</guid>
            <dc:creator><![CDATA[貓橘毛 aka Lanfon]]></dc:creator>
            <pubDate>Mon, 07 Dec 2020 17:38:26 GMT</pubDate>
            <atom:updated>2020-12-07T17:38:26.822Z</atom:updated>
            <content:encoded><![CDATA[<p>是說用<strong>數位</strong>這詞總是有點….</p><p>總之這篇和 programming 無關，大致上算是 murmur 廢話之類的。</p><p>放了好久的假，也多了蠻多時間整理各種雜七雜八的東西。實體的東西還算好整理，畢竟可以堆的空間也就那麼大，耗個幾天就整理得差不多了。</p><p>電腦裡的各種雜七雜八反而變得很難整理…大部份是堆到前年買的 NAS 裡之後也就沒什麼再動了…(雖然資料類的東西在存的時候就有好好分類過了，但搬來搬去還是覺得蠻麻煩的)</p><p>這陣子寫 resume 的時候，除了想不到有什麼超能力(畢竟我可不想讓其他人知道我通靈點到幾啊…)可以寫以外，更多的是不知道又或者何必如此的描述自己。</p><p>老實說那些可以把自我介紹、自傳寫得洋洋灑灑的人，我其實很好奇是否會在乎被標籤化(又或者是自我標籤化?)的眼光或立場？還是那些其實也不過就是為達目的(而不得不?)的一種手段？</p><p>但在<strong>寫</strong>給別人看之前的行為，是有選擇的吧？又或者說終究也就是對於社會的一種妥協罷了？</p><p>不知道為什麼總是會一直想到<strong>反身性</strong>這個詞…但也沒辦法多表達什麼，到底是腦袋轉太慢沒東西呢，還是轉太快沒辦法捕捉到東西…hmm…..</p><p>回過頭來說數位搬家這件事，原本的習慣是把一些生活廢文放在 Facebook 原有的 Note 裡面，但最近收掉之後反而變得沒地方放了…(是說雖然無名收了之後各種移來移去之後廢話也越來越少就是…)</p><p>然後跟著各種 social media 倦怠、逃難潮(mewe?)，反而回過頭來思考 blog 之類的東西(然後 medium 不太好用而且主要拿來發程式廢文的問題就…)，總之各種想來想去之下，也許可能說不定會自己 host blog 吧，但現在還沒決定就是了。<br>(然後在這種時候突然有點錯位的感受到那些自己 host blog 的人的心情…但實質上應該是一種87的誤會才對)</p><p>Medium 改版後可以自己設計主畫面但…預設還是沒有 dark mode 真的不太習慣啊…(而且你那個改顏色改字型什麼的難道不能再簡單一點有 themes 之類的東西可以選嗎…)</p><p>總之大概就是這樣了也沒什麼結論，也許可能說不定之後會註冊個 domain Name 然後把各種 link 轉過去之類的，但實質上目前來說就是好懶。</p><p>十幾二十歲的時候總是不停的想要說些什麼，也不知道是想要讓世界看到自己還是為什麼(應該就是中二病?)；工作後不知道什麼時候開始越來越不想要影響別人，確切的說應該是不想要<strong>影響</strong>到其他人的思考行為模式方法角度等等各種雜七雜八。</p><p>好累啊、好懶啊、好麻煩啊之類的想法。但也就只是「<strong>與我無關</strong>」，避免被後續的各種牽拖勒索之類的吧，想想就覺得好麻煩啊…</p><p>自己的事自己負責好嗎，我又蠢又懶又廢，找別人幫忙去吧。</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=c880597104dc" width="1" height="1" alt="">]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Using Asyncio in Python 中譯本]]></title>
            <link>https://lanf0n.medium.com/using-asyncio-in-python-%E4%B8%AD%E8%AD%AF%E6%9C%AC-8a777f6f98a7?source=rss-36352b1a93d4------2</link>
            <guid isPermaLink="false">https://medium.com/p/8a777f6f98a7</guid>
            <category><![CDATA[puthon3]]></category>
            <category><![CDATA[asyncio]]></category>
            <dc:creator><![CDATA[貓橘毛 aka Lanfon]]></dc:creator>
            <pubDate>Tue, 01 Sep 2020 16:53:05 GMT</pubDate>
            <atom:updated>2020-09-01T16:54:22.577Z</atom:updated>
            <content:encoded><![CDATA[<p>Using Asyncio in Python 中譯本</p><p>八月中出版的新書，林信良翻譯的，中譯的品質還算不錯</p><p>不過整本書內容偏少，example code 有些不是 <a href="http://sscce.org/">sscce</a> …..</p><p>還有像是 asyncio.shield, asyncio.wait, asyncio.wait_for 都沒有介紹到，但以入門來說有些東西提得又太少，蠻可惜的。</p><p>Goodreads 上面平均是 4/5，個人給 3.5/5 (無關中譯)。</p><p>只能說 100 多頁能寫的其實很多，花了快 50 頁介紹 3rd party libs 算是最浪費的地方吧。</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=8a777f6f98a7" width="1" height="1" alt="">]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[argparse multi command]]></title>
            <link>https://lanf0n.medium.com/argparse-multi-command-34723dbb42d8?source=rss-36352b1a93d4------2</link>
            <guid isPermaLink="false">https://medium.com/p/34723dbb42d8</guid>
            <category><![CDATA[argparse]]></category>
            <category><![CDATA[python]]></category>
            <category><![CDATA[chinese]]></category>
            <dc:creator><![CDATA[貓橘毛 aka Lanfon]]></dc:creator>
            <pubDate>Sun, 03 May 2020 19:13:30 GMT</pubDate>
            <atom:updated>2020-05-03T19:13:30.275Z</atom:updated>
            <content:encoded><![CDATA[<p>畢竟都用了就稍微記錄一下…(剛好也開桌機有鍵盤快速打字QQ)</p><p>前幾天寫了個 <a href="https://github.com/LFLab/gdrive">cli tool</a> 拿來操作 google drive，裡面花最多時間的是寫 argparse 的 multi command，認份點記錄一下。(code 可以看<a href="https://github.com/LFLab/gdrive/blob/b4207a586d2d515606d80f383adb71cf822c6912/gdrive.py">第一版</a>的比較安全，以免之後改惹)</p><p>簡單來說，37之後 add_subparsers 新增了 <em>required</em> 的參數，但有趣的是你如果只寫 arg.add_subparsers(required=True) 的話，sub command 在沒有輸入的時候並不會跳 required command 的 error 而是</p><pre>TypeError: sequence item 0: expected str instance, NoneType found</pre><p>簡單來從官方的 example 解釋：</p><pre><strong>&gt;&gt;&gt; </strong>parser.parse_args([&#39;--help&#39;])<br>usage: PROG [-h] [--foo] {a,b} ...<br><br>positional arguments:<br>  {a,b}   sub-command help<br>    a     a help<br>    b     b help</pre><p>sub command 在 default 的顯示方式會是類似 <em>set</em> 的表示式(如上的 <em>{a, b}</em>)，在 arg.dd_subparsers 的時候得額外加上 metavar <strong><em>或</em></strong> dest 讓你的 subcommand 有地方存才不會出現 argparse 的 TypeError。</p><p>題外話，</p><ol><li>雖然 add_subparsers 名稱最後有個 <strong>s</strong> ，但實際上它只能被呼叫一次。(完全就是 method naming 的 anti-pattern 最佳範例…)</li><li>不用使用 add_subparsers 也是可以實作 multi-commands 的，在 parser.add_argument 裡把 <em>nargs</em> 用 argparse.REMAINDER 全部抓下來之後就可以再 propagate 給另一個 ArgumentParser instance ，和 add_subparsers 做出來的效果是一樣的。</li><li><a href="https://click.palletsprojects.com/en/7.x/why/">Click</a> 是相對比較多人用 &amp; 完整(複雜)的 3rd party library 。(但有時候簡單的 tool 還要再裝 click 我是覺得有點捨本逐末惹)</li></ol><p>沒惹，好懶……..</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=34723dbb42d8" width="1" height="1" alt="">]]></content:encoded>
        </item>
    </channel>
</rss>