파일 이름을 열어서 열거 나 파일을 열어야합니까?


53

텍스트 파일로 작업을 수행하는 함수가 있다고 가정합니다. 예를 들어 텍스트 파일을 읽고 'a'라는 단어를 제거합니다. 파일 이름을 전달하고 함수에서 열기 / 닫기를 처리하거나 열린 파일을 전달하여 파일을 호출 한 사람이 파일을 닫을 것으로 예상 할 수 있습니다.

첫 번째 방법은 파일이 열려 있지 않은 것을 보장하는 더 좋은 방법처럼 보이지만 StringIO 객체와 같은 것을 사용하지 못하게합니다.

두 번째 방법은 약간 위험 할 수 있습니다-파일이 닫힐 지 여부를 알 수있는 방법은 없지만 파일과 같은 객체를 사용할 수 있습니다

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)

이들 중 하나가 일반적으로 선호됩니까? 일반적으로 함수가이 두 가지 방법 중 하나로 작동 할 것으로 예상됩니까? 또는 프로그래머가 기능을 적절하게 사용할 수 있도록 잘 문서화해야합니까?

답변:


39

편리한 인터페이스가 좋으며 때로는 갈 수도 있습니다. 그러나 컴포저 블 추상화를 통해 그 위에 다른 기능 (편의 래퍼 포함)을 구현할 수 있기 때문에 대부분의 경우 컴포지션이 컴포지션보다 중요 합니다.

함수가 파일을 사용하는 가장 일반적인 방법은 파일 시스템의 일부가 아닌 파일 핸들 (예 : 파이프, 소켓 등)을 사용할 수 있으므로 열린 파일 핸들을 매개 변수로 사용하는 것입니다.

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))


6
이 접근 방식의 유일한 단점은 오류보고 등의 경우 파일과 같은 객체의 이름이 필요하다는 것입니다. 최종 사용자는 "<stream @ 0x03fd2bb6>의 오류"대신 "foo.cfg (12)의 오류"를 선호합니다. (12) ". your_function이와 관련하여 선택적 "stream_name"인수를 사용할 수 있습니다.

22

실제 질문은 완전성 중 하나입니다. 파일 처리 기능이 파일의 전체 처리입니까, 아니면 일련의 처리 단계 중 하나입니까? 그것이 완전하고 완전하다면, 함수 내에서 모든 파일 액세스를 자유롭게 캡슐화하십시오.

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에서는 유형 기반 디스패치에 대한 언어 지원이 없습니다. 때로는 파이썬에서 직접 유형 테스트에 대한 비판을 볼 수 있지만 실제로는 매우 일반적이며 효과적입니다. 그것은 "Duck 타이핑"이라고하는 데이터 타입을 처리 할 수있는 기능을 제공합니다. 밑줄에 주목하십시오 _ver_file. 이는 "비공개"기능 (또는 방법)을 지정하는 일반적인 방법입니다. 기술적으로 직접 호출 할 수는 있지만 함수가 직접적인 외부 소비를위한 것이 아니라고 제안합니다.


2019 업데이트 : Python 3의 최신 업데이트를 고려할 때 경로가 잠재적으로 또는 (3.4+)가 pathlib.Path아닌 객체로 저장 될 수 있으며 유형 힌트가 밀교에서 주류로 (현재 활발하게 진화하고는 있지만 3.6 이상) 변경되었습니다. 이러한 발전을 고려한 업데이트 된 코드 :strbytes

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)

1
오리 타이핑은 객체의 유형이 아닌 객체로 수행 할 수있는 작업을 기반으로 테스트합니다. 예를 들어, read파일과 비슷한 것을 호출하거나 if를 호출 open(fileobj, 'r')하고 잡는 것은 문자열이 아닙니다. TypeErrorfileobj
user2357112

사용 중인 오리 타이핑에 대해 논쟁 중입니다 . 이 예에서는 오리 입력을 적용합니다. 즉, 사용자 ver는 유형에 관계 없이 작업을 수행합니다. ver당신이 말하는 것처럼 오리 타이핑을 통해 구현할 수도 있습니다 . 그러나 예외를 생성하고 포착하는 것은 단순 유형 검사보다 느리고 IMO는 특별한 이점 (명확성, 일반성 등)을 제공하지 않습니다. 내 경험상 오리 타이핑은 "대형"에서는 훌륭하지만 중소형에서는 "중립적"입니다 "
Jonathan Eunice

3
아니요, 아직도하고있는 일은 오리 타이핑이 아닙니다. hasattr(fileobj, 'read')테스트는 오리 타이핑 될 것이다; isinstance(fileobj, str)시험은 없습니다. 차이점의 예는 다음과 같습니다. isinstance유니 코드 파일 이름으로 테스트가 실패 u'adsf.txt'합니다 str. 너무 구체적인 유형을 테스트했습니다. 호출 open또는 가설 적 does_this_object_represent_a_filename기능을 기반으로하는 오리 타이핑 테스트 에는 문제가 없습니다.
user2357112

1
코드가 설명 예제가 아닌 프로덕션 코드 인 경우 PY2와 PY3에서 올바르게 작동 하도록 올바르게 설정 되어 is_instance(x, str)있지만 사용하지 않기 때문에 문제가 발생하지 않습니다 . 줄처럼 qua 거리는 무언가가 주어지면 제대로 반응 할 것입니다. 파일처럼 똑같은 것을주었습니다. A와 사용자 의 유형 검사 구현이 빠르게 실행됩니다 것을 제외하고 -, 차이가 없을 것이다. 오리 순수 주의자 : 동의하지 않는다. is_instance(x, string_types)string_typesverver
Jonathan Eunice

5

파일 핸들 대신 파일 이름을 전달하면 두 번째 파일이 열릴 때 첫 번째 파일과 동일한 파일이라는 보장이 없습니다. 이로 인해 정확성 버그와 보안 허점이 생길 수 있습니다.


1
참된. 그러나 이는 다른 트레이드 오프와 균형을 이루어야합니다. 파일 핸들을 전달하는 경우 모든 독자는 "현재 파일 위치"를 이동할 가능성이 있기 때문에 파일에 대한 액세스를 조정해야합니다.
Jonathan Eunice

@JonathanEunice : 어떤 의미에서 조정합니까? 그들이 원하는 것은 파일 위치를 원하는 곳으로 설정하는 것입니다.
Mehrdad

1
파일을 읽는 엔터티가 여러 개인 경우 종속성이있을 수 있습니다. 다른 하나가 중단 된 곳 (또는 이전에 읽은 데이터로 읽은 데이터에 의해 정의 된 위치)에서 시작해야 할 수도 있습니다. 또한 독자들은 다른 스레드에서 실행되어 웜의 다른 조정 캔을 열 수 있습니다. 전달 된 파일 개체는 관련된 모든 문제 (및 이점)와 함께 전역 상태로 노출됩니다.
Jonathan Eunice

1
중요한 파일 경로를 전달하지 않습니다. 하나의 함수 (또는 클래스, 메소드 또는 다른 제어 위치)가 "파일의 전체 처리"를 담당합니다. 파일 액세스가 어딘가에 캡슐화되어 있으면 열린 파일 핸들과 같은 변경 가능한 전역 상태를 전달할 필요가 없습니다.
Jonathan Eunice

1
우리는 그때 동의하지 않을 수 있습니다. 나는 변하기 쉬운 글로벌 상태를 넘나 드는 디자인에 결정적인 단점이 있다고 말하고 있습니다. 몇 가지 장점도 있습니다. 따라서 "무역". 파일 경로를 전달하는 디자인은 종종 캡슐화 된 방식으로 한 번에 I / O를 수행합니다. 나는 그것을 유리한 커플 링으로 본다. YMMV.
Jonathan Eunice

1

이것은 소유권과 파일을 닫을 책임에 관한 것입니다. 당신은 그것을 소유하고 특정이 사람 분명하다만큼 당신이 있는지 확인으로, 다른 방법으로 어떤 점에 배치 닫아야합니다 스트림 또는 파일 핸들이든 꼬추 /에 전달할 수 있습니다 것입니다 당신이 완료되면 소유자에 의해 폐쇄 . 이것은 일반적으로 시공 적 구성 또는 일회용 패턴을 포함합니다.


-1

열린 파일을 전달하기로 선택하면 다음과 같은 작업을 수행 할 수 있지만 파일에 쓰는 함수에서 파일 이름에 액세스 할 수 없습니다.

파일 / 스트림 작업을 100 % 책임지는 클래스와 순진하고 해당 파일 / 스트림을 열거 나 닫을 것으로 예상되지 않는 다른 클래스 또는 함수를 원한다면이 작업을 수행합니다.

컨텍스트 관리자는 finally 절을 사용하는 것처럼 작동합니다. 따라서 라이터 기능에서 예외가 발생하면 파일은 무엇이든 닫힙니다.

import contextlib

class FileOpener:

    def __init__(self, path_to_file):
        self.path_to_file = path_to_file

    @contextlib.contextmanager
    def open_write(self):
        # ...
        # Here you can add code to create the directory that will accept the file.
        # ...
        # And you can add code that will check that the file does not exist 
        # already and maybe raise FileExistsError
        # ...
        try:            
            with open(self.path_to_file, "w") as file:
                print(f"open_write: has opened the file with id:{id(file)}")            
                yield file                
        except IOError:
            raise
        finally:
            # The try/catch/finally is not mandatory (except if you want to manage Exceptions in an other way, as file objects have predefined cleanup actions 
            # and when used with a 'with' ie. a context manager (not the decorator in this example) 
            # are closed even if an error occurs. Finally here is just used to demonstrate that the 
            # file was really closed.
            print(f"open_write: has closed the file with id:{id(file)} - {file.closed}")        


def writer(file_open, data, raise_exc):
    with file_open() as file:
        print("writer: started writing data.")
        file.write(data)
        if raise_exc:
            raise IOError("I am a broken data cable in your server!")
        print("writer: wrote data.")
    print("writer: finished.")

if __name__ == "__main__":
    fo = FileOpener('./my_test_file.txt')    
    data = "Hello!"  
    raise_exc = False  # change me to True and see that the file is closed even if an Exception is raised.
    writer(fo.open_write, data, raise_exc)

이것을 사용하는 것보다 어떻게 더 좋거나 다른 with open가요? 이것은 파일 이름과 파일 같은 객체를 사용하는 문제를 어떻게 해결합니까?
Dannnno

파일 / 스트림 열기 / 닫기 동작을 숨기는 방법을 보여줍니다. 주석에서 명확하게 볼 수 있듯이 "작성기"에 투명한 스트림 / 파일을 열기 전에 논리를 추가하는 방법을 제공합니다. "writer"는 다른 패키지 클래스의 메소드 일 수 있습니다. 본질적으로 그것은 열린 포장지입니다. 답장과 투표에 감사드립니다.
Vls

그 행동은 이미 처리되고 with open있습니다. 그리고 당신이 효과적으로 옹호하는 것은 파일과 같은 객체만을 사용하고 어디서 왔는지 신경 쓰지 않는 기능입니까?
Dannnno
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.