ホームセンターと聞いたら、どんなイメージを抱くだろうか? 私(佐藤)は道具や部材を買うためのお店であり、あまり長居をする場所ではないと考えている。必要なものさえ見つけることができれば、あとは買って帰るだけ。そんな場所だ。 […]
Source link
まるでテーマパーク! 西日本最大級のホームセンター「ハンズマン」はワクワク感がスゴイ! とくに商品ディスプレイがすばらしい
【理系男子の株式投資】突きつけられた「会社員のままでは厳しい」という“お金の危機感” – 5年で1億貯める株式投資
大学時代からアニメや声優が好きな“典型的なモテないオタクの理系男子”が、入社した会社でボーナスを貯めた300万円を元手に株式投資をスタート。元手300万円を2年で10倍の3000万円に増やした。その3000万円を1年で5000万円に増やした。結局、しっかりとリスク管理をしながらわずか5年で働きながら資産1億円を突破! その間、月々の給料は日々の生活費やアニメグッズ、声優の推し活に使い、株式投資への資金追加はまったくのゼロ。それでも現在までに、資産3億円超に増やしている。さらに資産を拡大中だ。ズブの素人でも一つずつ階段をのぼりながら、比較的短期間でお金の不安を解消する投資法を初の著書『5年で1億貯める株式投資 給料に手をつけず爆速でお金を増やす4つの投資法』で徹底指南!

唯一の内定をくれた上場メーカーに就職
苦戦するなかで唯一内定をくれたのが、ビルの空調などを扱う東京証券取引所市場第一部(現・東証プライム)に上場するメーカーでした。
当時、この会社は海外のバイオテクノロジー系企業を買収し、空気中の細菌の量を計測する事業を展開し始めており、タイミングよく採用してもらえたのです。
恵まれた住環境と、趣味に使う給料
上場企業ということもあって、それほど給料が悪いわけではありません。また、勤務先は神奈川県・湘南にある研究所でしたが、最寄りの社員寮の寮費が1万円程度というかなりの好条件だったのです。
給料は相変わらず趣味の“推し活”につぎ込みましたが、ボーナスを貯蓄に回すことで、1年で100万円くらい貯めることができました。
30歳で社員寮を出なければならない現実
一方、会社員生活を4年ほど送っていると、だんだんと危機感を抱くようになりました。実は、寮に入居できるのは30歳までと決まっていたのです。
「30歳までにお金を貯めて、家を自分で借りるか買うかしなさい」という会社の方針だったのかもしれません。私は1年浪人し、大学院で修士号を取得してから就職したので、入社4年目ともなるともう30歳は目前。
「寮を出たら、余裕のある生活は送れない……」という現実が、目の前に迫ってきたのです。
将来のライフイベントと、お金の不安
さらに独身のうちはまだよくても、
✅結婚する
✅子どもができる
✅家を買う
✅子どもを塾に通わせる
……と生活が変化していくと、さらにお金が必要になります。
給料が増えない現実と、募る不安
勤務先の給料は、そこまで低いわけではないものの、高いわけでもない。また、入社して4年も経てば、昇給のスピードがそれほど速くないこともわかってきました。
会社に不満はなかったですし、働かせてもらっていることには感謝していました。
それでも、「このままだと、お金が増えない。それどころか徐々に苦しくなっていくかもしれない」と、急に危機感に襲われたのです。
※本稿は『5年で1億貯める株式投資 給料に手をつけず爆速でお金を増やす4つの投資法』(ダイヤモンド社)より一部を抜粋・編集したものです。
春アニメ『勘違いの工房主』第5話「四面楚歌の辺境町」場面カット&あらすじ | アニメイトタイムズ
アルファポリスファンタジー小説大賞で、読者賞受賞の話題作『勘違いの工房主~英雄パーティの元雑用係が、実は戦闘以外がSSSランクだったというよくある話~』。同作のTVアニメが、2025年4月6日(日)よりTOKYO MX・読売テレビ・BS日テレ・AT-Xにて放送開始、3月30日(日)22:30よりdアニメストア・ABEMAにて地上波1週間先行・最速配信も開始しています。
第5話「四面楚歌の辺境町」より、場面カット&あらすじが公開されました。
英雄パーティ「炎の竜牙」のマーレフィスは、所属パーティの失態を理由に司教から破門を告げられる。なんとか食い下がろうとするマーレフィスに提示された破門回避の条件は、第三王女・リーゼロッテの暗殺だった。
一方、リーゼの護衛を固めようとするオフィリアたちは、クルトの強力な「呪い返し」で難を逃れる。さらなる対策を考えようとしたその時、警鐘が鳴り響き、数千体のスケルトンが辺境町に襲来する…!
脚本:毛利のら
絵コンテ:佐々木こうじ
演出:飯村正之
総作画監督:谷津美弥子
トランプ大統領、パナマ運河とスエズ運河での米国船の無料通航求める – Bloomberg
トランプ米大統領はパナマ運河とスエズ運河での米国船舶の「無料」通航を求め、重要な商用・軍用水路に対する米国の影響力拡大に改めて焦点を当てた。
トランプ氏は1月のホワイトハウス復帰直後からパナマ運河周辺の中国系港湾運営会社の撤退を求めてきたが、26日のトゥルース・ソーシャルへの投稿では、エジプトを横断し地中海と紅海を結ぶスエズ運河にも関心を向けた。
「これらの運河は米国なしでは存在しなかった」とトランプ氏は述べ、ルビオ国務長官に「直ちにこの問題に対処するよう」指示したと付け加えた。

スエズ運河を航行する原油タンカー
Photographer: Bloomberg
トランプ氏は米造船業の復活を目指し、関税のほか、計画中の米国への入港料賦課などの措置を通じて中国の商業輸送と造船業に焦点を当ててきた。中国の造船業における優位性は不公正に達成されたものであり、米国の利益に害を及ぼしていると主張している。
かつて世界トップだった米国の商業造船は1980年代以降、補助金削減や海外との競争激化により縮小している。
トランプ氏がパナマ運河での中国の影響力を批判し、米国が「取り戻す」べきだと発言したことを受け、パナマは2月、米軍艦の同運河での自由通航を確約した。香港の長江和記実業(CKハチソン・ホールディングス)はパナマ運河の港湾資産を米ブラックロック率いるコンソーシアムに売却する計画だが、中国政府の反対とパナマ政府との金融紛争に直面している。
原題:Trump Says US Ships Need Free Travel Through Panama, Suez Canals(抜粋)
「春日影」に沸いたMyGO!!!!!×Ave Mujica合同ライブ、“わかれ道の、その先へ”進む
「BanG Dream!(バンドリ!)」プロジェクト発のガールズバンド・MyGO!!!!!とAve Mujicaの合同ライブ「わかれ道の、その先へ」が、4月26日・27日の2日間にわたり神奈川・Kアリーナ横浜で開催された。
Source link
EGO-WRAPPIN’がアナログ2タイトル同時リリース、“夏の野外で踊れる”恒例ワンマン開催
EGO-WRAPPIN’が7月2日に12inchアナログ「Treasures High / AQUA ROBE」と7inchアナログ「Sunny Side Steady / Sunny Side Dub -Prince Fatty Dubwise-」を同時リリースする。
Source link
「光合成を模倣して二酸化炭素から炭化水素を作り出すパネル」が開発される、無機物で光合成を再現
アメリカのローレンス・バークレー国立研究所の研究チームが、二酸化炭素から炭化水素を作り出すパネルを開発しました。パネルは植物性の材料ではなく無機物で光合成の模倣に成功したことを大きな特徴としており、プラスチックや燃料の生産に役立てられることが期待されています。
【保存版】 2 万文字で語る Python の with 文で始めるリソース管理 ── C++/Go/TypeScript の技法を横断
本記事では、Python の with
文を起点に、多言語の with
に相当する概念を横断的に比較し、
リソース管理という “地味だけど重要” なテーマ を一気に理解できるように整理しました。
「〇〇をしたら必ず △△ する」 をコードで保証する ── リソース管理とは?
A. 「そのタスク終わったら Slack で私に連絡してくださいね!」
B. 「はい、わかりました!」
(数日後…)
A. 「あれ、Slack で連絡来てないな。」
B. 「違う仕事していたら、Slack で連絡するの忘れてました!」
そんな経験、ありませんか?
「〇〇をしたら絶対にこれをやる」ということを強制する。
それを実現するのが、Python の with
文です。
〇〇をしたら絶対にこれをやるというのは、
例えばファイルを open したら必ず close をするなどのリソースに対して行うことは特に多く、
リソース管理のために with
文を使用することが多いです。
「最後にこれをするための約束をする」、そのための技法を一緒に整理しましょう!
TL;DR
-
with
文はtry…finally
の糖衣構文 - Python が 2006 年に with 構文を導入し、最近では TypeScript も 2023 年に using 構文を採用
- 多くの GC 言語で RAII 的安全性 を得るには明示的にスコープを作るしかない。だからこそ Python では
with
文という構文 -
with
文は__enter__
/__exit__
やcontextlib.contextmanager
で自作できる -
with
を使った簡単な活用事例 - SIGKILL などの OS に直接作用するイベントは
finally
が呼び出されないケースがある。特にデプロイなどではこのような事にならないように注意
2 万文字と長いブログ記事になってしまったのですが、
リソース管理技術について、
丁寧に説明してみましたので困った時などに読んでいただければと思います。
第 1 章. Python のリソース管理の核心:「with」構文を多言語比較で理解する
ファイルというものは開いたら必ず、 close をするべきです。
この例を元に、まずはリソース解放の歴史を、他の言語と比較しながら見ていきましょう。
必ず close が呼ばれるようにするとは?
file = open('example.txt', 'r')
content: str = file.read()
...
file.close()
このコードは … のところなどで例外が発生すると、file.close()
が呼び出さません。
open をしたら必ず close が呼び出されるようにするという行為を保証したいのですが、
これでは保証できていないことになるのです。
ここでは、まずファイルの open というのを例にしましたが、
- スレッドの Lock を取得したら解放をする
- User を作成したあとに削除する
- 一時的なファイルを作成してそのファイルを削除する
など、挙げればきりが無いほどに、
リソースを作ったあとに「後処理が行われることを保証する」ことを契約したいことがあります。
これを、このブログではリソース管理と呼んでおり、
最も簡単な例としてファイルの open/close の例を示します。
Python におけるリソース管理 : with 文の使い方
Python ではこのように記述します。
with open('example.txt', 'r') as file:
content: str = file.read()
...
このコードは、example.txt
というファイルを開いて内容を読み取るものです。with
文を使用することで、リソースの取得と解放を自動的に行うことができ、途中でエラーが発生してもリソース解放を保証します。
このコードと同等な内容は次のようなtry...finally
を使用してリソース管理を行う方法になります。
file = open('example.txt', 'r')
try:
content = file.read()
...
finally:
file.close()
これは等価なコードであり、どちらを選ぶべきかという問題があります。
try…finally から with への変革歴史
元々の Python では、リソース管理を行うためにtry...finally
を使った記述が行われていました。
つまり、
file = open('example.txt', 'r')
try:
content = file.read()
...
finally:
file.close()
という形のコードです。しかしながら、その後 2006 年にリリースされた Python 2.5 においてwith
文が導入されました。
つまり、
with open('example.txt', 'r') as file:
content: str = file.read()
...
という形のコードになります。この機能は PEP 343 によって提案され、リソース管理を簡潔かつ安全に行うための構文として設計されました。
try...finally
型の大きなデメリットは何かというと、
対応するリソース解放処理のメソッドが何かをユーザーが調べて実装する必要があるということ です。
例えば、open に対しては close, create に対してはもしかすると delete もしくは release という名前のメソッドかもしれません。
この処理の呼び出しを間違えると、リソース解放漏れを引き起こす可能性があります。
一方で、 with
は、その終了処理についても包含してくれているので、その呼び出し側が対応を忘れることができます。
Go における open/close の例 と try…finally の比較
Go では、defer
を使用してリソース解放を記述し、解放漏れを防ぎます。
package main
import (
"fmt"
"os"
)
func main() {
file, err := os.Open("example.txt")
if err != nil {
fmt.Println(err)
return
}
defer file.Close()
content := make([]byte, 100)
file.Read(content)
fmt.Println(string(content))
}
Go のこの構文は Python のtry...finally
に似ています。defer
節が finally
節に相当し、この箇所にリソース解放処理を記述します。
また、Python の例外のような panic と呼ばれるときにでも実行されることを保証しています。
defer を登録しておくと、関数終了時に逆順 (LIFO 順) で実行されます。
Python でこのような LIFO 順で実行する挙動は、後述する contextlib.ExitStack
で登場します。
Python の try...finally
型で説明した通り、
open を呼び出した側が忘れずに、defer
に対応するリソース処理を書く必要があります。
つまり、open に対応するコードは close であるという知識をもって書き、呼び出し側が間違えずに実装をする必要があるところが、
この記法の特徴であります。
個人的には、Go 言語に with
文のような仕様があれば、対応する処理を忘れることがなくなったり、不適切な実装をする間違いが減るので、
このような記法があると良いなと思っています。
しかしながら、このシンプルな設計思想がとても Go らしさでもあります!
TypeScript における open/close の例 と using への進化
TypeScript では、過去ではtry-finally
を使用してリソース解放を行っていました。
import * as fs from "fs";
fileDescriptor = fs.openSync("example.txt", "r");
try {
const buffer = Buffer.alloc(100);
fs.readSync(fileDescriptor, buffer, 0, 100, 0);
console.log(buffer.toString());
} finally {
fs.closeSync(fileDescriptor);
}
しかしながら、2023 年に TypeScript 5.2 において using
構文が導入されました。
この構文は、Python の with 文のようにリソース管理をより簡潔に記述できるように設計されています。
await using file = await fs.open("example.txt", "r");
const buffer = Buffer.alloc(100);
await file.read(buffer, 0, 100, 0);
console.log(buffer.toString());
using を使用することで、リソースの取得と解放を自動的に行うことができ、途中でエラーが発生してもリソース解放を保証します。
このように、歴史としては Python が導入した try-finally からwith
のように try-finally からusing
へと進化してきたことがわかります。
ちなみに補足すると、2024/04 現在、TypeScript の using は、
仕様(ECMAScript Stage 3 draft)ベースで TypeScript 5.2 に先行実装されてる段階です。
C++/Rust の RAII との比較 : RAII の本質 = 言語機能 + 破棄タイミングがコンパイルで決まる
今まで紹介した言語とは違い、GC を採用していない C++や Rust では、RAII (Resource Acquisition Is Initialization) という考え方が採用されています
(なお、C++や Rust のスマートポインタは、RAII を実現する代表的な仕組みですが、本記事では省略しています。)
オブジェクトを生成する瞬間にリソースを獲得し、スコープを抜けた瞬間に必ず破棄する
「コンパイラがデストラクタ呼び出しを強制的に挿入する」ため、開発者が後処理を忘れることは原理的にありません。
たとえ例外が発生しても、安全に確実にリソース解放が行われます。
例えば C++や Rust では次のように書きます。
#include
void read_file() {
std::ifstream file("example.txt");
...
}
use std::fs::File;
fn read_file() -> std::io::Result()> {
let file = File::open("example.txt")?;
...
Ok(())
}
Python や先ほどの TypeScript は、with
やusing
を使用してリソース管理を行うことはできますが、
RAII のようにコンパイラが強制的にリソース解放を行うことはできません。
Python や TypeScript、Go などの言語は GC (ガーベジコレクション) を採用しているため、
RAII のようにコンパイラの機能によって、強制的にリソース解放を行うことはできません。
なぜなら、GC 言語においては、他からもリソース参照されている可能性が否定できず、
スコープを抜けたからといって参照カウントが 0 になっているとは限らず破棄できないからです
(参照カウント方式の場合を例にしていますが別の実装方式でも似た問題が発生します)。
どういうことかというと、例えば次ようなコードを考えてみてください。
def create_file():
file = open("example.txt", "r")
return file
def main():
f = create_file()
content = f.read()
print(content)
main()
file というオブジェクトは create_file
のスコープが終了した後も生きているため、
関数スコープが終了したからと言って、close
をすることを保証することはできないのです。
そのため、Python や TypeScript などの GC 系の言語では、with
やusing
,defer
などを使用して「明示的に」
コードでリソースはこれ以上使わないという意図を表現し、リソース解放処理を行う必要があります。
項目 | C++/Rust (RAII) | Python などの GC 言語 |
---|---|---|
リソース破棄タイミング | スコープ終了時(コンパイラ保証) |
with などのキーワードでブロック終了時(コード挿入) |
オブジェクト破棄と一致? | はい(必ず破棄) | いいえ(コードで明示) |
この表から、Python の with
文は、C++/Rust の RAII と異なり、明示的にスコープを定義してリソース管理を行うことがわかります。
__del__
によるリソース解放は可能か?
Python にも一応、「オブジェクトが破棄されるときに実行される」特殊メソッドとして__del__
があります。
しかしながら、これを利用してリソース解放を行うことは 推奨されていません。
この __del__
は GC がオブジェクトを破棄することが決まったタイミングで呼び出されるときに呼ばれます。
例えば、次のようなコードを考えてみましょう。
class FileManager:
def __init__(self, path: Path) -> None:
self._f = open(path, "r")
def read(self) -> str:
return self._f.read()
def __del__(self) -> None:
print("Closing file...")
self._f.close()
def main() -> None:
file = FileManager(Path("example.txt"))
content = file.read()
main() 関数が終了した後、file
オブジェクトはガーベジコレクションによって破棄されるため、__del__
メソッドが呼び出され、ファイルが閉じられます。
しかし次のような問題があります。
- GC のタイミングに依存するため、破棄される時刻が予測できない
- コードに循環参照コードが存在すると、参照カウントが 0 にならないために
__del__
は呼ばれないことがある
Python における __del__
によるリソース解放処理は 「最後の非常手段」であって、通常のリソース管理には使うべきではないというのが原則です。
色々な言語とのリソース解放キーワードの歴史
こうした理由もあって、GC 型の言語は、 with
や using
, try
, defer
のようなリソース管理のためのキーワードを導入してきました。
それまでは try...finally
型のコード構文を利用して、リソース解放を行ってきました。
年代 | 言語 | 構文/イディオム | 典型的な書き方 | 備考 |
---|---|---|---|---|
1980s 後半 ~1990 年代 | C++ | RAII(デストラクタ) | { std::ifstream f("…"); … } |
構文ではなく仕組み。スコープ終了時に自動解放が保証される |
2002 | C# 1.0 |
using statement |
using(var f = …){…} |
.NET 最初期から存在 |
2006 | Python 2.5 |
with statement |
with open(…) as f: |
PEP 343 が採択され 2.5 で正式採用 |
2011 | Java SE 7 | try-with-resources |
try (var f = …){ … } |
AutoCloseable 実装で自動解放 |
2012 | Go 1.0 | defer |
defer f.Close() |
関数退出時に逆順実行。 |
2015 | Swift 2.0 | defer |
defer { fclose(f) } |
Xcode 7 で追加 |
2015 | Python 3.5 | async with |
async with aiofiles.open(…) |
非同期リソース対応 |
2023 | TypeScript 5.2 |
using / await using
|
await using file = await fs.open(…) |
ECMAScript「Explicit Resource Management」対応 (2024-04 現在 正式標準化前) |
この表は、各言語におけるリソース管理構文機能の歴史をまとめたものですが、
Python は歴史的にも早くにwith
文を導入しているように見えます。
自分としては、TypeScript においても、最近 using
構文が導入されたことは非常に嬉しいことでした。
第 2 章. Python で“自分専用 with を実装する
これまでの説明で、Python におけるリソース管理の歴史と他の言語との比較を行いました。
Python のリソース管理は、with
文を使用してリソースの取得と解放を自動的に行うことができ、途中でエラーが発生してもリソース解放を保証することを学びました。
紹介した file の open
メソッドは、with
文をサポートしていますが、自分で実装した関数やクラスをどのように with
文サポートする必要があるのでしょうか。
そのため、この with
文を使用することができるようにするコードを生成する方法を学ぶ必要があります。
__enter__
と __exit__
メソッドを実装する方法
Python では、with
文を使用するために、クラスに __enter__
と __exit__
メソッドを実装する必要があります。
from pathlib import Path
from typing import IO, Self
from types import TracebackType
class FileManager:
def __init__(self, path: Path, mode: str = "r") -> None:
self._f: IO[str] = open(path, mode)
def __enter__(self) -> Self:
return self
def read(self) -> str:
return self._f.read()
def __exit__(
self,
exc_type: type[BaseException] | None,
exc: BaseException | None,
tb: TracebackType | None,
) -> None:
self._f.close()
with FileManager(Path("example.txt")) as sf:
print(sf.read())
先ほど、__del__
メソッドを使用してリソース解放を行うことは推奨されていないと説明しましたが、
class FileManager:
def __init__(self, path: Path) -> None:
self._f = open(path, "r")
def read(self) -> str:
return self._f.read()
def __del__(self) -> None:
print("Closing file...")
self._f.close()
def main() -> None:
file = FileManager(Path("example.txt"))
content = file.read()
とするのではなく、先程のように __enter__
と __exit__
メソッドを実装することで、
def main() -> None:
with FileManager(Path("example.txt")) as sf:
print(sf.read())
とwith
文を使用して宣言的にリソースを管理することが GC 言語である Python の特徴です。
__enter__
・ __exit__
メソッドに関する注意点
この__enter__
と __exit__
メソッドへの注意点がいくつかあるので、注意書きを記載しておきます。
__enter__
が呼ばれない限り __exit__
メソッドは呼び出されない
例えば、このコードにおいて、
from pathlib import Path
from types import TracebackType
from typing import IO, Self
class FileManager:
def __init__(self, path: Path, mode: str = "r") -> None:
self._f: IO[str] = open(path, mode)
def __enter__(self) -> Self:
return self
def read(self) -> str:
return self._f.read()
def __exit__(
self,
exc_type: type[BaseException] | None,
exc: BaseException | None,
tb: TracebackType | None,
) -> None:
self._f.close()
def main() -> None:
with FileManager(Path("not_existing.txt")):
pass
として main()
を呼び出すとファイルが存在せずに __init__
メソッドで例外が発生するため、__enter__
メソッドは呼び出されず、__exit__
メソッドも呼び出されません。
なぜなら、このコードはもう少し分解すると、
manager = FileManager(Path("not_existing.txt"))
with manager:
pass
という文と等価であり、with
文の前の部分で例外が発生しているためです。
__exit__
メソッドの引数に関する注意点
def __exit__(
self,
exc_type: type[BaseException] | None,
exc: BaseException | None,
tb: TracebackType | None,
) -> None:
という引数を持ちます。これは、with
文のスコープ内で例外が発生しなかった場合は、この 3 つの引数はすべてNone
になります。
一方で、例外が発生した場合には、exc_type
には例外の型、exc
には例外オブジェクト、tb
にはトレースバックオブジェクトが渡されます。
__exit__
メソッドの戻り値を True
にすると例外を無視する
__exit__
メソッドの戻り値を True
にすると、例外が無視されます。
def __exit__(
self,
exc_type: type[BaseException] | None,
exc: BaseException | None,
tb: TracebackType | None,
) -> bool:
return True
一方で、__exit__
メソッドの戻り値を False
または None
にすると、
例外は無視されず呼び出し元に波及します。
def __exit__(
self,
exc_type: type[BaseException] | None,
exc: BaseException | None,
tb: TracebackType | None,
) -> bool:
return False
def __exit__(
self,
exc_type: type[BaseException] | None,
exc: BaseException | None,
tb: TracebackType | None,
) -> None:
return
これはどういう意味かというと、次の例を考えてみましょう
from pathlib import Path
from types import TracebackType
from typing import IO, Self
class FileManager:
def __init__(self, path: Path, mode: str = "r") -> None:
self._f: IO[str] = open(path, mode)
def __enter__(self) -> Self:
return self
def read(self) -> str:
data = self._f.read()
if not data:
raise ValueError("File is empty.")
if len(data) > 1_000_000:
raise MemoryError("File is too large.")
return data
def __exit__(
self,
exc_type: type[BaseException] | None,
exc: BaseException | None,
tb: TracebackType | None,
) -> bool:
print(exc_type)
print(exc)
print(tb)
self._f.close()
if isinstance(exc, MemoryError):
return True
return False
def main() -> None:
with FileManager(Path("empty.txt")) as f:
f.read()
print("done!")
File is empty.
Traceback (most recent call last):
File "/xxx/hoge.py", line 44, in
main()
File "/xxx/hoge.py", line 39, in main
f.read()
File "/xxx/hoge.py", line 18, in read
raise ValueError("File is empty.")
ValueError: File is empty.
このようになり、最後の print("done!")
は実行されません。
これは、ValueError
が発生したため、__exit__
メソッドが呼び出され、例外が無視されなかったためです。
一方で、
with FileManager(Path("too_large_file.py")) as f:
f.read()
print("done!")
と大きなファイルを読み込むと、MemoryError が発生しますが、__exit__
メソッドの中で例外を無視するようにしているため、with
を終えても例外は呼び出されず、with
文の後の print("done!")
が実行されます。
このコードは try-finally として書くと次のようになります。
from pathlib import Path
def main() -> None:
path = Path("valid_file.txt")
file = open(path, "r")
try:
data = file.read()
if not data:
raise ValueError("File is empty.")
if len(data) > 1_000_000:
raise MemoryError("File is too large.")
except MemoryError as e:
pass
finally:
file.close()
print("done!")
そのため、finally の処理を終えたあとに、MemoryError
の場合には例外が発生されず print("done!")
が呼ばれ、ValueError
の場合には、finally が終わったあとに例外が発生してプログラム自体が終了してしまい、print("done!")
が呼ばれなかったということになります。
こういう例外を無視するような処理をする場合には、__exit__
の引数を気をつける必要があるのですが、
通常そのようなことはあまりしないと思いますので、
def __exit__(
self,
exc_type: type[BaseException] | None,
exc: BaseException | None,
tb: TracebackType | None,
) -> None:
return
と覚えておけば十分な場合が多いです。
contextlib.contextmanager
モジュール
ここまで見ると、__enter__
と __exit__
メソッドを実装することで、with
文を使用してリソース管理を行うことができることがわかりましたが、
このようにクラスを実装するのは、行数が多く冗長で面倒な場合もあります。
そのために続く、contextlib
モジュールのcontextmanager
デコレータを使用することで、より簡潔にリソース管理を行うことができます。
from contextlib import contextmanager
from typing import Iterator, TextIO
@contextmanager
def open_file(path: Path, mode: str = "r") -> Iterator[TextIO]:
f = open(path, mode)
try:
yield f
finally:
f.close()
with open_file(Path("example.txt")) as f:
print(f.read())
このように書くことができます。
これは非常に直感的で、
最初の章で with
文は try-finally
と同等なコードであることを思い出すと、
この文は非常に自然であることがわかります。
こちらはとても簡潔にかけることもあり、__enter__
と __exit__
メソッドを実装するよりも、
こちら側で実装することは私は多いです。
第 3 章. Python で複数のリソースを管理する
多重のwith
文を使用することもできます。
with open("example.txt", "r") as f1, open("example2.txt", "r") as f2:
content1 = f1.read()
content2 = f2.read()
これは
with open("example.txt", "r") as f1:
with open("example2.txt", "r") as f2:
content1 = f1.read()
content2 = f2.read()
と同じ意味になります。
1 段ネストにできるのためシンプルに書けるのが良い点だと思います。
contextlib.ExitStack
クラス
複数のリソースを管理する場合、contextlib.ExitStack
クラスを使用することで、より柔軟にリソース管理を行うことができます。
from contextlib import ExitStack
filenames = ["example1.txt", "example2.txt", "example3.txt"]
with ExitStack() as stack:
files = [stack.enter_context(open(name, "r")) for name in filenames]
contents = [f.read() for f in files]
これにより、 登録したすべてのリソースが “逆順” でクローズされます。
この例では、stack が構築された with
文のスコープを抜けると、
stack に enter_context
でその時まで登録されていたリソースがすべてクローズされます。
順番としては example1.txt → example2.txt → example3.txt の順番で open され登録されたので、
順番としては example3.txt → example2.txt → example1.txt の順番で close されます。
第 4 章. 非同期の with
文
Python 3.5 からは、非同期処理を行うための async with
構文が導入されました。
非同期処理についてはここでは深くは触れませんが、async with
文を使用することで、非同期処理の中でもリソース管理を行うことができます。
例えば、
import aiofiles
from pathlib import Path
async def main() -> None:
async with aiofiles.open(Path("example.txt"), mode="r") as f:
content = await f.read()
print(content)
このように書くことができます。
これを try-finally で書くと次のようになります。
import aiofiles
from pathlib import Path
async def main() -> None:
try:
f = await aiofiles.open(Path("example.txt"), mode="r")
content = await f.read()
print(content)
finally:
await f.close()
となります。
__enter__
と __exit__
メソッドに対応するのが __aenter__
・__aexit__
になり、async with
文を利用できるようになります。
また、contextlib
モジュールにも非同期処理用のデコレータが用意されています。contextlib.contextmanager
に相当するもののが contextlib.asynccontextmanager
です。contextlib.ExitStack
に相当するものが contextlib.AsyncExitStack
となります。
第 5 章. with
文をサポートする主なライブラリと型
以下は、Python 標準ライブラリで with
文をサポートする主な標準ライブラリです。
ライブラリ/型名 | 役割 | 説明 |
---|---|---|
open (builtins) |
ファイルオープン |
with open(...) as f: で使うと安全にクローズできる |
threading.Lock , threading.RLock
|
ロック制御 |
with lock: と書けば確実に unlock される |
threading.Condition , threading.Semaphore
|
同期オブジェクト |
with cond: や with sem: でロック取得 |
tempfile.TemporaryFile |
一時ファイル |
with TemporaryFile() as f: で自動削除される |
tempfile.NamedTemporaryFile |
名前つき一時ファイル | 同上(close すると自動でファイル消える) |
zipfile.ZipFile |
ZIP ファイル操作 |
with ZipFile(...) as zipf: で自動クローズ |
tarfile.TarFile |
TAR ファイル操作 | with TarFile.open(...) as tar: |
sqlite3.connect |
SQLite 接続 |
with sqlite3.connect(...) as conn: でトランザクション管理 |
subprocess.Popen |
プロセス制御 |
with Popen(...) as proc: で終了処理自動化(Python 3.2〜) |
socket.socket |
ソケット通信 |
with socket(...) as s: で自動 close
|
このようにファイルやソケット、スレッドロック,DB など、さまざまなリソース管理に利用されています。
このようにリソース管理を行い、安全に最後のリソースを解放を行うことができます。
第 6 章. 応用例
時間計測
import time
from typing import Iterator
from contextlib import contextmanager
@contextmanager
def measure_time(task_name: str) -> Iterator[None]:
start = time.time()
try:
yield
finally:
end = time.time()
print(f"{task_name} にかかった時間: {end - start:.3f} 秒")
def main() -> None:
with measure_time("ファイル読み込み処理"):
...
とすることで、with 文を利用して、スコープの開始と終了を計測することができます。
これはリソース管理という使い方というよりは、finally が必ず呼ばれるという契約を利用している使い方になります。
AWS DynamoDB を使ったリソース lock
AWS DynamoDB を使って、ここでは、複数プロセス間でのリソースロックを実装する例を示します。
DynamoDB は、AWS の NoSQL データベースサービスで、スケーラブルなデータストレージを提供します。
この例では、DynamoDB テーブルを利用して、同一の リソース ID に対しては同時にアクセスできないようにロックを取得します。
ConditionExpression を利用して、すでにそのリソース ID が存在する場合には失敗するようにしていて、
単一のプロセスしか with スコープに入れないようにすることができます。
import logging
import boto3
from contextlib import contextmanager
from typing import Iterator
from botocore.exceptions import ClientError
logger = logging.getLogger(__name__)
dynamodb = boto3.resource("dynamodb")
lock_table = dynamodb.Table("locks")
@contextmanager
def process_lock(resource_id: str) -> Iterator[None]:
try:
lock_table.put_item(
Item={"resource_id": resource_id},
ConditionExpression="attribute_not_exists(resource_id)"
)
logger.info(f"ロック取得成功: {resource_id}")
yield
except ClientError as e:
if e.response["Error"]["Code"] == "ConditionalCheckFailedException":
logger.warning(f"ロック取得失敗(すでに他のプロセスがリソース確保中です): {resource_id}")
raise RuntimeError(f"リソース {resource_id} はロックされています") from e
else:
raise
finally:
try:
lock_table.delete_item(Key={"resource_id": resource_id})
logger.info(f"ロック解放成功: {resource_id}")
except Exception as e:
logger.error(f"ロック解放失敗: {resource_id} - {e}")
このような形で、contextlib を利用して、DynamoDB を利用したプロセス間のロックを実装することができます。
(実際に運用す際には、DynamoDB のテーブルの TTL (Time To Live) を利用して、ロックの有効期限を設定するなど、色々考慮する必要がありますが、参考となる考え方かなと思い掲載しておきました.
)
第 7 章. 補足
補足: with
文や finally
でも必ずしも動かない場合があるケース
ここまで、Python において with
文や try...finally
を使うことでリソース管理が安全にできる、という話をしてきました。
しかしながら、“必ず” 最後にリソース解放処理が走るとは限らない状況も存在します。
これはソフトウェアとして動いている以上仕方ない問題であったりします。
この章では、そうした 「防げないケース」 について整理し、
さらにこれは Python 特有の話ではなく、他言語にも共通する問題 であることを解説します。
Python において finally
が動作できないケース
ケース | 説明 |
---|---|
SIGKILL |
kill -9 などで送られる強制終了シグナルにより、OS カーネルがプロセスを即時終了させる |
OOM Killer |
メモリ不足時にカーネルがシステム保護のため特定プロセスを強制終了する |
os._exit() |
Python プロセスを即時終了させる。GC、finally 、with など一切実行されない |
os.abort() |
自プロセスに SIGABRT を送信して異常終了する。コアダンプを生成し、後処理は一切行われない |
Python インタプリタの異常終了 | CPython 自体がクラッシュ(バグや内部エラー)した場合、finally 文は実行されない |
ハードウェア障害 | 電源断・メモリ故障・ディスク故障などによりプログラムが強制的に停止する |
つまり、プロセスレベルで “即死” するような終了方法に対しては、Python の finally
や with
文は機能しません。
プロセスが終了する際に「後片付けの猶予」が与えられないため、
リソース解放(ファイル close・ロック解除・DB コネクション切断など)が実行されずに終わります。
これは Python だけの問題ではない
実はこの問題は Python に限りません。
例えば Go でも、os.Exit()
を呼び出すと defer 文が一切実行されず に即座にプロセスが終了します。
また、SIGKILL
シグナルを受けた場合も同様です。
つまり、多くの言語に共通する OS・プロセス管理レベルの限界 に由来する問題なのです。
逆にこれはちゃんと with
文や finally
が動作するよというケース
- 普通の 例外 (
try...except
でキャッチできる例外) が発生した場合 - SIGTERM や SIGINT などの一般的な終了シグナルを受け取った場合
-
sys.exit()
を呼び出して終了する場合
このため、Python でプログラムを正常終了させたい場合はos._exit()
のような「即死関数」ではなく、sys.exit()
を使うことが推奨されます。
なお、os._exit()
は名前にアンダースコア _ が含まれているとおり、
内部用途向け(private な関数) という位置づけです。
通常のアプリケーションコードでは、使わない方が安全です。
補足 : デプロイ時に発生する SIGTERM と SIGKILL ── プロセス終了のフロー
実際の運用環境(特に AWS ECS / Kubernetes / systemd / Docker など)では、
アプリケーションのデプロイやロールアウト時に、プロセスが強制終了される 場面が出てきます。
このとき、プロセスに対しては次のような流れでシグナルが送られます。
デプロイ時のプロセス終了フロー
- まず SIGTERM が送られて例外が発生する(優しく終了してね、という合図)
- アプリケーションがこれを受け取り、できるだけ速やかにリソース解放や後処理 (
finally
,with
) を行い、自発的に終了する - 一定時間内(例: 30 s)に終了しなければ SIGKILL が送られる (
finally
,with
文は実行されません)
つまり、猶予はあるが、永遠には待ってくれないということです。
このため、
SIGTERM を受け取ったらできるだけすぐ終了する
長時間かかるリソース解放(DB フラッシュ、大量ファイル保存など)は注意する
「SIGKILL される前に終了できる設計」 を意識する といった考え方が重要になります。
SIGKILL は先程述べたように、 OS カーネルレベルでの即時終了処理になりますので、finally
や with
文は一切実行されません。
SIGTERM を受けたら速やかに終了処理を行う必要があります。
SIGTERM の猶予時間はどれくらい?
これは実行環境によって違いますが、よくある例では:
実行環境 | SIGTERM -> SIGKILL までの デフォルト猶予時間 |
---|---|
Docker (docker stop) | 10 秒 (docker stop -t ) |
Kubernetes | 30 秒 (terminationGracePeriodSeconds) |
systemd | 90 秒 (TimeoutStopSec, ※ディストリビューション依存のため参考値) |
AWS ECS/Fargate | 30 秒 |
我々の会社では、AWS ECS/Fargate でのデプロイを行っているのですが、
30 秒制約を満たせるように設計しています。
まとめ
長くなりましたが、ここまで読んでいただきありがとうございました。
実際、ファイル、データベース、ネットワーク接続など、実務で扱うリソースの多くは明示的な管理が欠かせません。
Python のリソース管理は、単に便利なだけでなく、意図を明示し、コードを安全に保つための文化を根ざしています。
The Zen of Python の有名な言葉
Explicit is better than implicit. (明示的であることは暗黙的であることに勝る)
という言葉があるように、with
文を使用することで、リソース管理の意図を明確にし、コードの可読性と安全性を向上させることができます。
この文章を書いた理由
こちらで、 「もしいま、Python をイチから学び直すとしたら?」 というテーマで Findy さんの Engineer Lab に寄稿させていただきました。
これは初心者の方がどのように Python を学ぶと良いか、という内容を書いたものだったのですが、
その中で with 文や contextlib.contextmanager によるリソース管理 についても触れたので、
こちらで詳しく記載させていただきました。
こちらの内容で、Python を学んだばかりの方が Python のリソース管理について何か初心者の方の参考になれば幸いです。
Recustomerでは新しいメンバーを大大大募集しています!
Recustomerではこの記事で書いてあるようなことをコードレビューなどで議論しながら、
いつも楽しく技術についてお話しています。
今回の記事に関することや、Recustomerに興味を持っていただいた方はカジュアル面談でぜひお話ししましょう!