[8장] 공식 문서로 알아보는 Python Class의 모든 것

Jzahnny

[8장] 공식 문서로 알아보는 Python Class의 모든 것

Use Original Cover Image
Type
Post
Children
Language
ko
Tags
Python
Class
Scope
Namespace
Attribute
Method
Instance
Override
Iterator
Generator
Authors
Jzahnny
Published

개요

본 글은 Python v3.13 공식 Tutorial의 9. Classes 의 모든 내용을 보다 이해하기 쉽게 정리한 글이다.

1. Class의 기본 개념

  • Class는 data들과 함수들을 한 번에 묶을 수 있게 한다.
  • 새로운 class를 만든다는 것은 새로운 type의 object를 만든다는 것이다.
  • 그 class로부터 만들어진 object를 instance라고 한다. 쉽게 비유하자면 instance라는 붕어빵을 만드는 붕어빵 틀이 class이다.
 

2. Python Class의 특징

  1. Python class는 C++과 Modula-3를 섞은 메커니즘으로 최소한의 문법으로만 구성하려 했다.
  1. Python class는 객체지향 프로그래밍의 표준 특징을 모두 만족한다.
    1. 여러 개의 부모 class로부터 attribute와 method를 상속할 수 있다.
    2. Derived class (상속 받은 class) 는 base class (부모 class)의 어떤 method를 override할 수 있으며 동시에 base class의 method를 같은 name로 call할 수도 있다.
  1. C++ 용어로 설명하자면 Python class의 member (attribute와 method) 는 public이고 (예외도 있다.) 모든 member function은 virtual이다.
    1. Python에는 private, virtual과 같은 keyword가 존재하지 않는다.
    2. member가 public하다는 말은 어디서나 class의 attribute, method를 class 외부에서 접근할 수 있다는 것이다.
    3. member fucntion이 virtual하다는 말은 derived class는 base class의 어떤 method도 재정의할 수 있다는 뜻이다.
  1. Class 그 자체도 object이기 때문에 import와 renaming이 가능하다.
  1. C++, Modula-3와 다르게 type (int, string 등) 들도 base class로써 상속받을 수 있다.
  1. C++와 마찬가지로, built-in operator (+, -, /, % 등) 도 class instance로 재정의 할 수 있다.
 

3. Scopes and Namespaces

해당 내용은 조금 헷갈리고 복잡할 수 있지만 Python class가 어떻게 동작하는지 뼈대를 알 수 부분이고 이 부분을 넘기면 뒷 부분의 이해가 어렵기 때문에 매우 중요하다.

3.1. Names and Objects

같은 Object에 서로 다른 여러개의 name이 묶일 수 있다. 다른 언어에서 aliasing이라고 불리는 개념이다. Python에서는 보통 한 번에 알아차리기 어렵고 immutable basic types (numbers, strings, tuples) 를 다룬다면 안전하게 무시할 수 있다. 하지만 mutable objects (lists, dictionaries 등) 을 다룬다면 aliasing이 엄청난 결과를 가지고 온다. aliases는 어느 정도 pointer처럼 동작하기 때문에 유용하게 쓸 수 있는데, 예를 들어 pointer만 전달함으로써 object의 전달을 효율적으로 처리할 수 있고 function이 object를 수정한다면 굳이 2개의 다른 argument가 필요하지 않게 된다.

3.2. Namespace

namespace는 name들을 object들에 연결한 매핑이다. 대부분의 namespace는 Python dictionary로 만들어졌다.
namespace의 예시는 아래와 같다.
  • built-in names: abs()와 같은 기본 제공 함수 등
  • module의 global names
  • function의 local names
또한 object의 attributes도 namespace를 만든다.
한 가지 중요한 것은 서로 다른 namespaces에서 names는 아무런 관련이 없다는 것이다. 예를 들어 서로 다른 module이 maximize 라는 function을 만들었다고 해도 구분이 된다는 것이다.
 
Namespaces마다 lifetime이 아래와 같이 서로 다르다.
  • built-in names를 담은 namespace: Python interpreter가 시작할 때 만들어지고 절대 삭제되지 않는다.
  • module의 global namespace: module 정의를 읽을 때 (import moddule 과 같은 상황) 만들어지고 interpreter가 종료될 때까지 남는다.
  • function의 local namespace: function을 call할 때 만들어지고 값을 return하거나 exception을 raise하면 삭제한다. 물론 재귀 호출시 각자 local namespace를 가진다.
 

3.3. Attribute

. 다음에 오는 아무 name을 attribute라고 한다. 예를 들어 z.real 에서 realz라는 object의 attribute이다. module의 name을 참조할 때도 attribute를 참조하는 것이다. modname.funcname 에서 modnme이 module object이고 funcname이 attribute인 것이다. 그래서 module에 정의된 global name - module attribute가 서로 직접 매핑된다.
 
Attributes는 read-only 또는 writable하다. writable하면 attribute에 무언가를 할당하는 것이 가능하다. Module attributes는 writable하기 때문에 modname.the_answer = 42 와 같이 쓸 수 있다. 또는 del modname.the_answer 과 같이 써서 modname object에서의 attribute를 삭제할 수도 있다.

3.4. Scope

Scope는 어떤 namespace가 directly accessible한 텍스트 영역이다. 이때 directly accessible하다는 것은 . 을 통해 참조하지 않고 바로 x 와 같이 unqualified 참조하는 것을 말한다.
scope는 정적으로 만들어지만 동적으로 사용된다. 코드 실행 중 언제나 3개나 4개의 nested scope가 존재한다.
  • inner scope: 가장 안 쪽 scope이며 가장 먼저 검색한다. local name를 가지고 있다.
  • 아무 enclosing functions의 scope: 가장 가까이 둘러싸는 scope부터 검색한다. 둘러싸는 아무 function의 scope이고 non-local과 non-global name을 가진다.
  • next-to-last scope: 현재 module의 global name을 가진다.
  • outermost scope: 가장 바깥 scope이고 가장 나중에 검색한다. bulit-in name을 가진다.
 
global 선언을 하면 변수의의 모든 참조와 할당을 next-to-last scope로 이동한다.
nonlocal 선언을 하면 변수를 innermost scope 밖에 존재하게 한다. nonlocal 선언을 하지 않으면 enclosing scope의 변수는 read-only다. 만일 write하려 한다면 새로운 local variable을 innermost scope에 만들고 outer variable은 바뀌지 않는다.
 
보통 local scope는 현재 함수의 local name을 참조한다. 함수 밖에서 local scope는 global scope와 같은 namespace를 참조한다. Class 정의는 local scope에 또다른 namespace를 만든다.
 
또 기억해야하는 중요한 사실은 scope는 textually하게 정해진다는 것이다. 함수가 어떻게 import된지와 관련 없이 module에서 정의된 함수의 global scope는 module의 namespace이다. 따라서 scope는 정적으로 만들어지는 것이다. 하지만 검색할 때는 run time 도중에 일어난다.
 
Python에서 한 가지 유별난 점은 만일 global이나 nonlocal 선언이 없다면 name의 할당은 항상 innermost scope로 간다는 것이다. 할당은 data를 복사하는 게 아니라 name을 object에 연결시키기만 한다. del 선언도 local scope에서 참조하여 연결을 끊는다. 사실 새로운 name을 불러오는 모든 동작은 local scope를 사용한다. 특히 import 선언과 함수 정의는 module이나 함수명을 local scope 안에서 묶는다.
 

3.5. 예시

아래 예시에서 do_local() 을 하더라도 do_local 내의 spam은 local scope에 있고 scope_test의 spam은 enclosing scope라 print문이 변하지 않은 모습이다. do_nonlocal()을 하면 do_nonlocal 내의 spam이 enclosing scope로 옮겨지기에 print 값이 바뀐다. do_global 이후에는 spam이 global scope로 옮겨져 함수 밖에서 읽어야 값이 바뀐 것을 볼 수 있다.
def scope_test(): def do_local(): spam = "local spam" def do_nonlocal(): nonlocal spam spam = "nonlocal spam" def do_global(): global spam spam = "global spam" spam = "test spam" do_local() print("After local assignment:", spam) do_nonlocal() print("After nonlocal assignment:", spam) do_global() print("After global assignment:", spam) scope_test() print("In global scope:", spam)
page icon
After local assignment: test spam After nonlocal assignment: nonlocal spam After global assignment: nonlocal spam In global scope: global spam
 

4. Class의 원리

4.1. Class 정의

class ClassName: <statement-1> . . . <statement-N>
함수 정의와 마찬가지로 class를 정의할 때는 사용하기 전에 정의해야한다. class 선언으로 정의할 수 있다. class 내부의 statement는 주로 함수 정의로 사용되지만 다른 선언도 올 수 있다. class 정의에 도달하면 새로운 namespace가 만들어지고 local scope로써 사용한다.
Class 정의가 끝나면 class object가 만들어진다.
 

4.2. Class Objects

Class objects는 attribute reference와 instantiation이라는 두가지 operation을 지원한다.

4.2.1. Attribute References

Attribute references는 Python에서 모든 attribute를 참조할 때 쓰는 문법인 obj.name을 통해 class의 attribute를 참조하는 것이다. class object가 만들졌을 때의 namespace에 있는 모든 attribute name이 참조 가능하다.
class MyClass: """A simple example class""" i = 12345 def f(self): return 'hello world'
MyClass.i 은 integer object를 return하고 MyClass.f function object를 return한다. 각 attribute는 할당이 가능하기에 값을 바꿀 수 있다. MyClass.__doc__ 도 attribute이고 "A simple example class” 를 return한다.

4.2.2. Instantiation

instantiation operation (class object를 call)하여 빈 object를 만든다. function을 call하는 것처럼 쓴다. class object는 그저 parameter가 없고 새 instance를 return하는 함수일 뿐이다.
x = MyClass()
Class는 __init__() 이라는 특별한 method를 통해 구체적인 초기 상태를 customize할 수 있다. 이때는 instantiation에서 argument를 __init__() 에 전달한다.
class Complex: def __init__(self, realpart, imagpart): self.r = realpart self.i = imagpart x = Complex(3.0, -4.5) x.r, x.i
page icon
(3.0, -4.5)
 

4.3. Instance Objects

Instance objects에서 가능한 operation은 attribute references뿐이다. Vaild attribue name에는 data attributes와 methods라는 2가지 종류가 있다.
 
data attributes는 C++의 “data members”에 대응된다. data attributes는 일반 local variables처럼 값을 할당할 때부터 존재하기 때문에 C++처럼 선언을 할 필요 없다. 예를 들어 x는 위 MyClass의 instance일 때 MyClass에는 counter attribute가 정의할 때 없었지만 아래처럼 따로 선언하지 않고도 할당이 가능하다.
x,counter = 1 while x.counter < 10: x.counter = x.counter * 2 print(x.counter) del x.counter
 
method는 어떤 object에 속하는 function으로 instance attribute reference이다.
Valid method name은 class마다 다르다. class의 모든 function object들은 그에 상응하는 instance의 method를 정의한다. 따라서 MyClass 예시에서 MyClass.f 는 funtion object이기 때문에 x.f는 vaild method이고 MyClass.i는 integer object이기 때문에 vaild method가 아니다. 한편 x.f는 method object이고 MyClass.f는 function object이기 때문에 같지 않다.
 

4.4. Method Objects

아래와 같이 method를 call할 수 있다. 그런데 MyClass의 f function은 self 라는 argument가 필요한데 어떻게 argument를 적지도 않았는데 아래 코드가 동작하는 걸까?
x.f()
xf = x.f while True: print(xf())
그 이유는 다음과 같다. method의 특별한 점인데, instance object가 function의 첫 번째 argument로 전달된다는 것이다. 즉, x.f()MyClass.f(x) 와 완벽히 동일하다. 일반적으로 n개의 arguments가 담긴 list로 method를 call한다는 것은 instance object를 n개 argument 전에 삽입한 list로 function을 call하는 것과 같다.
 
전반적으로 method는 다음과 같이 작동한다.
  1. instance의 non-data attribute가 참조되면 instance의 class를 찾는다.
  1. name이 function object인 vaild class attribute이라면, instance object와 function object의 참조 둘 다를 method object에 포장한다.
  1. method object가 argument list로 call되었다면 instance object와 argument list로부터 새로운 argument list를 만들어 function object를 call한다.
 

4.5. Class Variables and Instance Variables

일반적으로 class variable은 모든 instance의 attribute와 method가 공유하고, instance variable은 각 instance마다 unique하다.
class Dog: kind = 'canine' # class variable shared by all instances def __init__(self, name): self.name = name # instance variable unique to each instance >>> d = Dog('Fido') >>> e = Dog('Buddy') >>> d.kind # shared by all dogs 'canine' >>> e.kind # shared by all dogs 'canine' >>> d.name # unique to d 'Fido' >>> e.name # unique to e 'Buddy'
3.1. Names and Objects에서 이야기했듯이, mutable object에서 충분히 놀라운 결과를 일으킨다.
예를 들어 tricks list는 class variable로 사용하면 하나의 list가 모든 Dog instance에 공유되기 때문에 의도치 않은 결과를 만든다.
class Dog: tricks = [] # mistaken use of a class variable def __init__(self, name): self.name = name def add_trick(self, trick): self.tricks.append(trick) >>> d = Dog('Fido') >>> e = Dog('Buddy') >>> d.add_trick('roll over') >>> e.add_trick('play dead') >>> d.tricks # unexpectedly shared by all dogs ['roll over', 'play dead']
 
올바른 방법은 아래와 같이 instance variable을 써야한다.
class Dog: def __init__(self, name): self.name = name self.tricks = [] # creates a new empty list for each dog def add_trick(self, trick): self.tricks.append(trick) >>> d = Dog('Fido') >>> e = Dog('Buddy') >>> d.add_trick('roll over') >>> e.add_trick('play dead') >>> d.tricks ['roll over'] >>> e.tricks ['play dead']
 

5. 기타 유용한 정보

class Warehouse: purpose = 'storage' region = 'west' w1 = Warehouse() print(w1.purpose, w1.region) # -> storage west w2 = Warehouse() w2.region = 'east' print(w2.purpose, w2.region) # -> storage east
  1. Data attribute는 일반적인 user (client)에 의해 참조될 수도 있다. 다른 말로, class는 pure abstract data types를 실행할 수 없다. 사실, Python에는 data hiding을 강제하는 방법이 존재하지 않는다. 오직 관례에 의해서만 data hiding을 관리한다. 한편, C로 쓰여진 Python extension은 data hiding을 강제할 수 있다.
 
  1. Client는 method의 validity를 고려하지 않고 data attributes를 추가할 수 있기 때문에 주의가 필요하다. 그래서 naming 관례가 이 골칫거리를 아주 잘 해결해준다.
 
  1. Python에는 data attributes를 참조할 때의 shorthand (축약 문법) 가 없다. 항상 self.attr로 참조해야한다. 덕분에 local variables와 instance variables 간의 혼돈이 줄어든다.
 
  1. Method의 첫 argument는 자주 self로 불린다. 사실 Python에서 self 라는 name은 아무런 특별한 의미를 가지지 않는다. 하지만 이 관례를 따르지 않는다면 다른 Python 프로그래머들이 읽기 어려워 할 것이다. 그리고 class browse 프로그램이 이 관례에 기반하여 동작할 수 있기 때문에 self로 쓴다.
 
  1. function object를 class attribute로 둘 때 class 정의 영역 안에서 함수를 정의할 필요는 없다. 아래 예시처럼 local variable의 function object를 할당해도 된다.
    1. # Function defined outside the class def f1(self, x, y): return min(x, x+y) class C: f = f1 def g(self): return 'hello world' h = g
      위 예시에서 f, g, h는 모두 class C의 attributes이고 instance C의 methods이다. hg와 완전히 동일하다.
 
  1. Methods는 self argument의 method attributes를 통해 다른 methods를 call할 수도 있다.
    1. class Bag: def __init__(self): self.data = [] def add(self, x): self.data.append(x) def addtwice(self, x): self.add(x) self.add(x)
 
  1. Methods는 일반적인 function처럼 global names를 참조할 수 있다. method에 할당된 global scope는 그 method의 정의 부분을 담는 module이다. (class는 global scope로 절대 사용되지 않는다.) method 안에서 global data를 써야 하는 이유를 찾기 힘들긴 하지만 global scope를 쓰는 합당한 용도도 많이 있다. 예를 들어, global scope로 import된 functions와 modules를 methods 내에서 사용할 수 있다.
 
  1. 각 value도 object다. 그러므로 class를 가지고 있다. object.__class__에 담겨 있다.
 

6. Inheritance

6.1. 정의

Class가 inheritance를 지원하지 않는다면 class라고 부를 가치가 없을 것이다. derived class (자식 class)의 정의는 아래와 같이 생겼다.
class DerivedClassName(BaseClassName): <statement-1> . . . <statement-N>
BaseClassName 이라는 name은 반드시 derived class 정의를 포함하는 scope에서 접근 가능한 namespace에 있어야한다. base class (부모 class) name 자리에는 다른 arbitrary expression도 올 수 있다. 예를 들어 base class가 다른 module에 정의되었다면 아래와 같이 쓸 수 있다.
class DerivedClassName(modname.BaseClassName):
 
Derived class 정의의 실행은 base class와 똑같이 진행된다. class object가 만들어지면 base class가 불러와진다. 만일 요청 받은 attribute나 method가 derived class에 없다면 base class에서 검색한다. base class 자신이 또 다른 class의 derived이라면 이 규칙이 재귀적으로 적용된다.
 
Derived class의 instantiation에 딱히 특별한 점은 없다.
 

6.2. Method Override

Derived class는 base class의 methods를 override할 수 있다. 같은 object의 다른 method를 call할 때는 method한테 특별한 권한이 없기 때문에, base class의 method가 자신에게 정의된 다른 method를 call하면 derived class가 그 method를 override한다.
복잡하니까 아래 예시를 살펴보자. apple.introduce() 에서 apple instance에는 introduce method가 없기 때문에 base class인 Food 를 찾는다. 이때 get_taste 라는 다른 method를 call하고 있는데 이때의 self object는 apple이기 때문에 “sweet” 으로 override된다. (C++ 용어를 빌리자면 Python의 모든 methods는 virutal하다.)
class Food: def __init__(self, name): self.name = name def introduce(self): print(f"I am {self.get_taste()} {self.name}!") def get_taste(self): return "salty" class Apple(Food): def get_taste(self): return "sweet" pizza = Food("pizza") pizza.introduce() # -> I am salty pizza! apple = Apple("apple") apple.introduce() # -> I am sweet apple!
 
Derived class에서 override 하는 method는 사실 base class의 method를 replace하는 게 아니라 extend하고자 한다. base class의 method를 직접적으로 call하고 싶다면 BaseClassName.methodname(self, arguments) 과 같이 call할 수 있다. (다만 BaseClassName 가 global scope에 있어야 한다.)
위 예시에서는 print(f"I am {self.get_taste()} {self.name}!")print(f"I am {Food.get_taste(self)} {self.name}!") 로 바꾸면 된다.
 

6.3. Built-in functions

isinstance() : instance의 type를 확인할 때 사용한다. obj.__class__int이거나 int로부터 derive되었다면 isinstance(obj, int)True가 된다.
 
issubclass(): class inheritance를 확인할 때 사용한다. issubclass(bool, int)True인데 그 이유는 boolint의 subclass이기 때문이다. 한편, issubclass(float, int)floatint의 subclass가 아니라서 False이다.
 

6.4. Multiple Inheritance

Python에서는 multiple inheritance도 지원한다. 아래와 같이 정의할 수 있다.
class DerivedClassName(Base1, Base2, Base3): <statement-1> . . . <statement-N>
Attribute를 찾을 때 가장 단순한 상황에서 순서는 depth-first-search로 왼쪽에서 오른쪽으로 찾는다. 그리고 계층 구조에서 겹치는 class를 두 번 찾지는 않는다. 즉, DerivedClassName 에서 못 찾으면 Base1에서 찾고, Base1의 base classes에서 찾고, 그래도 못 찾으면 Base2에서 찾는 방식이다.
 
사실 그보다는 좀 더 복잡한데, super()를 지원하기 위해선 MRO (method resolution order) 가 dynamically 변해야하기 때문이다. multiple inheritance에서는 다이아몬드 관계가 나오는데 이때 아래에 있는 class는 모두 가장 위의 class로부터 상속 받는다. 가장 위의 base class가 한 번 이상 접근되는 것을 막으려면 순서를 선형화하여 찾는다. 따라서 super() 는 부모 class를 call하는 것이 아니라 바로 다음 순서의 class를 call하는 것이다. 자세한 내용은 아래에서 확인할 수 있다.
6 --- Level 3 | O | (more general) / --- \ / | \ | / | \ | / | \ | --- --- --- | Level 2 3 | D | 4| E | | F | 5 | --- --- --- | \ \ _ / | | \ / \ _ | | \ / \ | | --- --- | Level 1 1 | B | | C | 2 | --- --- | \ / | \ / \ / --- Level 0 0 | A | (more specialized) ---
 

7. Private Variables

object 내부에서만 접근할 수 있는 “Private” instance variable은 Python에 존재하지 않는다. 하지만 대부분의 Python code에서 지켜지는 관례가 있다. 바로 underscore prefix를 붙이는 것이다. (e.g. _spam)이를 통해 API에서 함수든, method이든, data member이든 non-public이라고 여겨지게 한다.
 
한편 Python에서는 name mangling을 제한적으로 지원한다. base class와 subclasses 간의 names 충돌을 막기 위해 사용한다. Class의 정의 부분 내 어디에든 존재하기만 하고 어떤 identifier여도 __spam 과 같이 2개의 underscore로 name이 시작되면 Python 자체가 name을 _classname__spam 형태로 바꾼다. 이때 classname은 현재 위치의 class name이다.
 
아래 예시를 보면 name mangling을 통해 subclass의 method override는 허용하면서 부모 class는 망가뜨리지 않고 있다.
class Mapping: def __init__(self, iterable): self.items_list = [] self.__update(iterable) # name mangling def update(self, iterable): for item in iterable: self.items_list.append(item) __update = update # private copy of original update() method class MappingSubclass(Mapping): def update(self, keys, values): # provides new signature for update() # but does not break __init__() for item in zip(keys, values): self.items_list.append(item)
만일 MappingSubclass 에서 __update 를 사용하더라도 Mapping class에서는 _Mapping__update로 바뀌고 MappingSubclass 에서는 _MappingSubclass__update 로 바뀌기 때문에 충돌이 발생하지 않는다.
 
이때 주목할 점은 충돌을 피하기 위해 설계 됐을 뿐이지 여전히 private이라고 생각되는 variable을 접근하거나 수정할 수 있다.
 
여기서 주의해야하는 점은 exec(), eval(), global, getattr(), setattr(), delattr()와 같은 곳에서는 classname을 알 맥락이 없기 때문에 mangling 이후의 identifier 값을 직접 전달해줘야 한다.
 

8. Odds and Ends (잡다한 것들)

  1. C 언어의 “struct”과 같은 자료형이 유용할 때가 있는데 아래와 같이 dataclasses를 쓸 수 있다.
    1. from dataclasses import dataclass @dataclass class Employee: name: str dept: str salary: int
      john = Employee('john', 'computer lab', 1000) john.dept # -> 'computer lab' john.salary # -> 1000
 
  1. 특정한 data type을 기대하는 코드에서 그 type의 method를 emulate하는 class를 대신 전달할 수 있다. 예를 들어 file object를 받아들이는 함수가 있어도 직접 read()와 readline() method를 가지는 class를 정의해서 string buffer를 대신 받게 할 수 있다.
 
  1. Instance method objects도 attributes를 가진다. m.__self__ 는 method m()의 instance object이고 m.__func__은 그 method의 function object이다.
 

9. Iterators

대부분의 container objects는 for statement로 반복이 된다.
for element in [1, 2, 3]: print(element) for element in (1, 2, 3): print(element) for key in {'one':1, 'two':2}: print(key) for char in "123": print(char) for line in open("myfile.txt"): print(line, end='')
 
이때 for 문 뒤에서는 container object에서 iter() 를 call하고 있다. 이 함수는 __next__() method를 정의하는 iterator object를 return한다. __next__() 는 container의 element에 한 번에 하나씩 접근한다. 더 이상 element가 없다면 StopIteration exception을 raise한다. next()bulit-in 함수를 통해 __next__() method를 call할 수 있다.
s = 'abc' it = iter(s) it # -> <str_iterator object at 0x10c90e650> next(it) # -> 'a' next(it) # -> 'b' next(it) # -> 'c' next(it) # -> Traceback (most recent call last): # -> File "<stdin>", line 1, in <module> # -> next(it) # -> StopIteration
 
Iterator protocol을 봤으니 나만의 iterator를 만드는 것은 쉽다. __next__() method와 함께 object를 return하는 __iter__() 를 정의하면 된다. 만일 class가 __next__() 를 정의했다면 __iter__() 은 그저 self를 return하면 된다.
class Reverse: """Iterator for looping over a sequence backwards.""" def __init__(self, data): self.data = data self.index = len(data) def __iter__(self): return self def __next__(self): if self.index == 0: raise StopIteration self.index = self.index - 1 return self.data[self.index]
rev = Reverse('spam') iter(rev) # -> <__main__.Reverse object at 0x00A1DB50> for char in rev: print(char) # -> m # -> a # -> p # -> s
 

10. Generators

Generators는 iterator를 만드는 간단하고 강력한 도구다. 일반 함수처럼 쓰는데 data를 return할 때 yield statement를 사용한다. next()가 call될 때마다 마지막으로 실행된 statement를 기억해두며 계속 이어나간다.
def reverse(data): for index in range(len(data)-1, -1, -1): yield data[index] for char in reverse('golf'): print(char)
page icon
f l o g
 
Generators로 만들 수 있는 것은 이전 섹션에서 보인 듯 class iterators로도 만들 수 있다.
generator가 좀 더 간단하게 해주는 것은 아래와 같다.
  • __iter__()__next__() methods가 자동으로 만들어진다.
  • local variable과 실행 상태가 자동으로 저장돼서 self.data, self.index 를 쓰지 않아도 된다.
  • generators가 종료될 때 자동으로 StopIteration 을 raise한다.

 11. Generator Expressions

간단한 generators는 list comprehensions 문법과 유사한 문법으로 더 간략히 표현할 수 있다. 이 표현식은 generator가 곧바로 감싸는 함수에게 전달해야하는 상황에서 유용하다. Full generator 정의보다는 제한적이지만 list comprehensions보다는 메모리 친화적이다.
sum(i*i for i in range(10)) # sum of squares # -> 285 xvec = [10, 20, 30] yvec = [7, 5, 3] sum(x*y for x,y in zip(xvec, yvec)) # dot product # -> 260 unique_words = set(word for line in page for word in line.split()) valedictorian = max((student.gpa, student.name) for student in graduates) data = 'golf' list(data[i] for i in range(len(data)-1, -1, -1)) # range(start, stop, step) # -> ['f', 'l', 'o', 'g']
set(word for line in page for word in line.split()) 에서처럼 가장 앞에 최종적으로 얻으려 하는 값이 오고 이후에는 바깥 loop부터 오게 된다.
 
참고로 list comprehensions는 소괄호를 쓰는 generator와 달리 대괄호를 쓴다.
squares_list = [i*i for i in range(10)]
 
 

Reference

[1] Python Software Foundation. "9. Classes." The Python Tutorial, version 3.13, Python Software Foundation, 2024, docs.python.org/3.13/tutorial/controlflow.html#special-parameters. Accessed 20 July 2025.