11. Метапрограмиране
С 22 думи
Metaprogramming is the language feature that helps you write code that you won't be able to understand once the cocaine wears off.
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.nameFile " AttributeError: 'Person' object has no attribute 'name'", line 1, in
Класът 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__ е само инициализатор
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:
- проверява се дали name не присъства в obj.__dict__. Ако да - връща се тази стойност
- ако не - проверява се дали класът, obj.__class__ има такъв атрибут в своя __dict__. Ако да, и той няма метод __get__, се връща
- ако атрибута на 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
- __dict__
- __new__/__init__
- __getattribute__/__setattr__
- __get__/__set__/__del__
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).
Метакласове
- всичко в Пайтън е обект, включително и класовете
- всеки обект е инстанция на някакъв клас, включително и класовете
- класовете на класовете си имат специално име - метакласове
- има един стандартен метаклас - type
Забележка
В Пайтън type значи няколко неща
- с един аргумент type(x) връща типа на x
- без аргументи е просто класът type
- с три аргумента се конструира инстанция на type
Type "help"
# обекти >>> type(42), type("42"), type(object()) (, , ) # типове >>> type(int), type(str), type(object) ( , , ) # връзката >>> 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 - просто клас.
- name - име на новия клас
- bases - tuple с базовите му класове
- dict - речник с полетата му (не по-различно от __dict__)
Нинджа
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()File " AttributeError: 'R' object has no attribute 'forward' >>> r.drawrof() forward", line 1, in
Как?
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()File " AttributeError: 'Foo' object has no attribute 'bar' >>> Foo.bar()", line 1, in >>> 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().
Връзки
Още въпроси?
- Пишете ни на fmi@py-bg.net
- Страница на курса: http://fmi.py-bg.net/
- Форуми на курса: http://fmi.py-bg.net/topics
- Курсът в Twitter: http://twitter.com/pyfmi
- Курсът във Facebook: http://www.facebook.com/group.php?gid=104970619536589