<jupyter_start><jupyter_text>Декораторы II<jupyter_code>import functools
def deco(func):
    @functools.wraps(func)
    def wrapped():
        pass
    return wrapped

@deco
def foo():
    print('Hey')
    
    
foo.__name__
def validate_user_id(func):
    @functools.wraps(func)
    def wrapped(*args, **kwargs):
        if 'user_id' not in kwargs or kwargs['user_id'] == 0:
            print('user_id is wrong')
            return

        print('user_id is correct')
        return func(*args, **kwargs)
    
    return wrapped


def log_access(func):
    @functools.wraps(func)
    def wrapped(*args, **kwargs):
        print('{} was called'.format(func.__name__))
        return func(*args, **kwargs)
    return wrapped


@log_access
@validate_user_id
def feedback(user_id, message):
    print('Feedback received')


# feedback = log_access(validate_user_id(feedback))
    
data = {
    'message': 'Your app is awesome!',
    'user_id': 42
}

feedback(**data)<jupyter_output><empty_output><jupyter_text>Магические методы<jupyter_code>class User:
    def __init__(self, name, email):
        self.name = name
        self.email = email
    
    def get_email_data(self):
        return {
            'name': self.name,
            'email': self.email
        }
    
jane = User('<NAME>', '<EMAIL>')

print(jane.get_email_data())
class Singleton:
    instance = None

    def __new__(cls):
        if cls.instance is None:
            cls.instance = super().__new__(cls)
    
        return cls.instance
    

a = Singleton()
b = Singleton()

a is b
class User:
    def __init__(self, name, email):
        self.name = name
        self.email = email
    
    def __str__(self):
        return '{} <{}>'.format(self.name, self.email)


jane = User('<NAME>', '<EMAIL>')

print(jane)
class User:
    def __init__(self, name, email):
        self.name = name
        self.email = email
    
    def __hash__(self):
        return hash(self.email)

    def __eq__(self, obj):
        return self.email == obj.email

    
jane = User('<NAME>', '<EMAIL>')
joe = User('<NAME>', '<EMAIL>')


print(jane == joe)
print(hash(jane))
print(hash(joe))
user_email_map = {user: user.name for user in [jane, joe]}

print(user_email_map)
class Researcher:
    def __getattr__(self, name):
        return 'Nothing found :('
    
    def __getattribute__(self, name):
        return 'nope'
    

obj = Researcher()

print(obj.attr)
print(obj.method)
print(obj.DFG2H3J00KLL)
class Researcher:
    def __getattr__(self, name):
        return 'Nothing found :(\n'
    
    def __getattribute__(self, name):
        print('Looking for {}'.format(name))
        return object.__getattribute__(self, name)
    

obj = Researcher()

print(obj.attr)
print(obj.method)
print(obj.DFG2H3J00KLL)
class Ignorant:
    def __setattr__(self, name, value):
        print('Not gonna set {}!'.format(name))
        

obj  = Ignorant()
obj.math = True
print(obj.math)
class Polite:    
    def __delattr__(self, name):
        value = getattr(self, name)
        print(f'Goodbye {name}, you were {value}!')

        object.__delattr__(self, name)
        

obj = Polite()

obj.attr = 10
del obj.attr
class Logger:
    def __init__(self, filename):
        self.filename = filename
    
    def __call__(self, func):
        with open(self.filename, 'w') as f:
            f.write('Oh Danny boy...')
        return func
    
logger = Logger('log.txt')


@logger
def completely_useless_function():
    pass
completely_useless_function()

with open('log.txt') as f:
    print(f.read())
import random


class NoisyInt:
    def __init__(self, value):
        self.value = value

    def __add__(self, obj):
        noise = random.uniform(-1, 1)
        return self.value + obj.value + noise

    
a = NoisyInt(10)
b = NoisyInt(20)
for _ in range(3):
    print(a + b)<jupyter_output><empty_output><jupyter_text>Итераторы<jupyter_code>for number in range(5):
    print(number & 1)
for letter in 'python':
    print(ord(letter))
iterator = iter([1, 2, 3])
print(next(iterator))
class SquareIterator:
    def __init__(self, start, end):
        self.current = start
        self.end = end
        
    def __iter__(self):
        return self
    
    def __next__(self):
        if self.current >= self.end:
            raise StopIteration

        result = self.current ** 2
        self.current += 1
        return result
    
    
for num in SquareIterator(1, 4):
    print(num)
class IndexIterable:
    def __init__(self, obj):
        self.obj = obj
        
    def __getitem__(self, index):
        return self.obj[index]


for letter in IndexIterable('str'):
    print(letter)

    
# Какой еще объект может быть вместо строки?
import collections



print(isinstance(SquareIterator(1, 10), collections.Iterable))
print(isinstance(IndexIterable('123'), collections.Iterable))<jupyter_output><empty_output><jupyter_text>Написать свой контейнер с помощью __getitem__, __setitem__  Асинхронные итераторы<jupyter_code>import asyncio


class Ranger:
    def __init__(self, limit):
        self.limit = limit

    def __aiter__(self):
        return self

    async def __anext__(self):
        await asyncio.sleep(1)
        if self.limit == 0:
            raise StopAsyncIteration
            
        self.limit -= 1
        return self.limit + 1
       

async def main():
    async for val in Ranger(10):
        print(val)

    
loop = asyncio.get_event_loop()
loop.run_until_complete(main())<jupyter_output><empty_output><jupyter_text>Асинхронные генераторы<jupyter_code>async def arange(start, stop):
    current = start
    while current <= stop - 1:
        yield current
        current += 1


async def main():
    async for num in arange(10, 20):
        print(num)


loop = asyncio.get_event_loop()
loop.run_until_complete(main())<jupyter_output><empty_output><jupyter_text>Асинхронные comprehensions<jupyter_code>async def main():
    return [num async for num in arange(10, 20)]

loop = asyncio.get_event_loop()
loop.run_until_complete(main())<jupyter_output><empty_output><jupyter_text>Контекстные менеджеры<jupyter_code>with open('access_log.log', 'a') as f:
    f.write('New Access\n')

class open_file:
    def __init__(self, filename, mode):
        self.f = open(filename, mode)
    
    def __enter__(self):
        return self.f
    
    def __exit__(self, *args):
        self.f.close()
with open_file('test.log', 'w') as f:
    f.write('Inside `open_file` context manager')
with open_file('test.log', 'r') as f:
    print(f.readlines())
class suppress_exception:
    def __init__(self, exc_type):
        self.exc_type = exc_type
        
    def __enter__(self):
        return

    def __exit__(self, exc_type, exc_value, traceback):
        if exc_type == self.exc_type:
            print('Nothing happend.')
            return True
with suppress_exception(ZeroDivisionError):
    really_big_number = 1 / 0
import contextlib


with contextlib.suppress(ValueError):
    raise ValueError<jupyter_output><empty_output><jupyter_text>Напишите контекстный менеджер который считает и выводит время, проведенное внутри него<jupyter_code>import time<jupyter_output><empty_output><jupyter_text>Как добавить возможность получить текущее пройденное время?  Асинхронные контекстные менеджеры<jupyter_code>import aiohttp
import asyncio


async def fetch(client):
    async with client.get('http://python.org') as resp:
        assert resp.status == 200
        return await resp.text()


async def main(loop):
    async with aiohttp.ClientSession(loop=loop) as client:
        html = await fetch(client)
        print(html[:96])

        
loop = asyncio.get_event_loop()
loop.run_until_complete(main(loop))<jupyter_output><empty_output><jupyter_text>Дескрипторы<jupyter_code>class Descriptor:
    def __get__(self, obj, obj_type):
        print('get')
        
    def __set__(self, obj, value):
        print('set')
        
    def __delete__(self, obj):
        print('delete')


class Class:
    attr = Descriptor()
    

instance = Class()
instance.attr
instance.attr = 10
del instance.attr
class Value:
    def __init__(self):
        self.value = None
    
    @staticmethod
    def _prepare_value(value):
        return value * 10

    def __get__(self, obj, obj_type):
        return self.value
    
    def __set__(self, obj, value):
        self.value = self._prepare_value(value)
class Class:
    attr = Value()

    
instance = Class()
instance.attr = 10

print(instance.attr)
# Что происходит в момент доступа к атрибуту — instance.attr (Чтение)

def pseudo_read():
    if hasattr(instance.__class__, 'attr'):
        obj = instance.__class__.attr
        obj_type = obj.__class__

        if hasattr(obj_type, '__get__') and (
            hasattr(obj_type, '__set__') or 'attr' not in instance.__dict__
        ):
            return obj_type.__get__(obj, instance, instance.__class__)

    return instance.__dict__['attr']   


# 1. Data Descriptors
# 2. instance.__dict__
# 3. Non-Data Descriptors
# 4. class.__dict__
# 5. base classes


# instance.attr => type(instance).__dict__['attr'].__get__(instance, type(instance))
# Class.attr => Class.__dict__['attr'].__get__(None, Class)
    
# Что происходит во время — instance.attr = 10 (Записи)

def pseudo_write():
    if hasattr(instance.__class__, 'attr'):
        obj = instance.__class__.attr
        obj_type = obj.__class__

        if hasattr(obj_type, '__set__'):
            obj_type.__set__(obj, instance, 10)
            return

    instance.__dict__['attr'] = 10<jupyter_output><empty_output><jupyter_text>Напишите дескриптор, который пишет в переданный ему во время инициализации файл все присваиваемые значения  Функции — это дескрипторы<jupyter_code>class Class:
    def method(self):
        pass
    
    
obj = Class()    

print(obj.method)
print(Class.method)
def summator(x, y):
    return x + y


lolwhat = summator.__get__(10)
lolwhat(21)<jupyter_output><empty_output><jupyter_text>@classmethod & @staticmethod & @property — это дескрпиторы<jupyter_code>class User:
    def __init__(self, first_name, last_name):
        self.first_name = first_name
        self.last_name = last_name
    
    @property
    def full_name(self):
        return f'{self.first_name} {self.last_name}'
    

amy = User('Amy', 'Jones')

print(amy.full_name)
print(User.full_name)
class Property:
    def __init__(self, getter):
        self.getter = getter

    def __get__(self, obj, obj_type=None):
        if obj is None:
            return self

        return self.getter(obj)
class Class:
    @property
    def original(self):
        return 'original'
    
    @Property
    def custom_sugar(self):
        return 'custom sugar'
    
    def custom_pure(self):
        return 'custom pure'
    
    custom_pure = Property(custom_pure)
obj = Class()

print(obj.original)
print(obj.custom_sugar)
print(obj.custom_pure)
class StaticMethod:
    def __init__(self, func):
        self.func = func

    def __get__(self, obj, obj_type=None):
        return self.func
class ClassMethod:
    def __init__(self, func):
        self.func = func

    def __get__(self, obj, obj_type=None):
        if obj_type is None:
            obj_type = type(obj)

        def new_func(*args, **kwargs):
            return self.func(obj_type, *args, **kwargs)

        return new_func<jupyter_output><empty_output><jupyter_text>\_\_slots\_\_<jupyter_code>class Class:
    __slots__ = ['anakin']
    
    def __init__(self):
        self.anakin = 'the chosen one'

        
obj = Class()

obj.luke = 'the chosen too'<jupyter_output><empty_output><jupyter_text>Мета-классы<jupyter_code>class Class:
    ...
obj = Class()
type(obj)
type(Class)
type(type)
issubclass(Class, type)
issubclass(Class, object)<jupyter_output><empty_output><jupyter_text>![](https://blog.ionelmc.ro/2015/02/09/understanding-python-metaclasses/instance-of.png)<jupyter_code>def dummy_factory():
    class Class:
        pass
    
    return Class


Dummy = dummy_factory()
obj = Dummy()
NewClass = type('NewClass', (), {})

print(NewClass)
print(NewClass())
class Meta(type):
    def __new__(cls, name, parents, attrs):
        print('Creating {}'.format(name))

        if 'class_id' not in attrs:
            attrs['class_id'] = name.lower()

        return super().__new__(cls, name, parents, attrs)
class A(metaclass=Meta):
    pass
print('A.class_id: "{}"'.format(A.class_id))
class Meta(type):
    def __init__(cls, name, bases, attrs):
        print('Initializing — {}'.format(name))

        if not hasattr(cls, 'registry'):
            cls.registry = {}
        else:
            cls.registry[name.lower()] = cls
            
        super().__init__(name, bases, attrs)
        
        
class Base(metaclass=Meta): pass

class A(Base): pass

class B(Base): pass
print(Base.registry)
print(Base.__subclasses__())<jupyter_output><empty_output>
{'fraction_non_alphanumeric': 0.1190161127111388, 'fraction_numerical': 0.006772009029345372, 'mean_word_length': 2.9851116625310175, 'pattern_counts': {'":': 0, '<': 71, '<?xml version=': 0, '>': 73, 'https://': 1, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'chain_length': '15', 'dir': 'jupyter-structured-clean-dedup', 'id': '535306', 'license': 'BSD-3-Clause', 'n_tokens_mistral': 4416, 'n_tokens_neox': 4366, 'n_words': 983, 'path': 'lectures/05_Advanced_Python/notebook.ipynb', 'repo_name': 'park-python/course', 'size': '12896'}