Програмиране с Python: Напреднало ООП и метакласове
Author: Стефан Кънев, Николай Бачийски и Димитър Димитров

# Напреднало ООП и метакласове

### „ Програмиране с Python“, ФМИ

#### for lecturer in [stefan_kanev, nikolay_bachiisky, dimitur_dimitrov]:
lecturer.__class__ = Lecturer

#### 23.04.2008

# „Всичко е обект“

Всичко в Python е обект. Това включва:

- вградени типове
- модули
- функции
- инстанции
- класове

# `type()` и `dir()`

`dir()` ни позволява да видим атрибутите, на които един метод отговаря. `type()` пък ни връща класът на своя аргумент като обект.

    text = ["Ni!", "Shrubbery", "Knights"]
    print type(text), tupe(text) == tuple
    print dir(text)

    <type 'list'>

# На повърхността

Всъщност, представете си следното.

- Всеки обект е „нещо магическо“ в паметта, коети има атрибути, достъпвани с оператора `.`
- Зад всеки обект в Python е един специален речник. Той може да бъде достъпен чрез атрибута му `__dict__`.
- Когато достъпвате атрибути с `.` те се търсят в този речник. Ако не бъдат намерени се извиква `__getattr__` в случай че е дефиниран.

# На повърхността (2)

    class Foo(object): pass
    f = Foo()
    f.spam, f.eggs = "Spam", "Eggs"
    print "f.__dict__:", f.__dict__
    print "f.spam, f.eggs:", f.spam, f.eggs
    f.__dict__ = {'larodi' : "Larodi"}
    print "f.larodi: " + f.larodi
    print "f.spam: " + f.spam

    f.__dict__: {'eggs': 'Eggs', 'spam': 'Spam'}
    f.spam, f.eggs: Spam Eggs
    f.larodi: Larodi

# По-дълбоко

Всъщност, нещата са една идея по-сложни.

- Всеки обект пази връзка към класа си в `__class__`. Последния също е обект.
- На всяко `obj.name` се изпълнява функцията `__getattribute__(obj, name)` на `obj.__class__`.
- Ако не сте дефинирали такава се ползва тази на `object`. Тя е имплементирана както обяснихме два слайда по-рано.

# Класове

Полежението при класовете е идентично. Просто имате по-интересен синтаксис за дефиниране на `__dict__`.

    class Foo(object):
        spam = "Eggs"
        def bar(self): pass

    print Foo.__dict__.keys()

    ['__module__', 'bar', 'spam', '__dict__', '__weakref__', '__doc__']

# Методи

При извикване на `obj.name`:

1. Проверява се дали `name` не присъства в `obj.__dict__`. Ако да - връща се тази стойност
2. Ако не - проверява се дали класът, `obj.__class__` има такъв атрибут в своя `__dict__`. Ако да и атрибута не е функция, той се връща.
3. Ако атрибута на `obj.__class__` е функцията `mth` се връща нещо като `lambda *args: mth(obj, *args)`. Всъщност, това е малко по-особен тип `bound method`.

# Методи (2)

    class Person(object):
        def __init__(self, name): self.name = name
        def sayHi(self): print "Hi, I am", self.name
        def wave(self): print self.name, "is waving"

    mityo = Person("Mityo")
    print mityo.sayHi
    print Person.sayHi
    mityo.sayHi()
    Person.sayHi(mityo)

    <bound method Person.sayHi of <__main__.Person object at 0x2a955d71d0>>
    <unbound method Person.sayHi>
    Hi, I am Mityo
    Hi, I am Mityo

# Нещо, което не сте очаквали

    class Person(object):
        def __init__(self, name): self.name = name
        def sayHi(self): print "Hi, I am", self.name
        def wave(self): print self.name, "is waving"

    class Hacker(object):
        def sayHi(self): print self.name, "has been hacked by ninjas"
        def slash(self): print "Slash! Slash! Slash!"

    mityo = Person("Mityo")
    mityo.sayHi()
    mityo.wave()
    mityo.__class__ = Hacker
    mityo.sayHi()
    mityo.slash()
    mityo.wave()

    Hi, I am Mityo
    Mityo is waving
    Mityo has been hacked by ninjas
    Slash! Slash! Slash!

# `type()` revisited

Може да разглеждате `type` и като конструктор за нови класове. Следните дефиниции са еквивалентни:

    class Base(object): pass
    class Derived(Base):
        count = 0
        def foo(self): return "The foo of %s" % self

    Derived = type("Derived", (Base,), {
        'count': 0,
        'foo': lambda self: "The foo of %s" % self
    })

Всъщност, всички класове дефинирани с `class` са инстанции на `type`.

# Метакласове

> [Metaclasses] are deeper magic than 99% of the users should ever worry about. If you wonder whether you need them, you don't (the people who actually need them know with certainty that they need them, and don't need an explanation about why).

— Tim Peters

# Всъщност…

Всеки клас е инстанция или на `type`, или на друг тип, който го наследява. Това се нарича „клас на класа“ или „метаклас“.

Meta (from Greek: after, beyound, with) is a prefix used in English in order to indicate a concept which is an abstraction from another concept, used to complete or add to the latter.

# Дефиниране

Определянето на метакласа става чрез промяна на атрибута `__metaclass__` на класа.

    class Foo(object):
        __metaclass__ = type

    class BarMeta(type):
        pass

    class Bar(object):
        __metaclass__ = BarMeta

# Какво може да направите

Обекта поставен в `__metaclass__` трябва да дефинира оператор `()`, вземащ три аргументи - име на клас, n-орка от родители и речник с атрибути. Трябва да връща нов клас на базата на тези аргументи (така прави `type`). Съответно, може или да си направите наследник на `type` или да дадете функция с такава сигнатура.

# От camelCase към underscore_notation

    import re

    def Namer(clname, bases, cdict):
        for name in [_ for _ in cdict.keys() if re.search('[a-z][A-Z]', _)]:
            new_name = re.sub('([a-z])([A-Z])', r'\1_\2', name).lower()
            cdict[new_name] = cdict[name]
            del cdict[name]
        return type(clname, bases, cdict)

    class Person(object):
        __metaclass__ = Namer

        theNumberOfPersons = 1
        def __init__(self, name): self.name = name
        def getMyNameNow(self): return self.name

    p = Person("Mityo")
    print p.get_my_name_now()
    print Person.the_number_of_persons
    print p.getMyNameNow()

# Себичен питон

    from types import FunctionType

    class selfless(type):
        def __new__(cls, name, bases, classDict):
            for attr in classDict:
                if not isinstance(classDict[attr], FunctionType): continue
                classDict[attr] = selflessWrapper(classDict[attr])
            return type.__new__(cls, name, bases, classDict)

    def selflessWrapper(func):
        def wrapper(self, *args, **kwargs):
            hadSelf, oldSelf = func.func_globals.has_key('self'), func.func_globas.get('self')
            func.func_globals['self'] = self
            returnValue = func(*args, **kwargs)
            if hadSelf:
                func.func_globals['self'] = oldSelf
            else:
                del func.func_globals['self']
            return returnValue
        wrapper.func_name = func.func_name + "_wrapper"
        return wrapper

# Още въпроси?

- Страница на курса - http://fmi.py-bg.net/
- Форуми на курса - http://fmi.py-bg.net/forums/