【Python】「間違えようのないやり方が、一つだけある」という考え方
先日、 言語設計者たちが考えること (THEORY/IN/PRACTICE) | Federico Biancuzzi, Shane Warden, 伊藤 真浩, 頃末 和義, 佐藤 嘉一, 鈴木 幸敏, 村上 雅章 |本 | 通販 | Amazonという本を読みました。
数多く存在するプログラミング言語、その言語の設計者たちが何を考え、どんな目標をもって取り組んでいるのかということがインタビュー形式で読める書籍となっています。
この本の中で、Pythonの生みの親であるGuido van Rossumu氏の「間違えようのないやり方が、一つだけある」という考え方が非常に良いものだと思ったので記事に残しておきたいと思いました。
私自身Pythonはやったことがないのですが、Pythonの設計として上記の考え方は非常に重要視されています。
プログラムって書ければいいというだけにはいきません。むしろ他人が書いたものを読むことの方が圧倒的に多いです。
そこで目的を達成するための方法が一つに絞られる、というこの考え方は、読む側にとって非常にありがたいものです。
言語レベルで手法が標準化されているのはありがたいですね~
そのうちPython触ってみたいですね
【WSL2】wsl2のインストールで0x80370102 エラーが出たときの対処方法【HP】
Windows 10でwsl2をインストールするときに一発でうまくいかなかった部分の対応をメモしときます。
以下の手順で進めました。
Install Windows Subsystem for Linux (WSL) on Windows 10 | Microsoft Docs
MS StoreでUbuntuをインストールし起動すると、以下のエラーが起こりました。
WslRegisterDistribution failed with error: 0x80370102
手順にもある通り、コンピューターのBIOS内部で仮想化が有効化されていないことが原因のようです。
私の場合はHPのPCなので、F10でBiosを起動し、System Configuration
⇒ Virtualization Technology
をEnable
にすればうまくいきました。
Powershellで引数をMandatory(必須)にするときに地味にハマったこと
事象
Powershellでスペースありの文字列型の引数を受け取るとき、受け取った値にダブルクォーテーションがついてたりついてなかったりするので、それについて。
以下のようにparam
を使って引数を受け取る自作関数があったとします。
function Get-Hoge{ param( [Parameter(Mandatory)] [string]$arg ) Write-Host $arg }
で、関数を呼ぶ時は以下の2つの呼び出し方があります。 渡すのはスペースを含む文字列とします。
パターン1
PS C:\Users\nogam> Get-Hoge -arg "has space" has space
これは問題なさそうですね。
パターン2(Mandatoryにより入力待ちから入力する場合)
PS C:\Users\nogam> Get-Hoge コマンド パイプライン位置 1 のコマンドレット Get-Hoge 次のパラメーターに値を指定してください: arg: "has space" "has space"
のほうは、ダブルクォーテーション付きで渡されます。
"has space"ではなくhas spaceとだけ入力すれば、ふつうにhas spaceが渡されます。
これを回避するためには、以下のようにTrim
すればよいです。
Write-Host $arg.Trim("`"")
何がハマったのか
ここからはPowershellの仕様とは関係ない運用的な話になります。
例えば、上記の関数が以下のようにファイルパスを受け取るような関数だった場合を考えます。
function Open-HogeFile{ param( [Parameter(Mandatory)] [string]$file_path ) $file = [System.IO.File]::Open($file_path, [System.IO.FileMode]::Open) $file $file.Close() }
パターン1の場合
PS C:\Users\nogam> Open-HogeFile -file_path "C:\Users\nogam\Downloads\hoge.txt" CanRead : True CanWrite : True CanSeek : True IsAsync : False Length : 11 Name : C:\Users\nogam\Downloads\hoge.txt Position : 0 Handle : 4228 SafeFileHandle : Microsoft.Win32.SafeHandles.SafeFileHandle CanTimeout : False ReadTimeout : WriteTimeout :
問題なさそうです。しかしパターン2だと
PS C:\Users\nogam> Open-HogeFile コマンド パイプライン位置 1 のコマンドレット Open-HogeFile 次のパラメーターに値を指定してください: file_path: "C:\Users\nogam\Downloads\hoge.txt" "2" 個の引数を指定して "Open" を呼び出し中に例外が発生しました: "パスに無効な文字が含まれています。" 発生場所 C:\Users\nogam\Downloads\test.ps1:6 文字:5 + $file = [System.IO.File]::Open($file_path, [System.IO.FileMode]:: ... + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + CategoryInfo : NotSpecified: (:) [], MethodInvocationException + FullyQualifiedErrorId : ArgumentException null 値の式ではメソッドを呼び出せません。 発生場所 C:\Users\nogam\Downloads\test.ps1:8 文字:5 + $file.Close() + ~~~~~~~~~~~~~ + CategoryInfo : InvalidOperation: (:) []、RuntimeException + FullyQualifiedErrorId : InvokeMethodOnNull
当然ですが失敗します。
ところで、「この引数の渡し方は失敗するのがわかっているのに、なぜそんな渡し方をするの?」と思いますよね。
それは、この関数の運用を考えると見えてきます。この関数の利用者は、
1.エクスプローラーからファイルを右クリック⇒パスをコピー
2.関数を実行するときに、コピーしたパスをペースト
という手順を踏むことが多くなるはずです。
上記の1.でコピーしたパスはダブルクォーテーション付きでコピーされます。
また、この関数を利用するのがPowershellをあまり利用しない人だった場合、関数実行時に指定する引数と対話型プロンプトから指定する引数で渡し方が違うとは想像しにくいです
なので、引数はTrimして受けるのがいい気がしますね。
ここまでお読みいただきありがとうございます。
【雑記】javaで定数を比較する時の違和感
javaで定数を比較する時によく、
if(CONSTANT.equals(value)){ System.out.print("hoge"); }
という書き方をします。
※CONSTANT
は定数でvalue
はチェック対象の変数だと思ってください。
これあんまり好きじゃないんですよね。
理由はやりたいこととやっている事(前から読んだ時の意味)が逆になるからです。
上記の例だと、
□やりたいこと: value
がCONSTANT
と等しい時、hogeを出力
□やっている事: CONSTANT
がvalue
と等しい時、hogeを出力
いやまぁやっていることはどちらも同じなんですが、やりたいことをそのまま表現するには
if(value.equals(CONSTANT)){ System.out.print("hoge"); }
となっていた方が単純にいいですよね。。
最近は読みやすいプログラムを書くことが重要になってきているので、正直この辺は気持ち悪いですね。
ちなみに定数を前に持ってくるのは、value
がnullだったときのNullPointerExceptionを避けるためです。
【失敗談】Gitのコマンドとオプションを補完するコンソールアプリケーションを作ろうとした話
- 前置き
- 何がしたかったか
- コマンド、オプション、synopsisのスクレイプ
- スクレイプで躓いたこと
- ともあれスクレイプはひとまず完了
- スクレイプしたものを解析する。
- sinopsis解析のパターン
- そもそも前提が間違っていた
- 結論
前置き
gitのコマンドとオプションを補完するコンソールアプリケーションを作ろうとしていましたが、失敗しました。
何がだめだったか、どうすべきだったかを自戒も込めて記事にします。
何がしたかったか
作りたかったものはタイトルの通りです。
手順としては、
1. gitの公式サイトのコマンド一覧からコマンドとオプションをスクレイピングする
2. 1でスクレイピングしたものをもとに、補完するための仕組みを作成する。
です。
コマンド、オプション、synopsisのスクレイプ
まず、スクレイプするにあたり以下のことを考えなければいけません。
1. 対象のコマンドは?
2. スクレイプのルールは?
1については、command_list.txtと公式サイトの一覧をベースに、いらないもの(コマンドではないもの等)を除外してスクレイプ対象のコマンドを決めました。
2について、それぞれ以下のように決めました。
■ URL & command
「https://git-scm.com/docs/git-{command}」という形式でコマンドごとのリファレンスがあります。
例えばadd
コマンドだとhttps://git-scm.com/docs/git-add
といった感じです。
■ synopsis
ざっくりいうと、コマンドごとのリファレンスのHTMLを見ると、シノプシスは_synopsis
というidに属しています。
■ options
ざっくりいうと、コマンドごとのリファレンスのHTMLを見ると、オプションはすべて_options
というidとhdlist1
というクラスに属していることがわかります。
ここまでそろえば簡単ですね。...と思っていましたが実は少しめんどくさかったです。
スクレイプで躓いたこと
結論から言うと、スクレイプのルールの前提が甘かったです。甘かったといってもほとんどはルール通りスクレイプできましたが、一部のドキュメントがルールに沿わない構造となっていたため、個別ルールを追加してスクレイプしました。
そもそもの目標の1つとして「gitのアップデートに柔軟に対応する」というのがありました。
しかし、上記のような「例外(ルールを外れたスクレイプ)」を許してしまうと、gitのアップデートのたびにスクレイプ用のアプリを改修しなければいけない可能性があるので、あまりよくない状況になりました。
ともあれスクレイプはひとまず完了
自分のプロジェクトに実際にスクレイプしたものがあります。ご参考までに
スクレイプしたものを解析する。
上のセクションで、synopsis
もスクレイプしました。このセクションで使うためです。
まずはそれぞれのオプションがどのように使われるかを知る必要があります。そこで、synopsis
を解析し、オプション一つ一つにメタデータ(値を持つか、他のオプションと同時に指定できるか、等)を付けようと考えました。
sinopsis解析のパターン
まずはsynopsisの記法についてです。
調べてみましたが、あまり参考になるものを見つけられませんでした。正規表現の概念に依存するというようなことはあったのですが.....
まぁでも、何となくsynopsisの記法は直感的に理解していたので、適当にやってみるか!となりました。
...が、これが思ったより難しい。当たり前ですが、人が見れば普通に分かることでも、機械的に解析しようと思うと大変です。解析するにあたり、いろいろ考えなければいけませんでした。最も頭を悩まされたことは「そもそもコマンドによってsynopsisの書き方のクセが異なる」ということでした。
またスクレイプした時のように個別ルールが必要になるのでは…
まさにそうでした。1つの共通ルールでの解析は(私の能力では)無理そうでした。
だんだんと当初の考えとずれてきたので、1度原点回帰することに。
そもそも前提が間違っていた
当時(今もですが)、Powershellの補完機能に魅了されてました。従来のshellは「ヘルプを見て、すべてタイピングする」というのが基本ですが、Powershellは「途中までタイプすれば保管してくれる」ので初めてでも割と感覚だけで使えました。また特性としてコマンドが「動詞-名詞」の形式かつ、オプションも正式な英語(従来のshellのように略語ではない)なので、コマンド名、オプション名から何がしたいかを予測可能なのです。
例えば、PowershellのGet-ChildItem - File
とcmd.exeのdir /a-d
は同じ挙動をします。Powershell、cmd.exeを知らない人(例えば開発者ではない事務の人とか)が上記を見た場合、前者は「あぁ、なんか子アイテムのファイル取ろうとしてるわ」と容易に予想できますが、後者は予測不可能でしょう。
従来のshellの場合、その特性から「コマンド名、オプション名は短く、タイピングしやすいこと」が重要な要素だったと考えます。現にgitも主要なオプションに短い形式が用意されていました。 一方でPowershellは「わかりやすさ」に重きを置いている気がします。なので、逆に普通にシェルとしてプロンプトから利用するのは少し面倒くささもあります。(エイリアス使えば別ですが)
上記を踏まえて、「もともとタイピングを減らすために設計されているので初見では分かりにくいものを補完したところで、補完されたものの意味を予測できないので結局ヘルプ見るしかない」ことに考えが及ばなかったことが敗因だと思いました。
また、「GitのHelpページはひな形こそあれど、別に厳密なルールで構造化されているわけではない」、「synopsisも別に明確なルールがあるわけではなく、人にわかりやすい記法(≒機械にはわかりにくい)である」ということに気づけなかったのも良くなかったと思います。たしかにgit開発のチュートリアルとか見てもそんな感じですね。。
結局、ここで考えるのをやめて、アプリ作りも終わりました。
結論
今回のことで、
■ 公式ドキュメントを読め。
■ 根拠のない決めつけで物事を進めるな
ということを痛感させられました。ただ、経験としては良かったです。
ここまで読んでいただきありがとうございました。
Powershell Coreをビルドしてデバッグする。
前置き
最近Powershellを使うことが多かったのですが、なかなか使いこなすのが難しいので、内部仕様を見て理解を深めようと思ったのでやってみます。
※Powershell CoreはWindows Powershellコードベースにフォークされたプロジェクトです。
ビルドはwindows-core.mdにそってやります。
環境
Windows 10 (1909)
Visual Studio Community 2019 (16.7.2)
ビルド
■visual studioの設定
[ツール] -> [ツールと機能を取得] -> [ワークロード]
[.NET デスクトップ開発]と[.NET Coreクロスプラットフォームの開発]にチェックしてインストールする。
■リポジトリのクローン
今回は現在の最新のタグ(v7.1.0-preview.6)でやろうと思います。
(各種タグについての説明はここで確認できます)
$git clone https://github.com/PowerShell/PowerShell.git $git checkout v7.1.0-preview.6
■.NET Core SDKのインストール(またはglobal.jsonの書き換え)
現在.NET SDK 5.0以降をインストールしていない人はこのセクションの手順は実施不要です。
後述する「Powershell Coreのビルド」で自動的に.NET SDKがインストールされます。
.NET Core SDKをすでにインストール済みの人は、この手順が必要です。
- インストールされている.NET SDKのバージョン確認
$dotnet --list-sdks 3.1.301 [C:\Program Files\dotnet\sdk] 3.1.401 [C:\Program Files\dotnet\sdk] 5.0.100-preview.7.20366.6 [C:\Program Files\dotnet\sdk]
自分の場合は5.0.100-preview.7.20366.6
ですね。
\global.json
を書き換え
cloneしたPowershell
直下の.\global.json
を下記のように書き換えます。
$type global.json { "sdk": { "version": "5.0.100-preview.7.20366.6" } }
■Powershell Coreのビルド
cloneしたPowershell
直下で実行してください。 Powershellまたはpwshで実行してください。
Powershellのプロンプトってどう表現すればいいか分からなかったので、ここでは
PS>
としています 。
以下のコマンドで.NET SDKをインストールします。すでにインストール済みの場合はdotnet is already installed. Skipping installation.
のようなメッセージが出ます。
PS>Import-Module .\build.psm1 PS>Start-PSBootstrap
以下のコマンドでPowershell Coreをビルドします。
Start-PSBuild
正常終了されればOKです。
実行ファイルへのパスは下記のコマンドで確認できます。
PS>Get-PSOutput C:\develop\global_reps\PowerShell\src\powershell-win-core\bin\Debug\net5.0\win7-x64\publish\pwsh.exe
Visul Studioでデバッグ
cloneしたPowershell
直下の.\PowerShell.sln
を開きます。 f5
キーで実行します。
プロンプトが表示されるので、試しにGet-ChildItem
コマンドレットをたたいてみます。
PS C:\develop\global_reps\PowerShell\src\powershell-win-core\bin\Debug\net5.0> Get-ChildItem Directory: C:\develop\global_reps\PowerShell\src\powershell-win-core\bin\Debug\net5.0 Mode LastWriteTime Length Name ---- ------------- ------ ---- d---- 2020/08/22 10:07 cs d---- 2020/08/22 10:07 de d---- 2020/08/22 10:07 en-US d---- 2020/08/22 10:07 es d---- 2020/08/22 10:07 fr d---- 2020/08/22 10:07 it d---- 2020/08/22 10:07 ja d---- 2020/08/22 10:07 ko d---- 2020/08/22 10:07 Modules d---- 2020/08/22 10:07 pl d---- 2020/08/22 10:07 preview d---- 2020/08/22 10:07 pt-BR d---- 2020/08/22 10:07 ru d---- 2020/08/22 10:07 runtimes d---- 2020/08/22 10:07 Schemas d---- 2020/08/22 10:07 tr d---- 2020/08/21 16:19 win7-x64 d---- 2020/08/22 10:07 zh-Hans d---- 2020/08/22 10:07 zh-Hant -a--- 2020/08/21 16:13 8933 Install-PowerShellRemoting.ps1 -a--- 2020/08/21 16:13 2908 InstallPSCorePolicyDefinitions.ps1 -a--- 2020/08/21 16:13 1095 LICENSE.txt -a--- 2020/04/18 16:34 403456 Markdig.Signed.dll -a--- 2020/04/22 16:37 350072 Microsoft.ApplicationInsights.dll -a--- 2020/07/25 4:15 5437320 Microsoft.CodeAnalysis.CSharp.dll ....... PS C:\develop\global_reps\PowerShell\src\powershell-win-core\bin\Debug\net5.0>
大丈夫そうですね。
コマンドの実行エントリ
.\src\Microsoft.PowerShell.ConsoleHost\host\msh\ConsoleHost.cs
の2583行目当たりの
_exec.ExecuteCommand(line, out e, Executor.ExecutionOptions.AddOutputter | Executor.ExecutionOptions.AddToHistory);
からコマンドを実行します。引数のline
には(よく確認してないけどたぶん、、)プロンプトからの入力がそのまま入る感じですね。
ブレイクポイント
プロンプトの実行、コマンドの実行等の主要なポイントでブレイクポイントを用意しました。
ここからダウンロードしてVisual Studioのブレイクポイントウィンドウからインポートして下さい。
読んでいただきありがとうございました。