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

„ Програмиране с 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'>

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

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

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

По-дълбоко

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

Класове

Полежението при класовете е идентично. Просто имате по-интересен синтаксис за дефиниране на __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

Още въпроси?