# -*- coding: 1251 -*-

'''
Метаклассы+дескрипторы
Андрей Светлов
Если есть вопросы - пишите на andrew<dot>svetlov<at>gmail<dot>com
Или задавайте их на форуме python.com.ua
'''

' *** Метаклассы *** '

'''
Метаклассы - только от object.
по умолчанию - метакласс type

Создание экземпляра - вызов банального __call__

Обычно в проекте используется очень ограниченное число метаклассов -
классов гораздо больше.
Но примененные "к месту" они сильно облегчают жизнь.
'''

class A(object):
    '''Простой класс'''
    def f(self):
        '''Простой метод'''
        return 'A.f'

# 'create A instance'
a = A()
assert a.f() == 'A.f'


# 'create B instance'
B = type('B', (object,), {'f':lambda self:'B.f'})
b = B()
assert b.f() == 'B.f'

assert dir(a) == dir(b)

'''
метакласс
class meta(type):
    def __new__(cls, name, bases, dct):
        ...
    def __init__(cls, name, bases, dct):
        ...

'''
# 'Define metaclass'

class Meta(type):
    '''Наш метакласс, наследник type'''
    def __new__(cls, name, bases, dct):
        '''В __new__ можно изменить словарь класса. Preprocessing'''
        assert 'f' in dct
        def h(self):
            return 'Meta.h'
        dct['h'] = h
        return type.__new__(cls, name, bases, dct)

    def __init__(cls, name, bases, dct):
        '''Зато в __init__ мы имеем уже созданный класс. Postprocessing'''
        assert 'f' in dct
        assert 'h' in dct
        type.__init__(cls, name, bases, dct)
        assert 'f' in dir(cls)
        assert 'h' in dir(cls)


# 'create C class'

class C(object):
    __metaclass__ = Meta # указывает, что класс создается не с обычным
type, а с Meta метаклассом
    def f(self):
        return 'C.f'

assert 'h' in dir(C)

# 'create C instance'
c = C()
assert c.f() == 'C.f'
assert c.h() == 'Meta.h'

' *** Дескрипторы *** '

'''
Дескриптор
3.4.2.2, 3.4.2.4 (Language reference/Data model/Special method
names/Customizing attribute access)
class descr(object):
    def __get__(self, instance, owner):
        ...
    def __set__(self, instance, value):
        ...
    def __delete__(self, instance):
        ...
Стандартные дескрипторы: property, BoundMethod, UnboundMethod,
classmethod, staticmethod

'''

class descr(object):
    def __init__(self, default):
        self._default = default
    def __get__(self, inst, owner):
        ret = getattr(inst, '_descr_data', None)
        if ret is not None:
            return ret
        return self._default
    def __set__(self, inst, val):
        inst._descr_data = val

    def __delete__(self, inst):
        del inst._descr_data

class D(object):
    d = descr('default')

d = D()
assert d.d == 'default'
d.d = 123
assert d.d == 123
del d.d
assert d.d == 'default'


class read_property(object):
    '''Эмуляция readonly property'''
    def __init__(self, func):
        self.func = func
    def __get__(self, inst, owner):
        return self.func(inst)

class E(object):
    def f(self):
        return 'E.f:'+self.__class__.__name__

    p = read_property(f)


e = E()
assert e.p == 'E.f:E'

'''
Пример: дескрипторы для создания логов
Для создания теста потребуется довольно мощная фикстура, сопоставимая
по объему с самим примером.
Поэтому просто смотрите результат на экране
'''

import logging


def _get_instance_name(instance):
    # since getLogger() does not have any way of removing logger
objects from memory,
    # instance logging displays the instance id as a modulus of 16 to
prevent endless memory growth
    # also speeds performance as logger initialization is apparently slow
    return instance.__class__.__module__ + "." +
instance.__class__.__name__ + ".0x.." + hex(id(instance))[-2:]


class class_logger(object):
    '''Class logger descriptor'''
    def __init__(self, name=None):
        self._logger = None
        self._name = name

    def __get__(self, instance, owner):
        if self._logger is None:
            if self._name is None:
                self._name = owner.__module__ + "." + owner.__name__
            self._logger = logging.getLogger(self._name)
        return self._logger


class instance_logger(object):
    '''Instance logger descriptor'''
    def __init__(self, name=None):
        self._logger = None
        self._name = name

    def __get__(self, instance, owner):
        assert instance is not None
        if self._logger is None:
            if self._name is None:
                self._name = _get_instance_name(instance)
            self._logger = logging.getLogger(self._name)
        return self._logger


class F(object):
    class_log = class_logger()
    inst_log = instance_logger()

    def f(self):
        self.class_log.debug('Class logger message')
        self.inst_log.debug('Instance logger message')

logging.basicConfig(level=logging.DEBUG)
f = F()
f.f()

'''На этом все. Конечно, у меня есть и гораздо более сильные примеры.
Но детальный их разбор потребует отдельной статьи на каждый.
Повторю снова: если есть вопросы - пишите.
'''