Skip to main content

Python でいう with ブロックのようなもの

知りたいこと

Python でいう with ブロックのように、

  • ブロック内の処理を実行
  • 前後には決まった処理を入れる

という処理を自前で書きたい。

こういうやつ

py
with open(fname) as f:
...

概要

  • C++ と Rust はスコープ内のオブジェクトが、スコープ終端で解放されるのでデストラクタ(相当)に解放処理などを実装する
  • これを RAII という

それぞれの取っ掛かり用に用語だけ先に一覧しておく

  • scoped cleanup / deterministic disposal(スコープ終端で確実に後始末)
    • C++/Rust は RAII(destructor/Drop)
    • それ以外は with/using/defer/ensure などで「確定的に」後始末

言語別でいうと、

  • C++ : デストラクタ
  • Rust : Drop (デストラクタ相当)
  • Python : with / @contextmanager / enter / exit / yield
  • Swift : defer / rethrows / @discardableResult
  • Ruby : block_given? / yield / ensure
  • C# : using / IDisposable / Dispose
  • Go : defer
  • Kotlin : use

それぞれの言語の例

気が向いた分だけ+粒度バラバラのまま

C++ 例

概念だけ記載

cpp
{
std::lock_guard<std::mutex> lock(m);
// ...
} // デストラクタが呼ばれる
  • これを活用してリソース解放をするような手法を RAII という

Python 例

@contextmanager での実装例

py
from contextlib import contextmanager

class MyResource:
def __init__(self, path):
self.path = path
# ここで「獲得」(open/connect/lock など)
# 例: self.f = open(path, "rb")

def close(self):
# ここで「解放」(close/disconnect/unlock など)
# 例: self.f.close()
pass

@contextmanager
def open_resource(path):
res = MyResource(path)
try:
yield res
finally:
res.close()

# 使い方(Rubyのブロック版)
with open_resource("data.txt") as r:
# r を使う
pass

__enter__, __exit__ での実装例

py
class MyResource:
def __init__(self, path):
self.path = path
# ここで「獲得」(open/connect/lock など)

def close(self):
# ここで「解放」(close/disconnect/unlock など)
pass

def __enter__(self):
return self

def __exit__(self, exc_type, exc, tb):
self.close()
return False # 例外は握りつぶさない

# 使い方
with MyResource("data.txt") as r:
# r を使う
pass

Ruby 例

rb
class MyResource
def initialize(path)
@path = path
# ここで「獲得」(open/connect/lock など)
end

def close
# ここで「解放」(close/disconnect/unlock など)
end

def self.open(path)
res = new(path)

unless block_given?
return res
end

begin
yield res
ensure
res.close
end
end
end

MyResource.open("data.txt") do |r|
# r を使う
end

Swift

swift
final class MyResource {
let path: String

init(path: String) {
self.path = path
// ここで「獲得」(open/connect/lock など)
}

func close() {
// ここで「解放」(close/disconnect/unlock など)
}
}

@discardableResult
func withResource(_ path: String, _ body: (MyResource) throws -> Void) rethrows -> MyResource {
let res = MyResource(path: path)
defer {
res.close()
}
try body(res)
return res
}

try withResource("data.txt") { r in
// ...
}

以下広告