Метапрограмиране

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

03.05.2011г.

С 22 думи

Metaprogramming is the language feature that helps you write code that you won't be able to understand once the cocaine wears off.

twitter.com/bos31337

Data Model (aka MOP)

Objects are Python’s abstraction for data. All data in a Python program is represented by objects or by relations between objects.

Обектите отвътре

class Person:
    def __init__(self, name):
        self.name = name

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

Обектите отвътре

Обектите отвътре са просто речници. Всеки обект си има специален речник който пази атрибутите му:

>>> goshko = Person('Gospodin Goshko')
>>> hasattr(goshko, '__dict__')
True
>>> goshko.__dict__
{'name': 'Gospodin Goshko'}

>>> goshko.__dict__['profession'] = 'Hacker'
>>> goshko.profession
'Hacker'
>>> goshko.__dict__
{'profession': 'Hacker', 'name': 'Commander Gosh'}

>>> goshko.__dict__.clear()
>>> goshko.name
Traceback (most recent call last):
  File "", line 1, in 
AttributeError: 'Person' object has no attribute 'name'

Класът Ninja

class Ninja:
    def __init__(self, name, target):
        self.name = name
        self.target = target

    def say_hi(self):
        print("Ninja!")

    def kill_target(self):
        print("Slash ", self.target)

Класът е специален атрибут на обекта

>>> goshko = Person('Gospodin Goshko')
>>> goshko.say_hi()
Hi, I am Gospodin Goshko
>>> type(goshko)
<class '__main__.Person'>
>>> goshko.__class__
<class '__main__.Person'>

>>> goshko.__class__ = Ninja
>>> type(goshko)

>>> goshko.say_hi()
Ninja!
>>> goshko.kill_target()
Traceback (most recent call last):
  File "", line 1, in 
  File "", line 4, in kill_target
AttributeError: 'Ninja' object has no attribute 'target'

Конструиранйе

object.__new__(cls[, ...])

object.__init__(self[, ...])

__new__

__new__ е истинският конструктор на вашите обекти. __init__ е само инициализатор:
class Vector(tuple):

    def __new__(klass, x, y):
        return tuple.__new__(klass, (x, y))

    def __add__(self, other):
        if not isinstance(other, Vector):
            return NotImplemented
        return Vector(self[0] + other[0], self[1] + other[1])

Достъп на атрибути

object.__getattr__(self, name)

object.__setattr__(self, name, value)

object.__delattr__(self, name)

object.__dir__(self)

Още достъп на атрибути

object.__getattribute__(self, name)

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

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

Дескриптори

object.__get__(self, instance, owner)

object.__set__(self, instance, value)

object.__delete__(self, instance)

Извикване на дескриптори

# direct call
x.__get__(a)

# instance binding on a.x
type(a).__dict__['x'].__get__(a,  type(a))

# class binding on A.x
A.__dict__['x'].__get__(None,  A)

# super binding
# super black magic

До тук

Метаобектният протокол на Python

Tim Peters on metaclasses

[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 значи няколко неща:

Type „help“

# обекти
>>> type(42), type("42"), type(object())
(<class 'int'>, <class 'str'>, <class 'object'>)

# типове
>>> type(int), type(str), type(object)
(<class 'type'>, <class 'type'>, <class 'type'>)

# връзката
>>> issubclass(int, type)
False
>>> isinstance(int, type)
True

Help more

>>> issubclass(object, type)
False
>>> isinstance(object, type)
True

>>> issubclass(type, object)
True
>>> issubclass(object, type)
False

>>> isinstance(type, object)
True # естествено
>>> isinstance(type, type)
True # втф?!

Типът на всички типове

type разбира се. Инстанции на type създавате с:
type(name, bases, dict)

Какво е инстанция на type - просто клас.

Нинджа

def човек_инициализирай(self, name):
    self.name = name

def човек_кажи_здрасти(self):
    print("Здрасти, аз съм", self.name)

Човек = type( 'Човек', (), {
            '__init__' : човек_инициализирай,
            'кажи_здрасти': човек_кажи_здрасти }
        )

Човек('¡Испанска нинджа!').кажи_здрасти()

Как указваме метаклас

class Foo(A, B, C, metaclass=Bar):
    pass

Просто синтактична захар

class Foo(A, B, C, metaclass=Bar):
    x = 1
    y = 2

# е захар за

Foo = Bar('Foo', (A, B, C), {'x':1, 'y':2})

Простичко

class metacls(type):
    def __new__(mcs, name, bases, dict):
        dict['foo'] = 'metacls was here'
        return type.__new__(mcs, name, bases, dict)

Един пример

class R(metaclass=ReverseNames):
    def forward(self):
        print('forward')
>>> r = R()
>>> r.forward()
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
AttributeError: 'R' object has no attribute 'forward'
>>> r.drawrof()
forward

Как?

class ReverseNames(type):
    def __new__(klass, name, bases, _dict):
        reversed = [(k[::-1], v) for k, v in _dict.items()]
        return type.__new__(klass, name, bases, dict(reversed))

Атрибути в метакласа

class Meta(type):
    def bar(self):
        print(self)
class Foo(metaclass=Meta):
    pass
>>> f = Foo()
>>> f.bar()
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
AttributeError: 'Foo' object has no attribute 'bar'

>>> Foo.bar()
<class '__main__.Foo'>

>>> Meta.bar()
# ???

Себична Нинджа

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

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

Person("忍者").say_hi()

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

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)

2eval|!2eval?

Не ползвайте eval().

Връзки

Още въпроси?