[Python] with 문과 file에 대한 고찰 Develop Tip


파이썬의 with 문은 여러가지 경우에 있어 잘 사용하고 있습니다.

with <class new and __enter__> as alias:
    alias.method(...)

가장 많이 사용하는 예가 open built-in 함수 입니다.


with open(file, 'w') as ofp:
    ofp.write('...')

그런데 file 개체 말고 자신의 개체를 덮어 씌워 보겠습니다.

##########################################################################################
class script_open(object):
#=====================================================================================
SCRIPT_PATH = '/tmp/script'
#=====================================================================================
def __init__(self, path, type='shell', chmod=0700):
self.path = path
self.type = type
self.chmod = chmod
self.file = None
self._fp = None
self._open()
#=====================================================================================
def _open(self):
if not os.path.exists(self.SCRIPT_PATH):
os.makedirs(self.SCRIPT_PATH)
self.file = '%s/%s' % (self.SCRIPT_PATH, self.path)
self._fp = open(self.file, 'w')
if self.type == 'shell':
self._fp.write('#!/bin/sh\n\n')
os.chmod(self.file, self.chmod)
return self._fp
#=====================================================================================
def _close(self):
if self._fp is not None:
self._fp.close()
self._fp = None
#=====================================================================================
def __del__(self):
self._close()
#=====================================================================================
def __enter__(self):
return self._fp
#=====================================================================================
def __exit__(self, type, value, traceback):
self._close()


명심할 것은 with 와 as로 alias에 넘겨지는 것은 __enter__ 에서 return 되는 것입니다.
위와 같이 한 상태에서 


##########################################################################################
def write_test(write_func):
write_func('# second test from write_test\n')

##########################################################################################
def test():
with script_open('foo.sh') as ofp:
ofp.write('# my script test\n')
command = ofp.write
write_test(command)
print('[in with block] ofp is closed? %s' % ofp.closed)
print('[after with block] ofp is closed? %s' % ofp.closed)
return ofp

##########################################################################################
if __name__ == '__main__':
ofp = test()
print('[top] ofp is closed? %s' % ofp.closed)


위의 코드를 돌려보면 

[in with block] ofp is closed? False
[after with block] ofp is closed? True
[top] ofp is closed? True

와 같이 원하는 결과가 나옵니다.

with 블럭의 ofp는 script_open 객체에서 __enter__ 된 결과의 _fp file 개체가 리턴되고
이후 with 블럭을 나가면서 __exit__ 가 한번 호출되어 _close 메소드가 호출되며
객체가 해지되는 __del__이 호출되면서 한번 더 _close 가 호출됩니다.

결과적으로 파일이 잘 닫히는데요...

만약 위에 script_open 이 클래스 대신 일반 file 개체를 리턴하는 함수라면 어떤 결과가 나올까요?


우선 앞선 코드 대신

##########################################################################################
def script_open(path, _type='shell', chmod=0700):
SCRIPT_PATH = '/tmp/script'
s_file = '%s/%s' % (SCRIPT_PATH, path)
ofp = open(s_file,'w')
if _type == 'shell':
ofp.write('#!/bin/sh\n\n')
os.system('chmod %d %s' % (chmod, s_file))
return ofp

##########################################################################################
def write_test(write_func):
write_func('# second test from write_test\n')

##########################################################################################
def test():
with script_open('foo.sh') as ofp:
ofp.write('# my script test\n')
command = ofp.write
write_test(command)
print('[in with block] ofp is closed? %s' % ofp.closed)
print('[after with block] ofp is closed? %s' % ofp.closed)
return ofp

##########################################################################################
if __name__ == '__main__':
ofp = test()
print('[top] ofp is closed? %s' % ofp.closed)


와 같이 테스트를 진행했습니다.

결과는...


[in with block] ofp is closed? False
[after with block] ofp is closed? True
[top] ofp is closed? True

동일하게 파일이 잘 닫혔습니다.
도대체 무슨일이 벌어진 것일까요?

우선 앞선 script_open class인 경우의 with block 에서는

with script_open(file) as ofp:
# with 문장의 시작부
  # 1) script_open.__init__(self, file) 호출됨
  # 2) script_open.__enter__() 의 결과인 self._fp 가 리턴
  # 3) ofp 변수가 2의 결과를 받음
    ofp.write('...')
# with 문장이 종료될 때
  # 1) script_open 인스턴스의 __exit__() 호출 
  # 2) self._close()에 의해 self._fp.close 됨
  # 3) 객체 해지에 의해 __del__() 호출되면 한번 더 self._close() 호출 
  #     (이전에 _fp를 None 시켰으므로 두번째는 다시 close 안함)

위와 같은 방법이 가장 일반적인 방법인데..
어떻게 일반 file 개체를 리턴하는 함수를 with 문에 넣어도 동일한 결과 
(자동으로 파일이 close 되는)가 나올까요?

with script_open(file) as ofp:
# with 문장의 시작부
  # 1) script_open 함수 호출됨
  # 2) 1 결과인 파일 개체 리턴
  # 3) ofp 변수가 2의 결과를 받음
    ofp.write('...')
# with 문장이 종료될 때
  # 1) file 개체인 ofp의 __exit__() 호출 
  # 2) 1에 의해 파일 개체 close 됨

위와 같은 시나리오가 내부에서 벌어진다고 할 수 있겠군요.

따라서 결론은 
"file 개체를 return 하는 함수와 함께 with 문을 사용해도 안전하게 file이 close 된다."
가 오늘의 결론입니다.


어느 분께는 도움이 되셨기를... 

덧글

댓글 입력 영역

구글애드텍스트