17 OOP 4: Data Class, Iterators, Generators, Context Managers

image.png Դադիվանք, լուսանկարի հղումը, Հեղինակ՝ Naré Gevorgyan

📚 Նյութը

Dataclasses

class Person:
    def __init__(self, first_name, last_name, id_number=None):
        self.first_name = first_name
        self.last_name = last_name
        self.id_number = id_number
        
p = Person("John", "Smith", "509")

print(p)
<__main__.Person object at 0x0000023A18696710>
class Person:
    def __init__(self, first_name, last_name, id_number=None):
        self.first_name = first_name
        self.last_name = last_name
        self.id_number = id_number
        
    def __repr__(self):
        return f"Person({self.first_name}, {self.last_name}, {self.id_number})"
    
p = Person("John", "Smith", "509")

print(p)
Person(John, Smith, 509)
from dataclasses import dataclass

@dataclass
class Person:
    first_name: str # type hint
    last_name: str
    id_number: str

p = Person("John", "Smith", "509")
Person(first_name='John', last_name='Smith', id_number='509')

Here, __init__, __repr__, and __eq__ come for free.

from dataclasses import dataclass

@dataclass
class Person:
    first_name: str    # becomes a field
    last_name          # no annotation → ignored by dataclasses
    id_number: str     # becomes a field

p = Person("John", "Smith", "509")
---------------------------------------------------------------------------
NameError                                 Traceback (most recent call last)
Input In [54], in <cell line: 3>()
      1 from dataclasses import dataclass
      3 @dataclass
----> 4 class Person:
      5     first_name: str    # becomes a field
      6     last_name          # no annotation → ignored by dataclasses

Input In [54], in Person()
      3 @dataclass
      4 class Person:
      5     first_name: str    # becomes a field
----> 6     last_name          # no annotation → ignored by dataclasses
      7     id_number: str

NameError: name 'last_name' is not defined

Default values

datetime.date(2025, 6, 20)
from dataclasses import dataclass, field
from datetime import date

@dataclass
class Person:
    first_name: str
    last_name: str
    id_number: str
    email: str = "չունի"                                # simple default
    signup_date: date = field(default_factory=date.today)  # today by default
    lst: list = field(default_factory=list)  # empty list by default
# Usage
p = Person("John", "Smith", "509")
print(p.email, p.signup_date, p.lst)
չունի 2025-06-20 []

Excluding arguments for the initializer

@dataclass
class Person:
    first_name: str
    last_name: str
    id_number: str
    secret_code: str = field(init=False)  # not passed to __init__

# Usage
p = Person("John", "Smith", "509")
---------------------------------------------------------------------------
TypeError                                 Traceback (most recent call last)
Input In [60], in <cell line: 9>()
      6     secret_code: str# = field(init=False)  # not passed to __init__
      8 # Usage
----> 9 p = Person("John", "Smith", "509")

TypeError: Person.__init__() missing 1 required positional argument: 'secret_code'
class Person:
    def __init__(self, first_name, last_name, id_number=None):
        self.first_name = first_name
        self.last_name = last_name
        self.id_number = id_number
        
        
        self.full_name = f"{self.first_name} {self.last_name}"  # computed property
        if self.name == "a":
            raise ValueError("Name cannot be 'a'")  # validation
# Usage
p = Person("John", "Smith", "509", "12321")
print(p)
Person(first_name='John', last_name='Smith', id_number='509')

__post_init__

@dataclass
class Person:
    first_name: str
    last_name: str
    id_number: str
    full_name: str = field(init=False)

    def __post_init__(self):
        # derive full_name from other fields
        object.__setattr__(self, 'full_name', f"{self.first_name} {self.last_name}")

# Usage
p = Person("John", "Smith", "509")
print(p.full_name)
John Smith

Freezing a data class

@dataclass(frozen=True)
class Person:
    first_name: str
    last_name: str
    id_number: str

# Usage
p = Person("John", "Smith", "509")

p.first_name = "johnny la gente esta muy loca"  # FrozenInstanceError
---------------------------------------------------------------------------
FrozenInstanceError                       Traceback (most recent call last)
Input In [69], in <cell line: 10>()
      7 # Usage
      8 p = Person("John", "Smith", "509")
---> 10 p.first_name = "johnny la gente esta muy loca"  # FrozenInstanceError

File <string>:4, in __setattr__(self, name, value)

FrozenInstanceError: cannot assign to field 'first_name'

Regular staff

@dataclass(frozen=True)
class Person:
    first_name: str
    last_name: str
    id_number: str

    def get_paniramid(self):
        for i in range(15):
            print("🧀"*i)

p = Person("John", "Smith", "509")

p.get_paniramid()  

🧀
🧀🧀
🧀🧀🧀
🧀🧀🧀🧀
🧀🧀🧀🧀🧀
🧀🧀🧀🧀🧀🧀
🧀🧀🧀🧀🧀🧀🧀
🧀🧀🧀🧀🧀🧀🧀🧀
🧀🧀🧀🧀🧀🧀🧀🧀🧀
🧀🧀🧀🧀🧀🧀🧀🧀🧀🧀
🧀🧀🧀🧀🧀🧀🧀🧀🧀🧀🧀
🧀🧀🧀🧀🧀🧀🧀🧀🧀🧀🧀🧀
🧀🧀🧀🧀🧀🧀🧀🧀🧀🧀🧀🧀🧀
🧀🧀🧀🧀🧀🧀🧀🧀🧀🧀🧀🧀🧀🧀

Generators

Լավ նյութեր 1. https://realpython.com/introduction-to-python-generators/ 2. https://www.youtube.com/watch?v=bD05uGo_sVI


print(type(range(1,3)))
print(range(1,3))
<class 'range'>
range(1, 3)
a = [i**2 for i in range(-3, 3)]
print(a)
a = {i**2 for i in range(-3, 3)}
print(a)
a = (i**2 for i in range(-3, 3))
print(a)
a = tuple(i**2 for i in range(-3, 3))
print(a)
[9, 4, 1, 0, 1, 4]
{0, 9, 4, 1}
<generator object <genexpr> at 0x0000023A186F35A0>
(9, 4, 1, 0, 1, 4)
for i in [1,2,3,4,5,6,7,8,9,10]:
    print(i**2)

for i in range(1,11):
    print(i**2)
1
4
9
16
25
36
49
64
81
100
1
4
9
16
25
36
49
64
81
100
print([1,2,3,4,5,6,7,8,9,10].__sizeof__())
print(range(1,11).__sizeof__())
120
48
print([1,2,3,4,5,6,7,8,9,10,11].__sizeof__())
print(range(1, 1000000000000000000000000000000000000000).__sizeof__())


# # start = 1
# end = 10
# step = 3
# start = 4 + 3 = 7 
for i in range(1, 10, 3):
    start, stop, step   
136
48

image.png

image.png

քառակուսի բարձրացնելու օրինակ

def qarakusi_bardzracnel(tver):
    qarakusiner = []
    for i in tver:
        qarakusiner.append(i**2)
    return qarakusiner

tver = list(range(-5, 5))
print(qarakusi_bardzracnel(tver))
[25, 16, 9, 4, 1, 0, 1, 4, 9, 16]
def qarakusi_bardzracnel(tver):
    for i in tver:
        yield i**2

tver = list(range(-5, 5))
print(qarakusi_bardzracnel(tver))
<generator object qarakusi_bardzracnel at 0x0000023A18715F50>
generator = qarakusi_bardzracnel(tver)
next(generator)
4
for i in generator:
    print(i)
1
0
1
4
9
16
next(generator) # .__next__()
next(generator)
next(generator)
next(generator)
next(generator)
next(generator)
next(generator)
---------------------------------------------------------------------------
StopIteration                             Traceback (most recent call last)
Input In [89], in <cell line: 1>()
----> 1 next(generator)
      2 next(generator)
      3 next(generator)

StopIteration: 

Comprehension-ի տեսքով

qarakusiner = [i**2 for i in range(-5, 5)]
print(qarakusiner)
[25, 16, 9, 4, 1, 0, 1, 4, 9, 16]
qarakusiner = (i**2 for i in range(-5, 5))
print(qarakusiner)
<generator object <genexpr> at 0x0000023A18717C30>
next(qarakusiner)

# print(range(3,5))
# print(list(range(3,5)))
25
list(qarakusiner)
[16, 9, 4, 1, 0, 1, 4, 9, 16]
list(range(-5,5))

# [next(), StopIteration 
[-5, -4, -3, -2, -1, 0, 1, 2, 3, 4]

Iterators, iterables

Նյութեր

  1. https://realpython.com/python-iterators-iterables/#iterating-through-iterables-with-for-loops
  2. https://www.programiz.com/python-programming/iterator
  3. https://www.youtube.com/watch?v=jTYiNjvnHZY
  4. https://www.youtube.com/watch?v=C3Z9lJXI6Qw

եկեք հիմա էլ նայենք ոնցա աշխատում for loopը

lst = [1, 2, 3]

for i in lst:
    print(i)
    
# lst[0] # lst.__getitem__(0)
# print(lst) # lst.__str__()

# {1,2,3}[0]
# lst -> for loop 
1
2
3
lst = [1, 2, 3]

next(lst) # a.__next__()
---------------------------------------------------------------------------
TypeError                                 Traceback (most recent call last)
Input In [97], in <cell line: 3>()
      1 lst = [1, 2, 3]
----> 3 next(lst) # a.__next__()

TypeError: 'list' object is not an iterator
type(lst)
list
iter(lst)
<list_iterator at 0x23a186ecf70>
print(type(lst))
a_iter = iter(lst) # __iter__()
print(type(a_iter))
<class 'list'>
<class 'list_iterator'>
next(a_iter) # a_iter.__next__()
---------------------------------------------------------------------------
StopIteration                             Traceback (most recent call last)
Input In [105], in <cell line: 1>()
----> 1 next(a_iter)

StopIteration: 
class VrazList:
    def __init__(self, lst):
        self.lst = lst

    def __iter__(self):
        pass
a = VrazList([1,2,3,4,5])
print(a)
print(type(a))
print(a.lst)
<__main__.VrazList object at 0x0000023A186EEB00>
<class '__main__.VrazList'>
[1, 2, 3, 4, 5]
for i in a:
    print(i)
---------------------------------------------------------------------------
TypeError                                 Traceback (most recent call last)
Input In [108], in <cell line: 1>()
----> 1 for i in a:
      2     print(i)

TypeError: iter() returned non-iterator of type 'NoneType'
class VrazList:
    def __init__(self, lst):
        self.lst = lst
        self.index = 0

    def __iter__(self):
        return self

    def __next__(self):
        current = self.lst[self.index]
        self.index += 2
        return current
for i in [1, 2, 3, 4, 5]:
    print(i)
1
2
3
4
5
a = VrazList([1,2,3,4,5])

# a_iter = iter(a)
# next(a_iter)
print(type(a))

for i in a:
    print(i)
    
# a -> iter(a)
# next(a)
# next(a)
# while StopIteration:
    
    
<class '__main__.VrazList'>
1
3
5
---------------------------------------------------------------------------
IndexError                                Traceback (most recent call last)
Input In [110], in <cell line: 7>()
      3 # a_iter = iter(a)
      4 # next(a_iter)
      5 print(type(a))
----> 7 for i in a:
      8     print(i)

Input In [109], in VrazList.__next__(self)
      9 def __next__(self):
---> 10     current = self.lst[self.index]
     11     self.index += 2
     12     return current

IndexError: list index out of range
class VrazList:
    def __init__(self, lst):
        self.lst = lst
        self.index = 0

    def __iter__(self):
        return self

    def __next__(self):
        if self.index >= len(self.lst):
            raise StopIteration

        current = self.lst[self.index]
        self.index += 2
        return current
a = VrazList([1,2,3,4,5])

for i in a:
    print(i)
1
3
5
a = VrazList([1,2,3,4,5])
while True:
    try:
        i = next(a)
        print(i)

    except StopIteration:
        break
1
3
5

Context managers (with)

լավ նյութ՝ https://www.youtube.com/watch?v=-aKFBoZpiqA

f = open("panir.txt", "w")
f.write("պանիր պանիր պանիր")

print(f.closed)  # False

f.close()
print(f.closed)  # True
False
True
with open("panir.txt", "w") as f:
    f.write("պանիր պանիր պանիր")

# f.__exit__(None, None, None)  # f.close() is called automatically

print(f.closed)
# ոնցա՞ հասկանում որ պետքա close անի վերջում
True

Իրականում մենք ճկունություն ունենք ոչ միայն ընդգծելու թե ինչ պետք ա աշխատեի էդ f.write("պանիր պանիր պանիր")-ից հետո (էս դեպքում՝ f.close()), բայց նաև ինչ աշխատի մինչև էդ with-ի մեջ եղած կոդը

class Bacich:
    def __init__(self, name, mode):
        self.name = name
        self.mode = mode

    def __enter__(self):
        print(f"\nհեսա փորձելու ենք բացել {self.name}{self.mode} նպատակով")
        self.file = open(self.name, self.mode)
        return self.file

    def __exit__(self, exc_type, exc_val, traceback):
        print("\nփակենք")
        self.file.close()
a = Bacich("panir.txt", "r")
a.__enter__()

հեսա փորձելու ենք բացել panir.txt-ը r նպատակով
<_io.TextIOWrapper name='panir.txt' mode='r' encoding='UTF-8'>
a.file
<_io.TextIOWrapper name='panir.txt' mode='r' encoding='UTF-8'>

with Bacich("panir.txt", "r") as f:
    # some code
    print(f.read())

# print(f.closed)
# # Bacich("panir.txt", "r") -> __enter__()
# enter's returns -> f

# print(f.read())

# f.__exit__(None, None, None)  # __exit__ is called automatically


# f = Bacich("panir.txt", "r").__enter__()
# # some code
# Bacich("panir.txt", "r").__exit__()

հեսա փորձելու ենք բացել panir.txt-ը r նպատակով
պանիր պանիր պանիր

փակենք
f.closed
True

աշխատեց enter-ը հետո with-ի մեջի կոդը, հետո exit

# exc_type ... 
class Bacich:
    def __init__(self, name, mode):
        self.name = name
        self.mode = mode

    def __enter__(self):
        print("\nհեսա փորձելու ենք բացել {self.name}ը {self.mode} նպատակով\n")
        self.file = open(self.name, self.mode)
        return self.file

    def __exit__(self, exc_type, exc_val, traceback):
        print("\nփակենք")
        print("exc_type =", exc_type) # exception type
        print("exc_val =", exc_val)
        print("traceback = ", traceback)
        self.file.close()
with Bacich("panir.txt", "r") as f:
    print("ա")
    print(f.read())

print(f.closed)

հեսա փորձելու ենք բացել {self.name}ը {self.mode} նպատակով

ա
պանիր պանիր պանիր

փակենք
exc_type = None
exc_val = None
traceback =  None
True
with Bacich("panir.txt", "r") as f:
    print("ա")
    print(lav_katakner_haykic)
    print(f.read())
    
# ...
# f.__exit__(Errort, ...., traceback)

հեսա փորձելու ենք բացել {self.name}ը {self.mode} նպատակով

ա

փակենք
exc_type = <class 'NameError'>
exc_val = name 'lav_katakner_haykic' is not defined
traceback =  <traceback object at 0x0000023A1873C180>
---------------------------------------------------------------------------
NameError                                 Traceback (most recent call last)
Input In [134], in <cell line: 1>()
      1 with Bacich("panir.txt", "r") as f:
      2     print("ա")
----> 3     print(lav_katakner_haykic)
      4     print(f.read())

NameError: name 'lav_katakner_haykic' is not defined

ԻՆչ-որ հարցրեք - հլը մի րոպե բայց, բայց open-ը ֆունցկիա ա ոչ թե class, էս ո՞նց ա աշխատում

Ֆունկցիաների համար - contextmanager decorator

type(open)
builtin_function_or_method
from contextlib import contextmanager

@contextmanager
def bacich(name, mode):
    # __enter__ method
    print(f"հեսա փորձելու ենք բացել {name}ը {mode} նպատակով\n")
    file = open(name, mode)
    yield file # return
    # __exit__ method
    print("\nժամանակն է փակելու ֆայլը")
    file.close()


# class Bacich:
#     def __init__(self, name, mode):
#         self.name = name
#         self.mode = mode

#     def __enter__(self):
#         print("\nհեսա փորձելու ենք բացել {self.name}ը {self.mode} նպատակով\n")
#         self.file = open(self.name, self.mode)
#         return self.file

#     def __exit__(self, exc_type, exc_val, traceback):
#         print("\nփակենք")
#         print("exc_type =", exc_type)
#         print("exc_val =", exc_val)
#         print("traceback = ", traceback)
#         self.file.close()
with bacich("panir.txt", "r") as f:
    print("a")
    print(f.read())

print(f.closed)
հեսա փորձելու ենք բացել panir.txtը r նպատակով

a
պանիր պանիր պանիր

ժամանակն է փակելու ֆայլը
True
  1. Աշխատումա բացիչ ֆունկցիայի բոլոր տողերը մինչև yield
  2. yield արած արժեքը վերագրվումա fին
  3. աշխատումա withի միջի կոդը
  4. աշխատումա yieldից հետո գրվածը
with bacich("panir.txt", "r") as f:
    print("a")
    print(lav_katakner_haykic_2)
    print(f.read())
---------------------------------------------------------------------------
NameError                                 Traceback (most recent call last)
Input In [45], in <cell line: 1>()
----> 1 with bacich("panir.txt", "r") as f:
      2     print("a")
      3     print(lav_katakner_haykic_2)

NameError: name 'bacich' is not defined
f.closed
True
from contextlib import contextmanager

@contextmanager
def bacich(name, mode):
    try:
        print(f"հեսա փորձելու ենք բացել {name}ը {mode} նպատակով\n")
        file = open(name, mode)
        yield file
    finally: # որ միշտ աշխատի
        print("\nժամանակն է փակելու ֆայլը")
        file.close()
with bacich("panir.txt", "r") as f:
    print("a")
    print(lav_katakner_haykic_2)
    print(f.read())
հեսա փորձելու ենք բացել panir.txtը r նպատակով

a

ժամանակն է փակելու ֆայլը
---------------------------------------------------------------------------
NameError                                 Traceback (most recent call last)
Input In [139], in <cell line: 1>()
      1 with bacich("panir.txt", "r") as f:
      2     print("a")
----> 3     print(lav_katakner_haykic_2)
      4     print(f.read())

NameError: name 'lav_katakner_haykic_2' is not defined
print(f.closed)
True

Օրինակ - timer

import time
from contextlib import contextmanager

@contextmanager
def timer():
    start_time = time.time()
    yield
    end_time = time.time()
    print(f"Duration: {end_time - start_time} seconds")
with timer():
    print("սկսենք երգել")
    print("շուդադի շուդադա վիզաութ յոռ լավ")
    time.sleep(2)
    print("պակլոն "*3)
սկսենք երգել
շուդադի շուդադա վիզաութ յոռ լավ
պակլոն պակլոն պակլոն 
Duration: 2.0101306438446045 seconds

Պրոեկտի համար թիմերի բաժանվել

import random

TEAM_SIZE = 3
teams = []
lst = [f"Մարդ {i}" for i in range(1, 11)]
# 3 player teams
random.shuffle(lst)

num_players = len(lst)
num_teams = num_players / 3

print(f"Num players: {len(lst)}")
print(f"Num teams: {num_teams}")

if num_players % num_teams != 0:
    num_teams = int(num_teams) + 1

print(num_players % num_teams)
if num_players % TEAM_SIZE == 1:
    num_teams -= 2
    
print(num_teams)
for i in range(num_teams):
    print(lst[3*i:3*(i+1)])
    
if num_players % TEAM_SIZE == 1:
    print(lst[-4:-2])
    print(lst[-2:])
Num players: 10
Num teams: 3.3333333333333335
2
2
['Մարդ 5', 'Մարդ 6', 'Մարդ 3']
['Մարդ 8', 'Մարդ 7', 'Մարդ 10']
['Մարդ 9', 'Մարդ 1']
['Մարդ 2', 'Մարդ 4']

🎲 17

Flag Counter