Декораторы II


In [ ]:
import functools

In [ ]:
def deco(func):
    @functools.wraps(func)
    def wrapped():
        pass
    return wrapped

@deco
def foo():
    print('Hey')
    
    
foo.__name__

In [ ]:
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)

In [ ]:

Магические методы


In [ ]:
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('Jane Doe', 'janedoe@example.com')

print(jane.get_email_data())

In [ ]:


In [ ]:
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

In [ ]:


In [ ]:
class User:
    def __init__(self, name, email):
        self.name = name
        self.email = email
    
    def __str__(self):
        return '{} <{}>'.format(self.name, self.email)


jane = User('Jane Doe', 'janedoe@example.com')

print(jane)

In [ ]:


In [ ]:
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('Jane Doe', 'jdoe@example.com')
joe = User('Joe Doe', 'jdoe@example.com')


print(jane == joe)

In [ ]:
print(hash(jane))
print(hash(joe))

In [ ]:
user_email_map = {user: user.name for user in [jane, joe]}

print(user_email_map)

In [ ]:


In [ ]:
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)

In [ ]:


In [ ]:
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)

In [ ]:


In [ ]:
class Ignorant:
    def __setattr__(self, name, value):
        print('Not gonna set {}!'.format(name))
        

obj  = Ignorant()
obj.math = True

In [ ]:
print(obj.math)

In [ ]:


In [ ]:
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

In [ ]:


In [ ]:
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

In [ ]:
completely_useless_function()

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

In [ ]:


In [ ]:
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)

In [ ]:
for _ in range(3):
    print(a + b)

In [ ]:

Итераторы


In [ ]:
for number in range(5):
    print(number & 1)

In [ ]:
for letter in 'python':
    print(ord(letter))

In [ ]:
iterator = iter([1, 2, 3])

In [ ]:
print(next(iterator))

In [ ]:


In [ ]:
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)

In [ ]:


In [ ]:
class IndexIterable:
    def __init__(self, obj):
        self.obj = obj
        
    def __getitem__(self, index):
        return self.obj[index]


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

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

In [ ]:


In [ ]:
import collections



print(isinstance(SquareIterator(1, 10), collections.Iterable))
print(isinstance(IndexIterable('123'), collections.Iterable))

In [ ]:

Написать свой контейнер с помощью getitem, setitem


In [ ]:


In [ ]:


In [ ]:


In [ ]:


In [ ]:

Асинхронные итераторы


In [ ]:


In [ ]:
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())

In [ ]:

Асинхронные генераторы


In [ ]:


In [ ]:
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())

In [ ]:

Асинхронные comprehensions


In [ ]:
async def main():
    return [num async for num in arange(10, 20)]

loop = asyncio.get_event_loop()
loop.run_until_complete(main())

In [ ]:

Контекстные менеджеры


In [ ]:


In [ ]:
with open('access_log.log', 'a') as f:
    f.write('New Access\n')

In [ ]:
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()

In [ ]:
with open_file('test.log', 'w') as f:
    f.write('Inside `open_file` context manager')

In [ ]:
with open_file('test.log', 'r') as f:
    print(f.readlines())

In [ ]:


In [ ]:
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

In [ ]:
with suppress_exception(ZeroDivisionError):
    really_big_number = 1 / 0

In [ ]:
import contextlib


with contextlib.suppress(ValueError):
    raise ValueError

In [ ]:

Напишите контекстный менеджер который считает и выводит время, проведенное внутри него


In [ ]:
import time

In [ ]:


In [ ]:

Как добавить возможность получить текущее пройденное время?


In [ ]:


In [ ]:

Асинхронные контекстные менеджеры


In [ ]:


In [ ]:
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))

In [ ]:

Дескрипторы


In [ ]:


In [ ]:
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()

In [ ]:
instance.attr

In [ ]:
instance.attr = 10

In [ ]:
del instance.attr

In [ ]:


In [ ]:
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)

In [ ]:
class Class:
    attr = Value()

    
instance = Class()
instance.attr = 10

print(instance.attr)

In [ ]:


In [ ]:


In [ ]:
# Что происходит в момент доступа к атрибуту — 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)

In [ ]:


In [ ]:
# Что происходит во время — 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

In [ ]:

Напишите дескриптор, который пишет в переданный ему во время инициализации файл все присваиваемые значения


In [ ]:


In [ ]:


In [ ]:


In [ ]:

Функции — это дескрипторы


In [ ]:


In [ ]:
class Class:
    def method(self):
        pass
    
    
obj = Class()    

print(obj.method)
print(Class.method)

In [ ]:


In [ ]:


In [ ]:
def summator(x, y):
    return x + y


lolwhat = summator.__get__(10)
lolwhat(21)

In [ ]:

@classmethod & @staticmethod & @property — это дескрпиторы


In [ ]:
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)

In [ ]:
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)

In [ ]:
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)

In [ ]:
obj = Class()

print(obj.original)
print(obj.custom_sugar)
print(obj.custom_pure)

In [ ]:


In [ ]:
class StaticMethod:
    def __init__(self, func):
        self.func = func

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

In [ ]:
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

__slots__


In [ ]:
class Class:
    __slots__ = ['anakin']
    
    def __init__(self):
        self.anakin = 'the chosen one'

        
obj = Class()

obj.luke = 'the chosen too'

In [ ]:

Мета-классы


In [ ]:
class Class:
    ...

In [ ]:
obj = Class()

In [ ]:
type(obj)

In [ ]:
type(Class)

In [ ]:
type(type)

In [ ]:
issubclass(Class, type)

In [ ]:
issubclass(Class, object)


In [ ]:


In [ ]:
def dummy_factory():
    class Class:
        pass
    
    return Class


Dummy = dummy_factory()
obj = Dummy()

In [ ]:
NewClass = type('NewClass', (), {})

print(NewClass)
print(NewClass())

In [ ]:


In [ ]:
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)

In [ ]:
class A(metaclass=Meta):
    pass

In [ ]:
print('A.class_id: "{}"'.format(A.class_id))

In [ ]:
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

In [ ]:
print(Base.registry)
print(Base.__subclasses__())