いまさら Office2019 のインストールに戸惑ったのでメモ

Amazon で購入した Office Home and Business 2019 を2台目にインストールしようとして、出てきたメッセージなどに戸惑ったのでメモ。

 

Amazon なので、「アカウント&リスト」→「ゲーム&PCソフトダウンロードライブラリ」とたどると、結局「Office.comへ」と setup.office.com に飛ばされます。

 

そこで MS アカウントでログインするわけですが、その後に出てくるのがいかにもエラー然とした「このプロダクトキーはすでに使用されています。」というメッセージがなので、ここでドパっといやな汗が出るわけです。

f:id:naga_sawa:20210211103501p:plain

まぁMSだし。と気を取り直し、そこに出ている「Microsoft アカウントからインストールする」リンクをたどると今度は「サブスクリプションが見つかりません。別アカウントを使用して購入した場合。 foobar@example.com でサインインしています。」と、またいかにも機械翻訳なメッセージを吐き出してくれるのでいやな汗の粘度も増すわけです。

f:id:naga_sawa:20210211103759p:plain

ここで出ているメッセージやら「Office 2019 インストール」やらでググったりしてしまうわけですが、ここでの正解は「サブスクリプションに戻る」をクリックすることで、そうすると「サービスとサブスクリプション」のページに移動して購入した Office 2019 が表示され、そこの「インストールする」からネットワークインストーラがダウンロードできるという流れです。
とりあえず最初からこのページを出してくれと問い詰めたい。。

f:id:naga_sawa:20210211103508p:plain


ちなみにこのインストーラ、(ちょっと知ってるパソコン先生にとっては)性格が悪く、問答無用でフルインストールするので「64bit版入れろ。WordとExcelパワポだけでいい。いらんもの入れるな。」とかいう場合はカスタムインストールを設定する XML ファイルを書いてインストーラの起動時オプション configure に渡してやらないといけません。

<Configuration>
  <Add OfficeClientEdition="64">
    <Product ID="HomeBusiness2019Retail">
      <Language ID="ja-jp"/>
      <ExcludeApp ID="Access"/>
      <ExcludeApp ID="Groove"/>    <!-- Onedrive for Business -->
      <ExcludeApp ID="Lync"/>      <!-- Skype for Business -->
      <ExcludeApp ID="OneDrive"/>
      <ExcludeApp ID="OneNote"/>
      <ExcludeApp ID="Outlook"/>
      <ExcludeApp ID="Publisher"/>
    </Product>
  </Add>
</Configuration>
OfficeSetup.exe /configure custom.xml


昨今は1TB程度のストレージが当たり前になってるので、「とりあえず全部入れときゃ文句でないだろ」な富豪的プログラミングならぬ富豪的インストールなものが多くなりましたねと小並感。


Office のカスタムインストールについては以下サイトを参考にしました。

フロント向け webpack で "regeneratorRuntime is not defined" エラーが出たときの対処

まとめ

@babel/preset-env の targets 設定を見直して、Babel のターゲット指定を適切にしてやると無駄なPolyfillも使わずエラーもなくなる。

内容

webpack@4.43.0, babel-loader@8.1.0 な環境。対象はフロント(ブラウザ)用。

エラーメッセージで検索すると先例が色々とあるので、対処そのものは難しくないと思います。
async/await を bable でトランスパイルしたはいいが、必要な regeneratorRuntime が無いのというのがエラー要因なので、

あたりが対処方法になるかと思います。

自分の場合はモダンブラウザがターゲットで IE11 は対象外なので、polyfill は放り投げてトランスパイル先をちゃんと指定してやる方向で webpack.config.js の babel-loader の設定に targets を次のように指定してやりました。

loader: 'babel-loader',
options: {
  presets: [ ['@babel/preset-env',
              {
                "targets": {
                  "browsers": "since 2019"
                }
              } ] ]
}

@babel/preset-env · Babel targets.browsers

@babel/preset-env のデフォルトだと次の通りの指定になっているので IE11 もターゲットに含まれているのですが、

> 0.5%, last 2 versions, Firefox ESR, not dead

since 2019 をターゲットにしてやると文字通り 2019 年以降リリースのブラウザのみがターゲットになるので async/await はトランスパイルされず、そのまま使われるのでエラーも無くなり無用なpolyfillもいらずで丸く収まります。


node.js にしろブラウザにしろ、最近のものは async/await には対応しているので、Babel 使うときはターゲットをちゃんと指定しましょうというオチでした。

x264 + L-SMASH を Windows 上でビルドする

MP4 muxer に L-SMASH を使った x264 のビルド手順。

基本は以下の過去トピの通り MSYS2 上で進め、 「GPAC のビルド」の部分を L-SMASH に置き換えるだけ。

L-SMASH のビルド

L-SMASH を公式GitHubリポジトリ GitHub L-SMASH's official repo から clone します。

$ git clone https://github.com/l-smash/l-smash.git
Cloning into 'l-smash'...
remote: Enumerating objects: 5, done.
remote: Counting objects: 100% (5/5), done.
remote: Compressing objects: 100% (5/5), done.
remote: Total 7412 (delta 0), reused 1 (delta 0), pack-reused 7407
Receiving objects: 100% (7412/7412), 7.71 MiB | 4.84 MiB/s, done.
Resolving deltas: 100% (5445/5445), done.

$

続いて configure。
64bit 版をビルドする場合は prefix 指定を mingw32 → mingw64 に変更。

$ cd l-smash/

$ ./configure --prefix=/mingw32 (32bit版)

$ ./configure --prefix=/mingw64 (64bit版はこっち)
generating config.mak ...

SRCDIR = .
DESTDIR =
prefix = /mingw32
exec_prefix = ${prefix}
bindir = ${exec_prefix}/bin
libdir = ${exec_prefix}/lib
includedir = ${prefix}/include
CC = gcc
AR = ar
LD = gcc
RANLIB = ranlib
STRIP = strip
STATICLIBNAME = liblsmash.a
STATICLIB = liblsmash.a
SHAREDLIBNAME = liblsmash-2.dll
SHAREDLIB =
IMPLIB = liblsmash.dll.a
CFLAGS = -Os -ffast-math -Wshadow -Wall -std=c99 -pedantic -I. -I. -D__USE_MINGW_ANSI_STDIO=1  -fexcess-precision=fast
LDFLAGS = -L.  -Wl,--large-address-aware
SO_LDFLAGS =
LIBS = -lm
LIBARCH = i386
DEFNAME = liblsmash-2.def
SLIB_CMD = sed -i "s/ @[^ ]*//" $(DEFNAME); dlltool -m $(LIBARCH) -d $(DEFNAME) -l lsmash.lib -D $(SHAREDLIBNAME)

configure finished

  type 'make'             : compile library and tools
  type 'make install'     : install all into system
  type 'make lib'         : compile library only
  type 'make install-lib' : install library and header into system

$

ビルド。ライブラリだけでいいので、ターゲットは lib を指定します。

$ make lib -j15 j15 は環境に合わせて適当に
gcc -c -Os -ffast-math -Wshadow -Wall -std=c99 -pedantic -I. -I. -D__USE_MINGW_ANSI_STDIO=1  -fexcess-precision=fast -o common/alloc.o common/alloc.c
gcc -c -Os -ffast-math -Wshadow -Wall -std=c99 -pedantic -I. -I. -D__USE_MINGW_ANSI_STDIO=1  -fexcess-precision=fast -o common/bits.o common/bits.c
gcc -c -Os -ffast-math -Wshadow -Wall -std=c99 -pedantic -I. -I. -D__USE_MINGW_ANSI_STDIO=1  -fexcess-precision=fast -o common/bytes.o common/bytes.c
(省略)
gcc -c -Os -ffast-math -Wshadow -Wall -std=c99 -pedantic -I. -I. -D__USE_MINGW_ANSI_STDIO=1  -fexcess-precision=fast -o core/timeline.o core/timeline.c
gcc -c -Os -ffast-math -Wshadow -Wall -std=c99 -pedantic -I. -I. -D__USE_MINGW_ANSI_STDIO=1  -fexcess-precision=fast -o core/write.o core/write.c
ar rc liblsmash.a common/alloc.o common/bits.o common/bytes.o common/list.o common/multibuf.o common/osdep.o common/utils.o codecs/a52.o codecs/alac.o codecs/description.o codecs/dts.o codecs/h264.o codecs/hevc.o codecs/id.o codecs/mp4sys.o codecs/mp4a.o codecs/mp4v.o codecs/nalu.o codecs/qt_wfex.o codecs/vc1.o codecs/wma.o importer/a52_imp.o importer/adts_imp.o importer/als_imp.o importer/amr_imp.o importer/dts_imp.o importer/importer.o importer/isobm_imp.o importer/mp3_imp.o importer/nalu_imp.o importer/vc1_imp.o importer/wave_imp.o core/box.o core/box_default.o core/box_type.o core/chapter.o core/file.o core/fragment.o core/isom.o core/meta.o core/print.o core/read.o core/summary.o core/timeline.o core/write.o
ranlib liblsmash.a

$

ビルドされたライブラリのインストール。

$ make install-lib
install -d /mingw32/include
install -m 644 ./lsmash.h /mingw32/include
install -d /mingw32/lib/pkgconfig
install -m 644 liblsmash.pc /mingw32/lib/pkgconfig
install -m 644 liblsmash.a /mingw32/lib

$ cd ~
$

これで L-SMASH 入り x264 のビルド環境ができあがり。

x264 のビルド

あとは 公式の git リポジトリ から x264 のソースを clone してきてビルドするだけ。

公式 git リポジトリからソースを clone する。

$ git clone http://code.videolan.org/videolan/x264.git
Cloning into 'x264'...
warning: redirecting to https://code.videolan.org/videolan/x264.git/
remote: Enumerating objects: 22359, done.
remote: Counting objects: 100% (22359/22359), done.
remote: Compressing objects: 100% (3848/3848), done.
remote: Total 22359 (delta 18548), reused 22258 (delta 18468), pack-reused 0
Receiving objects: 100% (22359/22359), 5.20 MiB | 1.23 MiB/s, done.
Resolving deltas: 100% (18548/18548), done.

$

configure。 host オプション付けないと妙な exe ができるので注意。
Commit 71ed44c7 でビット深度 10bit, 8bit 両対応のバイナリがデフォルトになったので、ビット深度設定オプション --bit-depth は不要に。
64bit 版をビルドする場合は host 指定を i686-w64-mingw32 → x86_64-w64-mingw32 に変更する。

$ cd x264/

$ ./configure --extra-cflags="-pipe -march=native" --extra-ldflags="-static -static-libgcc" --enable-strip --host=i686-w64-mingw32 (32bit版)

$ ./configure --extra-cflags="-pipe -march=native" --extra-ldflags="-static -static-libgcc" --enable-strip --host=x86_64-w64-mingw32 (64bit版)

platform:      X86  64bit版では X86_64
byte order:    little-endian
system:        WINDOWS
cli:           yes
libx264:       internal
shared:        no
static:        no
asm:           yes
interlaced:    yes
avs:           avisynth
lavf:          no
ffms:          no
mp4:           lsmash  ここが lsmash になっているか確認
gpl:           yes
thread:        win32
opencl:        yes
filters:       crop select_every
lto:           no
debug:         no
gprof:         no
strip:         yes
PIC:           no
bit depth:     all
chroma format: all

You can run 'make' or 'make fprofiled' now.

$

make する。何かしらの動画を使って最適化する場合は make fprofiled を使う。
ここでは、そこまでこだわってないので普通に make します。

$ make -j15 j15 オプションは環境に応じて(ry
cat common/opencl/x264-cl.h common/opencl/bidir.cl common/opencl/downscale.cl common/opencl/intra.cl common/opencl/motionsearch.cl common/opencl/subpel.cl common/opencl/weightp.cl | ./tools/cltostr.sh common/oclobj.h
dependency file generation...
windres --target=pe-i386  -I. -o x264res.o x264res.rc
(省略)
gcc-ranlib libx264.a
gcc -o x264.exe  x264res.o x264.o autocomplete.o input/input.o input/timecode.o input/raw.o input/y4m.o output/raw.o output/matroska.o output/matroska_ebml.o output/flv.o output/flv_bytestream.o filters/filters.o filters/video/video.o filters/video/source.o filters/video/internal.o filters/video/resize.o filters/video/fix_vfr_pts.o filters/video/select_every.o filters/video/crop.o input/avs.o output/mp4_lsmash.o filters/video/cache-8.o filters/video/depth-8.o input/thread-8.o filters/video/cache-10.o filters/video/depth-10.o input/thread-10.o libx264.a -L/mingw32/lib -llsmash -lm  -lshell32 -m32  -static -static-libgcc  -Wl,--large-address-aware -Wl,--dynamicbase,--nxcompat,--tsaware -s


$

動作確認する。

$ ./x264 --version
x264 0.160.3009 4c9b076
built on Jun 20 2020, gcc: 10.1.0
x264 configuration: --chroma-format=all
libx264 configuration: --chroma-format=all
x264 license: GPL version 2 or later

$

exit して msys をインストールしたフォルダの home\(ユーザ名)\x264\x264.exe を適当な場所に移動なりコピーするなりすればできあがり。


それにしても 2017年だと -j3 だったのが -j15 になって隔世感ある。

続・SameSite指定されたCookieはCORS fetch時にどう働くか

まとめ

A request is "same-site" if its target's URI's origin's registrable domain is an exact match for the request's client's "site for cookies", or if the request has no client.

  • 例えば
    • www.example.com から api.example.com を fetch → 「Registrable Domain」が合致するので「Same-Site」
    • www.foobar.com から api.example.com を fetch → 「Registrable Domain」が違うので「Cross-Site」
  • CORS fetch リクエストに Cookie が付くかどうか
SameSite判定 Set-Cookie時のSameSiteの指定 リクエストにCookieが…
Same-Site none ○付く
Same-Site lax ○付く
Same-Site strict ○付く
Cross-Site none ○付く
Cross-Site lax ×付かない
Cross-Site strict ×付かない
  • ここでの「Same-Site」「Cross-Site」はあくまでも Cookie に対しての話なので、 CORSリクエストのそれとは分けて考えること。
  • サブドメインが違うだけの CORS fetch では SameSite=lax/strict 指定の Cookie もリクエストに付加されるので、そういう構成がとれる場合は API の認証に SameSite=lax/strict な Cookie が使える。
    • ただしブラウザ設定でサードパーティーCookieをブロックするようになっている場合はSet-Cookieがブロックされてしまうので使えない。

経緯とか

昨日すっとぼけたことを書いていたトピック の続きとして、よく見かけるサイトURLが www.example.comAPI用URLが api.example.com のようなサブドメインが違うだけという構成を試してみたところ、Set-Cookie に domain=example.com 指定もしていないのに APIサーバに Cookie が送られてる……という謎に遭遇して調べ回った結果が↑のまとめの通り。


確認するには、ブラウザアクセス用のドメインとして www.example.com を hosts に追加(テストが済んだら削除すること)。

 127.0.0.1       apitest.example.com
+127.0.0.1       www.example.com

この状態で、ブラウザで http://www.example.com:5500/ を開くと、 http://www.example.com:5500/ のサイトから http://apitest.example.com:3000/ に CORS fetch リクエストをすることになるので、SameSite のセレクトボックスから strict や lax を選んで Cookie を SET すると、 GET, POST には Cookie が付かないはず……が、 msg = undefined にはならず、入力したテキストが表示される。
デベロッパーツールで確認してもちゃんとリクエストには Cookie が付いている。


「もしかしてサブドメインが無視されてる?」と考え、 hosts にちょっと違うドメインを追加。

 127.0.0.1       apitest.example.com
 127.0.0.1       www.example.com
+127.0.0.1       www.example2.com

その上で http://www.example2.com:5500/ から strict / lax で Cookie SET すると、その後の GET/POST には Cookie は付かず。
ブラウザのバグという線は限りなく低いので調べていった結果、上記のサイトに出会った次第。

SameSite指定されたCookieはCORS fetch時にどう働くか

まとめ

2020/05/17 修正

fetch などを使った CORS リクエストにおいて、API サーバから SameSite 設定付きで Set-Cookie が返された場合、以降の CORS リクエストに Cookie は付くのかどうか → SameSite=none の場合のみ Cookie が付く
ただし、サブドメイン部だけが異なるドメイン間での CORS の場合、lax/strict でも Cookie が付くもうちょっと調べたトピック 参照


以下の通り、lax や strict を指定された Cookieは 別ドメインに対する CORS fetch リクエストには付かない。

SameSite Cookie
none ○付く
lax ×付かない
strict ×付かない

CORS で Cookie を使う場合、 SameSite=none にしつつ API の応答に Access-Control-Allow-Credentials: true を付けたり、 fetch 時のオプションに credentials: 'include' 付けたりと、CORSの仕様に沿う必要もある。

また、ブラウザの設定でサードパーティーCookieがブロックされている場合、 SameSite の指定に関係なく Cookie は付かない(Set-Cookieが無視されるのでブラウザに記憶されない)。


この挙動を考えると、Cookie認証なAPI サーバを別ドメインにする構成では SameSite=none の指定が必須になるわけですね。

サードパーティーCookieが拒否される風潮を鑑みると、次のような対応にしたほうがいいのやも。

  • リバースプロキシなどを使い、表向きは同一ドメインでのAPIアクセスにして裏で振り分ける構成にする
  • Cookie の代わりに JWT などの Token で認証するようにする(XSS攻撃された場合のToken漏洩問題が出てきますが…)

以下打ち消し線部分は間違い

Cookie の挙動と Same Origin Policy を混同してました。
Cookie の場合、ドメイン名のみで識別されスキーマとポート番号は無視されるので、APIサーバを別ポートで動かしただけでは CORS が必要になるけれども Cookie の挙動は同一ドメイン時と同じ、という状態でのテストになっていた。という次第。
(そりゃ全部Cookie付くわなorz)
fetch などを使った CORS リクエストにおいて、API サーバから SameSite 設定付きで Set-Cookie が返された場合、以降の CORS リクエストに Cookie は付くのかどうか → 付く。

SameSite Cookie
none
lax
strict

ただし、API サーバの応答に Access-Control-Allow-Credentials: true を付けたり、 fetch 時のオプションに credentials: 'include' 付けたりと、CORS 下で Cookie を使う仕様に沿う必要はある。

Cookieを使ったAPIの認証をする場合は、 httponly + samesite としておけばよさそう。

検証に使った適当なコード

簡易API サーバ

指定された条件で Set-Cookie を発行する API と、リクエスト中の Cookie を取得して返す API の簡易APIサーバ。
ドメインに見せかけるため、 hosts ファイルに以下を追加して apitest.example.com でアクセスする(検証が終わったら削除する)。

127.0.0.1       apitest.example.com

/api/ に { 'msg': 'hoge', 'samesite': 0} みたいな JSON を POST すると Set-Cookie 入りのレスポンスを返す。
/api/cookie/ に POST したり GET したりすると、リクエスト中の Cookie の中身を JSON で返す。

// cors_api.js
const express = require('express');
const cookieParser = require('cookie-parser');
const cors = require('cors');

const app = express();
app.use(cookieParser());
app.use(express.json()); // application/json

// see https://expressjs.com/en/resources/middleware/cors.html#configuration-options
const cors_options = {
    origin: true,       // Access-Control-Allow-Origin: <Origin>
    credentials: true   // Access-Control-Allow-Credentials: true
}

// set cookie to response
app.options('/api/', cors(cors_options));
app.post('/api/', cors(cors_options), (req, res) => {
    console.log(req.cookies);
    const msg = req.body.msg;
    const samesite = req.body.samesite == 0 ? 'none' : req.body.samesite == 1 ? 'lax' : 'strict';

    res.cookie('msg', msg, {
        httpOnly: true,
        sameSite: samesite
    });
    res.json({ msg : `msg=${msg}; samesite=${samesite}`});
});

// get cookie from request
const getCookie = (req, res) => {
    console.log(req.cookies);

    const msg = req.cookies['msg'];

    res.json({ msg : `msg=${msg}`});
};
app.options('/api/cookie/', cors(cors_options));
app.get('/api/cookie/',  cors(cors_options), getCookie);
app.post('/api/cookie/', cors(cors_options), getCookie);

// start server
app.listen(3000, 'localhost', () => console.log('Listening on port 3000'));
ドライバHTML

簡易APIサーバの API を叩くドライバHTMLを、VS Code の LiveServerなどの適当なWebサーバ介して localhost ドメインとしてブラウザで開く。
テキストボックスに Cookie に設定する文字列を入れ、その後ろのセレクトボックスは Cookie に設定する SameSite の種類を選ぶ。SET ボタンを押すと /api/ が叩かれて Set-Cookie 付きのレスポンスが得られる。
そして、GETボタンやPOSTボタンを押すと、それぞれのメソッドで /api/cookie/ が叩かれて、それらのリクエストに付いてくる Cookie の中身が表示される。
msg=undefined となったり、テキストボックスに入れた文字列と違うものが表示された場合はサーバ側で Cookie が取得できていないことを表す。

<!DOCTYPE html>
<html>
  <body>
    <input type="text" id="msg"></input>
    <select id="samesite">
      <option value="0">none</option>
      <option value="1">lax</option>
      <option value="2">strict</option>
    </select>
    <button id="s">SET</button>
    <button id="s_get">GET</button><button id="s_post">POST</button><span id="ans"></span>

    <script>
        document.getElementById('s').addEventListener('click', async (e) => {
            e.preventDefault();
            const msg = document.getElementById('msg').value;
            const samesite = document.getElementById('samesite').value * 1;
            const req = { msg, samesite };
            res = await fetch("http://apitest.example.com:3000/api/",
                { method: 'POST',
                  body: JSON.stringify(req),
                  mode: "cors",
                  credentials: 'include',
                  headers: {
                      "Content-Type": "application/json; charset=utf-8",
                  }
                });
            json = await res.json();
            document.getElementById('ans').innerText = json.msg;
        });
        document.getElementById('s_get').addEventListener('click', async (e) => {
            e.preventDefault();
            res = await fetch("http://apitest.example.com:3000/api/cookie/",
                { method: 'GET',
                  mode: "cors",
                  credentials: 'include'});
            json = await res.json();
            document.getElementById('ans').innerText = json.msg;
        });
        document.getElementById('s_post').addEventListener('click', async (e) => {
            e.preventDefault();
            res = await fetch("http://apitest.example.com:3000/api/cookie/",
                { method: 'POST',
                  mode: "cors",
                  credentials: 'include'});
            json = await res.json();
            document.getElementById('ans').innerText = json.msg;
        });
    </script>
  </body>
</html>

速い Python コードを書くためのマイクロベンチマーク

Python コードを書くにあたって特性というかクセのようなものは知っておいた方がいいだろうと、Pythonコードの書き方の違いによる時間計測してみた。

計測環境は 2020/5/9 時点の Google の Colaboratory で以下のスペック。

  • Python 3.6.9
  • OS Ubuntu 18.04.3 LTS
  • CPU Xeon 2.30GHz 2core Model 63 Stepping 0
  • メモリ 12GB

まとめ

  • malloc(N) 的なサイズ指定で list を作成する場合、 * 演算子が速い。
  • list に入れる要素数が既知の場合、事前にサイズ指定の list を作ると速い。
  • ループ中で変化しない値で比較する場合、一時変数に代入しておく方が速い。
  • Google Colaboratory 便利かも

指定サイズでの list の確保

malloc(N) 的なサイズ指定で list を作成する場合、 * 演算子を使うほうが10倍速い。
* 演算子 < リスト内包表現 < たぶん for ループ

import timeit

t1 = timeit.timeit('[0] * 1000', number=10000)
t2 = timeit.timeit('[0 for _ in range(1000)]', number=10000)

print(f'[0] * 1000               => {t1:.3f} [sec]')
print(f'[0 for _ in range(1000)] => {t2:.3f} [sec]')
print(f'{t2/t1:.3f} times')
[0] * 1000               => 0.026 [sec]
[0 for _ in range(1000)] => 0.333 [sec]
12.774 times

入れる要素数が既知な場合の list 作成

list に入れる要素数が既知の場合、事前にサイズ指定の list を作っておいた方が1.4倍ほど速い。
append ではバッファの拡張があるので、リストに入れる要素数がより大きくなると影響も大きくなる予感。それは計ってないけれども。

import timeit

def pre_alloc():
  buf = [None] * 1000
  for i in range(1000):
    buf[i] = 'a'

def simple_append():
  buf = []
  for i in range(1000):
    buf.append('a')

t1 = timeit.timeit('pre_alloc()', number=10000, globals=globals())
t2 = timeit.timeit('simple_append()', number=10000, globals=globals())
print(f'pre_alloc     => {t1:.3f} [sec]')
print(f'simple_append => {t2:.3f} [sec]')
print(f'{t2/t1:.3f} times faster')
pre_alloc     => 0.454 [sec]
simple_append => 0.667 [sec]
1.468 times faster

ループ内不変値の比較

例えばNested loop joinのような処理で、外ループで決まって内ループでは変化しない値と比較する場合は、一時変数に代入しておく方が速い。(完全に固定値の場合は、即値で書く方が速い)
リストやタプル、dict、その他オブジェクトを介したアクセスはその分重くなる模様。
動的型付けなので比較そのもの以外の処理の影響も大きい&ループ内不変値の最適化はしてないっぽい。

import timeit

def cmp_by_immidiate():  # 完全固定値の場合
  for i in range(1000):
    if i == 999:
      break

def cmp_by_scala():
  buf = [None] * 1000
  buf[999] = 999
  local_static = buf[999]
  for i in range(1000):
    if i == local_static:
      break

def cmp_by_list():
  buf = [None] * 1000
  buf[999] = 999
  for i in range(1000):
    if i == buf[999]:
      break

t1 = timeit.timeit('cmp_by_immidiate()', number=10000, globals=globals())
t2 = timeit.timeit('cmp_by_scala()', number=10000, globals=globals())
t3 = timeit.timeit('cmp_by_list()', number=10000, globals=globals())
print(f'eval_by_immidiate => {t1:.3f} [sec]')
print(f'eval_by_scala     => {t2:.3f} [sec]')
print(f'eval_by_list      => {t3:.3f} [sec]')
print(f'eval_by_immidiate {t2/t1:.3f} times faster than cmp_by_scala')
print(f'eval_by_scala     {t3/t2:.3f} times faster than cmp_by_list')
eval_by_immidiate => 0.389 [sec]
eval_by_scala     => 0.415 [sec]
eval_by_list      => 0.530 [sec]
eval_by_immidiate 1.068 times faster than cmp_by_scala
eval_by_scala     1.276 times faster than cmp_by_list

計測環境

計測は 2020/5/9 時点の Google Colaboratory の python 環境を利用。

import sys
from pathlib import Path

osinfo = Path('/etc/issue')
cpuinfo = Path('/proc/cpuinfo')
meminfo = Path('/proc/meminfo')

for path in [osinfo, cpuinfo, meminfo]:
  with path.open(mode='r') as fp:
   for line in fp:
      print(line.strip())
print(sys.version_info)
Ubuntu 18.04.3 LTS \n \l

processor	: 0
vendor_id	: GenuineIntel
cpu family	: 6
model		: 63
model name	: Intel(R) Xeon(R) CPU @ 2.30GHz
stepping	: 0
microcode	: 0x1
cpu MHz		: 2300.000
cache size	: 46080 KB
physical id	: 0
siblings	: 2
core id		: 0
cpu cores	: 1
apicid		: 0
initial apicid	: 0
fpu		: yes
fpu_exception	: yes
cpuid level	: 13
wp		: yes
flags		: fpu vme de pse tsc msr pae mce cx8 apic sep mtrr pge mca cmov pat pse36 clflush mmx fxsr sse sse2 ss ht syscall nx pdpe1gb rdtscp lm constant_tsc rep_good nopl xtopology nonstop_tsc cpuid tsc_known_freq pni pclmulqdq ssse3 fma cx16 pcid sse4_1 sse4_2 x2apic movbe popcnt aes xsave avx f16c rdrand hypervisor lahf_lm abm invpcid_single ssbd ibrs ibpb stibp fsgsbase tsc_adjust bmi1 avx2 smep bmi2 erms invpcid xsaveopt arat md_clear arch_capabilities
bugs		: cpu_meltdown spectre_v1 spectre_v2 spec_store_bypass l1tf mds swapgs itlb_multihit
bogomips	: 4600.00
clflush size	: 64
cache_alignment	: 64
address sizes	: 46 bits physical, 48 bits virtual
power management:

processor	: 1
vendor_id	: GenuineIntel
cpu family	: 6
model		: 63
model name	: Intel(R) Xeon(R) CPU @ 2.30GHz
stepping	: 0
microcode	: 0x1
cpu MHz		: 2300.000
cache size	: 46080 KB
physical id	: 0
siblings	: 2
core id		: 0
cpu cores	: 1
apicid		: 1
initial apicid	: 1
fpu		: yes
fpu_exception	: yes
cpuid level	: 13
wp		: yes
flags		: fpu vme de pse tsc msr pae mce cx8 apic sep mtrr pge mca cmov pat pse36 clflush mmx fxsr sse sse2 ss ht syscall nx pdpe1gb rdtscp lm constant_tsc rep_good nopl xtopology nonstop_tsc cpuid tsc_known_freq pni pclmulqdq ssse3 fma cx16 pcid sse4_1 sse4_2 x2apic movbe popcnt aes xsave avx f16c rdrand hypervisor lahf_lm abm invpcid_single ssbd ibrs ibpb stibp fsgsbase tsc_adjust bmi1 avx2 smep bmi2 erms invpcid xsaveopt arat md_clear arch_capabilities
bugs		: cpu_meltdown spectre_v1 spectre_v2 spec_store_bypass l1tf mds swapgs itlb_multihit
bogomips	: 4600.00
clflush size	: 64
cache_alignment	: 64
address sizes	: 46 bits physical, 48 bits virtual
power management:

MemTotal:       13333540 kB
MemFree:        10687540 kB
MemAvailable:   12492736 kB
Buffers:           74588 kB
Cached:          1878092 kB
SwapCached:            0 kB
Active:           711776 kB
Inactive:        1681196 kB
Active(anon):     409164 kB
Inactive(anon):      320 kB
Active(file):     302612 kB
Inactive(file):  1680876 kB
Unevictable:           0 kB
Mlocked:               0 kB
SwapTotal:             0 kB
SwapFree:              0 kB
Dirty:               480 kB
Writeback:             0 kB
AnonPages:        440280 kB
Mapped:           223268 kB
Shmem:               904 kB
Slab:             162112 kB
SReclaimable:     125380 kB
SUnreclaim:        36732 kB
KernelStack:        3408 kB
PageTables:         5316 kB
NFS_Unstable:          0 kB
Bounce:                0 kB
WritebackTmp:          0 kB
CommitLimit:     6666768 kB
Committed_AS:    2539472 kB
VmallocTotal:   34359738367 kB
VmallocUsed:           0 kB
VmallocChunk:          0 kB
Percpu:              920 kB
AnonHugePages:         0 kB
ShmemHugePages:        0 kB
ShmemPmdMapped:        0 kB
HugePages_Total:       0
HugePages_Free:        0
HugePages_Rsvd:        0
HugePages_Surp:        0
Hugepagesize:       2048 kB
Hugetlb:               0 kB
DirectMap4k:       79036 kB
DirectMap2M:     6211584 kB
DirectMap1G:     9437184 kB
sys.version_info(major=3, minor=6, micro=9, releaselevel='final', serial=0)

Python str 1個入りタプルやリストを作る時の罠を踏んだ

まとめ

str 要素 1個入りの tuple, list を作るときは括弧で作ると安全。

t = ('abc',)   # ケツカンマがないと怒られる
l = ['abc']

tuple() や list() のコンストラクタ形式を使うと罠にはまるかも。

経緯

('abc',)
['abc']

↑の結果が欲しくて↓のようにすると、

t = tuple('abc')
l = list('abc')

↓のようになって Why となったわけです。

('a', 'b', 'c')
['a', 'b', 'c']

よくよく考えると、tuple, list のコンストラクタは class tuple([iterable]) , class list([iterable]) で、 Python では str 自体が Iterable なので、

t = tuple('abc')
l = list('abc')

とすると、'abc' が Itarable 扱いされて 1 文字ごとになったタプル/リストが作られたと。

('a', 'b', 'c')
['a', 'b', 'c']


str 単体でも str の Iterable でも受け付けるようなメソッド書くとき

def hoge (arg: Union[str. Iterable[str]]) -> None:

後のコードを共通にするため、単体の時は tuple や list でラップして、というのがよくやる手かと思いますが、↓のようにしてしまうと罠を踏み抜くわけですね。

def hoge (arg: Union[str. Iterable[str]]) -> None:
  if instanceof(arg, str):
    arg = tuple(arg)

  for x in arg:
     # 何かする

こういうラップするときは

  if not instanceof(arg, Iterable):
    arg1 = (arg, )

とするのが安全ぽい