テキストファイルを使って処理を行う関数があるとします。たとえば、ファイルから読み取って単語「a」を削除します。ファイル名を渡して関数の開始/終了を処理するか、開かれたファイルを渡して、それを呼び出す人は誰でもそれを閉じることを期待できます。
最初の方法は、ファイルが開いたままにならないことを保証するより良い方法のようですが、StringIOオブジェクトなどを使用できません
2番目の方法は少し危険かもしれません-ファイルが閉じられるかどうかを知る方法はありませんが、ファイルのようなオブジェクトを使用することができます
def ver_1(filename):
with open(filename, 'r') as f:
return do_stuff(f)
def ver_2(open_file):
return do_stuff(open_file)
print ver_1('my_file.txt')
with open('my_file.txt', 'r') as f:
print ver_2(f)
これらの1つは一般的に好ましいですか?一般に、関数はこれらの2つの方法のいずれかで動作すると予想されますか?それとも、プログラマーが適切に関数を使用できるように、それを十分に文書化する必要がありますか?
便利なインターフェースは素晴らしいです。ただし、たいていの場合、便利ですコンポーザビリティは利便性よりも重要です。構成可能な抽象化により、その上に他の機能(コンビニエンスラッパーを含む)を実装できるためです。
関数がファイルを使用する最も一般的な方法は、開いているファイルハンドルをパラメーターとして取ることです。これにより、ファイルシステムの一部ではないファイルハンドル(パイプ、ソケットなど)も使用できるようになります。
_def your_function(open_file):
return do_stuff(open_file)
_
with open(filename, 'r') as f: result = your_function(f)
の綴りがユーザーに尋ねるには多すぎる場合は、次の解決策のいずれかを選択できます。
your_function
_は、開いているファイルまたはファイル名をパラメーターとして受け取ります。ファイル名の場合は、ファイルが開いて閉じ、例外が伝播します。名前付き引数を使用して回避できる可能性のある、あいまいさに関する問題が少しあります。ファイルを開く処理を行う単純なラッパーを提供します。
_def your_function_filename(file):
with open(file, 'r') as f:
return your_function(f)
_
私は一般的にそのような関数をAPIの肥大化と認識していますが、それらが一般的に使用される機能を提供する場合、得られる便利さは十分に強力な議論です。
_with open
_機能を別の構成可能な関数でラップします。
_def with_file(filename, callback):
with open(filename, 'r') as f:
return callback(f)
_
with_file(name, your_function)
またはより複雑な場合にはwith_file(name, lambda f: some_function(1, 2, f, named=4))
として使用
本当の問題は完全性の1つです。あなたのファイル処理機能はファイルの完全な処理ですか、それとも一連の処理ステップの一部にすぎませんか?それ自体が完全である場合は、すべてのファイルアクセスを関数内にカプセル化してかまいません。
def ver(filepath):
with open(filepath, "r") as f:
# do processing steps on f
return result
これには、with
ステートメントの最後でリソースをファイナライズする(ファイルを閉じる)という非常に優れたプロパティがあります。
ただし、既に開いているファイルを処理する必要がある可能性がある場合は、ver_1
とver_2
を区別する方が理にかなっています。例えば:
def _ver_file(f):
# do processing steps on f
return result
def ver(fileobj):
if isinstance(fileobj, str):
with open(fileobj, 'r') as f:
return _ver_file(f)
else:
return _ver_file(fileobj)
この種の 明示的な型のテストはしばしば嫌われます 、特にJava、Julia、Goなどの言語では、型ベースまたはインターフェイスベースのディスパッチが直接サポートされています。ただし、Pythonでは、型ベースのディスパッチの言語サポートはありません。 Pythonでの直接型テストの批判を時折見るかもしれませんが、実際には、それは非常に一般的であり、非常に効果的です。これにより、関数に高度な一般性を持たせることができ、「ダックタイピング」とも呼ばれる、あらゆるデータ型が処理される可能性があります。 _ver_file
の先頭のアンダースコアに注意してください。これは、「プライベート」関数(またはメソッド)を指定する従来の方法です。技術的には直接呼び出すことができますが、関数は直接の外部消費を目的としていないことを示唆しています。
2019の更新:Python 3での最近の更新を考えると、たとえば、パスがstr
またはbytes
(3.4+)だけでなくpathlib.Path
オブジェクトとして潜在的に格納されるようになったことなどそして、その型のヒントは難解なものから主流になりました(およそ3.6+ですが、まだ活発に進化しています)。これらの進歩を考慮に入れて更新されたコードは次のとおりです。
from pathlib import Path
from typing import IO, Any, AnyStr, Union
Pathish = Union[AnyStr, Path] # in lieu of yet-unimplemented PEP 519
FileSpec = Union[IO, Pathish]
def _ver_file(f: IO) -> Any:
"Process file f"
...
return result
def ver(fileobj: FileSpec) -> Any:
"Process file (or file path) f"
if isinstance(fileobj, (str, bytes, Path)):
with open(fileobj, 'r') as f:
return _ver_file(f)
else:
return _ver_file(fileobj)
ファイルハンドルの代わりにファイル名を渡す場合、2番目のファイルを開いたときに、最初のファイルと同じファイルである保証はありません。これは、正しさのバグやセキュリティホールにつながる可能性があります。
これは所有権とファイルを閉じる責任についてです。ストリームまたはファイルハンドル、またはある時点で閉じたり破棄したりする必要のあるものを、誰がそれを所有しているかを明確にし、それを確実にする限り、willを閉じることができます。あなたが終わったときに所有者によって。これには通常、try-finally構成または使い捨てパターンが含まれます。
他の回答が指摘していない1つの側面は、「機能」アプローチです。これは通常 security のコンテキストで適用されますが、設計と開発(たとえば、防御的なコーディング、デバッグ、テスト容易性など)にも役立ちます。
大まかに:「機能」とは、何らかのアクションを実行するために必要な情報です。能力があれば、それに対応するアクションを実行できます。対応する機能なしではアクションを実行できません。機能の例には、URL、APIキー、ユーザー名/パスワードのペアなどが含まれます。機能はすべきの発生よりも必要なの発生に焦点を当てているので便利です。
あなたの場合、それはseemsのように、ファイル名とハンドルはほぼ同等です:ファイルを読み取ることができます。しかし、物事はそれほど単純ではなく、機能は私たちに違いについて考える方法を提供します。
私たちのコードすべきファイルからデータを読み取るため、そのための機能(別名情報が必要)が必要です。ファイル名はこの機能を提供しますか?結構です。特に:
開かれたファイル(ハンドルまたは「ファイルのようなオブジェクト」とも呼ばれます)にはこれらの問題はありません。許可またはファイルが見つからないというエラーは、関数が呼び出される前にスローされます。おそらく(単一責任の原則により)これらのファイルを開こうとしているコードは、それらのエラーを処理するか、それらを渡すためにより適切に配置されます。
したがって、ファイル名は必ずしも必要な機能ではありません。また、extra不要な機能も提供される場合があります。たとえば、ファイル名を指定すると、削除、名前の変更、移動などができます。ハンドルを使用してそれを行うことはできませんが(少なくとも、それほど簡単ではありません)。より制限された機能のセットを使用してコードを記述した場合、誤って不要なアクションをトリガーする可能性は低くなります。これが、ファイル名ではなくハンドルを受け入れるもう1つの理由です。また、関数が問題の原因であるかどうか(ファイルが誤って削除されるなど)かどうかをシグネチャから推測できるため、理解とデバッグが容易になると私は主張します。重要な側面は、システムの他の部分をカプセル化/モジュール化/保護しながら、各コンポーネントがその仕事を行うために必要なすべてを備えていることを確認することです(つまり、適切な機能が利用可能であるため)。干渉(つまり、これらの機能の制限)。
警告:securityの機能モデルに依存している場合、機能を秘密にしておくことが重要です。はnguessable(たとえば、1つの有効なIDをインクリメントして別のIDを取得することはできません);およびndiscoverable(たとえば、ファイル名を一覧表示する機能を制限する必要があります)。何かがもっともらしいである場合、悪意のある攻撃者がそれを悪用することを想定する必要があります(たとえば、ファイルハンドルの属性を深く掘り下げてファイル名を特定します)。私たちが悪意のある俳優について心配していない場合、私は開発者(私を含む)がほとんど怠惰であると仮定する傾向があります:遅延ソリューションとトリッキーソリューションがある場合(たとえば、特定のハンドルからの読み取りとファイル名の把握とオープン)の場合、設計時にトリッキーなアプローチを無視することができます。これは、(ab)使用される可能性が低いためです。