base64 の複数ファイルから元ファイルを復元する Python コード
Linux 環境で そこへのデータ送信は文字列のコピーのみできるところ
ssh 等を通したファイルのコピーは不可
その環境からインターネット接続は不可
だけどそこにファイル持っていきたい

ファイルを zip にまとめて base64 文字列で複数回に分けて貼り付けてから Linux 環境で復元する
PHP や Node.js は入れられないけど Python はデフォルトで入ってるので使える
Python のコードは文字列なのでプログラム自体を持っていくのも問題なくできる

そのときの復元用コード

import argparse
import base64
from pathlib import Path

parser = argparse.ArgumentParser()
parser.add_argument("directory")
parser.add_argument("output")
args = parser.parse_args()

directory = Path(args.directory)
files = list(directory.glob("*.part"))
files = sorted(files, key=lambda x: x.name)

if len(files) == 0:
print("対象のファイルが見つかりません")
exit(1)
else:
print("対象ファイル:")
print([file.name for file in files])

b64str = ""

for file in files:
b64str += file.read_text()

result = base64.b64decode(b64str)

with open(args.output, "wb") as f:
f.write(result)

print("完了しました")

適当なフォルダ (dir1) に 01.part, 02.part, ... のようにファイルを作って 次のコマンドを実行

python3 b64restore.py dir1 output.zip

dir1 の中の .part のファイルをソートして結合して base64 デコードして output.zip に出力



考えてみると 全体をメモリに一旦乗せるならファイルに保存する必要ないし 標準入力に貼り付けるようにしました

import sys
import base64

text = sys.stdin.read()
result = base64.b64decode(text)
sys.stdout.buffer.write(result)
python3 b64restore.py > data.zip

Base64 文字列を全部貼り付けたら Ctrl-D で EOF を送って終了させます
pyscript が script タグになってた
去年にブラウザで Python を動かせるとして出てきた pyscript
あれからも大きく更新されてたようです
https://jeff.glass/post/whats-new-pyscript-2024-1-1/

最初の頃は Python のコードを書く場所に py-script タグを使ってました
それが stable 版になった今だと py-script タグは推奨されず script タグを使って type に py や mpy を指定するのが推奨のようです
https://pyscript.github.io/docs/2024.1.1/user-guide/first-steps/
カスタムエレメントだと HTML として解析されてしまうことがあるからだとか

最初見たときにも script タグを使わないとエスケープとかおかしなことになりそうと思いましたが やっぱりそこが問題だったみたいです
ただすべての py-* タグが推奨されないわけではなく設定を書く <py-config> は使われてるようです

ところで script タグを使うときの type の文字列
HTML の仕様的には 今後追加されるものは module や importmap みたいに MIME type 以外の文字列で追加予定なのでデータブロックを表すために type に指定するのは有効な MIME type で JavaScript 用に予約されてるもの以外とされてたはずです
https://html.spec.whatwg.org/multipage/scripting.html#the-script-element
py や mpy を使ってるので pyscript ではそれには則ってないみたいですね
Python の type hints は実行時チェックされない
Python の type hints はこれといって書いたことがなく記法とそういうのがあるということを知ってるくらいでした

type hints 付きで書かれてるコードをいじってたときのこと
実装を変えて型が変わったのに関数の引数と返り値の型はそのままだったのですがエラーはなく動きました

def foo(num: int) -> int:
return num * 2

foo("A")
# "AA"

みたいな感じでエラーはなく動いてます
PHP みたいに実行時にチェックが入って一致しないとエラーになるのかと思いましたが 調べてみるとツールでチェックするためのもので実行時には Python 側でチェックなどはしないようですね

一応実行時にチェックさせようとするツールもあるようですが 単純に Python ファイルをそれに渡すだけじゃダメのようです
デコレーターを使ったり継承したりソースコード自体をそのツールに合わせて書く必要があるようです
型チェックのツールを変えるのにソースコードを書き換えないといけないとなると Flow から TypeScript に移行するようなもので 使いやすそうにも思えません
なので実行前に静的解析するだけで TypeScript に近い扱いみたいです

そうなると 以前話題になってた TypeScript の型記述をコメントとして JavaScript に持ってくるみたいなのも意外と現実性があったりするんでしょうか
そういえば最近全然話題になりませんが どうなったんでしょうね
ただ これとは違って Python の場合は型情報を値として持っていて実行時に参照可能です
TypeScript 構文をコメントとみなすあれは本当にコメントなので 実行時に何も残らず JavaScript からすると紛らわしいだけの無意味なものです
最悪入るとしても Python みたいに実行時に参照できるようにはなって欲しいものです

もう少し調べてみたら Python の type hints は annotations という仕組みであって type hints だけのものじゃないようです
だからか型を書くところに書けるものは型に限らず任意の式となってました

なのでこういうのも有効です

>>> import random
>>> from datetime import datetime
>>>
>>> def foo(a: random.random(), b: datetime.now(), c: [1,2,3][1:]) -> 1 + 2:
... return 0
...
>>> foo(1, 2, 3)
0
>>> foo.__annotations__
{'a': 0.8245880245015794, 'b': datetime.datetime(2023, 12, 29, 10, 16, 38, 266420), 'c': [2, 3], 'return': 3}

関数を実行した結果だったり datetime みたいなオブジェクトだったり 足し算した結果だったり
もう型のようには見えません

annotations の中で type hints を書いてるのなら dict で type みたいなキーに対して書くほうが適してそうには思います

>>> def foo(a: { "type": int }) -> { "type": str }:
... return "A" * a

これなら他の情報も追加できます
でもほとんどが type hints のみで コードが長くなるので type hints を直接書く形なのでしょうか
他のものも記載したくなったらどうするのでしょうね
dict 以外なら dict 化して値を type が key の value として追加するとかでしょうか

こういうことができるものなので Python では型は実体のある値として実行時に扱えるものです
これはすごくいいですね
C++ や C# みたいな言語だとクラスは実体がないものであって 実行時に触れられるものでもないので扱いづらく好きになれなかったです
その点 JavaScript や Python はクラスそのものもオブジェクトとして実行時に変数に入れたりプロパティを見たりなどできるものです
こういう扱いになってるのが好きなので型情報も変数に格納されてる値であるのはいいところです

ただ 変数の型については評価されるので関数は実行されますが どこにも保存されず実行時に扱えないようでした

>>> foo: 10 = 1

>>> f = lambda: print("called")
>>> bar: f() = 1
called

annotations で指定している 10 は変数 foo の情報であって foo に入ってる値の 1 に対するものではありません
foo でアクセスできるのは変数に入ってる値であって変数そのものではないので変数の情報である変数の型が取れないのは仕方なくはありますね

またジェネリクスを表現する型パラメーターというのもあります

>>> def foo[T](a: T) -> T:
... return a
...
>>> foo.__type_params__
(T,)
>>> type(foo.__type_params__[0])
<class 'typing.TypeVar'>
>>> foo.__annotations__
{'a': T, 'return': T}

これは引数みたいな感じで関数やクラスの中では変数に入ってる値として扱えます

>>> def foo[X]():      
... print(X, type(X))
...
>>> foo()
X <class 'typing.TypeVar'>
>>> class C[T]:
... print(T)
...
T
rye sync でエラーになる
パッケージを追加して rye sync を実行したらなぜかエラーになりました
原因がわからなくて困ってましたが パッケージとは関係なく src フォルダ内にデフォルトで用意されてるフォルダを消すとまずいようでした

foo という名前で rye init したら src フォルダの中はこんな感じになっています

src/
└── foo/
└── __init__.py

自分で記述するファイルは単一ファイルでよかったのでこう変更してました

src/
└── main.py

実行するときは

python3 src/main.py

で直接実行してました

foo フォルダを消したのがまずかったようで 消したあとに rye sync をするとエラーになりました
パッケージを追加しなければ最初以降に rye sync することもなかったので気づかなかったです

エラーは production lockfile の生成中に subprocess-exited-with-error というもの
エラー全体はすごく長くて最後の方を見ても lock ファイルの書き込みに失敗したくらいしかわからないです

[root@4728010fbd1b foo]# rye sync
Reusing already existing virtualenv
Generating production lockfile: /opt/foo/requirements.lock
error: subprocess-exited-with-error

× Preparing metadata (pyproject.toml) did not run successfully.
│ exit code: 1
╰─> [41 lines of output]
Traceback (most recent call last):
File "/root/.rye/pip-tools/cpython@3.12/lib/python3.12/site-packages/pip/_vendor/pyproject_hooks/_in_process/_in_process.py", line 353, in <module>
main()
File "/root/.rye/pip-tools/cpython@3.12/lib/python3.12/site-packages/pip/_vendor/pyproject_hooks/_in_process/_in_process.py", line 335, in main
json_out['return_val'] = hook(**hook_input['kwargs'])
^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/root/.rye/pip-tools/cpython@3.12/lib/python3.12/site-packages/pip/_vendor/pyproject_hooks/_in_process/_in_process.py", line 152, in prepare_metadata_for_build_wheel
whl_basename = backend.build_wheel(metadata_directory, config_settings)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/tmp/pip-build-env-ok8nhepv/overlay/lib/python3.12/site-packages/hatchling/build.py", line 58, in build_wheel
return os.path.basename(next(builder.build(directory=wheel_directory, versions=['standard'])))
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/tmp/pip-build-env-ok8nhepv/overlay/lib/python3.12/site-packages/hatchling/builders/plugin/interface.py", line 155, in build
artifact = version_api[version](directory, **build_data)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/tmp/pip-build-env-ok8nhepv/overlay/lib/python3.12/site-packages/hatchling/builders/wheel.py", line 412, in build_standard
for included_file in self.recurse_included_files():
File "/tmp/pip-build-env-ok8nhepv/overlay/lib/python3.12/site-packages/hatchling/builders/plugin/interface.py", line 176, in recurse_included_files
yield from self.recurse_selected_project_files()
File "/tmp/pip-build-env-ok8nhepv/overlay/lib/python3.12/site-packages/hatchling/builders/plugin/interface.py", line 180, in recurse_selected_project_files
if self.config.only_include:
^^^^^^^^^^^^^^^^^^^^^^^^
File "/tmp/pip-build-env-ok8nhepv/overlay/lib/python3.12/site-packages/hatchling/builders/config.py", line 781, in only_include
only_include = only_include_config.get('only-include', self.default_only_include()) or self.packages
^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/tmp/pip-build-env-ok8nhepv/overlay/lib/python3.12/site-packages/hatchling/builders/wheel.py", line 231, in default_only_include
return self.default_file_selection_options.only_include
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/root/.rye/py/cpython@3.12.0/install/lib/python3.12/functools.py", line 995, in __get__
val = self.func(instance)
^^^^^^^^^^^^^^^^^^^
File "/tmp/pip-build-env-ok8nhepv/overlay/lib/python3.12/site-packages/hatchling/builders/wheel.py", line 219, in default_file_selection_options
raise ValueError(message)
ValueError: Unable to determine which files to ship inside the wheel using the following heuristics: https://hatch.pypa.io/latest/plugins/builder/wheel/#default-file-selection

At least one file selection option must be defined in the `tool.hatch.build.targets.wheel` table, see: https://hatch.pypa.io/latest/config/build/

As an example, if you intend to ship a directory named `foo` that resides within a `src` directory located at the root of your project, you can define the following:

[tool.hatch.build.targets.wheel]
packages = ["src/foo"]
[end of output]

note: This error originates from a subprocess, and is likely not a problem with pip.
Traceback (most recent call last):
File "/root/.rye/pip-tools/cpython@3.12/lib/python3.12/site-packages/pip/_internal/operations/build/metadata.py", line 35, in generate_metadata
distinfo_dir = backend.prepare_metadata_for_build_wheel(metadata_dir)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/root/.rye/pip-tools/cpython@3.12/lib/python3.12/site-packages/pip/_internal/utils/misc.py", line 772, in prepare_metadata_for_build_wheel
return super().prepare_metadata_for_build_wheel(
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/root/.rye/pip-tools/cpython@3.12/lib/python3.12/site-packages/pip/_vendor/pyproject_hooks/_impl.py", line 186, in prepare_metadata_for_build_wheel
return self._call_hook('prepare_metadata_for_build_wheel', {
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/root/.rye/pip-tools/cpython@3.12/lib/python3.12/site-packages/pip/_vendor/pyproject_hooks/_impl.py", line 311, in _call_hook
self._subprocess_runner(
File "/root/.rye/pip-tools/cpython@3.12/lib/python3.12/site-packages/pip/_internal/utils/subprocess.py", line 252, in runner
call_subprocess(
File "/root/.rye/pip-tools/cpython@3.12/lib/python3.12/site-packages/pip/_internal/utils/subprocess.py", line 224, in call_subprocess
raise error
pip._internal.exceptions.InstallationSubprocessError: Preparing metadata (pyproject.toml) exited with 1

The above exception was the direct cause of the following exception:

Traceback (most recent call last):
File "/root/.rye/pip-tools/cpython@3.12/bin/pip-compile", line 8, in <module>
sys.exit(cli())
^^^^^
File "/root/.rye/pip-tools/cpython@3.12/lib/python3.12/site-packages/click/core.py", line 1157, in __call__
return self.main(*args, **kwargs)
^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/root/.rye/pip-tools/cpython@3.12/lib/python3.12/site-packages/click/core.py", line 1078, in main
rv = self.invoke(ctx)
^^^^^^^^^^^^^^^^
File "/root/.rye/pip-tools/cpython@3.12/lib/python3.12/site-packages/click/core.py", line 1434, in invoke
return ctx.invoke(self.callback, **ctx.params)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/root/.rye/pip-tools/cpython@3.12/lib/python3.12/site-packages/click/core.py", line 783, in invoke
return __callback(*args, **kwargs)
^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/root/.rye/pip-tools/cpython@3.12/lib/python3.12/site-packages/click/decorators.py", line 33, in new_func
return f(get_current_context(), *args, **kwargs)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/root/.rye/pip-tools/cpython@3.12/lib/python3.12/site-packages/piptools/scripts/compile.py", line 592, in cli
results = resolver.resolve(max_rounds=max_rounds)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/root/.rye/pip-tools/cpython@3.12/lib/python3.12/site-packages/piptools/resolver.py", line 593, in resolve
is_resolved = self._do_resolve(
^^^^^^^^^^^^^^^^^
File "/root/.rye/pip-tools/cpython@3.12/lib/python3.12/site-packages/piptools/resolver.py", line 625, in _do_resolve
resolver.resolve(
File "/root/.rye/pip-tools/cpython@3.12/lib/python3.12/site-packages/pip/_internal/resolution/resolvelib/resolver.py", line 76, in resolve
collected = self.factory.collect_root_requirements(root_reqs)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/root/.rye/pip-tools/cpython@3.12/lib/python3.12/site-packages/pip/_internal/resolution/resolvelib/factory.py", line 534, in collect_root_requirements
reqs = list(
^^^^^
File "/root/.rye/pip-tools/cpython@3.12/lib/python3.12/site-packages/pip/_internal/resolution/resolvelib/factory.py", line 490, in _make_requirements_from_install_req
cand = self._make_base_candidate_from_link(
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/root/.rye/pip-tools/cpython@3.12/lib/python3.12/site-packages/pip/_internal/resolution/resolvelib/factory.py", line 207, in _make_base_candidate_from_link
self._editable_candidate_cache[link] = EditableCandidate(
^^^^^^^^^^^^^^^^^^
File "/root/.rye/pip-tools/cpython@3.12/lib/python3.12/site-packages/pip/_internal/resolution/resolvelib/candidates.py", line 318, in __init__
super().__init__(
File "/root/.rye/pip-tools/cpython@3.12/lib/python3.12/site-packages/pip/_internal/resolution/resolvelib/candidates.py", line 156, in __init__
self.dist = self._prepare()
^^^^^^^^^^^^^^^
File "/root/.rye/pip-tools/cpython@3.12/lib/python3.12/site-packages/pip/_internal/resolution/resolvelib/candidates.py", line 225, in _prepare
dist = self._prepare_distribution()
^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/root/.rye/pip-tools/cpython@3.12/lib/python3.12/site-packages/pip/_internal/resolution/resolvelib/candidates.py", line 328, in _prepare_distribution
return self._factory.preparer.prepare_editable_requirement(self._ireq)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/root/.rye/pip-tools/cpython@3.12/lib/python3.12/site-packages/pip/_internal/operations/prepare.py", line 696, in prepare_editable_requirement
dist = _get_prepared_distribution(
^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/root/.rye/pip-tools/cpython@3.12/lib/python3.12/site-packages/pip/_internal/operations/prepare.py", line 71, in _get_prepared_distribution
abstract_dist.prepare_distribution_metadata(
File "/root/.rye/pip-tools/cpython@3.12/lib/python3.12/site-packages/pip/_internal/distributions/sdist.py", line 67, in prepare_distribution_metadata
self.req.prepare_metadata()
File "/root/.rye/pip-tools/cpython@3.12/lib/python3.12/site-packages/pip/_internal/req/req_install.py", line 577, in prepare_metadata
self.metadata_directory = generate_metadata(
^^^^^^^^^^^^^^^^^^
File "/root/.rye/pip-tools/cpython@3.12/lib/python3.12/site-packages/pip/_internal/operations/build/metadata.py", line 37, in generate_metadata
raise MetadataGenerationFailed(package_details=details) from error
pip._internal.exceptions.MetadataGenerationFailed: metadata generation failed
error: could not write production lockfile for project

Caused by:
failed to generate lockfile

まだ 0.15 だし そういうこともある?
とか思いましたが 念のため新プロジェクトで同じパッケージを入れて rye sync してみると問題なく動いてました
違いというと src フォルダ内のフォルダ構成くらいなので ここを元に戻してみるとエラーが出なくなりました
このフォルダは必須だったみたいです

エラーを見返すと中間部分にそれらしいメッセージがありました
内部で使用している hatch というツールにビルド対象を設定する必要があってそこでエラーになっていたようです
tool.hatch.build.targets.wheel の設定が必要みたいです
設定ファイルに記載していないとプロジェクト名から推測してくれるようで プロジェクト名と同じフォルダあったので初期状態だと動作していたようです

main.py というファイルに置き換えていたので↓のような設定を pyproject.toml に追加すると foo フォルダを消しても動作しました

[tool.hatch.build.targets.wheel]
packages = ["src/main"]

パッケージとして公開するつもりがなく パッケージをインストールするためだけに rye を使っていてもこういう設定が必要なのは少し面倒ですね
py ランチャーでバージョン一覧表示
py -0

-0 数字の 0
-0p にすると場所が表示されます

Windows で Python を入れるとついてくる py ランチャー
複数の場所やインストール方法で入れた Python を起動できるので便利です

しばらく使ってなかった環境で どのバージョンが入ってるのか一覧を見ようとしたのですが ヘルプにはそれらしい機能がありません
ランチャーなら見れそうなのに

ぐぐってみると普通にコマンドがあるようです
しかし 認識されないコマンドのようでエラーでした
原因は単純にバージョンが古かったみたいです

Python を新しく入れても py ランチャーは更新されないみたいです
カスタムインストールにしても py ランチャーの項目は灰色になってインストールできなくなってました
管理者権限が必要みたいなことが書いてるので管理者権限でインストーラーを起動してみましたが同じでした
一旦手動でアンインストールが必要みたいです

アンインストール後に再度 Python をインストールすると 最新の py ランチャーが入りました
ヘルプに -0 が出ていますし -0 でインストール済みバージョンの一覧が見れます
IronPython3 使ってみた
Python で .NET 機能が使えるというものです
https://github.com/IronLanguages/ironpython3

以前こんな記事を書きました
当時はまだ Python2.7 互換しかありませんでした
さすがに 3 じゃないのは嫌かなと使わなかったのですが 今年 7 月に 3.4 が出ました
本家 Python は 3.11 が Stable なので結構古いのですが それでも 3 系なのは嬉しいところです

とりあえずどんな感じで使えるのか試してみようと思います
ドキュメントにインストール方法があったのでこの通りに進めます
https://github.com/IronLanguages/ironpython3/blob/master/Documentation/installing.md

dotnet コマンドのツールとしてスタンドアロンインタプリタが使えるようです

dotnet tool install --global ironpython.console

グローバルに入れたので ipy コマンドが使えるようになりました
これが普通の python3 や py コマンド相当のもののようです

単純に ipy だけで実行すると REPL が使えます

C:\Users\WDAGUtilityAccount>ipy
IronPython 3.4.1 (3.4.1.1000)
[.NETCoreApp,Version=v6.0 on .NET 6.0.21 (64-bit)] on win32
Type "help", "copyright", "credits" or "license" for more information.
>>>

引数に py ファイルを渡すとそのファイルを実行できます

ほぼ普通の Python と同じみたいですね
ライブラリのフォルダは

C:\Users\<USER>\.dotnet\tools\.store\ironpython.console\3.4.1\ironpython.console\3.4.1\tools\net6.0\any\lib

ここに http とか os.py とか glob.py などのパッケージが配置されています
3.4.1 や net6.0 の部分はバージョンで変わるはずです

.NET 機能に触れてみます

C:\Users\WDAGUtilityAccount>ipy
IronPython 3.4.1 (3.4.1.1000)
[.NETCoreApp,Version=v6.0 on .NET 6.0.21 (64-bit)] on win32
Type "help", "copyright", "credits" or "license" for more information.
>>> import System
>>> System
<module 'System' (CLS module from System.Private.CoreLib, Version=6.0.0.0, Culture=neutral, PublicKeyToken=7cec85d7bea7798e)>
>>> System.Console
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
AttributeError: 'System' object has no attribute 'Console'
>>> import clr
>>> clr.AddReference("System.Console")
>>> System.Console
<class 'Console'>
>>> System.Console.WriteLine("a")
a
>>> System.Console.WriteLine("{0} + {1} = {2}", 10, 20, 30)
10 + 20 = 30

.NET の System は 「import System」 でインポートできます
デフォルトで System.Runtime.dll のアセンブリがロードされてるようで System.Array とか System.Activator とかが使えます
System.Console は System.Console.dll なので System.Console にアクセスしても見つかりません
clr.AddReference で参照に追加することで使えるようになります

ただ Python は Python のままでも十分高機能なのでコンソールだとあまり .NET 機能を使える嬉しさがないです
やっぱり GUI を使えてこそだと思うので GUI を試してみようと思います
Readme では WinForms でダイアログを出す例があるのでこれを試してみます

import clr
clr.AddReference("System.Windows.Forms")
from System.Windows.Forms import MessageBox, MessageBoxButtons

MessageBox.Show("Hello World!", "Greetings", MessageBoxButtons.OKCancel)

OSError: [Errno -2146232800] Could not add reference to assembly System.Windows.Forms

アセンブリの読み込みに失敗するようです
WinForms のアセンブリはライブラリに含まれていないのでしょうか
そういえばインストール時に指定した名前は ironpyton.console です
ターゲットフレームワークが console になってるのもかもしれません
スタンドアロンのインストールですし コマンドライン専用ツールの可能性もあります
一応 SDK には dll があるので動的なロードでフルパスで指定してみましたがそれでもエラーでした
このツールは OS も問わないものみたいですし Windows 固有の WinForms などは対応してないのかもですね

インストール方法には .NET アプリに埋め込むタイプもあるようですし WinForms や WPF アプリに IronPython を埋め込めば GUI も使えるのかもです
Excel で Python が使えるようになるらしい
数年前にもそんな話題があったものの結局実現せず xlwings を使って外から操作できるくらいでした
ですが 今回は完全にエクセルの内部の機能として Python が統合されるようです

まだ一般には公開されてないみたいで Insider 登録してる人に順次公開らしいです
期待してさっそく OneDrive のエクセルオンラインで試したのに動かなったです

VBA 的な立ち位置かなと思ったのですが 数式として記述できるようです
便利そうだけど不安もあります
数式に複雑なものが書かれることが増えそうですし 他人の作ったエクセルのメンテ不可度がかなり上がりそうです
VBA マクロだとまだハードルがあるので 詳しくない人は触れない領域でしたが 数式だと気軽に使えすぎですよね

オフィスの中に Python の実行環境が含まれるのかなと思いましたが 実行はクラウドでやるそうです
ユーザー的にはインストール不要で楽そうですけど エクセル使うのにインターネット環境が必須になるというのも不便そうに思います
Microsoft が Python 推しみたいですし もういっそ Windows の標準機能として統合してもいいんじゃないかと思います
VBScript とか JScript みたいなレガシー機能は捨てて 代わりに Python を使えるようにしてくれたほうがうれしいです

今回のエクセルで使える Python は Python だけじゃなくて pandas や scikit-learn 等のライブラリも含まれていて 機械学習機能やグラフの作成などもできるようです
こういった機能まで入れるとなると ローカルで実行よりはクラウドのほうがいいのかもですね

また 実行がクラウドなら他言語の追加も容易そうに思いますが JavaScript も追加されたりしないのでしょうか?
一応アドオンという形でエクセル内部で JavaScript を動かすことはできるのですが アドインである以上 開くエクセル側にアドインが入ってないといけないです
自分だけならともかく .xlsx ファイルとして共有して他の人がその機能を使うことはできないです

ただそれを考えると今回の Python の機能ですら怪しそうです
最新のバージョンが要求される上に Microsoft のクラウドにアクセスする必要があります
オフィスって未だに 2007 みたいなサポート切れてるのを使う人を結構見かけます
企業だとアクセスできるサービスを制限していてクラウド実行ができないというのもありそうです
他人との共有で当たり前のように使っていけるのはまだ何年も先になりそうな気がしますね
LET や LAMBDA 関数ですら使わないようしてるくらいですし
ファイルから重複行を除外する(つづき)
前記事の続きです
重複行を削除して同じ行は 1 回だけにしたいですが Linux コマンドだと sort コマンドが必要です
ソートするのは無駄だなと思ってソートしないものを作ってみたらソートするより遅かったです
sort コマンドでのソートはマルチスレッド化されていることや自作の方は Python のコードという理由もありますが実行時間ではソートしたほうが速くなっていました

無駄な処理があっても速いのならソートすればいいとなるのですが 本来の並びが維持されない問題があります
ググっても並びをもとに戻す良さそうな方法が見つからないということもあり コマンドで頑張るよりは Python で作ったものを使えばいいかなと思ってます
ただ ソートしてるのより速度で負けてるのが気になるところです
Python みたいなスクリプト言語ではなくネイティブな言語ならもっと速くなりそうということで別言語でも作ってみることにしました

そうは言っても C/C++ 言語は使いたくないです
今のコードが Python なので 構文が似てる Nim にすることにしました

まずは元の Python のコード

import sys

if len(sys.argv) < 2:
exit()

filename = sys.argv[1]

with open(filename) as file:
set = set()
while line := file.readline():
line = line.rstrip("\n")
if line not in set:
set.add(line)
print(line)

Nim 版

import std/sets
import std/cmdline

let params = commandLineParams()
if params.len == 0:
quit()

let filename = params[0]

block:
let file = open(filename)
defer: file.close

var set = HashSet[string]()
var line: string
while file.readLine(line):
if line notin set:
set.incl(line)
echo line

似てはいるものの 細かいところは結構違っていて Python からなら移植が楽かというと そうとも言い切れないくらいでした
慣れていて これはこう書き換えるというのが頭に入ってるならともかく 初心者がやってみるには結構時間がかかるものだと思います
一応以前にも少し使ったことのある言語なのですが ほぼ覚えてなくて調べながら書いてました
それに Nim はちょうど今月の頭に v2.0 のリリースがされて色々変更があったようです
std/os にあった関数が std/cmdline などに移動していました
https://nim-lang.org/blog/2023/08/01/nim-v20-released.html

あと一番困るのがインデントはスペースのみという制限です
1 種類だけに制限するならインデントのためにある文字のタブにすべきだと思いますね
タブと違ってスペースは可変じゃないのでアクセシビリティなどでも劣ってると言われることが増えているのにこの考えなのは残念です
コンパイル前の処理として全 .nim ファイルのタブをスペースに置換した一時ファイルを作ってそれらを使ってコンパイルするよう一手間必要になります

それは置いておき実行時間の比較です

[root@552d14e2dbe8 n]# time ./a data.txt
aaa
bbb
ccc

real 0m34.028s
user 0m33.867s
sys 0m0.160s

[root@552d14e2dbe8 n]# time ./a data.txt
aaa
bbb
ccc

real 0m33.575s
user 0m33.438s
sys 0m0.136s

遅っ

Python でも

real    0m12.368s
user 0m12.249s
sys 0m0.110s

くらいだったのですけど
ネイティブ系の言語で遅いときはだいたい最適化オプションがないからだと思うので探してみます

nim c --d:release --opt:speed a.nim

のようにしてビルドすれば リリース用ビルドで速度に最適化できるそうです
これでビルドし直して試してみます

[root@552d14e2dbe8 n]# time ./a data.txt
aaa
bbb
ccc

real 0m5.737s
user 0m5.697s
sys 0m0.040s

[root@552d14e2dbe8 n]# time ./a data.txt
aaa
bbb
ccc

real 0m5.809s
user 0m5.749s
sys 0m0.060s

Python よりも速くなりました
sort + uniq よりも速いですね

ただ条件としてほとんどが重複行という前提があります
重複率を下げるため出力条件を変更してみました

for i in range(50 * 1000 * 1000):
print(i % 1000000)

これで実行してみると Nim だと 19 ~ 20 秒です
ソートを行う方は 21 秒だったので 差は縮まったけど一応は勝ってると思ってました
ただやっぱりソートをする方は実行するごとに速くなって 21 → 10 → 7.1 → 6.8 秒まで速くなりました
これ以上は速くならないようです

読み込んでるファイルは同じなのでファイルのキャッシュなら Nim 側の時点でキャッシュされてるはずですし そっちでも高速化しそうですがそんなことはなかったです
どういう仕組みなんでしょうね
ファイルから重複行を除外する
foo
bar
foo
foo
bar
bar
foo

みたいな中身のファイルがあったときに重複する行を削除して

foo
bar

だけにしたいです

Linux なら uniq コマンドが使えます
ただし uniq コマンドが除外するのは連続する重複する行なので今回みたいなケースではソートも行う必要があります

sort file.txt | uniq

でもこれってソートが無駄に思います
重複行を除去するだけなら一旦並び替えなくても 1 行ずつ読み取っていくだけでもできるはずです
あと 並びが替わってしまうので本来の並び順でほしい場合には使えません

特にちょうど今 重複除去したいデータはファイルは大きめですがほとんどが重複で最終的には 100 行にも満たないものです
1 行ずつ見ていくプログラムを作ったほうが速く処理できたりしそうです

ということで

import sys

if len(sys.argv) < 2:
exit()

filename = sys.argv[1]

with open(filename) as file:
set = set()
while line := file.readline():
line = line.rstrip("\n")
if line not in set:
set.add(line)
print(line)

というコードを用意しました
全件のソートという重そうな処理をスキップするわけですし スクリプト言語の Python ですがこっちのほうが速いのではないでしょうか

試してみます

ほとんどの行が重複するデータを作ります

values = ["a", "b", "c"]
values_len = len(values)

for i in range(10 * 1000 * 1000):
value = values[i % values_len]
print(value)

1000 万行で a,b,c のいずれかが入ってます
各行に改行文字も入るのでファイルサイズは 20MB になりました

それぞれの処理速度を比べてみます

[root@552d14e2dbe8 i]# python3 gen.py > data.txt

[root@552d14e2dbe8 i]# time python3 uniq.py data.txt
a
b
c

real 0m2.161s
user 0m2.160s
sys 0m0.000s

[root@552d14e2dbe8 i]# time ( sort data.txt | uniq )
a
b
c

real 0m1.242s
user 0m5.444s
sys 0m0.533s

ソートしたほうが速くなってますね……
user が real を超えているのでソートはマルチスレッドで処理しているのでしょうけど それにしても速すぎません?
数回実行してもだいたい同じような速度でした

1 行ずつ読み取って set に入ってるかチェックするだけなので Python でも十分な速度だと思いましたが 結構遅いのでしょうか?
set のハッシュ値計算や末尾の改行を除去する部分が遅め?

データ量を変えてもう 1 パターン試してみます
20MB より小さくしても 十分高速で速度を気にする必要もなさそうなので もっとサイズを大きくします

values = ["aaa", "bbb", "ccc"]
values_len = len(values)

for i in range(50 * 1000 * 1000):
value = values[i % values_len]
print(value)

5000 万行で 各行の文字数も増やしました
ファイルサイズは 191MB です

[root@198ac3e26189 opt]# time python3 uniq.py out.txt
aaa
bbb
ccc

real 0m12.368s
user 0m12.249s
sys 0m0.110s

[root@198ac3e26189 opt]# time ( sort out.txt | uniq )
aaa
bbb
ccc

real 0m8.984s
user 0m31.583s
sys 0m3.061s

これでもソートするほうが速いですね

ところで最初は遅めで何度か実行すると速くなりました
ブラウザと違って内部で情報を保持しないので 2 回目以降で劇的に速度が変わることはなさそうに思うのですけど
OS 側のファイルキャッシュ都合とかでしょうか

間で特別なことはせず単純に連続して実行した結果です

[root@198ac3e26189 opt]# time ( sort out.txt | uniq )
aaa
bbb
ccc

real 0m30.542s
user 1m16.845s
sys 0m13.606s

[root@198ac3e26189 opt]# time ( sort out.txt | uniq )
aaa
bbb
ccc

real 0m21.338s
user 0m46.432s
sys 0m15.657s

[root@198ac3e26189 opt]# time ( sort out.txt | uniq )
aaa
bbb
ccc

real 0m12.536s
user 0m32.160s
sys 0m7.133s

[root@198ac3e26189 opt]# time ( sort out.txt | uniq )
aaa
bbb
ccc

real 0m9.317s
user 0m30.287s
sys 0m3.861s

[root@198ac3e26189 opt]# time ( sort out.txt | uniq )
aaa
bbb
ccc

real 0m8.795s
user 0m31.538s
sys 0m2.983s

とりあえず ソートしても十分に速いのであまり気にしなくて良さそうです
並列処理前提なのでコアが 1 つしかない VM 環境とかだと話は変わってきそうですけど

また sort -u にすればソート時に重複する行を排除してくれて結果が同じになります
こっちのほうが速いかなと思いましたが 特に変わらないようでした

残る問題は並びが変わってしまうことです
uniq で得られる各行を元ファイルから探索して行数をもとに並び替えというのは面倒そうです
それに重複率が低い場合はほとんどの行を探索することになってここで時間がかかりそうです
コマンドのオプションを見てもいい感じにできそうなものがなかったですが いい方法ないのでしょうか
AlmaLinux9 は普通に Python が入ってる
CentOS8 では Python は初期状態では入ってませんでした
一応システム内部用としてはあるのですがパスは通っていなくて 使うことは推奨されない形でした
直接 /usr/libexec/platform-python を実行すれば使えます
🔗 CentOS8 の Python の場所

これは AlmaLinux8 でも同じです
AlmaLinux9 でもここは変わらないかなと思っていたのですが python3 コマンドを実行したら普通に動きました

調べてみると platform-python は同じ場所にあるのですが /usr/bin/python3.9 へのシンボリックリンクになっていました

[AlmaLinux9]
[root@061e426ad383 opt]# ls -l /usr/libexec/ | grep platform
lrwxrwxrwx 1 root root 18 Apr 16 2022 platform-python -> /usr/bin/python3.9
lrwxrwxrwx 1 root root 18 Apr 16 2022 platform-python3.9 -> /usr/bin/python3.9

/usr/bin/python3.9 はバイナリの実行ファイルです

AlmaLinux8 ではこうなってました

[AlmaLinux8]
[root@4394942894ff /]# ls -l /usr/libexec/ | grep platform
lrwxrwxrwx 1 root root 20 Apr 29 2022 platform-python -> ./platform-python3.6
-rwxr-xr-x 2 root root 11864 Apr 29 2022 platform-python3.6
-rwxr-xr-x 2 root root 11864 Apr 29 2022 platform-python3.6m

platform-python3.6 や platform-python3.6m がバイナリの実行ファイルです

dnf の shebang を見てみると AlmaLinux9 では platform-python ではなく直接 python3 を指定してました

[wsl@LAPTOP-W10:~]$ sudo podman run --rm almalinux:8 head -n 1 /usr/bin/dnf
#!/usr/libexec/platform-python
[wsl@LAPTOP-W10:~]$ sudo podman run --rm almalinux:9 head -n 1 /usr/bin/dnf
#!/usr/bin/python3

どういう経緯があったのかはわかりませんが不評だったので元に戻したのでしょうか
Python のファイル読み書きは改行コードの扱いが微妙
エディタの設定ができてない環境だったので作ったファイルが全部 CRLF 改行になってた
一括変換したいけど これといったツールがない
一括でファイルの文字列を置換して上書きしてくれる方法があればいいけどなさそう?

ファイルを開いて文字列置換して保存の繰り返しは自分でスクリプト書いてもすぐにできそう
Node.js はファイルを glob で取得が面倒だし PHP はインストールしないと入ってないしということで とりあえず Python にする
苦労するなんて全く思ってなかったけど Python では改行コードの扱いが少し特殊で苦戦した

CRLF でも file.read() で文字列を取得したら \r\n はなくて全部 \n になってる
読み取り時に全部 \n にして 書き込み時に OS の改行コードに合わせるらしい
そんな特殊な扱いしなくていいのに

文字列として読み取るのをやめてバイナリとして読み取って decode して文字列を取得すると特殊な扱いを受けずに済んだ
書き込みも encode したバイナリで書き込む

import sys
import glob

for arg in sys.argv[1:]:
for path in glob.glob(arg, recursive=True):
with open(path, "rb") as f:
text1 = f.read().decode()

text2 = text1.replace("\r\n", "\n")

if text1 == text2:
print("SKIP", path)
else:
with open(path, "wb") as f:
f.write(text2.encode())

print("CONV", path)
Python のデフォルト引数の値は関数作成時に決まる
デフォルト引数の挙動が思っていたのと違いました

>>> foo = 100
>>> def fn(x=foo):
... return x
...
>>> fn(10)
10
>>> fn()
100
>>> foo = 200
>>> fn()
100

デフォルト引数には外側スコープの変数を指定しています
その変数を実行前に変えたら実行時にも変わってると思っていたのに変わりませんでした
関数は labmda でも一緒です

>>> foo = 100
>>> fn = lambda x=foo: x
>>> fn(10)
10
>>> fn()
100
>>> foo = 200
>>> fn()
100

外側の変数を実行時に見るにはデフォルト引数を使わず関数内で参照します
関数内だと毎回実行時に参照してくれます

>>> foo = 100
>>> def fn(x=None):
... xx = foo if x is None else x
... return xx
...
>>> fn(10)
10
>>> fn()
100
>>> foo = 200
>>> fn()
200

JavaScript だとそんなことないので同じ感覚で書くとハマりますね

let foo = 100
const fn = (x=foo) => x
console.log(fn(10)) // 10
console.log(fn()) // 100
foo = 200
console.log(fn()) // 200

Python のデフォルト引数は毎回同じオブジェクトになるという変わった動きなのでこれと同じ原因の気がします

>>> def fn(x=[]):
... x.append(1)
... print(x)
...
>>> fn()
[1]
>>> fn()
[1, 1]

リテラルや関数実行じゃなくて既存変数の参照なので関係ないように思ってましたが 動作的には関数作成時にデフォルト引数の値を評価して内部で保持しているようです

⇩のコードが

def fn(a=1, b=2):
print(a, b)

fn()
# 1 2
fn(10)
# 10 2
fn(10, 20)
# 10 20

⇩のような処理に変換されてると考えるとわかりやすいかもしれません

_fn_default_args = (1, 2)

def _fn(a, b):
print(a, b)

def fn(*args):
lack = 2 - len(args)
padded_args = args if lack == 0 else [*args, *_fn_default_args[-lack:]]
return _fn(*padded_args)

fn()
# 1 2
fn(10)
# 10 2
fn(10, 20)
# 10 20

それなら関数定義後に変数の値を変更しても変わらないのも納得です

_fn_default_args に当たるものは見えたりしないのかなと思って探してみると関数の __defaults__ 属性に入っていました

>>> def fn(a, b=1, c={}): pass
...
>>> fn.__defaults__
(1, {})

__defaults__ はあとから書き換えできて デフォルト引数の数も変えられます

>>> def fn(a, b, c, d): print(a, b, c, d)
...
>>> fn.__defaults__ = (10, 20)
>>> fn(1, 2)
1 2 10 20
>>> fn(1, 2, 3)
1 2 3 20
>>> fn(1, 2, 3, 4)
1 2 3 4
>>> fn.__defaults__ = (0, 0, 0, 0)
>>> fn()
0 0 0 0
>>> fn.__defaults__ = None
>>> fn()
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: fn() missing 4 required positional arguments: 'a', 'b', 'c', and 'd'

思った以上に自由度がありました
Python の splice
Python のコードを見てると

items[:] = something()

のようなのを見かけました

なんだっけこれ
この構文有効なんだっけ?
……とか思って試してみるとリストの部分入れ替えでした
なんかあったような気もする

>>> items = [1,2,3,4,5,6,7,8]
>>> items[2:6] = [100,200]
>>> items
[1, 2, 100, 200, 7, 8]

JavaScript でいう splice ですね

const items = [1,2,3,4,5,6,7,8]
items.splice(2, 4, 100, 200)
console.log(items)
// [1, 2, 100, 200, 7, 8]

[:] だとリスト内全部が置換対象になっています
それだと [:] なしでも同じなのですが 既存リストが変更される違いがあります
[:] なしは新しいリストを変数に代入です

>>> items = [1, 2]
>>> id(items)
5792232
>>> items = [3, 4]
>>> id(items)
5794504

[:] ありだと変数に入っているリストオブジェクトはそのままです

>>> items = [1, 2]
>>> id(items)
5792232
>>> items[:] = [3, 4]
>>> id(items)
5792232

別のところでもこのリストを保持していて そっちにも影響を与えたいときには良さそうです
Python でブロック作る
Python だと変数がブロックスコープじゃないのでブロックを作る意味もほとんどなく それ以前に {} でブロックを作れず 自由にブロックを作れないはず
だけど JavaScript などでも変数スコープを分けるためじゃなく 関数化するほどじゃない処理を簡単にまとめて見やすくするためにブロックを作ることは割とある
コメントでもいいけど 終わりがわかりづらいのでブロック作ったほうがわかりやすかったりもする

Python で考えてみると if に True を入れれば実質ブロックみたいなもの
「if True:」 の横やブロック内にコメントを入れるくらいなら if の条件として文字列入れれば良さそう

def exec():
if """チェック""":
...
...

if """データ収集""":
...
...

if """変換実行""":
...
...

if """出力""":
...
...

if """クリーンアップ""":
...
...

文字列は """ にして普通の文字列を使った if 文ではないことを少しアピール
Python の unpack 仕様がやっぱりつらい
久々に Python に触れてやぱり unpack が辛いです
これはどっちもエラーです

(a, b, c) = [1, 2]
(a, b, c) = [1, 2, 3, 4]

足りないなら None でいいし 多いなら無視してくれていいのに これで例外が出ます
配列の要素数が不定の場合に一々チェックが必要になります
楽にするために

(a, b, c) = [*list, *[None] * 3][:3]
(x, y) = [*list, *[None] * 2][:2]

としてもやっぱりイマイチ
関数にまとめれば多少は見やすくなるもののやっぱりスッキリはしません

def resize(list, size):
return [*list, *[None] * size][:size]

(x, y) = resize(list, 2)
Python で XXX object is not callable エラーが出る
「TypeError: 'str' object is not callable」 ってエラー出るのに原因がわからず苦戦しました
原因はこういうコードがあったこと

if __name__ == "__main__":
for x in getvalues():
hex = conv(x)
fn(hex)

この hex はビルトイン関数名で ここはグローバルスコープなんですよね
同じファイル内の関数で hex() を使っていて その呼び出し時には hex には文字列が入っているので not callable となっていました

Python のビルトイン関数って hex とか bin とか str とか変数名に使いそうなものが揃ってます
それでいてブロックスコープじゃないので 意図せず上書きしてることが多いです

JavaScript の場合は上書きしていてもエラーメッセージがこうなります
「TypeError: parseInt is not a function」
関数として呼び出そうとしたものの名前が出てきます
それで parseInt をどこかで上書きしてるなってわかるのですが Python のエラーメッセージだとどの変数かわかりません

いっそビルトイン関数は builtins をインポートして

import builtins as b

b.hex(100)

のように使おうかとも思いましたが print などの多用する関数もこれだと不便なのでやめました
Python に ++ はない
>>> x = 10
>>> ++x
10

あれ? 10 のまま

そういえば Python に ++ 演算子はなかったです
後置で x++ にすると Syntax Error になります
前置だと構文的に間違ってなくて動いてしまうのが困るところですね

+ や - は符号として単項演算子でもあるのでいくつ前についてもエラーにはなりません

>>> -+-+-10
# -10
Python は非ブロックスコープだけど hoisting がないので
if False:
a = 1
print(a)

これは None ではなく エラー
PHP でも同じ
JavaScript の var なら hoisting されてスコープの最初で宣言されることになるのでエラーにはならず undefined

これでエラーになると 結局ブロック直前で初期化の代入が必要
ブロックスコープじゃないのにブロックスコープの不便なところは真似しないといけない
Python の場合は var みたいな特殊な宣言文じゃなくてただの代入だから難しいんだろうけど
いっそブロックスコープにしてくれたらよかったのに
pip のアップデート
pip コマンドを使ったときにバージョンが古いって警告が出た

WARNING: You are using pip version 19.1.1, however version 20.0.2 is available.
You should consider upgrading via the 'pip install --upgrade pip' command.

今が 19 だけど 20 があるみたい
アップデートしようとすると

pip install --upgrade pip
ERROR: Could not install packages due to an EnvironmentError: [Errno 13] Permission denied: '/usr/local/lib/python3.7/site-packages'
Consider using the `--user` option or check the permissions.

インストール先の '/usr/local/lib/python3.7/site-packages' の権限が一般ユーザにないからインストールできないみたい
--user を使えばいいみたいだけど pip だし全ユーザに対してインストールしたいので sudo

sudo pip install --upgrade pip
WARNING: Running pip install with root privileges is generally not a good idea. Try `pip install --user` instead.

良い方法じゃないから --user 使ってって言われたけどインストールはできたみたい

だけどいざ使おうとしたら

ModuleNotFoundError: No module named 'pip._internal.cli.main'

インストールされた場所が違うみたい

sudo pip --version
pip 20.0.2 from /usr/local/lib/python3.7/site-packages/pip (python 3.7)
python -m pip --version
pip 19.1.1 from /usr/lib/python3.7/site-packages/pip (python 3.7)

新しい pip 20 は local の方にインストールされてる
pip コマンドだと この local のほうが優先される
一般ユーザには読み取り権限がないからエラー?
sudo すれば一応動く

一般ユーザだと --user 使ってインストールもできない
python -m pip を使うと古い版が使えたけど 古いままだしコマンド長くなったし pip が 2 バージョン存在するしでやらないほうが良かった
ちゃんと Warning に従うべき
pip を --user で入れるしかないみたい
--user でインストールされる場所は

~/.local/lib/python3.7/site-packages/
python -m pip --version
pip 20.0.2 from /home/fedorauser/.local/lib/python3.7/site-packages/pip (python 3.7)

sudo つけて入れた local の方の pip はいらないので削除
だけど削除すると dnf install pip で入れた /usr/bin/pip も消える
だけど同じパスなので中身が違うなら sudo で pip をインストールした時点で dnf 版は上書きされてたはず
pip コマンドが必要なら dnf で再インストール

dnf reinstall pip

そもそも dnf で入れた pip なのでそれを他の方法で更新しようとしたのがダメだった気がする
dnf 使わずに

python -m ensurepip

でインストールすれば大丈夫かな
Python のパッケージの場所
site パッケージを使う

WSL の例

>>> import site
>>> site.getsitepackages()
['/usr/local/lib/python3.6/dist-packages', '/usr/lib/python3/dist-packages',
'/usr/lib/python3.6/dist-packages']
>>> site.getuserbase()
'/home/wsluser/.local'
>>> site.getusersitepackages()
'/home/wsluser/.local/lib/python3.6/site-packages'

実際にインポートされるのは sys.path の場所から

>>> import sys
>>> sys.path
['', '/usr/lib/python36.zip', '/usr/lib/python3.6', '/usr/lib/python3.6/lib-dynload',
'/home/wsluser/.local/lib/python3.6/site-packages', '/usr/local/lib/python3.6/dist-packages',
'/usr/lib/python3/dist-packages']

-m site を使うとまとめて見れる

~> python3 -m site
sys.path = [
'/home/wsluser',
'/usr/lib/python36.zip',
'/usr/lib/python3.6',
'/usr/lib/python3.6/lib-dynload',
'/home/wsluser/.local/lib/python3.6/site-packages',
'/usr/local/lib/python3.6/dist-packages',
'/usr/lib/python3/dist-packages',
]
USER_BASE: '/home/wsluser/.local' (exists)
USER_SITE: '/home/wsluser/.local/lib/python3.6/site-packages' (exists)
ENABLE_USER_SITE: True

msys2 の場合

$ python -m site
sys.path = [
'/home/msysuser',
'/usr/lib/python37.zip',
'/usr/lib/python3.7',
'/usr/lib/python3.7/lib-dynload',
'/usr/lib/python3.7/site-packages',
]
USER_BASE: '/home/msysuser/.local' (exists)
USER_SITE: '/home/msysuser/.local/lib/python3.7/site-packages' (doesn't exist)
ENABLE_USER_SITE: True

msys2 (mingw64) の場合

$ python -m site
sys.path = [
'C:/softwares/msys2-x86_64/home/mingwuser',
'C:/softwares/msys2-x86_64/mingw64/lib/python38.zip',
'C:/softwares/msys2-x86_64/mingw64/lib/python3.8',
'C:/softwares/msys2-x86_64/mingw64/lib/python3.8/lib-dynload',
'C:/softwares/msys2-x86_64/mingw64/lib/python3.8/site-packages',
]
USER_BASE: 'C:\\Users\\mingwuser/.local' (doesn't exist)
USER_SITE: 'C:\\Users\\mingwuser/.local/lib/python3.8/site-packages' (doesn't exist)
ENABLE_USER_SITE: True

Windows (Anaconda3) の場合

>py -m site
sys.path = [
'C:\\Users\\winuser',
'C:\\Users\\winuser\\Anaconda3\\python37.zip',
'C:\\Users\\winuser\\Anaconda3\\DLLs',
'C:\\Users\\winuser\\Anaconda3\\lib',
'C:\\Users\\winuser\\Anaconda3',
'C:\\Users\\winuser\\Anaconda3\\lib\\site-packages',
'C:\\Users\\winuser\\Anaconda3\\lib\\site-packages\\win32',
'C:\\Users\\winuser\\Anaconda3\\lib\\site-packages\\win32\\lib',
'C:\\Users\\winuser\\Anaconda3\\lib\\site-packages\\Pythonwin',
]
USER_BASE: 'C:\\Users\\winuser\\AppData\\Roaming\\Python' (doesn't exist)
USER_SITE: 'C:\\Users\\winuser\\AppData\\Roaming\\Python\\Python37\\site-packages' (doesn't exist)
ENABLE_USER_SITE: True

Windows (CPython インストーラでユーザインストールの場合)

>py -m site
sys.path = [
'C:\\Users\\winuser',
'C:\\Users\\winuser\\AppData\\Local\\Programs\\Python\\Python38-32\\python38.zip',
'C:\\Users\\winuser\\AppData\\Local\\Programs\\Python\\Python38-32\\DLLs',
'C:\\Users\\winuser\\AppData\\Local\\Programs\\Python\\Python38-32\\lib',
'C:\\Users\\winuser\\AppData\\Local\\Programs\\Python\\Python38-32',
'C:\\Users\\winuser\\AppData\\Local\\Programs\\Python\\Python38-32\\lib\\site-packages',
]
USER_BASE: 'C:\\Users\\winuser\\AppData\\Roaming\\Python' (doesn't exist)
USER_SITE: 'C:\\Users\\winuser\\AppData\\Roaming\\Python\\Python38\\site-packages' (doesn't exist)
ENABLE_USER_SITE: True

fedora 31 の場合

~> python -m site
sys.path = [
'/home/fedorauser',
'/usr/lib64/python37.zip',
'/usr/lib64/python3.7',
'/usr/lib64/python3.7/lib-dynload',
'/home/fedorauser/.local/lib/python3.7/site-packages',
'/usr/lib64/python3.7/site-packages',
'/usr/lib/python3.7/site-packages',
]
USER_BASE: '/home/fedorauser/.local' (exists)
USER_SITE: '/home/fedorauser/.local/lib/python3.7/site-packages' (exists)
ENABLE_USER_SITE: True


Windows だと DLLs に .pyd ファイル
Linux は lib-dynload に .so ファイル (dyn は dynamic)

ここのフォルダは _ から始まるファイルばかり
.py ファイルからインポートされる内部モジュールばかりでユーザが直接 import するのはなさそう

Windows
>>> import site
>>> site
<module 'site' from 'C:\\Users\\winuser\\Anaconda3\\lib\\site.py'>
>>> import _tkinter
>>> _tkinter
<module '_tkinter' from 'C:\\Users\\winuser\\Anaconda3\\DLLs\\_tkinter.pyd'>

Linux
>>> import _sqlite3
>>> _sqlite3
<module '_sqlite3' from '/usr/lib64/python3.7/lib-dynload/_sqlite3.cpython-37m-x86_64-linux-gnu.so'>
Python の -m
-m pip
-m http.server

とか

あまり気にせず使ってたけど -m に指定するのは import で指定するモジュールと同じもの
「.」 区切りで拡張子の .py はつけない
フォルダの場合もあるし

import と違ってメインのモジュールとして扱われる
「python3 foo.py」 で実行した foo.py みたいなもの

if __name__ == "__main__":
pass

の if 文が実行される

http.server だとここの処理で 引数からポートなどの設定を受け取ってサーバを起動するようにしてる
https://github.com/python/cpython/blob/3.8/Lib/http/server.py#L1262
Python で引数を分割して受け取れない
引数で受け取るタプルを受取時に分解したくてこういうコードを書いてみましたが エラーでした

def fun(foo, (bar, baz)):
return bar + baz

JavaScript みたいにできてくれればいいのに

記法違うだけで機能はあったりしないのかなとググってみるとこんなページが

https://www.python.org/dev/peps/pep-3113/

Python2 の頃にはあったのに Python3 でなくなったようです
JavaScript ではできるようになったのに逆ですね

引数で受け取るときに指定できるほうが便利だと思うのですけど
mac にデフォルトのスクリプト言語なくなるんだ
mac でスクリプト言語がデフォルトで入らなくなるというのを見かけて気になったので調べてみました

https://developer.apple.com/documentation/macos_release_notes/macos_catalina_10_15_release_notes
「Scripting Language Runtimes」 のところ

Python, Ruby, and Perl がデフォルトで含まれなくなるようです
Python や Perl ならともかく Ruby まで入ってたんですね
Ruby あるのに Node.js はないんだ……

デフォルトで使えるスクリプト言語がなくなるって結構つらそうですが 最近は多いのでしょうか
CentOS8 もデフォルトだとなにもないです
正確には Python3 がシステム用に入ってますが ユーザが使うべきじゃないという扱いでパスも通されずわかりづらい場所にあります

読んでてもう一つ気になったのが 「あなたのソフトウェアがスクリプト言語に依存するなら ランタイムをアプリと一緒にバンドルすることをおすすめします」って部分
各ソフトウェアが Python や Ruby のランタイムを独自に持ってたらすごく重くなりそうです
Electron みたいなことになりそう
ただ 言語仕様の追加変更が速くなると想定バージョンも色々あって独自に持ってるのがなんだかんだユーザには一番便利なのかもしれません
コマンドライン環境で JSON フォーマットしたい
自動生成された圧縮された JSON をみたいのですが インデントもない状態だと読むのが辛いです
短ければコピペして手元の Windows でフォーマットすればいいのですがちょっと長めです
ローカルまでコピーしてくるのも面倒ですし フォーマットするツールをインストールするのもできれば避けたいです

いい方法ないものかと探してみると Python でできました

python3 -m json.tool min.json

これで min.json をフォーマットして表示できます
デフォルトでいろいろな機能が揃ってるのでやっぱり Python は便利ですね
CentOS8 はデフォルトで入ってないのが残念ですが これくらいなら platform-python 使ってしまって良いかも

注意したいのは 複数指定できないところです

python3 -m json.tool a.json b.json

とすると 2 つを見れるのではなく b.json へ出力となって もとの b.json は消えてしまいます

使い方

user@ubuntu ~> python3 -m json.tool --help
usage: python -m json.tool [-h] [--sort-keys] [infile] [outfile]

A simple command line interface for json module to validate and pretty-print
JSON objects.

positional arguments:
infile a JSON file to be validated or pretty-printed
outfile write the output of infile to outfile

optional arguments:
-h, --help show this help message and exit
--sort-keys sort the output of dictionaries alphabetically by key
Python のコンストラクタ
__init__ メソッドに書けばいいって思ってたけど 正確にはコンストラクタじゃないみたい
そういうことを書いたのを時々見かける
コンストラクタのあとに __init__ が呼び出されるとか

じゃあ コンストラクタってどれ?と思ったけど普通に class に書いてるブロックか
このブロック メソッド定義だけというわけじゃなくて 関数みたいな感じで普通に Python の文を書けるし

class C:
foo = 1

if foo == 1:
bar = 2
else:
bar = 4

となると

class Foo:
a = 1

def __init__(self, x):
self.b = x

foo = Foo(10)
print(foo.b)
# 10

は JavaScript 的にはだいたいこういう感じ?

// これを継承したクラスには constructor を書かない
class Class {
constructor(...a) {
return this.__init__(this, ...a)
}
}

class Foo extends Class {
a = 1

__init__(self, x) {
self.b = x
}
}

const foo = new Foo(10)
console.log(foo.b)
// 10

JavaScript の class 構文はブロック内に任意の文は書けないので Python みたいに if とかは使えないけど
Python の id
オブジェクトの参照を表す数値が見える

print(id("a")) # 140576450230568
print(id("b")) # 140576471436904

a = "a"
b = "b"

print(id(a)) # 140576450230568
print(id(b)) # 140576471436904

ab1 = a + b
ab2 = a + b
ab3 = ab1

print(id(ab1)) # 140576448953400
print(id(ab2)) # 140576448953456
print(id(ab3)) # 140576448953400
print(id("1")) # 140576448952896
print(id("2")) # 140576448952952

s = "1"
t = "2"

print(id(s)) # 140576448952896
print(id(t)) # 140576448952952

st1 = s + t
st2 = s + t
st3 = st1

print(id(st1)) # 140576448953512
print(id(st2)) # 140576448953568
print(id(st3)) # 140576448953512
print(id(1)) # 10914496
print(id(2)) # 10914528

x = 1
y = 2

print(id(x)) # 10914496
print(id(y)) # 10914528

xy1 = x + y
xy2 = x + y
xy3 = xy1

print(id(xy1)) # 10914560
print(id(xy2)) # 10914560
print(id(xy3)) # 10914560
print(id(123456789)) # 139698002811888
print(id(987654321)) # 139698002809552

p = 123456789
q = 987654321

print(id(p)) # 139698002811888
print(id(q)) # 139698002809552

pq1 = p + q
pq2 = p + q
pq3 = pq1

print(id(pq1)) # 1111111110
print(id(pq2)) # 1111111110
print(id(pq3)) # 1111111110

数値は足しても同じ値は同じになってるけど 文字列は結合したときに同じじゃない
Python の == と is
よく違いがわからなくなるやつ
JavaScript の == と === と書いてるのを見かけるけど違う

== は値を比較するので [1, 2] のリストを 2 回作って == で比較すると True
is は参照を比較するので False

値の比較は言語のビルトインだけ特別なものがあるわけじゃなくて 単純に比較用のメソッドを実行した結果を返してるだけ
__eq__ メソッドを実装すれば自分でも扱える
list などはデフォルトで中身が一緒なら同じなるように __eq__ が定義されてる
一部の値は違っても xxx プロパティが一緒なら同じ扱いにするとかもできる

is はオブジェクトの参照を比較するので __eq__ 関係なく別オブジェクトなら False
メソッド実行せず参照比較だけなので速度的には速いはず

◯ 全部 True にする
class A:
def __init__(self, val):
self.value = val

def __eq__(self, target):
return True

a1 = A(100)
a2 = A(200)

print(a1.value) # 100
print(a2.value) # 200
print(a1 == a2) # True
print(a1 is a2) # False

◯ 中の値で比較する
class B:
def __init__(self, val):
self.value = val

def __eq__(self, target):
return self.value == target.value

b1 = B(100)
b2 = B(200)

print(b1.value) # 100
print(b2.value) # 200
print(b1 == b2) # False
print(b1 is b2) # False

こっちも参照
Python のフォルダ移動
フォルダを準備

mkdir a1 a2 b1 b2 c1
touch a1/1.txt a2/2.txt b1/1.txt b2/2.txt c1/1.txt
tree
.
├── a1
│ └── 1.txt
├── a2
│ └── 2.txt
├── b1
│ └── 1.txt
├── b2
│ └── 2.txt
└── c1
└── 1.txt

python の処理

import shutil

shutil.move("a1", "a2/")
shutil.move("b1", "b2")
shutil.move("c1", "c2")

a は a1 を a2 の中に入れたい
b は b1 を b2 へマージしたい
c は c1 を c2 へリネームしたい

結果は

.
├── a2
│ ├── 2.txt
│ └── a1
│ └── 1.txt
├── b2
│ ├── 2.txt
│ └── b1
│ └── 1.txt
└── c2
└── 1.txt

最後の / とか関係なくて移動先にフォルダがあれば中に入るみたい
Windows の move や Linux の mv コマンドと同じ

エクスプローラでフォルダを D&D するみたいにマージするには中身全部を移動してから元フォルダを消す必要あり
Python の PDF ライブラリ
PDF ビュワーで既存 PDF にテキストボックスを配置したり前に配置したのを編集できたりするので あれを Python でまとめてやりたくて調べたもの
フリーソフトでできるくらいなのに 無理そうな感じ


Python の有名どころ PDF ライブラリ
カッコの中は調べたタイミングでの Github スターと最終更新があった年

PyPDF2 (2721/2018)

メタデータ取り出したり 分割結合とかはできそうだけど テキスト入れたりはできなそう
サンプル見ても reportlab っていう別ライブラリ使ってる

pdfminer (3710/2016)

スターは最多だけど Python2 のみの対応
Python 3 対応はフォークした別リポジトリ

pdfminer.six (1380/2019)

pdfminer の Python2, 3 の両方対応のフォークだけど スター数的にはそこまで活発じゃなさそう
ドキュメントというドキュメントもないし リンクにあった wiki はページ一つだけでそのページもコンテンツなし
元のリポジトリのドキュメントも大したこと書かれてないし 何ができるかよくわからない
README の簡易説明見た感じだと テキスト抽出はできるけど テキストボックス編集とかはできなそうに見える

pdfrw (1032/2018)

PyPDF2 に似た感じ

pyfpdf (416/2018)

PHP の FPDF の Python 版らしい
期待してなかったけどドキュメントが一番まともで 機能的にも編集はこれが一番機能ありそう
ただ残念なことに 既存 PDF の編集は FPDI というロード用の別モジュールが必要で Python 版だとこのモジュールが無いみたい
なので新規作成のみ
xlwings も良いところばかりじゃない
続き
一応 xlwings 使ってみたけど エクセルを使う分ロードは遅めだし エラーが起きたら finally でちゃんとエクセルを終了するような処理を作っておかないとエクセルが開きっぱなしになるなど気を使わないといけない部分もあって そこまで使いやすいわけでもないです
ただのスクリプトならまだしも ウェブサーバで使うとまた別の問題もあったし

スレッド関係のものみたいで

CoInitialize has not been called.

というエラーがでます

import xlwings
import pythoncom

def edit_excel(path):
pythoncom.CoInitialize()
exapp = xlwings.App(visible=False)
book = exapp.books.open(path)
...

といった感じで pythoncom.CoInitialize() を実行する必要があるみたいです
各リクエスト中の処理で edit_excel を実行します

マルチスレッドで起きるらしいので ここだけは同時に実行されないようにしてるのかなって思ったのですけどスレッドを待機とかそういう系でもなさそう
COM の初期化みたいだけど結局よくわからなかった
こういうのがあると動かなくなったときの対処に困るしやっぱりエクセル使わずやりたいな
openpyxl は罫線消えるのがけっこう致命的
Excel を自動で操作したくて C# より Python のほうが楽でいいかなと Python のツールを探して見るとフリーだと xlwings か openpyxl が人気みたい
xlwings は C# の標準の Excel 操作みたいに Excel が必要でマクロ風に Excel 自体を操作できるものらしい
openpyxl のほうはバイナリファイル(といっても xml)を処理するもので Excel 不要

Linux のサーバで動かす予定なので openpyxl を選んで使ってみたらけっこう使いやすくていい感じ
Excel 直接操作にありそうな範囲全体のコピペができないのは不便だけど for でループしてセルごとに処理しても対して遅くない

だけど保存してみたら 罫線が壊れてた
けっこう有名な既知の問題らしくて何年も議論されてるのに直ってない

対処法に openpyxl で全部の罫線を引き直すというのがあるみたいだけど 例を見るとコード中に太さや色等を指定してる
これを見る限りは正しい罫線情報を openpyxl 中で取得もできなそう
ただデータを出力するだけのものならともかく見た目を調整して罫線もいっぱいあるものだと全部手動でやるなんてやってられないので結局使えなかった

openpyxl 中で引いた罫線は残るなら テンプレートを使わず 1 から openpyxl でフォーマット作ればできそうだけどそれはそれで大変すぎるし
罫線使わずただデータを出力するだけなら問題なさそうだけどそれなら pandas とかでもできそうだし あえてこれ使う必要もなさそう
Python でクリップボードの REPL 出力を実行できる形式に変換する
import pyperclip

text = pyperclip.paste()
lines = text.splitlines()
lines = [line[4:] if line.startswith(">>>") or line.startswith("...") else "# " + line for line in lines]
pyperclip.copy("\n".join(lines))

⇩のような REPL の出力を

>>> class A: pass
...
>>> def x():
... return 1
...
>>> x()
1
>>> A()
<__main__.A object at 0x7f64c810be48>

⇩に変換する

class A: pass

def x():
return 1

x()
# 1
A()
# <__main__.A object at 0x7f64c810be48>

現在のクリップボードのテキストを変換するので REPL 出力をコピペするときに貼り付けの前にこれを実行する
REPL のフォーマットじゃなくて実行できる正しい Python 構文にしたいときに便利
複数文まとめて再実行するときにも使える
実行にはライブラリ pyperclip が必要

これまで web ページで変換ツールにしていたけど
1)ページ開いて
2)入力ボックスに貼り付けて
3)出力ボックスをコピーして
という手順が面倒でクリップボードのデータをそのまま変更したほうが楽そうなのでやってみた

クリップボード履歴管理ツールを使ってると変更前のも残るので便利
Python のクラスの継承
クラス名のあとのカッコに親クラス名をいれる

class X:
def __init__(self):
self.a = 10

def method():
return 1

class Y(X):
def __init__(self):
self.b = 20

print(vars(Y()))
# {'b': 20}

print(Y().method())
# 1

それだけだと親クラスの初期化処理が行われなくて vars の結果に a が存在しない
初期化されてないだけで継承されてるので method メソッドは使える

他言語で言う super の実行が必要
なくてもエラーはないけど初期化されない

super() は親コンストラクタを表してるわけじゃないので super() で取得できるインスタンスの __init__ メソッド呼び出しが必要

class X:
def __init__(self):
self.a = 10

class Y(X):
def __init__(self):
super().__init__()
self.b = 20

print(vars(Y()))
# {'a': 10, 'b': 20}

子クラスの方で __init__ を書かなければ自動で親クラスが実行される
子クラスで __init__ を定義して初期化するなら 必要あるときだけ親クラスの初期化メソッドを実行できる
実行したくないならしないことができる

class X:
def __init__(self):
self.a = 10

class Y(X):
pass

print(vars(Y()))
# {'a': 10}

実行タイミングも自由に指定できる
self を使う前とか最初じゃなくていい

class X:
def __init__(self):
self.a = 10

class Y(X):
def __init__(self):
self.a = 100
super().__init__()
print(self.a)

Y()
# 10
VS Code の Python 拡張がメモリ使いすぎ
VS Code の Python 拡張を使うとメモリが使用どんどん増えていく
制限なく増えて 10GB 超え
この辺からは物理メモリに乗らなくなってきて PC の動きが重くなってくるから強制的にプロセス落としてるけど自動で再起動するし しばらくしたらまたメモリが 10GB くらい使ってる
さすがに 10GB 使うのが正常とは思えないし 定義ジャンプ機能使ったらずっとロード中になってるしバグっぽい
バージョンは最新の (2019.1.0) だし しばらくは無効にして使うしかないかなぁ
Python で「,」で終わると
dictionary だったのを個別の変数にコードを修正したときに「,」を消し忘れてたのですが動いてました

こういうの⇩
a = 1,
b = "foo",

Python ってカンマあっても動くんだ
複文みたいな機能があって カンマのあと何もないから何も処理しないってことかな
JavaScript などで ; をいっぱい書いても動くのと同じようなものかな

と思ってたら あっても意味がないというのじゃなく別の意味になってました
カンマで終わるとタプルになります

a
// (1,)
b
// ('foo',)

という感じです
一時的に動かすだけのコードだったのであってもいいならそのままでのつもりだったのですが ちゃんと消さないとダメですね
一見問題なさそうに見える
if [x == y]:
print(1)
else:
print(0)

何がおかしいんだろうと思ったけどこれは絶対 1 が表示される
[] は比較条件を書くところじゃなくてただの配列
Python は配列に要素が 1 つでもあったら True になる
比較結果の boolean 値があるので常に True の場合の分岐になる
Python の __
こんなモジュールを用意

# y.py
a = 1
_b = 2
__c = 3

class Y:
a = 10
_b = 20
__c = 30

>>> import y
>>> y.a
1
>>> y._b
2
>>> y.__c
3

このあたりは普通にアクセスできる

>>> yi = y.Y()
>>> yi.a
10
>>> yi._b
20
>>> yi.__c
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
AttributeError: 'Y' object has no attribute '__c'

クラスのプロパティにすると __ から始まるのは見えない

プロパティ一覧をみると

>>> dir(yi)
['_Y__c', '__class__', '__delattr__', '__dict__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', '__gt__', '__hash__', '__init__', '__init_subclass__', '__le__', '__lt__', '__module__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__', '__weakref__', '_b', 'a']
>>> yi._Y__c
30

_Y__c というそれらしい名前があるのでアクセスすると __c のデータにアクセスできた
__ から始めるとそのままアクセスできないように名前が変更されるみたい

名前変わるだけでアクセス権という概念はないから名前わかればアクセスできる
Python で WPF 作れるらしい
WPF のコードで調べ物してたら Python で書いてるコードをみつけた
.NET Framework 上で動く Python 実装の IronPython というのを使えばできるらしい
.NET 上の Python は聞いたことあったけど WPF や WinForms も普通に使えるのは驚き
そういえば VisualStudio で Python 使えるようになってたし最近は流行りの言語でもあるから マイクロソフトが力入れて作ってたり?

さっそく使ってみようとしたけど最新版は 2.7 互換の IronPython 2.7
3 じゃない!
私 Python 3 しかわからないです
ということで 3 はないのか調べてみるとリポジトリを発見
https://github.com/IronLanguages/ironpython3

だけど思ったより規模が小さそう
最近のコミットはあるし 放置されてるプロジェクトではないけど活発とは言えないと思う
Build は failing だし tag もないあたり 1.0 的な部分にも達してないベータ版な気がする
ビルド済みの配布もなくて 使うなら自分で make してって状態

調べてみたら もともとはマイクロソフトじゃない人が初めて マイクロソフトもプロジェクトに参加してたけど 今ではやめてプロジェクトが停滞気味みたいな話がでてきた


使ってみるのは正式リリースしたらでいいかな
また今度覚えてれば使ってみる
. で終わる言語
X = 1.
Y = 2.
Z = 3.

このコードはどの言語で書かれているでしょう?



変数が大文字だし 「.」 で終わってるし Erlang? と思う人が多いかもしれません

ですが実は 文末に記号 (主にセミコロン) が不要で変数の事前宣言が不要な言語ならたいてい動きます
JavaScript とか Python とか Ruby とか

数字の後なので「.」は小数点なんです
0 ~ 1 の範囲の数値を書くときに 「.15」 のように書くみたいなものです

JavaScript だと後ろに「.」を書いても完全に無意味です
しかし Python だと「.」のあるなしで int/float が変わってきます
0 まで書いてもいいですが 「.15」 と書くように 「1.」 も実用範囲でしょう
Ruby はちょっと特殊で 「1.」 は 1 と同じ int で 「1.0」 にすると float です
JavaScript や Python では↓も有効ですが Ruby だとエラーです
a = (1.)
Python も PHP 的な揃ってない感ある
Python は結構人気あって良い言語って聞くし 比較的最近 3 になって大きく変わったので良くない部分は切り捨てられたんだろうと思ってました

なのに
upper, find, replace, startswith などはメソッドで len は関数という統一感のない仕様
そのままメソッドかプロパティでとれればいいのに

len(" a b c ".strip())
# 5

Python in Excel に期待
Excel で Python 使えるようになるかもってニュースをみかけた
https://forest.watch.impress.co.jp/docs/serial/yajiuma/1097447.html

この Excel への要望のトピックで投票数がすごい高いとか
https://excel.uservoice.com/forums/304921-excel-for-windows-desktop-application/suggestions/10549005-python-as-an-excel-scripting-language

VBA のおきかえだけじゃなくてセルの関数 (=SUM(A1:A2)) の置き換えにもなる って書いてるので色々便利そう

アンケートに参加して みたいなのがあったのでとりあえずやってみた