Теми за Python

Може да сте срещали в някой код на Python ключовата дума yield в даден момент, особено в ситуации с интензивно изчисление, които изискват големи пространствени и времеви сложности. В тази статия ще обсъдим генераторите, които са необходими за разбиране на разликата между yield и return.

Какво представляват генераторите?

Генераторитеса повторяеми елементи на Python, които са ефективни по отношение на паметта, където съхраняват само един елемент наведнъж. Те се изпълняват по начин, който генерира всеки елемент в движение и го връща (или yields) обратно на процеса на извикването.

Преди разработката с пример е по-добре първо да знаете как да създавате генератори. И за това имаме два начина:

1. По функции на генератора:

Генераторните функции са функции, които връщат генераторни обекти. Те използват yield вместо return. Тази ключова дума излиза от функцията, връщайки посочения след нея елемент като генераторен екземпляркойто се третира като итерируем. Така че всеки път, когато го повторите, функцията се възобновява от yield, на който е спряла. Ключовата характеристика е, че той няма да запомни предишния елемент, но може да се използва в движение и без да е необходимо да се чака целият итерируем елемент да се зареди в паметта. Ето един пример за по-добра илюстрация

# Figure 1

# Non generator Function
# This function will exec the first return each time
# The second return is never reached
def foo():
    return 1
    return 2  

print(foo())
# 1

за функцията foo, когато се извика return, останалата част от функцията никога не се достига

# Figure 2

# Generator function
# This function will exec the first yield in the first iteration
# and the second yield in the second iteration
# Returning a generator object
def gen_func():
  yield 1
  yield 2

print(type(gen_func()))
# <class 'generator'>

# loop over the yields
for i in gen_func():
  print(i)
# 1
# 2

за gen_func това е функция генераторкоято връща обект от тип generator . В известен смисъл изглежда, че е възможно да преминете през всички извиквания на yield от функцията чрез for цикъл.

Броят на yield извикванията във функцията на генератора може да бъде безкраен и това показва полезността на генераторите, тъй като дори за безкрайни повторяеми, ние сме в състояние да извършим известна обработка за всеки елемент, без да се нуждаем от безкрайна памет или безкрайно чакане. проверете следния пример:

# Figure 3

# Function that counts to infinity 
# then returns the counter
# Obviously will never end
def count_to_infinity():
    count = []
    counter = 0
    while True:
        counter += 1
        count.append(counter)
    return count

print(count_to_infinity())

за функцията count_to_infinity очевидно ще накара програмата да се изпълнява безкрайно, докато паметта не се препълни и се срине, и междувременно не може да има никаква обработка. така че print(count_to_infinity()) никога не се изпълнява.

# Figure 4

# Function that counts to infinity 
# and returns the counter each step
# while still incrementing

def count_to_infinity_yield():
    count = 0
    while True:
        count += 1
        yield count

natural_numbers = count_to_infinity_yield()

# access the first iterator
print(next(natural_numbers))
# 1
 

Въпреки че count_to_infinity_yield също никога не свършва, но позволява връщане на стойността на брояча при всяко увеличение и може да се обработва едновременно с увеличаването. В примера можем да видим, че имаме достъп до първия елемент в генератора, въпреки че той все още се създава.

# Figure 5

for i in natural_numbers:
  print(i)
# 2
# 3
# ...

Можем също така да преминем през генератора, докато се генерира, това доказва колко мощни са те

Ако искате повече кодове като този, моля, проверете връзката тази.

2. Чрез генераторен израз:

Генераторните изразиса подобни на разбирането на списъци в синтаксиса, единствената разлика е, че се записват в скоби, а не в скоби. Това не означава, че те са кортежи, както можете да видите в следващия пример.

# Figure 6

# List comprehension

l = [ i for i in natural_numbers ]
print(type(l))
# <class 'list'>

# Generator Expressions

g = ( i for i in natural_numbers )
print(type(g))
# <class 'generator'>

# Tuples 

t = tuple([i for i in natural_numbers])
print(type(t))
# <class 'tuple'>

Но по отношение на производителността има голяма разлика между двете. За разбирането на списъка, както знаем, списъкът ще бъде инициализиран, след като for цикълът приключи с всички създадени елементи, съхранени в паметта. За Генераторния израз генераторният обект ще съдържа само първия елемент и след това ще го разменя със следващия при повторение. ето защо винаги ще има едно и също място в паметта.

# Figure 7

# we will use the natural numbers generated in the previous example
# recall naturall numbers is a generator for infinite natural numbers
natural_numbers = count_to_infinity_yield()

# List comprehension
l = [ i for i in natural_numbers]

print( l )

Както в figure 3, редът print(l) никога няма да бъде изпълнен, тъй като разбирането на списъка преминава през безкраен брой елементи, докато паметта не бъде препълнена и програмата се срине

# Figure 8

# Generator Expression
g = ( i for i in natural_numbers )

print(next(g))
# 1

Сега стана ясно, че редът print ще се изпълни добре, освен това можем да създадем цикъл, който преминава през g и показва съдържанието му, дори ако е безкрайно

Друг важен момент е, че Генераторните изрази създават генератори, които имат постоянна пространствена сложност, което означава, че без значение как увеличавате броя на елементите (безкрайно), ще имате същото изразходвано пространство. За разлика от това, разбиранията на списъци създават списъци, които имат линейна пространствена сложност, което означава, че колкото повече елементи има списъкът, толкова повече място ще заема. щракнете тук за тестове на пространствената сложност на генераторния израз спрямо разбирането на списък

Изводи:

Генераторите са мощни структури от данни на python, които подобряват пространствената сложност, както и времевата сложност на големи програми, като алгоритми за търсене, AI, криптография .. и т.н.

Ключовата дума yield може да замени return, когато искате да обработите елементи, докато се добавят

За по-добро разбиране силно бих препоръчал да играете с код в това хранилище на github.