13.4. Generator Inspect

13.4.1. SetUp

>>> from inspect import isgenerator

13.4.2. Is Generator

>>> a = [x for x in range(0,5)]
>>> b = (x for x in range(0,5))
>>>
>>> isgenerator(a)
False
>>>
>>> isgenerator(b)
True
>>> data = range(0, 10)
>>>
>>> isgenerator(data)
False

13.4.3. Introspection

>>> data = (x for x in range(0,10))
>>>
>>>
>>> next(data)
0
>>>
>>> data.gi_code  
<code object <genexpr> at 0x..., file "<...>", line 1>
>>>
>>> data.gi_running
False
>>>
>>> data.gi_frame  
<frame at 0x..., file '<...>', line 1, code <genexpr>>
>>>
>>> data.gi_frame.f_locals  
{'.0': <range_iterator object at 0x...>, 'x': 0}
>>>
>>> data.gi_frame.f_code  
<code object <genexpr> at 0x...0, file "<...>", line 1>
>>>
>>> data.gi_frame.f_lineno
1
>>>
>>> data.gi_frame.f_lasti
18
>>>
>>> data.gi_yieldfrom

13.4.4. Memory Footprint

  • sys.getsizeof(obj) returns the size of an obj in bytes

  • sys.getsizeof(obj) calls obj.__sizeof__() method

  • sys.getsizeof(obj) adds an additional garbage collector overhead if the obj is managed by the garbage collector

>>> from sys import getsizeof
  • 192 bytes for generator in Python 3.13

  • 192 bytes for generator in Python 3.12

  • 200 bytes for generator in Python 3.11

  • 104 bytes for generator in Python 3.10

  • 112 bytes for generator in Python 3.9

  • 112 bytes for generator in Python 3.8

  • 120 bytes for generator in Python 3.7

>>> gen1 = (x for x in range(0,1))
>>> gen10 = (x for x in range(0,10))
>>> gen100 = (x for x in range(0,100))
>>> gen1000 = (x for x in range(0,1000))
>>>
>>> getsizeof(gen1)
192
>>>
>>> getsizeof(gen10)
192
>>>
>>> getsizeof(gen100)
192
>>>
>>> getsizeof(gen1000)
192
>>> com1 = [x for x in range(0,1)]
>>> com10 = [x for x in range(0,10)]
>>> com100 = [x for x in range(0,100)]
>>> com1000 = [x for x in range(0,1000)]
>>>
>>>
>>> getsizeof(com1)
88
>>>
>>> getsizeof(com10)
184
>>>
>>> getsizeof(com100)
920
>>>
>>> getsizeof(com1000)
8856
../../_images/generator-function-performance.png

Figure 13.1. Source: https://www.askpython.com/python/python-yield-examples

13.4.5. Assignments

# %% License
# - Copyright 2025, Matt Harasymczuk <matt@python3.info>
# - This code can be used only for learning by humans
# - This code cannot be used for teaching others
# - This code cannot be used for teaching LLMs and AI algorithms
# - This code cannot be used in commercial or proprietary products
# - This code cannot be distributed in any form
# - This code cannot be changed in any form outside of training course
# - This code cannot have its license changed
# - If you use this code in your product, you must open-source it under GPLv2
# - Exception can be granted only by the author

# %% Run
# - PyCharm: right-click in the editor and `Run Doctest in ...`
# - PyCharm: keyboard shortcut `Control + Shift + F10`
# - Terminal: `python -m doctest -v myfile.py`

# %% About
# - Name: Generator Function Iris
# - Difficulty: easy
# - Lines: 8
# - Minutes: 5

# %% English
# 1. Write filter for `DATA` which returns `features: list[float]` for given `species: str`
# 2. Implement solution using function
# 3. Implement solution using generator and `yield` keyword
# 4. Compare results of both using `sys.getsizeof()`
# 5. What will happen if input data will be bigger?
# 6. Note, that in different Python versions you'll get slightly
#    different values for getsizeof generator and function:
#    - 216 for generator in Python 3.13
#    - 216 for generator in Python 3.12
#    - 224 for generator in Python 3.11
#    - 104 for generator in Python 3.10
#    - 112 for generator in Python 3.9
#    - 112 for generator in Python 3.8
#    - 120 for generator in Python 3.7
# 7. Run doctests - all must succeed

# %% Polish
# 1. Napisz filtr dla `DATA` zwracający `features: list[float]` dla danego gatunku `species: str`
# 2. Zaimplementuj rozwiązanie wykorzystując funkcję
# 3. Zaimplementuj rozwiązanie wykorzystując generator i słowo kluczowe `yield`
# 4. Porównaj wyniki obu używając `sys.getsizeof()`
# 5. Co się stanie, gdy ilość danych będzie większa?
# 6. Zwróć uwagę, że w zależności od wersji Python wartości getsizeof
#    dla funkcji i generatora mogą się nieznaczenie różnić:
#    - 216 dla generator w Python 3.13
#    - 216 dla generator w Python 3.12
#    - 224 dla generator w Python 3.11
#    - 104 dla generator w Python 3.10
#    - 112 dla generator w Python 3.9
#    - 112 dla generator w Python 3.8
#    - 120 dla generator w Python 3.7
# 7. Uruchom doctesty - wszystkie muszą się powieść

# %% Tests
"""
>>> import sys; sys.tracebacklimit = 0
>>> assert sys.version_info >= (3, 9), \
'Python 3.9+ required'

>>> from sys import getsizeof
>>> from inspect import isfunction, isgeneratorfunction

>>> assert isfunction(function)
>>> assert isgeneratorfunction(generator)

>>> list(function(DATA, 'setosa'))
[[5.1, 3.5, 1.4, 0.2], [4.7, 3.2, 1.3, 0.2]]
>>> list(generator(DATA, 'setosa'))
[[5.1, 3.5, 1.4, 0.2], [4.7, 3.2, 1.3, 0.2]]

>>> getsizeof(function(DATA, 'setosa'))
88
>>> getsizeof(function(DATA*10, 'setosa'))
248
>>> getsizeof(function(DATA*100, 'setosa'))
1656
>>> getsizeof(generator(DATA, 'setosa'))
216
>>> getsizeof(generator(DATA*10, 'setosa'))
216
>>> getsizeof(generator(DATA*100, 'setosa'))
216
"""

DATA = [
    (5.8, 2.7, 5.1, 1.9, 'virginica'),
    (5.1, 3.5, 1.4, 0.2, 'setosa'),
    (5.7, 2.8, 4.1, 1.3, 'versicolor'),
    (6.3, 2.9, 5.6, 1.8, 'virginica'),
    (6.4, 3.2, 4.5, 1.5, 'versicolor'),
    (4.7, 3.2, 1.3, 0.2, 'setosa'),
]


# Function get `features: list[float]` from `DATA` for given `species`
# type: Callable[[list[float|str], str], list[float]]
def function(data: list, species: str):
    ...


# Generator get `features: list[float]` from `DATA` for given `species`
# type: Generator
def generator(data: list, species: str):
    ...