Iteration in Python Flashcards

1
Q

What makes an object iterable? Give the simple AND Pythonic explanation, and give 3 examples.

A

Simple: Can be looped (iterated) over

Pythonic: Returns an iterator when passed to iter(), i.e. the object has dunder methods \_\_iter\_\_() and/or \_\_getitem\_\_() defined.

Examples: str, list, dict

How well did you know this?
1
Not at all
2
3
4
5
Perfectly
2
Q

What is an iterator? Give the simple AND Pythonic explanation.

A

Simple: An iterable object that has two main actions:
* Returns data one item at a time
* Keeps track of current and visited items

Pythonic: An iterable object that implements the iterator protocol, i.e. defines \_\_next\_\_() and returns itself within \_\_iter\_\_()

How well did you know this?
1
Not at all
2
3
4
5
Perfectly
3
Q

What is the purpose of \_\_next\_\_()?

A

It’s used by an iterator to:
* Define exactly how it will traverse through its stored values
* Return the next value in the iteration
* Keep track of which values have already been called
* Raise a StopIteration exception when no more values are available

How well did you know this?
1
Not at all
2
3
4
5
Perfectly
4
Q

What’s one memory-related benefit of using iterators?

A

Iterators allow you to process the entire dataset one item at a time, which removes the need to store the entire dataset into memory.

How well did you know this?
1
Not at all
2
3
4
5
Perfectly
5
Q

What are the functions of the 3 types of iterators?

A
  1. Take stream of data, yield original data (classic iterator)
  2. Take stream of data, transform original data and yield new data
  3. Take no input data, yield results from a computation
How well did you know this?
1
Not at all
2
3
4
5
Perfectly
6
Q

What does it mean when you get a StopIteration error when calling an iterator with next()?

A

The iterator object is consumed, i.e. it has iterated through all of its values and you’ll need to create another one if you want to perform another iteration.

How well did you know this?
1
Not at all
2
3
4
5
Perfectly
7
Q

Implement a classic iterator that takes a sequence of values as an input and yields each value sequentially

A

create an iterator that takes a sequence of values at instantiation and is

class SequenceIterator:
    # store the sequence and set the index to 0 at initialization
    def \_\_init\_\_(self, sequence):
        self._sequence = sequence
        self._index = 0

    # per iterator protocol, iterators will always return itself in \_\_iter\_\_()
    def \_\_iter\_\_(self):
        return self

    # return the current value of the sequence and increment the index by 1
    # if index is incremented past the sequence, raise StopIteration
    def \_\_next\_\_(self):
        if self._index < len(self._sequence):
            item = self._sequence[self._index]
            self._index += 1
            return item
        raise StopIteration
How well did you know this?
1
Not at all
2
3
4
5
Perfectly
8
Q

What does iter() do?

A

Calls an iterable object’s \_\_iter\_\_() method to return an iterator. If \_\_iter\_\_() doesn’t exist but \_\_getitem\_\_() does, it will return an iterator that calls \_\_getitem\_\_() with i = range(inf) until a StopIteration exception is raised

How well did you know this?
1
Not at all
2
3
4
5
Perfectly
9
Q

T/F: An object isn’t iterable without \_\_iter\_\_() defined

A

False, as long as \_\_getitem\_\_() exists instead. If both don’t exist, then it’s True.

How well did you know this?
1
Not at all
2
3
4
5
Perfectly
10
Q

Implement Python’s for loop using the iterator protocol

A
iterator = sequence.\_\_iter\_\_()

while True:
    try:
        # get the next item using \_\_next\_\_()
        item = iterator.\_\_next\_\_()
    except StopIteration:
        break
    else:
        # the loop's code goes here
        print(item)
How well did you know this?
1
Not at all
2
3
4
5
Perfectly
11
Q

What does it mean when an iterator is lazy?

A

It means that an iterator will only calculate and/or return one value at a time. It is hugely efficient in terms of memory consumption because your code will only require memory for a single item at a time.

How well did you know this?
1
Not at all
2
3
4
5
Perfectly
12
Q

How do you create an infinite iterator?

A

Create a normal iterator, but don’t include the StopIteration exception

How well did you know this?
1
Not at all
2
3
4
5
Perfectly
13
Q

Which of Python’s abstract base classes (ABCs) can you inherit to more easily create custom iterators?

A

collections.abc.Iterator, which

How well did you know this?
1
Not at all
2
3
4
5
Perfectly
14
Q

What is a generator function?

A

A unique function that returns a generator iterator and keeps track of its state with the yield keyword

How well did you know this?
1
Not at all
2
3
4
5
Perfectly
15
Q

How do generator functions/expressions differ from custom iterable classes?

A
How well did you know this?
1
Not at all
2
3
4
5
Perfectly
16
Q

Write a generator function that yields an infinite sequence of numbers, then state the built-in one from Python

A
def infinite_sequence():
    num = 0
    while True:
        yield num
        num += 1

itertools.count()

17
Q

How does the syntax differ between a generator expression and list comprehension?

A

Generator expression uses (), while list comprehension uses []

18
Q

What’s a downside of using generator expressions in place of list comprehension?

A

Since generator expressions are equivalent to creating a generator function and then calling it, there is added overhead of calling the function. Because of this, generator expressions are slightly slower than list comprehension

19
Q

T/F: Generator expressions are more performant than generator functions

A

False, expressions just allow you to define simple generators in a single line with an assumed yield at the end of each inner iteration

20
Q

How does yield work in a generator function?

A

When the generator from a generator function is called, the code within the function is executed up to the yield statement. The program then suspends the function, returns the yielded value to the caller, and then saves the state of the function (i.e. the local variables, instruction pointer, internal stack, and exception handling). When the generator is called again, the function continues off from yield until it hits another yield, or when StopIteration is returned

21
Q

What happens when you use return in a generator function?

A

It will stop the function execution completely. It’s useful if you want more control over function termination than just hitting StopIteration

22
Q

What are the main differences between yield and return?

A
  • return stops the function execution completely, while yield suspends function execution
  • return returns the entire output of a function, while yield returns a generator object
  • yield keeps track of the state of the function execution, while return does not
23
Q

Explain what’s happening in the code below, assuming that is_palindrome(num) returns True or False depending on whether num is a palendrome:
~~~
def infinite_palindromes():
num = 0
while True:
if is_palindrome(num):
i = (yield num)
if i is not None:
num = i
num += 1
~~~
and you execute the code below:
~~~
pal_gen = infinite_palindromes()
for j in pal_gen:
print(f”current palendrome: {i}”)
digits = len(str(j))
pal_gen.send(10 ** (digits))
~~~

A
  1. pal_gen is the generator returned by the infinite_palindromes() gen function.
  2. pal_gen will find the next palindrome in the sequence, set variable i to the yielded result within the gen function with i = (yield num), and then yield the result as j in the for loop.
  3. digits stores the number of digits in the current palindrome.
  4. 10 ** digits is then sent back to pal_gen using .send() where i is now updated.
  5. The gen function resumes execution, now with an updated num value.
current palendrome: 11
current palendrome: 111
current palendrome: 1111
current palendrome: 10101
current palendrome: 101101
24
Q

If pal_gen is a generator, what’s the difference between the following methods?
~~~
pal_gen.throw(ValueError(“We don’t like large palindromes!”))
~~~
and
~~~
pal_gen.close()
~~~

A

Both methods will cause the generator to finish, however .throw() will tell pal_gen to throw the given exception, while .close() will always tell pal_gen to throw StopIteration