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

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

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

27.04.2009

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

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

type() и dir()

dir() ни позволява да видим атрибутите, на които един метод отговаря. type() пък ни връща класът на своя аргумент като обект.
text = ["Ni!", "Shrubbery", "Knights"]
print(type(text), tupe(text) == tuple)
print(dir(text))

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

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

На повърхността (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)

По-дълбоко

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

Класове

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

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

print(Foo.__dict__.keys())

Методи

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

  1. Проверява се дали name не присъства в obj.__dict__. Ако да - връща се тази стойност
  2. Ако не - проверява се дали класът, obj.__class__ има такъв атрибут в своя __dict__. Ако да, и той няма метод __get__, се връща
  3. Ако атрибута на obj.__dict__ има метод __get__, то методът obj.__dict__.__get__ се изпълнява със съответните оргументи

Методи (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)

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

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()

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.

Дефиниране

Определянето на метакласа става така:

class Foo(object, metaclass=type):
    pass

class BarMeta(type):
    pass

class Bar(object, metaclass=BarMeta):
    pass

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

Обекта поставен в 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())

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

def without_ego(func):
    def wrapped(self, *args, **kwargs):
        old_self = func.__globals__.get('self')
        func.__globals__['self'] = self
        result = func(*args, **kwargs)
        func.__globals__['self'] = old_self
        return result
    wrapped.__name__ = func.__name__
    return wrapped

class selfless(type):
    def __new__(cls, name, bases, attrs):
        for key, value in attrs.items():
            if not hasattr(value, '__call__'): continue
            attrs[key] = without_ego(value)
        return type.__new__(cls, name, bases, attrs)

class Person(metaclass=selfless):
    def __init__(name):
        self.name = name

    def sayHi():
        print("Hi, I am", self.name)

Person("Mityo").sayHi()

Още въпроси?