<jupyter_start><jupyter_text>-->Язык программирования Python<NAME>. <NAME>e-mail: <EMAIL>, <EMAIL> Лекция 7. Объектно-ориентированное программирование в Python  classClasses provide a means of bundling data and functionality together. Creating a new class creates a new type of object, allowing new instances of that type to be made. Each class instance can have attributes attached to it for maintaining its state. Class instances can also have methods (defined by its class) for modifying its state.<jupyter_code>class MyClass: # минимальный класс
    pass

MyClass
my_instance = MyClass() # экземпляр класса (объект)
my_instance
# класс в Python 3 автоматически наследуется от класса object

# проверим, является ли my_instance экземпляром класса object или его наследников

for cls in [str, dict, object]:
    print(cls, isinstance(my_instance, cls))
# наследование от класса object позволяет выполнять интроспекцию и другие полезные действия
# какие уже есть методы у объекта my_instance? а какие унаследованы от object?

inst = set(dir(my_instance))
obj  = set(dir(object))
inst.intersection(obj)
inst.difference(obj)
# например, всегда можно узнать класс, к которому принадлежит экземпляр
my_instance.__class__<jupyter_output><empty_output><jupyter_text>`__repr__` - представление, образ объекта<jupyter_code># метод, предназначенный для вывода объекта на экран с целью его восприятия и понимания человеком
# по-умолчанию он возвращает строку, в которой указан класс и адрес экземпляра в памяти
my_instance.__repr__()<jupyter_output><empty_output><jupyter_text>`__doc__` - документация объекта<jupyter_code># docstring - строка, содержащая документацию (описание) объекта
# по-умолчанию отсутствует
print(my_instance.__doc__)
class MyClass:
    '''MyClass. Документация для класса должна быть здесь.''' # документирующая строка для класса
    
    def __repr__(self):
        '''MyClass.__repr__.
        Документация для метода __repr__ должна быть здесь.''' # документирующая строка для метода __repr__
        return "MyClass. Nothing to represent here!"
MyClass # <Shift+Tab>, чтобы увидеть документацию класса
my_instance = MyClass() # аналогично
my_instance.__repr__() # <Shift+Tab>, чтобы увидеть документацию метода<jupyter_output><empty_output><jupyter_text>`__method__` - magic method, dunder method, special methodТермин dunder, вероятно, произошел от double underscore.Во многих обучающих видео употребляют этот термин.  `__init__` - конструктор<jupyter_code>class MyClass:
    def __init__(self, value): # объявление конструктора с одним позиционным параметром value
        self.value = value # каждый экземпляр класса MyClass будет иметь свое поле (атрибут) value
        
my_instance = MyClass()<jupyter_output><empty_output><jupyter_text>self - ссылка на объект, для которого вызван методВ C++ аналог *this<jupyter_code>m1, m2 = MyClass(5), MyClass(10)
m1.value, m2.value<jupyter_output><empty_output><jupyter_text>`__add__` - оператор бинарного сложения<jupyter_code>MyClass(5) + MyClass(6)
class MyClass:
    def __init__(self, value): # объявление конструктора с одним позиционным параметром value
        self.value = value # каждый экземпляр класса MyClass будет иметь свое поле (атрибут) value
        
    def __add__(self, other):
        return MyClass(self.value+other.value)
    
    def __repr__(self):
        return '%r'%self.__dict__
    
MyClass(5) + MyClass(6)<jupyter_output><empty_output><jupyter_text>Пример класса - Animal (Животное) ![Животные](https://upload.wikimedia.org/wikipedia/commons/1/14/Animal_diversity.png)<jupyter_code># класс Animal - базовый класс, представляющий общие свойства животного
class Animal:
    
    def __init__(self, limbs, eyes, carnivore=False):
        self.limbs = limbs
        self.eyes = eyes
        self.carnivore = carnivore
                    
    def __repr__(self):
        return "%s animal with %r limbs and %r eyes" % \
               ('Carnivore' if self.carnivore else 'Herbivore', self.limbs, self.eyes)
a = Animal(4, 2, True)
a
a.limbs = 6
a<jupyter_output><empty_output><jupyter_text>Поля (атрибуты) класса и статические методы @staticmethod<jupyter_code>class Animal:
    
    animals = [] # поле класса - аналог статических полей в C++
                 # единое поле для всех объектов
            
    # аналог статических методов в C++; не имеет доступа к полям объекта, работает только с полями класса
    @staticmethod        
    def count():
        return len(Animal.animals)
    
    # просто функция внутри пространства имен класса
    def nonstatic_count():
        return len(Animal.animals)
    
    def __init__(self, limbs, eyes, carnivore=False):
        self.limbs = limbs
        self.eyes = eyes
        self.carnivore = carnivore
        Animal.animals.append(self)
                    
    def __repr__(self):
        return "%s animal with %r limbs and %r eyes"%('Carnivore' if self.carnivore else 'Herbivore',
                                          self.limbs, self.eyes)
# создание двух экземпляров класса Animal
tiger, bird = Animal(4, 2, True), Animal(2, 2, False)
Animal.animals
Animal.count(), Animal.nonstatic_count()
tiger.count()
tiger.nonstatic_count()<jupyter_output><empty_output><jupyter_text>Наследование<jupyter_code>class Tiger(Animal):
    def __init__(self, white=False): # перегрузка конструктора
        super().__init__(4, 2, True) # вызов конструктора родителя
        self.white = white
        
    def __repr__(self): # перегрузка метода __repr__
        return "%s tiger"%('White' if self.white else 'Regular')    
tiger = Tiger()
tiger.animals
tiger.limbs
tiger.limbs = 5
tiger.limbs<jupyter_output><empty_output><jupyter_text>getter, setter<jupyter_code>class Animal:
    
    animals = []
    
    def __init__(self, limbs, eyes, carnivore=False):
        self.__limbs = limbs
        self.eyes = eyes
        self.carnivore = carnivore
        Animal.animals.append(self)
        
    def get_limbs(self):
        return self.__limbs
    
    def set_limbs(self, limbs):
        self.__limbs = limbs
                    
    def __repr__(self):
        return "%s animal with %r limbs and %r eyes"%('Carnivore' if self.carnivore else 'Herbivore',
                                          self.__limbs, self.eyes)
a = Animal(2, 2, False)
a
a.set_limbs(3)
a
class Tiger(Animal):
    def __init__(self, white=False): # перегрузка конструктора
        super().__init__(4, 2, True) # вызов конструктора родителя
        self.white = white
        
    def set_limbs(self, limbs):
        pass
    
    def __repr__(self): # перегрузка метода __repr__
        return "%s tiger"%('White' if self.white else 'Regular')    
tiger = Tiger()
tiger, tiger.get_limbs()
tiger.set_limbs(5)
tiger.get_limbs()
tiger._<jupyter_output><empty_output><jupyter_text>Свойства - property<jupyter_code>class Animal:
    
    animals = []

    @staticmethod        
    def count():
        return len(Animal.animals)
    
    def __init__(self, limbs, eyes, carnivore=False):
        self._limbs = limbs
        self.eyes = eyes
        self.carnivore = carnivore
        Animal.animals.append(self)
        
    def _get_limbs(self):
        return self._limbs
    
    def _set_limbs(self, limbs):
        self._limbs = limbs
        
    limbs = property(_get_limbs, # getter
                     _set_limbs, # setter
                     doc='Animal limbs count property') # docstring
                    
    def __repr__(self):
        return "%s animal with %r limbs and %r eyes"%('Carnivore' if self.carnivore else 'Herbivore',
                                          self.limbs, self.eyes)
a = Animal(4,2,True)
a.limbs
a.limbs = 5
a<jupyter_output><empty_output><jupyter_text>@property<jupyter_code>class Animal:
    
    animals = []

    @staticmethod        
    def count():
        return len(Animal.animals)
    
    def __init__(self, limbs, eyes, carnivore=False):
        self._limbs = limbs
        self.eyes = eyes
        self.carnivore = carnivore
        Animal.animals.append(self)
        
    @property
    def limbs(self):
        return self._limbs
    
    @limbs.setter
    def limbs(self, limbs):
        self._limbs = limbs
                    
    def __repr__(self):
        return "%s animal with %r limbs and %r eyes"%('Carnivore' if self.carnivore else 'Herbivore',
                                          self.limbs, self.eyes)
a = Animal(4,2,True)
a.limbs
a.limbs = 5
a
class Tiger(Animal):
    def __init__(self, white=False): # перегрузка конструктора
        super().__init__(4, 2, True) # вызов конструктора родителя
        self.white = white

    @property
    def limbs(self):
        return self._limbs

    @limbs.setter
    def limbs(self, limbs):
        pass
        
    def __repr__(self): # перегрузка метода __repr__
        return "%s tiger"%('White' if self.white else 'Regular')    
tiger = Tiger()
tiger.limbs
tiger.limbs = 5
tiger.limbs<jupyter_output><empty_output><jupyter_text>Наследование свойств<jupyter_code>class Animal:
    
    animals = []

    @staticmethod        
    def count():
        return len(Animal.animals)
    
    def __init__(self, limbs, eyes, carnivore=False):
        self._limbs = limbs
        self.eyes = eyes
        self.carnivore = carnivore
        Animal.animals.append(self)

    def _get_limbs(self):
        return self._limbs
    
    def _set_limbs(self, limbs):
        self._limbs = limbs
        
    @property
    def limbs(self):
        return self._get_limbs()
    
    @limbs.setter
    def limbs(self, limbs):
        self._set_limbs(limbs)
                    
    def __repr__(self):
        return "%s animal with %r limbs and %r eyes"%('Carnivore' if self.carnivore else 'Herbivore',
                                          self.limbs, self.eyes)
class Tiger(Animal):
    def __init__(self, white=False): # перегрузка конструктора
        super().__init__(4, 2, True) # вызов конструктора родителя
        self.white = white

    def _set_limbs(self, limbs):
        pass
        
    def __repr__(self): # перегрузка метода __repr__
        return "%s tiger"%('White' if self.white else 'Regular')    
tiger = Animal(4, 2, True)
tiger.limbs
tiger.limbs = 5
tiger.limbs<jupyter_output><empty_output><jupyter_text>@classmethodВ качестве первого параметра получает ссылку на класс (cls), а не на экземпляр класса (self).Часто используется для создания альтернативных конструкторов.<jupyter_code>class Animal:
    
    animals = []

    @staticmethod        
    def count():
        return len(Animal.animals)
    
    def __init__(self, limbs, eyes, carnivore=False):
        self.limbs = limbs
        self.eyes = eyes
        self.carnivore = carnivore
        Animal.animals.append(self)
       
    @classmethod
    def from_list(cls, params):
        return cls(*params) # вызов конструктора __init__ у класса cls
                    
    def __repr__(self):
        return "%s animal with %r limbs and %r eyes"%('Carnivore' if self.carnivore else 'Herbivore',
                                          self.limbs, self.eyes)
a = Animal.from_list([4,2,True])
a<jupyter_output><empty_output><jupyter_text>Декораторы- Это способ изменить поведение функции, не меняя ее код- Это функции высшего порядка (т.е. функции, определенные на пространстве функций)<jupyter_code>def my_decorator(func): # функция my_decorator применяется к функции func
    def wrapper(): # функция-обертка, которую возвращает декоратор после применения к func
        print('I can do something before calling func')
        func()
        print('and after func also')
    return wrapper
def my_func(): # простая функция
    print('Function without arguments')
    
my_func()
my_decorator(my_func) # примененив функцию my_decorator к my_func получаем ссылку на функцию-обертку
my_decorator(my_func)() # вызываем обертку без аргументов
my_func = my_decorator(my_func) # заменяем my_func на ее декорированный вариант
my_func()<jupyter_output>I can do something before calling func
Function without arguments
and after func also<jupyter_text>синтаксис @decorator<jupyter_code># тоже самое, но с использованием синтаксического сахара
@my_decorator
def my_func(): # простая функция
    print('Function without arguments')
    
my_func()<jupyter_output>I can do something before calling func
Function without arguments
and after func also<jupyter_text>декоратор для функции с аргументами<jupyter_code>def my_decorator(func): # декоратор с аргументами
    def wrapper(*args, **kwargs):
        print('I can do something before calling func')
        ret = func(*args, **kwargs)
        print('and after func also')
        return ret
    return wrapper
@my_decorator
def my_func(x, y):
    print('my_func(%r, %r)'%(x,y))
    return x+y
my_func(1,5)<jupyter_output>I can do something before calling func
my_func(1, 5)
and after func also<jupyter_text>примеры декораторов<jupyter_code>def benchmark(func):
    """
    Декоратор, измеряющий время выполнения декорируемой функции
    """
    import time
    def benchmark_wrapper(*args, **kwargs):
        cnt = time.perf_counter()
        ret = func(*args, **kwargs)
        print(func.__name__, '%.3e'%(time.perf_counter() - cnt), 's')
        return ret
    return benchmark_wrapper
@benchmark
def my_func(x, y):
    return x+y
my_func(1,2)
def logging(func):
    """
    Декоратор, выводящий информацию о вызове функции в файл
    """
    def logging_wrapper(*args, **kwargs):
        with open('log.txt', 'at') as f:
            res = func(*args, **kwargs)
            f.write('%r %r %r\n'%(func.__name__, args, kwargs))
        return res
    return logging_wrapper
@logging
def my_func(x, y):
    return x+y
my_func(1,2)
!TYPE log.txt
def counter(func):
    """
    Декоратор, подсчитывающий количество вызовов функции
    """
    def counter_wrapper(*args, **kwargs):
        counter_wrapper.count += 1 # функция - объект, поэтому можно ему добавить атрибут
        res = func(*args, **kwargs)
        print("%r была вызвана %r раз(а)" %(func.__name__, counter_wrapper.count))
        return res
    counter_wrapper.count = 0
    return counter_wrapper
@counter
def my_func(x, y):
    return x+y
my_func(1,2)
for i in range(5):
    my_func(i,i*2)
my_func.count # атрибут (поле) count является публичным
@logging
@counter
@benchmark
def my_func(x, y):
    return x+y
for i in range(5):
    my_func(i,i*2)
!TYPE log.txt<jupyter_output>'my_func' (1, 2) {}
'counter_wrapper' (0, 0) {}
'counter_wrapper' (1, 2) {}
'counter_wrapper' (2, 4) {}
'counter_wrapper' (3, 6) {}
'counter_wrapper' (4, 8) {}
'my_func' (1, 2) {}
'counter_wrapper' (0, 0) {}
'counter_wrapper' (1, 2) {}
'counter_wrapper' (2, 4) {}
'counter_wrapper' (3, 6) {}
'counter_wrapper' (4, 8) {}
{'fraction_non_alphanumeric': 0.09944976564092113, 'fraction_numerical': 0.006045784933088784, 'mean_word_length': 3.112290502793296, 'pattern_counts': {'":': 0, '<': 75, '<?xml version=': 0, '>': 76, 'https://': 1, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 12, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'chain_length': '18', 'dir': 'jupyter-structured-clean-dedup', 'id': '458172', 'license': 'MIT', 'n_tokens_mistral': 5246, 'n_tokens_neox': 5461, 'n_words': 1277, 'path': 'Lecture_7/Lecture_7.ipynb', 'repo_name': 'YuriyKozhev/PythonCourse', 'size': '14796'}