Iterators and generators

Колекции в Python

  • List
  • Tuple
  • Set
  • Dict

List

animals = ['panda', 'dog', 'cat', 'tiger']
for animal in animals:
    print('I like {}'.format(animal))

Има подредба! Може да индексираме!

List slicing

a[start:end]      # items start through end-1
a[start:]         # items start through the rest of the array
a[:end]           # items from the beginning through end-1
a[start:end:step] # start through not past end, by step
a[:]              # a copy of the whole array

Списъците съдържат "указатели" към елементи

frameworks = ['django', 'angular', 'rails']
frameworks.append(frameworks)

frameworks[-1] is frameworks # True
print(frameworks) # ['django', 'angular', 'rails', [...]]
>>> python_frameworks = ["Django", "Flask"]
>>> frameworks = [python_frameworks, 'angular', 'rails']
>>> frameworks
[['Django', 'Flask'], 'angular', 'rails']
>>> python_frameworks.append("Pyramid")
>>> frameworks
[['Django', 'Flask', 'Pyramid'], 'angular', 'rails']

tuple

Като списък, но не може да се променя

python_frameworks = ("Django", "Flask")
python_frameworks[1] = Pyramid

Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: 'tuple' object does not support item assignment
>>> web_framewokrs = (["Django", "Flask"], ["AngularJS", "Dojo"])
>>> web_framewokrs[0].append("Pyramid")
(['Django', 'Flask', 'Pyramid'], ['AngularJS', 'Dojo'])

Set

Просто множества!

hackers = {"Rado", "Kamen", "Ivo"}
set3 = set1 & set2        # Intersection
set4 = set1 | set2        # Union
set5 = set1 - set3        # Set difference
set6 = set1 ^ set2        # Symmetric difference
issubset = set1 <= set2   # Subset test
issuperset = set1 >= set2 # Superset test

Нямаме подредба!

dict

lotr_heroes = {
    'Gandalf': 'the grey',
    'Frodo': 'Baggins',
}
lotr_heroes['Saruman'] = 'the white'

Нямаме подредба!

Iterable

  • list
  • str
  • tuple
  • dict
  • file object
  • object of any class with defined __iter__() method

Object capable of returning it's members one at a time

Можем да ги използваме с for, zip, map, etc.

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

When no more data is available a StopIteration exception is raised instead

Iterators are required to have an __iter__() method that returns the iterator object itself

 An iterator is an object which implements the iterator protoco

class MyIterator:
    def __init__(self):
        self.index = 0
        self.data = [1, 2, 3]

    def __iter__(self):
        return self

    def __next__(self):
        index = self.index
        self.index += 1

        try:
            return self.data[index]
        except IndexError:
            raise StopIteration

Как итерираме?

class Panda:
    def __init__(self, name):
        self.name = name

pandas = [Panda("Ivo"), Panda("Rado"), Panda("Roza")]
for panda in pandas:
    print(panda.name)

Как итерираме?(2)

class Panda:
    def __init__(self, name):
        self.name = name

    def __str__(self):
        return self.name


class Pandas(object):
    def __init__(self, pandas):
        self.pandas = pandas

    def __iter__(self):
        return self
    
    def __next__(self):
        if not self.pandas:
            raise StopIteration
    
        return self.pandas.pop()

pandas = Pandas([Panda("Ivo"), Panda("Rado"), 
                 Panda("Roza")])
iterator = iter(pandas)
print(next(iterator)) # Ivo
print(next(iterator)) # Rado

for panda in pandas:
    print(panda)

>>> Ivo
    Rado
    Roza

__iter__ и __next__

  • __iter__  -> Връща итератор, с който можем да обходим нашата колекция
  • __next__ -> Връща следващата стойност в обхождането

 

  1.  извиква __iter__ метода на arg, но ако се окаже, че такъв няма:
  2.  извиква __getitem__ с последователни естествени числа, започвайки от нула, докато не се хвърли StopIteration, ако няма такъв
  3. Хвърля грешка,че обектът не е итеруем

Какво прави iter(arg)?

>>> team = dict([('Ivo', '22'), ('Rado', '24'), ('Fandalf', 400)])
>>> iterator = iter(team)
>>> next(iterator)
'Rado'
>>> next(iterator)
'Fandalf'
>>> next(iterator)
'Ivo'
>>> next(iterator)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
StopIteration
>>> 

Пример 2

class MyMoney:
    def __getitem__(self, index):
        if index > 20:
            raise StopIteration()
        return 30 ** index

>>> ivo_money = MyMoney()
>>> for money in ivo_money:
>>>    print(money)
1
30
900
27000
810000
24300000
729000000
21870000000
656100000000
19683000000000
590490000000000
17714700000000000
531441000000000000
15943230000000000000
478296900000000000000
14348907000000000000000
430467210000000000000000
12914016300000000000000000
387420489000000000000000000
11622614670000000000000000000
348678440100000000000000000000
class FibUpTo:
    def __init__(self, up_to):
        self.up_to = up_to
        self.num = 0
        self.a = 1
        self.b = 1

    def __iter__(self):
        return self

    def __next__(self):
        if self.num > self.up_to:
            raise StopIteration

        if self.num < 2:
            self.num += 1
            return 1
        self.a, self.b = self.b, self.a + self.b
        self.num += 1
        return self.b

fib = FibUpTo(10)

for number in fib:
    print(number)

Мързеливост

Това означава, че всеки елемент се генерира чак когато е необходим.

>>> numbers = [[1,2,3], [1,2,3], [1,2,3]]
>>> sums = map(lambda x: sum(x), numbers)
>>> numbers[2].append(22)
>>> next(sums)
6
>>> next(sums)
6
>>> next(sums)
28

Други мързеливи функции:

  • any
  • all
  • map
  • filter
  • zip
  • enumerate

Function execution

  • an Exception is raised
  • there is a return statement
  • return implies the function is returning control of execution to the point where the function was called
  • and then there's yield
  • yield implies the transfer of control is temporary and the function expects to regain it in the future

Execution starts from the first line and continues until​

Generator

A generator generates values

If the body of a def contains yield, the function automatically becomes a generator function

Generator function creates generator iterator a.k.a. generator

We can get the values from a generator by calling next

Once a generator has been exhausted it will raise StopIteration

You can only ever consume all values from a generator once

Генератори

def panda_meals():
    yield 'Bamboo One'
    yield 'Bamboo Two'
    yield 'Bamboo Three'
    yield 'Bamboo Four'
    yield 'Bamboo Five'
    yield 'Bamboo Six'

meals = panda_meals()
for meal in meals:
    print("Num num num " + meal)

>>> Num num num Bamboo One
    Num num num Bamboo Two
    Num num num Bamboo Three
    Num num num Bamboo Four
    Num num num Bamboo Five
    Num num num Bamboo Six

Пример 2

def degrees(number):
    i = 2

    while i < 5:
        yield number ** i

        i += 1
        print(i)

degrees = degrees(2)
for degree in degrees:
    print(str(degree) + "\n")

>>> 4
    8
    16

И тях ги мързи!

def fib():
    a, b = 0, 1

    while True:
        yield a
        a, b = b, a + b

for f in fib():
    print(f)

.send

.send(value) - resumes execution & passes value into the generator

Simplified - calling .send(None) is equivalent to calling .next()

`send` returns the next value yielded by the generator

Or raises StopIteration exception

def complex_generator():
    print('Hey I just met you')

    items = [1, 2, 3]

    for item in items:
        print('And this is crazy')

        wtf = (yield item)

        print(wtf)

    print('But here is my number.')
    print('So call me maybe.')

    return 42

Iterators and Generators

By Hack Bulgaria

Iterators and Generators

  • 1,191