Python Language
발전기
수색…
소개
생성자는 생성자 함수 ( yield
사용하여 yield
) 또는 생성자 표현식 (an_expression for x in an_iterator)
사용)을 통해 생성되는 지연 반복자입니다.
통사론
- yield
<expr>
-
<expr>
-
<var>
= yield<expr>
- 다음 (
<iter>
)
되풀이
생성자 객체는 반복자 프로토콜을 지원합니다. 즉, Python 3.x에서 next()
메서드 ( __next__()
를 제공합니다.이 메서드는 실행을 단계별로 실행하는 데 사용되며 __iter__
메서드는 자체를 반환합니다. 즉, Generator는 일반적인 iterable 객체를 지원하는 모든 언어 구문에서 사용할 수 있습니다.
# naive partial implementation of the Python 2.x xrange()
def xrange(n):
i = 0
while i < n:
yield i
i += 1
# looping
for i in xrange(10):
print(i) # prints the values 0, 1, ..., 9
# unpacking
a, b, c = xrange(3) # 0, 1, 2
# building a list
l = list(xrange(10)) # [0, 1, ..., 9]
next () 함수
next()
빌트인은 반복자 (생성자 반복자 포함)에서 값을 받고 반복자가 고갈 된 경우 기본값을 제공하는 데 사용할 수있는 편리한 래퍼입니다.
def nums():
yield 1
yield 2
yield 3
generator = nums()
next(generator, None) # 1
next(generator, None) # 2
next(generator, None) # 3
next(generator, None) # None
next(generator, None) # None
# ...
구문은 next(iterator[, default])
입니다. 이터레이터가 종료되고 디폴트 값이 전달되면 리턴됩니다. 기본값이 제공되지 않으면 StopIteration
이 발생합니다.
개체를 생성기로 보내는 중
발전기로부터 값을 수신하는 것에 더하여, 상기 사용되는 발전기에 객체를 전송할 수있다 send()
방법.
def accumulator():
total = 0
value = None
while True:
# receive sent value
value = yield total
if value is None: break
# aggregate values
total += value
generator = accumulator()
# advance until the first "yield"
next(generator) # 0
# from this point on, the generator aggregates values
generator.send(1) # 1
generator.send(10) # 11
generator.send(100) # 111
# ...
# Calling next(generator) is equivalent to calling generator.send(None)
next(generator) # StopIteration
여기서 일어나는 일은 다음과 같습니다.
- 처음
next(generator)
호출하면 프로그램은 첫 번째yield
문으로 진행하고 해당 지점의total
값 (0)을 반환합니다.이 시점에서 생성기의 실행이 일시 중단됩니다. - 그런 다음
generator.send(x)
를 호출하면 인터프리터는 인수x
를 가져 와서value
지정된 마지막yield
문의 반환 값으로 만듭니다. 그런 다음 발전기는 다음 값을 얻을 때까지 평소와 같이 진행합니다. - 마지막으로
next(generator)
호출하면, 프로그램은 이것을 Generator에None
을 보내는 것처럼 취급합니다.None
대해서는 특별한 것이 없지만,이 예제는None
을 특별한 값으로 사용하여 발전기가 멈추도록 요청합니다.
생성자 표현식
이해력과 유사한 구문을 사용하여 생성자 반복자를 만들 수 있습니다.
generator = (i * 2 for i in range(3))
next(generator) # 0
next(generator) # 2
next(generator) # 4
next(generator) # raises StopIteration
함수가 반드시 목록을 전달할 필요가없는 경우 함수 호출 내에 생성자 표현식을 배치하여 문자를 저장하고 (가독성을 향상시킬 수 있습니다.) 함수 호출의 괄호는 표현식을 생성기 표현식으로 암시 적으로 만듭니다.
sum(i ** 2 for i in range(4)) # 0^2 + 1^2 + 2^2 + 3^2 = 0 + 1 + 4 + 9 = 14
또한, 위의 예제에서 [0, 1, 2, 3]
을 반복하는 전체 목록을로드하는 대신 Generator를 사용하여 Python이 필요에 따라 값을 사용할 수 있기 때문에 메모리를 절약 할 수 있습니다.
소개
생성자 표현식 은 list, dictionary 및 set comprehensions과 유사하지만 괄호로 묶입니다. } 호는 함수 호출에 대한 유일한 인수로 사용될 때 존재할 필요는 없습니다.
expression = (x**2 for x in range(10))
이 예제는 0을 포함한 10 개의 첫 번째 완벽한 사각형을 생성합니다 (x = 0).
Generator 함수 는 본문에 하나 이상의 yield
문이 있다는 점을 제외하면 일반 함수와 비슷합니다. 이러한 함수는 값을 return
할 수 없습니다 (그러나 생성기를 일찍 중지하려면 빈 return
이 허용됩니다).
def function():
for x in range(10):
yield x**2
이 생성자 함수는 이전 생성자 표현식과 동일하며 동일한 생성자 표현식을 출력합니다.
참고 : 모든 발전기 표현이 자신의 동등한 기능을 가지고 있지만 그 반대의 경우도 마찬가지입니다.
두 개의 괄호가 다르게 반복 될 경우 생성자 표현식은 괄호없이 사용할 수 있습니다.
sum(i for i in range(10) if i % 2 == 0) #Output: 20
any(x = 0 for x in foo) #Output: True or False depending on foo
type(a > b for a in foo if a % 2 == 1) #Output: <class 'generator'>
대신에:
sum((i for i in range(10) if i % 2 == 0))
any((x = 0 for x in foo))
type((a > b for a in foo if a % 2 == 1))
하지만:
fooFunction(i for i in range(10) if i % 2 == 0,foo,bar)
return x = 0 for x in foo
barFunction(baz, a > b for a in foo if a % 2 == 1)
생성기 함수를 호출하면 생성자 객체가 생성되고 나중에 생성자 객체를 반복 할 수 있습니다. 다른 유형의 반복자와 달리 생성기 객체는 한 번만 통과 할 수 있습니다.
g1 = function()
print(g1) # Out: <generator object function at 0x1012e1888>
생성자의 본문은 즉시 실행 되지 않습니다 . 위 예제에서 function()
을 호출하면 첫 번째 print 문을 실행하지 않고 생성자 객체를 즉시 반환합니다. 이를 통해 생성자는 목록을 반환하는 함수보다 적은 메모리를 소비 할 수 있으며 무한히 긴 시퀀스를 생성하는 생성자를 만들 수 있습니다.
이러한 이유로 발전기는 대용량 데이터가 포함 된 데이터 과학 및 기타 상황에서 자주 사용됩니다. 또 다른 장점은 완성 된 시퀀스가 생성되기를 기다리지 않고도 다른 코드가 생성기에서 얻은 값을 즉시 사용할 수 있다는 것입니다.
그러나 생성자에서 생성 된 값을 두 번 이상 사용해야하는 경우 저장하는 것보다 저장하는 것이 더 많은 경우 시퀀스를 다시 생성하는 것보다 생성 된 값을 list
으로 저장하는 것이 좋습니다. 자세한 내용은 아래의 '발전기 재설정'을 참조하십시오.
일반적으로 생성자 객체는 루프 또는 반복 가능한 함수가 필요한 모든 함수에서 사용됩니다.
for x in g1:
print("Received", x)
# Output:
# Received 0
# Received 1
# Received 4
# Received 9
# Received 16
# Received 25
# Received 36
# Received 49
# Received 64
# Received 81
arr1 = list(g1)
# arr1 = [], because the loop above already consumed all the values.
g2 = function()
arr2 = list(g2) # arr2 = [0, 1, 4, 9, 16, 25, 36, 49, 64, 81]
생성자 객체는 반복자이므로 next()
함수를 사용하여 수동으로 반복 처리 할 수 있습니다. 이렇게하면 이후에 호출 할 때마다 값이 하나씩 반환됩니다.
후드에서 생성기에서 next()
를 호출 할 때마다 파이썬은 다음 yield
문에 도달 할 때까지 생성자 함수 본문에서 명령문을 실행합니다. 이 시점에서 yield
명령의 인수를 반환하고 그 결과가 발생한 지점을 기억합니다. next()
를 다시 한 번 호출하면 해당 지점에서 실행이 다시 시작되고 다음 yield
문까지 계속됩니다.
파이썬이 더 이상 yield
를 만나지 않고 생성자 함수의 끝에 도달하면 StopIteration
예외가 발생합니다 (정상적으로 모든 반복자는 같은 방식으로 동작합니다).
g3 = function()
a = next(g3) # a becomes 0
b = next(g3) # b becomes 1
c = next(g3) # c becomes 2
...
j = next(g3) # Raises StopIteration, j remains undefined
파이썬 2에서는 생성자 객체가 .next()
메소드를 가지고 있습니다.이 메소드는 생성 된 값을 수동으로 반복하는 데 사용할 수 있습니다. Python 3에서이 메서드는 모든 반복자에 대한 .__next__()
표준으로 대체되었습니다.
발전기 리셋하기
생성기에서 생성 된 객체를 한 번만 반복 할 수 있다는 것을 기억하십시오. 이미 스크립트의 객체를 반복 한 경우 추가 시도는 None
합니다.
제너레이터에 의해 생성 된 객체를 두 번 이상 사용해야하는 경우에는 제네레이터 함수를 다시 정의하고 두 번째로 사용할 수도 있고 아니면 처음 사용시 목록에 생성기 함수의 출력을 저장할 수도 있습니다. 대용량의 데이터를 처리하고 모든 데이터 항목의 목록을 저장하면 많은 디스크 공간을 차지하는 경우 생성기 기능을 다시 정의하는 것이 좋은 옵션입니다. 반대로 초기에 항목을 생성하는 데 비용이 많이 드는 경우 생성 된 항목을 목록에 저장하여 다시 사용할 수 있습니다.
생성기를 사용하여 피보나치 숫자 찾기
생성자의 실제 사용 사례는 무한 시리즈의 값을 반복하는 것입니다. 다음은 피보나치 시퀀스 의 처음 10 가지 항목을 찾는 예입니다.
def fib(a=0, b=1):
"""Generator that yields Fibonacci numbers. `a` and `b` are the seed values"""
while True:
yield a
a, b = b, a + b
f = fib()
print(', '.join(str(next(f)) for _ in range(10)))
0, 1, 1, 2, 3, 5, 8, 13, 21, 34
무한 시퀀스
생성자를 사용하여 무한 시퀀스를 나타낼 수 있습니다.
def integers_starting_from(n):
while True:
yield n
n += 1
natural_numbers = integers_starting_from(1)
위와 같이 숫자의 무한 시퀀스는 itertools.count
사용하여 생성 할 수도 있습니다. 위의 코드는 다음과 같이 작성할 수 있습니다.
natural_numbers = itertools.count(1)
무한 생성기에 대한 생성기 보급을 사용하여 새로운 생성자를 생성 할 수 있습니다.
multiples_of_two = (x * 2 for x in natural_numbers)
multiples_of_three = (x for x in natural_numbers if x % 3 == 0)
무한 생성기에는 끝이 없으므로 생성기를 완전히 소비하려고 시도하는 함수에 전달하면 무서운 결과가 발생합니다 .
list(multiples_of_two) # will never terminate, or raise an OS-specific error
대신 range
(또는 python <3.0)의 경우 xrange
를 사용하여 list / set comprehension을 사용하십시오.
first_five_multiples_of_three = [next(multiples_of_three) for _ in range(5)]
# [3, 6, 9, 12, 15]
iterator를 부분 집합으로 슬라이스하려면 itertools.islice()
를 사용하십시오.
from itertools import islice
multiples_of_four = (x * 4 for x in integers_starting_from(1))
first_five_multiples_of_four = list(islice(multiples_of_four, 5))
# [4, 8, 12, 16, 20]
동일한 "루트"에서 오는 다른 모든 발전기와 마찬가지로 원래의 발전기도 업데이트됩니다.
next(natural_numbers) # yields 16
next(multiples_of_two) # yields 34
next(multiples_of_four) # yields 24
무한 시퀀스도 함께 반복 될 수 for
-loop . 루프가 결국 종료되도록 조건부 break
문을 포함시켜야합니다.
for idx, number in enumerate(multiplies_of_two):
print(number)
if idx == 9:
break # stop after taking the first 10 multiplies of two
고전적인 예 - 피보나치 수
import itertools
def fibonacci():
a, b = 1, 1
while True:
yield a
a, b = b, a + b
first_ten_fibs = list(itertools.islice(fibonacci(), 10))
# [1, 1, 2, 3, 5, 8, 13, 21, 34, 55]
def nth_fib(n):
return next(itertools.islice(fibonacci(), n - 1, n))
ninety_nineth_fib = nth_fib(99) # 354224848179261915075
다른 반복 가능한 값으로부터 모든 값을 얻는다.
다른 반복 가능한 값으로부터 모든 값을 얻고 싶다면 yield from
사용하십시오 :
def foob(x):
yield from range(x * 2)
yield from range(2)
list(foob(5)) # [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 0, 1]
이것은 발전기에서도 잘 작동합니다.
def fibto(n):
a, b = 1, 1
while True:
if a >= n: break
yield a
a, b = b, a + b
def usefib():
yield from fibto(10)
yield from fibto(20)
list(usefib()) # [1, 1, 2, 3, 5, 8, 1, 1, 2, 3, 5, 8, 13]
코 루틴
생성자는 코 루틴을 구현하는 데 사용할 수 있습니다.
# create and advance generator to the first yield
def coroutine(func):
def start(*args,**kwargs):
cr = func(*args,**kwargs)
next(cr)
return cr
return start
# example coroutine
@coroutine
def adder(sum = 0):
while True:
x = yield sum
sum += x
# example use
s = adder()
s.send(1) # 1
s.send(2) # 3
코 루틴은 일반적으로 상태 시스템을 구현하는 데 사용됩니다. 상태가 제대로 작동해야하는 단일 메서드 프로 시저를 만드는 데 주로 유용하기 때문입니다. 이들은 기존 상태에서 작동하고 작업 완료시 얻은 값을 반환합니다.
재귀가있는 수율 : 디렉터리의 모든 파일을 반복적으로 나열
먼저 파일과 함께 작동하는 라이브러리를 가져 오십시오.
from os import listdir
from os.path import isfile, join, exists
디렉토리에서 파일을 읽는 도우미 함수 :
def get_files(path):
for file in listdir(path):
full_path = join(path, file)
if isfile(full_path):
if exists(full_path):
yield full_path
하위 디렉토리 만 가져 오는 또 다른 도우미 함수는 다음과 같습니다.
def get_directories(path):
for directory in listdir(path):
full_path = join(path, directory)
if not isfile(full_path):
if exists(full_path):
yield full_path
이제이 함수를 사용하여 디렉토리 및 모든 하위 디렉토리 (생성자 사용)에서 모든 파일을 재귀 적으로 가져옵니다.
def get_files_recursive(directory):
for file in get_files(directory):
yield file
for subdirectory in get_directories(directory):
for file in get_files_recursive(subdirectory): # here the recursive call
yield file
이 기능은 다음의 yield from
사용하여 간단하게 할 수 있습니다.
def get_files_recursive(directory):
yield from get_files(directory)
for subdirectory in get_directories(directory):
yield from get_files_recursive(subdirectory)
발전기를 병렬로 반복
병렬로 여러 생성기를 반복하려면, 내장 된 zip
사용하십시오.
for x, y in zip(a,b):
print(x,y)
결과 :
1 x
2 y
3 z
파이썬 2에서는 대신 itertools.izip
을 사용해야합니다. 여기에서는 모든 zip
함수가 튜플을 생성한다는 것을 볼 수 있습니다.
zip은 iterables 중 하나가 항목을 모두 사용하는 즉시 반복을 중지합니다. 가장 긴 iterable만큼 반복하고 싶다면 itertools.zip_longest()
.
리팩토링 목록 작성 코드
빈리스트로 시작하여 반복적으로리스트에 추가함으로써리스트를 생성하고 리턴하는 복잡한 코드가 있다고 가정하십시오.
def create():
result = []
# logic here...
result.append(value) # possibly in several places
# more logic...
return result # possibly in several places
values = create()
내부 논리를 목록 이해로 바꾸는 것이 실용적이지 않은 경우 전체 기능을 제자리에서 생성기로 전환 한 다음 결과를 수집 할 수 있습니다.
def create_gen():
# logic...
yield value
# more logic
return # not needed if at the end of the function, of course
values = list(create_gen())
논리가 재귀 적이면 yield from
을 사용하여 재귀 호출의 모든 값을 "병합 된"결과에 포함시킵니다.
def preorder_traversal(node):
yield node.value
for child in node.children:
yield from preorder_traversal(child)
수색
next
함수는 반복하지 않아도 유용합니다. 생성자 표현식을 next
전달하면 일부 술어와 일치하는 요소의 첫 x 째 항목을 신속하게 검색 할 수 있습니다. 절차상의 코드
def find_and_transform(sequence, predicate, func):
for element in sequence:
if predicate(element):
return func(element)
raise ValueError
item = find_and_transform(my_sequence, my_predicate, my_func)
다음으로 대체 될 수 있습니다 :
item = next(my_func(x) for x in my_sequence if my_predicate(x))
# StopIteration will be raised if there are no matches; this exception can
# be caught and transformed, if desired.
이를 위해 first = next
와 같은 별칭을 만들거나 예외를 변환하는 래퍼 함수를 만드는 것이 바람직 할 수 있습니다.
def first(generator):
try:
return next(generator)
except StopIteration:
raise ValueError