CodeOnWeb
로그인

챕터 7. 클래스와 반복자(Classes & Iterators)

클래스와 반복자에 대해서 공부해 봅니다.

Park Jonghyun 2015/09/18, 15:27

내용

파이썬 3에 뛰어들기 (22)

챕터 -1. “파이썬 3로 뛰어들기”에서 달라진 점 챕터 0. 파이썬 설치하기 챕터 1. 첫 파이썬 프로그램 챕터 2. 고유 자료형 챕터 3. 컴프리헨션(Comprehensions) 챕터 4. 문자열(Strings) 챕터 5. 정규표현식(Regular Expressions) 챕터 6. 클로저와 제너레이터(Closures & Generators) 챕터 7. 클래스와 반복자(Classes & Iterators) 챕터 8. 고급 반복자(Advanced Iterators) 챕터 9. 단위 테스트(Unit Testing) 챕터 10. 리팩토링(Refactoring) 챕터 11. 파일 챕터 12. XML 챕터 13. 파이썬 객체 직렬화 챕터 14. HTTP 웹서비스 챕터 15. 사례 연구: chardet을 파이썬 3로 이식하기 챕터 16. 파이썬 라이브러리 패키징하기 부록 A. 2to3를 이용해서 코드를 파이썬 3로 이식하기 부록 B. 특수 함수 이름 부록 C. 이제 어디로 가야 할까요? 부록 D. 문제 해결

"동쪽은 동쪽이고 서쪽은 서쪽이다, 그 둘이 섞이는 일은 없을 것이다."
— 러디어드 키플링(Rudyard Kipling)

뛰어들기

반복자(iterator)는 파이썬 3의 "약방의 감초" 같은 녀석입니다. 눈에 잘 보이지는 않지만 속을 잘 파보면 어디에나 존재합니다. 컴프리헨션은 반복자의 단순한 형태입니다. 제너레이터도 반복자의 한 종류입니다. yield 문으로 값을 반환하는 함수를 이용하면, 반복자를 처음부터 다 작성하지 않더라도 간단하게 반복자를 만들 수 있습니다. 무슨 말인지 한 번 볼까요.

피보나치 수열 기억나시죠? 반복자를 이용해서 한 번 만들어 봅시다.

한 줄씩 살펴봅시다.

클래스? 클래스가 뭐죠?

클래스 정의하기

파이썬은 완벽하게 객체지향적(object-oriented)입니다. 여러분만의 클래스를 정의할 수도 있고, 이미 있는 클래스를 상속할 수도 있으며, 클래스의 개체(instance)를 만들 수도 있습니다.

파이썬에서 클래스를 만들기는 쉽습니다. 함수와 마찬가지로 별도의 구현부(interface)를 정의할 필요 없이, 그냥 클래스를 정의하고 써나가면 됩니다. 파이썬 클래스는 class라는 미리 정의된 키워드로 시작을 알리고 곧바로 클래스의 이름이 나옵니다. 기술적으로만 이야기 하자면 그게 필요한 전부입니다. 다른 클래스로부터 상속받지 않아도 클래스는 클래스입니다.

  1. 이 클래스의 이름은 PapaysWhip이고, 다른 클래스를 상속하지 않습니다. 클래스 이름은 EachWordLikeThis와 같이 대문자화 해서 표현하는데 것이 관례인데, 반드시 이를 따를 필요는 없습니다.
  2. 아마 예상하셨을 수도 있겠지만, 클래스에 포함되는 모든 내용은 들여써야 합니다. 함수나 if, for 문, 또는 다른 모든 코드 블록과 마찬가지입니다. 들여쓰기가 되어 있지 않은 줄을 만나면 클래스가 끝났다고 인식합니다.

문법적인 이유로 코드 블록에는 뭐라도 하나 있어야 합니다. 이 PapayaWhip 클래스는 어떤 메소드나 속성도 가지고 있지 않지만, 비워둘 수는 없으므로 pass 라는 키워드를 썼습니다. 이 파이썬 예약어는 "아무 것도 없으니 그냥 지나가세요"라는 의미입니다. 정말 아무 것도 하지 않으므로, 함수나 클래스를 일단 정의만 해둘 때 유용하게 쓸 수 있습니다.

파이썬의 pass 문은 자바나 C로 따지자면 빈 중괄호({})와 같습니다.

많은 클래스가 다른 클래스로를 상속하지만 여기 이 클래스는 아닙니다. 많은 클래스가 메소드를 가지고 있지만, 이 클래스는 그렇지도 않습니다. 파이썬에서는 클래스가 이름 이외에 반드시 가져야할 것은 없습니다. 특히 C++ 프로그래머라면 파이썬의 클래스가 명시적인 생성자(constructor)와 소멸자(destructor)를 가지지 않는다는 점을 의아하게 생각할 수도 있습니다. 필수는 아니지만 파이썬 클래스도 생성자와 비슷한 것을 가지고 있습니다. __init__() 메소드 말이지요.

__init__() 메소드

이 예제는 Fib __init__ 메소드를 이용해서 Fib 클래스를 초기화(initialisation)하는 모습을 보여주고 있습니다.

  1. 클래스는 모듈이나 함수처럼 docstring을 가질 수 있습니다. 말이 나왔으니 하는 이야기지만, docstring은 반드시 쓰는 습관을 들이세요.
  2. __init__() 메소드는 클래스의 개체(instance)가 만들어지는 즉시 호출됩니다. 입이 근질근질 하겠지만 이 메소드를 "생성자"라고 불러서는 안 됩니다. 엄청 근질근질 하실거에요. 생긴 것도 C++의 생성자와 비슷하고(관례적으로 __init__() 메소드를 클래스의 첫 메소드로 정의하는 것도 그렇고), 하는 일도 비슷하고(새롭게 생성된 개체가 가장 먼저 실행하는 코드이지요), 심지어 이름도 비슷하네요. 하지만 생성자라고 불러서는 안 됩니다. __init__() 메소드가 호출되기 전에 이미 객체가 생성되어 있기 때문입니다. 이 클래스의 새로운 개체를 가리키는 유효한 참조(reference)도 이미 존재합니다.

__init__()을 포함한 모든 클래스 메소드의 첫 번째 인자는 항상 클래스 개체 자신을 가리키는 참조입니다. 관습적으로 이 인자의 이름은 self라고 짓습니다. C++이나 자바의 this 키워드와 같은 역할을 하지만, 파이썬의 self는 이름을 그렇게 붙였을 뿐 예약어가 아닙니다. 그렇다고 self 대신 다른 이름을 붙이는 일은 제발 하지 마세요. 이것은 꽤나 강한 관습이거든요.

모든 클래스 메소드에서 self는 그 메소드를 호출한 개체를 가리킵니다. __init()__ 메소드에서라면 그 개체는 새롭게 생성된 객체가 되겠지요. 메소드를 정의할 때 첫 번째 인자로 self를 반드시 적어줘야 하지만, 그 메소드를 호출할 때는 생략합니다. 파이썬이 자동으로 붙여 주거든요.

클래스 개체(instance) 생성하기

파이썬에스 클래스를 개체화 하는 것은 단순합니다. 그냥 클래스를 함수 마냥 호출하면서 __init__() 메소드가 요구하는 인자를 넘겨주면 됩니다. 그러면 새롭게 생성된 객체가 반환될겁니다.

  1. Fib 클래스의 개체를 생성해서 fib 변수에 할당하고 있습니다. 인자로 100 하나를 넘겨주는데, 이 값은 Fib의 __init__() 메소드의 max로 들어갑니다.
  2. fib는 이제 Fib 클래스의 개체입니다.
  3. 모든 클래스 개체는 __class__라는 속성을 가지는데, 이는 객체의 클래스를 의미합니다. 자바 프로그래머라면 Class 클래스에 익숙할 겁니다. 객체의 메타정보를 얻을 수 있는 getName()이나 getSuperclass() 같은 메소드를 가지고 있지요. 파이썬에서는 이런 메타 정보가 __class__라는 속성에 담겨 있다는 점만 빼면 기본 아이디어는 같습니다.
  4. 함수나 모듈에서와 마찬가지 방식으로 개체의 docstring를 불러올 수 있습니다. 클래스의 모든 개체는 같은 docstring을 가집니다.

파이썬에서 클래스의 새 개체를 만들려면 함수를 호출하는 것처럼 클래스를 호출하세요. C++이나 자바의 new 연산자 같은 키워드는 따로 없습니다.

개체 변수(Instance Variables)

다음 줄로 넘어갑시다.

  1. self.max는 뭘까요? 개체 변수(instance variable)입니다. __init__() 메소드의 인자로 넘어온 max와는 아무 상관이 없습니다. self.max는 그 개체에서 "global" 변수처럼 사용할 수 있습니다. 즉, 그 개체의 어느 메소드에서든 접근할 수 있다는 말이지요.

  1. self.max가 __init__() 메소드에서 선언되었습니다...
  2. ... 그리고 __next__() 메소드에서 참조했네요.

개체 변수는 클래스의 각 개체마다 독립적으로 존재합니다. 예를 들어, Fib 개체 두 개를 생성할 때 인자로 서로 다른 값을 넘겨주면, 두 개체의 max 값은 서로 다르다는 것입니다.

피보나치 반복자

이제 여러분은 반복자를 만들 준비가 되어 있습니다. 반복자는 단지 __iter__() 메소드를 정의하고 있는 클래스입니다.

이상하게 생각하신 분이 계실지도 모르겠는데, Fib 클래스의 모든 메소드(__init__, __iter__, __next__)는 두 개의 밑 줄(_)로 시작하고 끝납니다. 왜 그럴까요. 거창한 이유는 없고요, 그냥 이 메소드들이 "특별한 메소드"라는 것을 나타내기 위해 관습적으로 붙인 겁니다. 여기서 "특별하다"는 것은 일반적으로 그 메소드가 사용자에 의해 직접 호출되지 않고, 클래스나 개체의 다른 문법을 통해 파이썬이 간접적으로 호출한다는 의미입니다. 자세한 사항은 "특수 메소드" 편을 참고하세요.

  1. 반복자를 기초부터 직접 구현하기 위해서는 함수가 아니라 클래스가 필요합니다.
  2. Fib(max)를 호출하면 실제로는 이 클래스의 개체를 만들고 그 개체의 __init__() 메소드를 max와 함께 호출하게 됩니다. __init__() 메소드는 넘겨 받은 최대값(max)을 개체 변수인 self.max에 저장해서 후에 다른 메소드에서 쓸 수 있게 만듭니다.
  3. __iter__() 메소드는 누군가가 iter(fib)를 호출할 때마다 호출됩니다. (곧 보게 되겠지만, for 문은 알아서 iter()를 호출합니다. 물론 여러분이 직접 해줄 수도 있습니다.) 반복을 시작하기 위한 초기 작업(여기서는 self.a와 self.b를 초기화 합니다)을 수행한 후, __iter__() 메소드는 __next__() 메소드를 가지고 있는 어떤 객체를 반환하게 됩니다. 이 예제에서는(사실 대부분의 경우에) 그냥 self를 반환합니다. 이 클래스 자체가 __next__()를 가지고 있기 때문입니다.
  4. __next__() 메소드는 누군가가 반복자 개체의 next()를 호출할 때 호출됩니다. 뒤에 좀 더 이야기 하지요.
  5. __next__() 메소드에서 StopIteration 예외를 발생되면 호출한 쪽에 반복이 끝났다는 신호가 갑니다. 대부분의 다른 예외와는 달리 이 예외는 오류가 아닙니다. 아주 정상적인 상황이고 단지 반복자가 더 이상 값을 생성할 수 없다는 것을 의미합니다. 반복자를 호출한 것이 for 문이라면, StopIteration 예외가 발생했을 때 자동으로 처리되어 for 문이 종료됩니다. (다시 말해, 예외가 발생했다는 흔적이 남지 않습니다.) for 문에서 반복자를 사용했을 때 일어나는 마법 같은 일의 핵심이지요.
  6. 반복자의 __next__() 메소드는 값을 정상적으로 반환하기만 하면 됩니다. 여기서 yield를 쓰지는 마세요. yield는 제너레이터에서 쓸 수 있는 조미료입니다. 지금은 반복자를 처음부터 만들고 있는 중이지요. 그냥 return 문을 쓰세요.

좀 이해가 가시나요? 좋습니다. 이 반복자를 어떻게 호출하는지 한 번 봅시다.

아니, 완전히 똑같네요! 피보나치 제너레이터를 쓰는 것과 글자 하나까지 동일합니다(Fib의 대문자 F는 빼고요). 어떻게 이런 일이 가능할까요?

몇 앞서 잠깐 언급하기도 했지만, for 문은 좀 마법사 같은 면모가 있습니다. 무슨 일이 일어났는지 따져보겠습니다.

  • for 문은 보시다시피 Fib(1000)을 호출합니다. 이 명령으로 Fib 클래스의 개체 하나가 반환 될겁니다. 이 개체를 fib_inst라고 부르겠습니다.
  • 보이지는 않지만, for 문은 영리하게도 iter(fib_ist)를 알아서 호출합니다. 그러면 반복자 객체가 반환될텐데, 이 것은 fib_iter라고 부릅시다. 이 경우 __iter__() 메소드가 self를 반환하고 있으므로 fb_iter == fib_inst 입니다. 사실 for 문은 뭐를 반환하든지 별로 신경쓰지 않습니다만.
  • 반복자를 "돌기" 위해, for 문은 next(fib_iter)를 호출하고, 이는 앞서 언급했듯이 fib_iter 객체의 __next__() 메소드를 호출합니다. __next__()는 다음 피보나치 수를 계산해서 반환하게 되지요. for 문은 이 값을 받아 n에 할당하고 코드 블록 본체를 실행합니다.
  • for 문은 끝내야 할 때를 어떻게 알까요? 질문해주셔서 감사합니다! next(fib_iter)가 StopIteration 예외를 발생시키면 for 문은 그 예외를 흡수하고 자연스럽게 종료됩니다. (StopIteration을 제외한 다른 예외는 for 문 내에서도 흡수되지 않고 평소처럼 발생됩니다.) StopIteration 예외를 어디서 봤죠? 물론 __next__() 함수에서 봤죠!

복수 규칙 반복자

이제 피날레를 향해 갑니다. 앞서 작성했던 복수 규칙 제너레이터를 반복자로 바꾸어 봅시다.

이 클래스는 __iter__()와 __next__()를 구현하고 있으므로 반복자로 쓰일 수 있습니다. 클래스를 개체화해서 규칙에 할당합니다. 이 일은 import 할 때 딱 한 번 일어납니다.

이 클래스를 하나씩 살펴봅시다.

  1. LazyRules 클래스를 개체화하면 규칙이 담긴 파일을 열어서 pattern_file에 할당하는데, 아직 아무 것도 읽어오지는 않습니다. (파일 내용은 이후에 읽습니다.)
  2. 규칙 파일을 연 후 cache라는 변수를 초기화 합니다. 이 cache는 이후 (__next__() 메소드에서) pattern 파일을 읽을 때 사용됩니다.

계속 진행하기 전에 rules_filename에 대해 좀 보겠습니다. 이 변수는 __iter__() 메소드 내에서 초기화 되지 않았습니다. 사실 메소드가 아니라 클래스 수준에서 초기화가 되어 있습니다. 이런 변수는 클래수 변수(class variable)혹은 클래스 속성이라고 하는데, 개체 변수와 같은 빙식으로 접근할 수 있지만(self.rules_filename) LazyRules 클래스의 모든 개체에 공유된다는 점이 다릅니다.

  1. 클래스의 모든 개체는 클래스에 정의된 rules_filename 속성을 상속 받습니다. 이 속성의 값은 클래스에 정의된 값으로 초기화 됩니다.
  2. 한 개체에서 이 속성의 값을 바꾼다고 해서 다른 개체에 있는 속성에 영향을 주지는 않습니다.
  3. 클래스 속성에도 영향을 미치지 않습니다. 클래스 속성에 는 __class__ 라는 특별한 속성을 통해 접근할 수 있습니다. (개별 개체의 속성에 접근하는 것과는 좀 다르지요.)
  4. 만약 클래스 속성의 값에 변화를 주면, 그 값을 여전히 상속하고 있는 모든 개체(여기서는 r1)의 속성도 따라 변하게 됩니다.
  5. 물론 r2 개체와 같이 해당 속성을 덮어써버린(overridden) 개체에는 영향을 주지 않습니다.

이제 계속 진행하지요.

  1. __iter__() 메소드는 iter(rules)가 호출될 때마다(for 문에서는 자동으로 호출됩니다) 호출됩니다.
  2. 모든 __iter__() 메소드가 반드시 수행해야만 하는 단 한 가지 일은 반복자를 반환하는 것입니다. 이 경우에는 self를 반환하는군요. self가 가지고 있는 __next__() 메소드는 반복되는 동안 값을 연산하고 반환하는 일을 수행합니다.

  1. __next__() 메소드는 (for 문 등에서) next(rules)를 호출할 때마다 호출됩니다. 이 메소드는 끝에서부터 거꾸로 읽어나가는 편이 이해하기 좋습니다.
  2. 적어도 이 함수의 마지막 부분은 좀 익숙하게 다가옵니다. build_match_and_apply_functions() 함수는 변하지 않고 언제나 같은 모습을 가지고 있습니다.
  3. 유일한 차이점은 매칭/적용 함수(funcs이라는 튜플에 저장되어 있군요)를 반환하기 전에 self.cache에 따로 저장을 해둔다는 것입니다. 이번에는 한 단계 위로 가보지요.

  1. 파일을 다루는 방식이 조금 더 세련되어 졌습니다. readline() 메소드는(마지막에 s가 붙은 readlines()와는 다릅니다) 열린 파일에서 차례대로 정확하게 한 줄씩만 읽어옵니다. (눈치 빠르신 분은 감 잡으셨겠지만, 파일 객체도 반복자입니다! 깊숙히 파보면 반복자 아닌 것이 별로 없을 정도군요...)
    2.readline()이 읽어들인 내용이 있으면 line은 빈 문자열이 될 수 없습니다. 설사 파일이 빈 줄을 포함하고 있더라도 그 줄은 '\n'(carriage return)이라는 문자 하나로 이루어진 문자열이 됩니다. line이 정말 아무 것도 없는 빈 문자열이라면, 파일에서 더 이상 읽어들일 수 있는 줄이 없다는 의미입니다.
  2. 파일의 마지막에 다다르면 파일을 닫고 마법같은 StopIteration 예외를 발생시킵니다. 기억하세요. __next__() 메소드에 왔다는 것은 우리가 다음 규칙을 필요로 한다는 것 말입니다. 다음 규칙은 파일의 다음 줄에 담겨 있는데 다음 줄이 없네요! 그러므로 무언가를 반환할 필요가 없습니다. 반복은 끝났습니다. (♫ 파티가 끝났습니다... ♫)

한 단계 더 위로 가서 이제 __next__() 메소드의 첫 부분으로 가봅시다.

  1. self.cache는 모든 매칭/적용 함수를 튜플 형태로 담고 있는 리스트입니다. (이것도 좀 익숙하지요?) self.cache_index는 cache 리스트 내에서 다음에 반환되어야 할 함수들이 위치한 인덱스 정보를 가지고 있습니다. 아직 사용할 수 있는 cache 아이템이 남아 있다면(즉, self.cache의 길이가 self.cache_index보다 크다면), 그 아이템(겨별 매칭/적용 함수의 튜플)을 반환압니다. 만세! 게다가 파일에서 규칙을 읽어서 함수를 만드는 과정을 처음부터 다시 반복하지 않고, 그냥 cache의 항목 하나를 반환해주기만 하면 됩니다.
  2. 반대로 cache에 남은 항목이 없고 파일이 닫혀진 상태라면(이 메소드의 아랫 부분에서 파일이 닫힐 수 있다는 것은 이전 예제 코드에서 봤지요?), 더이상 우리가 할 수 있는 일은 없습니다. 파일이 닫혀 있다면 할 수 있는 모든 걸 다 해본 것이지요. 규칙 파일을 한 줄 한 줄씩 읽어서 함수를 만들어 cache에 저장해둔 뒤, 그 모두를 매칭하고 적용해본 것이지요. 파일이 닫혔다면 cache도 다 써버렸습니다. 그리고 저도 지쳤네요. 잠깐. 그래도 할 건 해야죠. 조금만 더 기다리세요. 거의 다 왔어요.

이 모든 것을 한 곳에 모아 정리 한 번 해봅시다.

  • 먼저 rule이라고 불리는 LazyRule 클래스의 개체 하나를 생성합니다. __init__() 메소드가 바로 호출되어 규칙이 담긴 파일을 열지만 내용을 읽지는 않습니다.
  • 첫 번째 매칭/적용 함수쌍을 요청하면 cahce를 확인해보는데 지금은 비어있다는 것을 알게 됩니다. 그러면 규칙 파일에서 줄 하나를 읽어 매칭/적용 함수를 생성한 후 cache에 저장합니다.
  • 예를 들어 첫 번째 규칙이 매칭되었다고 가정해 봅시다. 그러면 다른 매칭/적용 함수를 생성할 필요가 없고 규칙 파일을 더 이상 읽을 필요도 없습니다.
  • 이제 다른 단어를 복수화 하기 위해 plural() 함수를 다시 호출했다고 생각해볼까요. plural() 함수에 있는 for 문은 알아서 iter(rules)를 호출하는데 cache_index는 초기화하지만 이미 열린 파일 객체는 그대로 둡니다.
  • for 문을 처음으로 돌 때 rules에 값을 요청하게 되고, 이는 바로 __next__() 메소드를 호출합니다. 하지만 이번에는 cache가 비어있지 않습니다. 첫 번째 규칙에 해당하는 매칭/적용 함수쌍 하나가 이미 있지요. 이전 단어를 복수화 할 때 생성되어서 cache에 추가해 둔 함수들 말입니다. cache_index가 하나 증가하지만 그 값(1)은 cache의 길이와 같으므로, 이미 저장된 첫 번째 매칭/적용 함수를 그냥 반환하고 끝납니다. 열려 있는 파일 객체는 건드리지 않고요.
  • 이번에는 첫 번째 매칭이 실패했다고 가정합시다. for 문은 다시 돌아서 rules에 값을 요청합니다. 이 요청은 __next__() 메소드를 두 번째로 호출합니다. 이번에는 cache가 모두 소진되군요. cache는는 단 하나의 항목만 가지고 있는데 우리는 두 번째 항목을 요청했기 때문이죠. 그래서 __next__() 메소드는 계속 진행하게 됩니다. 열린 파일에서 두 번째 줄을 읽어 그 규칙에 해당하는 매칭/적용 함수를 생성한 뒤 cache에 추가합니다.
  • 규칙을 읽고-생성하고-저장(cache)하는 이 과정은 규칙 파일에서 읽어들인 패턴이 복수화 하려는 단어와 매칭되지 않는 한 계속됩니다. 파일이 끝나기 전에 매칭되는 규칙을 찾으면 그것을 적용하고 멈춥니다. 파일은 여전히 열려 있는 상태이지요. 파일의 포인터는 마지막으로 읽은 곳에 멈춰서서 다음 readline() 명령을 계속 기다립니다. cache에는 아이템이 하나씩 더 추가되겠지요. 다른 단어를 복수화 하기 위해 처음부터 다시 시작하면, cache에 저장되어 있는 항목들로 먼저 매칭을 시도한 후에야 다른 규칙을 파일에서 읽어들일 것입니다.

복수화와 관련해서는 거의 열반의 경지에 들 지경이군요.

  1. 시작 비용을 최소화 했습니다. 가장 먼저 일어나는 일은 클래스 하나를 개체화하고 파일 하나를 여는 것 뿐입니다. (하지만 내용을 읽지는 않습니다.)
  2. 성능도 최고에요. 이전 챕터에서 작성했던 코드는 다른 단어를 복수화 할 때마다 파일을 열고 모든 매칭/적용 함수를 동적으로 생성했습니다. 이번 챕터에서 작성한 코드는 함수가 생성되면 바로 cache에 저장해버리지요. 가장 나쁜 경우라 하더라도 규칙 패일을 단 한 번만 읽습니다. 복수화 해야 할 단어가 아무리 많아도요.
  3. 코드와 데이터를 분리했습니다. 모든 규칙은 별도의 파일에 저장됩니다. 코드는 코드고 데이터는 데이터입니다. 그 둘이 섞이는 일은 없습니다.

그런데 이것으로 정말 열반에 들었을까요? 음. 뭐 그렇기도 하고 아니기도 합니다. LazyRules 예제에서 고려해봐야 할 점이 한 가지 있습니다. __init__() 메소드에서 열린 규칙 파일은 마지막 규칙에 도달할 때까지 열려 있습니다. 파이썬이 종료되거나 LazyRules 클래스의 마지막 개체가 더 이상 쓰이지 않게 되면 결국 이 파일은 닫히게 됩니다. 하지만 한참 뒤에 일어나는 일이지요. 이 클래스가 오랫동안 실행되는 파이썬 프로세스의 일부분이라면, LazyRules 객체는 굉장히 오랫동안 살아남을 수도 있습니다. 아무 곳에서도 쓰이지 않고 메모리만 갉아먹으면서요.

피해갈 수 있는 방법도 있습니다. 파일을 __init__()에서 연 뒤에 규칙을 읽는 동안 계속 열어두지 말고, 파일을 열자마자 모든 규칙을 읽고 바로 닫아버리면 될 겁니다. 아니면 파일을 열고 규칙 하나를 읽은 후, 파일 포인터가 가리키는 위치를 tell() 메소드에 저장하고, 파일을 닫고, 새 규칙을 읽어야 하면 파일을 다시 열고, seek() 메소드로 이전 파일 포인터의 위치를 받아와서 다음 규칙을 읽을 수도 있겠지요. 그것도 아니라면 굳이 골치아프게 신경쓰지 말고 파일을 계속 열어둬도 별 문제 없을지도 모릅니다. 이 예제에서 한 것 처럼요. 프로그래밍은 디자인이고, 디자인은 기회비용과 제약의 문제입니다. 파일을 연 채로 오래 두는 것은 문제가 될 수 있습니다. 코드를 너무 복잡하게 하는 것도 마찬가지입니다. 어떤 문제가 더 큰 문제인지는 여러분의 개발 팀이나 애플리케이션, 혹은 실행 환경에 따라 다르겠지요.

더 읽을거리

Iterator types
PEP 234: Iterators
PEP 255: Simple Generators
Generator Tricks for Systems Programmers


이 강의는 영어로 된 원문을 기초로 작성되었으며, Creative Commons Attribution Share-Alike 라이센스하에 자유로운 변경, 배포가 가능합니다

8761 읽음
이전 챕터 6. 클로저와 제너레이터(Closures & Generators)
다음 챕터 8. 고급 반복자(Advanced Iterators)

저자

토론이 없습니다

Please log in to leave a comment

16.5.11618.20190612.allo
문제 보고 · 사용조건 · 개인정보보호
래블업 주식회사 · 대한민국 서울 테헤란로 145 · 대표: 신정규 · 사업자번호: 864-88-00080 · +82 70-8200-2587

거절 확인

닫기
좋아요 책갈피 토론

아래 주소를 복사하세요