14  Iterables/Iterator

Iterables/Iterator는 파이썬 언어의 여러 곳에 숨어있는데, 이를 잘 이해하면 파이썬 언어를 이해하는 데 큰 도움이 된다.

14.1 Iterable을 사용했을 때의 장점(Copilot)

Using iterables in Python offers several advantages that make them a powerful and flexible tool for handling collections of data. Here are some key benefits:

  • 메모리 효율성

    • Iterables like generators and iterators allow you to process data one item at a time, which can be more memory-efficient than loading an entire collection into memory.

    • Example: Using a generator to process a large file line by line.

    def read_large_file(file_path):
        with open(file_path) as file:
            for line in file:
                yield line
    
    for line in read_large_file('large_file.txt'):
        process(line)
  • 게으른 평가(Lazy Evaluation)

    • Iterables like generators evaluate items on-the-fly, which can improve performance by avoiding unnecessary computations.

    • Example: Using a generator expression for lazy evaluation.

    squares = (x**2 for x in range(10))
    for square in squares:
        print(square)
  • 지원하는 여러 내장 함수

    • Many built-in functions in Python, such as sum(), max(), min(), and sorted(), work seamlessly with iterables.
    • Example: Using sum() to calculate the sum of elements in an iterable.
    numbers = [1, 2, 3, 4, 5]
    total = sum(numbers)
    print(total)

14.2 Iterables과 Iterator의 정의

이것을 먼저 암기하거나 이해할 필요가 없다. 뒤에서 사용하는 예와 설명을 가지고 다시 읽으면서 내용을 이해하면 된다.

Python 용어집iterable에 대한 정의가 다음과 같이 되어 있다.

  • An object capable of returning its members one at a time. Examples of iterables include all sequence types (such as list, str, and tuple) and some non-sequence types like dict, file objects, and objects of any classes you define with an __iter__() method or with a __getitem__() method that implements sequence semantics.

    • 한번에 하나의 값을 반환하는 기능을 가진 객체로, 모든 sequence types(list, str, tuple 등)과 non-sequence types(dict, file objects, 사용자가 __iter__() 메서드 또는 __getitem__()를 정의하여 sequence 맥락을 가진도록 한 객체들) 등이 여기에 해당한다.
  • Iterables can be used in a for loop and in many other places where a sequence is needed (zip(), map(), …).

    • Iterables은 for 루프와 sequence가 필요한 여러 곳에서 사용할 수 있다.
  • When an iterable object is passed as an argument to the built-in function iter(), it returns an iterator for the object. This iterator is good for one pass over the set of values.

    • iterables을 iter() 내장 함수에 넣으면 객체에 대한 iterator가 반환된다. 이 iterator는 일련의 값에 대하여 하나의 값을 이용하는 데 사용된다.
  • When using iterables, it is usually not necessary to call iter() or deal with iterator objects yourself. The for statement does that automatically for you, creating a temporary unnamed variable to hold the iterator for the duration of the loop.

    • iterables을 사용할 때 iter() 내장 함수에 넣어서 iterator를 반환하여 사용할 필요가 없다. 일반적으로, for 문은 자동으로 그런 일을 대신하기 때문에 내부에서 루핑 동안에 iterator를 대리하는 이름 없는 임시 변수가 만들어진다.

Python 용어집에 Iterator에 대한 정의는 다음과 같이 되어 있다.

  • An object representing a stream of data. Repeated calls to the iterator’s __next__() method (or passing it to the built-in function next()) return successive items in the stream.

    • 즉, iterator는 “데이터 스트림”을 나타내는 객체이고, 이 객체에 대해서 __next__() 메서드를 호출하면(또는 내장 함수 next()에 넣으면) 스트림에 존재하는 항목들을 순차적으로 반환한다.
  • When no more data are available a StopIteration exception is raised instead. At this point, the iterator object is exhausted and any further calls to its __next__() method just raise StopIteration again.

    • 즉, 값을 반환하다가 더이상 반환할 값이 떨어지면 StopIteration이라는 예외를 발생시킨다. 이 상태에서는 완전히 값이 소진된 상태이기 때문에 다시 __next__() 메서드를 적용해도 StopIteration 예외만이 발생할 뿐이다.
  • Iterators are required to have an __iter__() method that returns the iterator object itself so every iterator is also iterable and may be used in most places where other iterables are accepted. One notable exception is code which attempts multiple iteration passes.

    • 즉, Iterator는 iterator를 반환하는 __iter__() 메서드를 가지고 있어야 하기 때문에, 모든 iterator는 iterable이다. 따라서 iterables이 들어갈 수 있는 곳이라면 대부분의 장소에 이 iterator를 넣는 것이 가능하다.
  • A container object (such as a list) produces a fresh new iterator each time you pass it to the iter() function or use it in a for loop. Attempting this with an iterator will just return the same exhausted iterator object used in the previous iteration pass, making it appear like an empty container.

    • list와 같은 컨테이너 객체를 iter() 함수나 for 루프에 넘기면, 넘길 때마다 새로운 iterator가 만들어진다. iterator를 다시 이런 방식으로 사용하면 이전 순회에서 사용되던 소진된 iterator 객체가 반환되어 마치 빈 컨테이너처럼 보인다.

14.3 파이썬 리스트(list)를 가지고 이해해 보기

파이썬 리스트는 squence이고, 모든 sequence는 iterable이기 때문에, 리스트는 iterable에 속한다.

# my_list는 iterable의 일종 
my_list = [2, 3, 5, 7, 11]

my_listiter() 내장 함수에 보내면 iterator가 반환된다.

# it라는 iterator
it = iter(my_list)
print(it)
print(type(it))
<list_iterator object at 0x1129c08b0>
<class 'list_iterator'>

즉, iterable을 iter() 함수에 넘겨서 실행하면 바로 어떤 값을 반환하는 것이 아니라 중간 단계의 iterator 객체를 반환한다. (바꾸어 말하면, iterable이란 iter() 함수에 넣었을 때 iterator를 반환시킬 수 있는 객체를 말한다.)

이제 값을 실제로 반환시켜 보자. 값을 반환시킬 때는 iterator를 next() 함수에 보내야 한다. 이 함수를 실행할 때마다 값을 하나씩 반환한다.

print(next(it))
print(next(it))
print(next(it))
print(next(it))
print(next(it))
2
3
5
7
11

반환할 값이 소진된 경우에는 StopIteration 예외를 발생시킨다.

next(it)
---------------------------------------------------------------------------
StopIteration                             Traceback (most recent call last)
Cell In[4], line 1
----> 1 next(it)

StopIteration: 

이제 it라는 iterator는 값을 완전히 소진한 상태이기 때문에 다시 for 문에 사용하면 빈 컨테이너처럼 작동한다. 빈 컨테이너를 대하여 for 문을 실행하면 바로 루프가 멈춰버리기 때문에 아무런 코드도 실행되지 않는다.

for x in it:
    print(x)

만약 다시 같은 값들을 사용하여 뭔가를 하려면 iterator를 다시 생성한 다음 사용해야 한다.

it2 = iter(my_list)
for x in it2:
    print(x)
2
3
5
7
11

위와 같은 경우는 내부 작동 방식을 이해하기 위한 것이다. 보통은 iterableiter() 함수로 보내서 iterator를 만들지 않아도 자동으로 하도록 되어 있다. 따라서, 보통은 다음과 같이 사용한다.

for x in my_list:
    print(x)
2
3
5
7
11

14.4 Iterables로 할 수 있는 오퍼레이션 (Copilot)

  • 다음은 Copilot 응답이다.
  • Sequences는 iterables의 일종이기 때문에, 이 내용들은 장에서 설명한 내용과 거의 비슷하다.

In Python, iterables are objects that can be looped over (iterated) using a for loop. Common examples of iterables include lists, tuples, strings, dictionaries, and sets. Here are some common operations you can perform with Python iterables:

  • Iteration

    You can iterate over the elements of an iterable using a for loop.

my_list = [1, 2, 3, 4, 5]
for item in my_list:
    print(item)
1
2
3
4
5
  • Membership Test

    You can check if an element is in an iterable using the in keyword.

my_list = [1, 2, 3, 4, 5]
print(3 in my_list)  
print(6 in my_list)  
True
False
  • Comprehensions

    You can create new iterables using list comprehensions, set comprehensions, and dictionary comprehensions.

# List comprehension
squares = [x**2 for x in range(6)]
print(squares)  
# Set comprehension
unique_squares = {x**2 for x in range(6)}
print(unique_squares) 

# Dictionary comprehension
square_dict = {x: x**2 for x in range(6)}
print(square_dict)  
[0, 1, 4, 9, 16, 25]
{0, 1, 4, 9, 16, 25}
{0: 0, 1: 1, 2: 4, 3: 9, 4: 16, 5: 25}
  • Built-in Functions

    You can use various built-in functions with iterables.

my_list = [1, 2, 3, 4, 5]

# len() - Get the length of the iterable
print(len(my_list))  

# sum() - Get the sum of elements in the iterable
print(sum(my_list))  

# max() - Get the maximum element in the iterable
print(max(my_list))  

# min() - Get the minimum element in the iterable
print(min(my_list))  
5
15
5
1
  • Slicing
my_list = [1, 2, 3, 4, 5]
print(my_list[1:4])  
[2, 3, 4]
  • Enumerate
my_list = ['a', 'b', 'c']
for index, value in enumerate(my_list):
    print(index, value)
0 a
1 b
2 c
  • Zip
list1 = [1, 2, 3]
list2 = ['a', 'b', 'c']
for item in zip(list1, list2):
    print(item)
(1, 'a')
(2, 'b')
(3, 'c')
  • Iterable Unpacking

    하나의 iterable은 보통 복수의 값을 가지고 있는데, 이것을 하나의 문장으로 여러 변수들에 값을 할당하는 것을 말한다.

      - 할당을 할 때  
a, b, c = (1, 2, 3) # tuple
x, y, z = [4, 5, 6] # lsit
아이템의 개수와 변수가 일치하지 않는 경우에는 "나머지"를 의미하는 `*`를 사용한다. 
a, *b, c = range(10)
print(a)
print(b)
print(c)
0
[1, 2, 3, 4, 5, 6, 7, 8]
9
    - 루프에서 
pairs = [(1, 'a'), (2, 'b'), (3, 'c')]
for number, letter in pairs:
    print(f"Number: {number}, Letter: {letter}")
Number: 1, Letter: a
Number: 2, Letter: b
Number: 3, Letter: c
    - 함수의 인자로 사용되는 경우 
def add(a, b, c):
    return a + b + c

numbers = [1, 2, 3]
result = add(*numbers)
print(result) 
6
# dictionary는 **을 사용
def greet(name, greeting):
    print(f"{greeting}, {name}!")

info = {'name': 'Alice', 'greeting': 'Hello'}
greet(**info)
Hello, Alice!

14.5 iter() 내장 함수

  • iter() 함수는 iterable을 받아서 iterator를 반환하는 함수이다.

  • for 문 등에서 자동으로 호출되기 때문에 겉으로 (명시적으로) 이 함수를 사용하는 경우는 드물다. iterable이 작동하는 맥락에서는 자동으로 이 함수가 실행된다고 이해하면 된다.

  • iter() 함수는 객체의 __iter__() 메서드를 호출하고, 이것이 구현되어 있지 않으면 __getitem__()을 사용하는 내부 iterator를 만든다. __getitem__()가 구현된 객체는 sequence이기 때문에, sequence는 iterable의 일종이다.

14.6 Iterator와 next() 메서드와 StopIteration 예외

  • next()에 iterator를 넣으면 값을 하나 반환하고, 또 다시 호출하면 그 다음 값을 반환한다.

  • 값이 소진되면 StopIteration 예외가 발생한다.