Давайте поговоримо про специфіку мови Python і його функціонал.
Щоб створити свій власний метаклас в Python, потрібно скористатися підклас type , стандартний метаклас в Python. Найчастіше метакласи використовуються в ролі віртуального конструктора. Щоб створити екземпляр класу, потрібно спочатку викликати цей самий клас. Точно так чинить і Python: для створення нового класу викликає метаклас. Метакласи визначаються за допомогою базових класів в атрибуті __metaclass__ . При створенні класу допускається використання методів __init__ і __new__ . З їх допомогою можна користуватися додатковими функціями. Під час виконання оператора class генерується простір імен, які будуть містити атрибути майбутнього класу. Потім, для безпосереднього створення, викликається метаклас з ім'ям і атрибутами.
приклад:
def make_hook(f):
"""Decorator to turn 'foo' method into '__foo__'"""
f.is_hook = 1
return f
class MyType(type):
def __new__(mcls, name, bases, attrs):
if name.startswith('None'):
return None
# Go over attributes and see if they should be renamed.
newattrs = {}
for attrname, attrvalue in attrs.iteritems():
if getattr(attrvalue, 'is_hook', 0):
newattrs['__%s__' % attrname] = attrvalue
else:
newattrs[attrname] = attrvalue
return super(MyType, mcls).__new__(mcls, name, bases, newattrs)
def __init__(self, name, bases, attrs):
super(MyType, self).__init__(name, bases, attrs)
# classregistry.register(self, self.interfaces)
print "Would register class %s now." % self
def __add__(self, other):
class AutoClass(self, other):
pass
return AutoClass
# Alternatively, to autogenerate the classname as well as the class:
# return type(self.__name__ + other.__name__, (self, other), {})
def unregister(self):
# classregistry.unregister(self)
print "Would unregister class %s now." % self
class MyObject:
__metaclass__ = MyType
class NoneSample(MyObject):
pass
# Will print "NoneType None"
print type(NoneSample), repr(NoneSample)
class Example(MyObject):
def __init__(self, value):
self.value = value
@make_hook
def add(self, other):
return self.__class__(self.value + other.value)
# Will unregister the class
Example.unregister()
inst = Example(10)
# Will fail with an AttributeError
#inst.unregister()
print inst + inst
class Sibling(MyObject):
pass
ExampleSibling = Example + Sibling
# ExampleSibling is now a subclass of both Example and Sibling (with no
# content of its own) although it will believe it's called 'AutoClass'
print ExampleSibling
print ExampleSibling.__mro__
Класи як об'єкти
Перед тим як починати розбиратися в метаклассом, потрібно добре розуміти як працюють звичайні класи в Python, а вони дуже своєрідні. У більшості мов це просто фрагменти коду, які описують створення об'єкта. Дане судження вірно і для Python:
>>> class ObjectCreator(object):
... pass
...
>>> my_object = ObjectCreator()
>>> print(my_object)
<__main__.ObjectCreator object at 0x8974f2c>
Але є один нюанс. Класи в Python це об'єкти. Коли виконується оператор class , Python створює в пам'яті об'єкт з ім'ям ObjectCreator .
>>> class ObjectCreator(object):
... pass
...
Об'єкт здатний сам створювати екземпляри, так що це клас. А об'єкт ось чому:
- його можна призначити в якості змінної
- копіюється
- є можливість додати до нього атрибути
- передається в ролі параметра функції
>>> print(ObjectCreator) # you can print a class because it's an object
<class '__main__.ObjectCreator'>
>>> def echo(o):
... print(o)
...
>>> echo(ObjectCreator) # you can pass a class as a parameter
<class '__main__.ObjectCreator'>
>>> print(hasattr(ObjectCreator, 'new_attribute'))
False
>>> ObjectCreator.new_attribute = 'foo' # you can add attributes to a class
>>> print(hasattr(ObjectCreator, 'new_attribute'))
True
>>> print(ObjectCreator.new_attribute)
foo
>>> ObjectCreatorMirror = ObjectCreator # you can assign a class to a variable
>>> print(ObjectCreatorMirror.new_attribute)
foo
>>> print(ObjectCreatorMirror())
<__main__.ObjectCreator object at 0x8997b4c>
Динамічне створення класів
Якщо класи в Python - це об'єкти, значить, як і будь-який інший об'єкт, їх можна створювати на ходу. Приклад створення класу в функції за допомогою class :
>>> def choose_class(name):
... if name == 'foo':
... class Foo(object):
... pass
... return Foo # return the class, not an instance
... else:
... class Bar(object):
... pass
... return Bar
...
>>> MyClass = choose_class('foo')
>>> print(MyClass) # the function returns a class, not an instance
<class '__main__.Foo'>
>>> print(MyClass()) # you can create an object from this class
<__main__.Foo object at 0x89c6d4c>
Але це не дуже-то динамічно, так як все одно доведеться прописувати весь клас самостійно.
Виходячи з того, що класи є об'єктами, можна зробити висновок, що вони повинні чимось генеруватися. Під час виконання оператора class, Python автоматично створює цей об'єкт, але є можливість зробити це вручну.
Пам'ятайте функцію type ? Стара добра функція, що дозволяє визначити тип об'єкта:
>>> print(type(1))
<type 'int'>
>>> print(type("1"))
<type 'str'>
>>> print(type(ObjectCreator))
<type 'type'>
>>> print(type(ObjectCreator()))
<class '__main__.ObjectCreator'>
Ця функція може створювати класи на ходу. Як параметр type приймає опис класу, і повертає клас.
Функція type працює наступним чином:
type(name of the class,
tuple of the parent class (for inheritance, can be empty),
dictionary containing attributes names and values)
наприклад:
>>> class MyShinyClass(object):
... pass
Можна створити вручну:
>>> MyShinyClass = type('MyShinyClass', (), {}) # returns a class object
>>> print(MyShinyClass)
<class '__main__.MyShinyClass'>
>>> print(MyShinyClass()) # create an instance with the class
<__main__.MyShinyClass object at 0x8997cec>
Ймовірно, ви звернули увагу на те, що MyShinyClass виступає і в якості імені класу, і в якості змінної для зберігання посилань на клас.
type приймає словник для визначення атрибутів класу.
>>> class Foo(object):
... bar = True
Можна написати як:
>>> Foo = type('Foo', (), {'bar':True})
Використовується як звичайний клас:
>>> print(Foo)
<class '__main__.Foo'>
>>> print(Foo.bar)
True
>>> f = Foo()
>>> print(f)
<__main__.Foo object at 0x8a9b84c>
>>> print(f.bar)
True
Звичайно ж, його можна успадковувати:
>>> class FooChild(Foo):
... pass
вийде:
>>> FooChild = type('FooChild', (Foo,), {})
>>> print(FooChild)
<class '__main__.FooChild'>
>>> print(FooChild.bar) # bar is inherited from Foo
True
Потім в свій клас потрібно буде додати методи. Для цього просто визначте функцію з відповідною сигнатурою і призначте її як атрибут.
>>> def echo_bar(self):
... print(self.bar)
...
>>> FooChild = type('FooChild', (Foo,), {'echo_bar': echo_bar})
>>> hasattr(Foo, 'echo_bar')
False
>>> hasattr(FooChild, 'echo_bar')
True
>>> my_foo = FooChild()
>>> my_foo.echo_bar()
True
Після динамічного створення можна додати ще методів:
>>> def echo_bar_more(self):
... print('yet another method')
...
>>> FooChild.echo_bar_more = echo_bar_more
>>> hasattr(FooChild, 'echo_bar_more')
True
У чому ж суть? Класи в Python є об'єктами, тому можна динамічно створювати клас на ходу. Саме це і робить Python під час виконання оператора class.
Що ж таке метаклас?
Якщо говорити в двох словах, то метаклас - це "штуковина", що створює класи. Щоб створювати об'єкти, ми визначаємо класи, правильно? Але ми дізналися, що класи в Python є об'єктами. Насправді метакласи - це те, що створює дані об'єкти. Досить складно пояснити. Краще наведемо приклад:
MyClass = MetaClass()
my_object = MyClass()
Раніше вже згадувалося, що type дозволяє робити щось на зразок цього:
MyClass = type('MyClass', (), {})
Швидше за все, ви ставите питанням: чому ім'я функції пишеться з маленької літери?
Це питання відповідності зі str - класом, який відповідає за створення рядків, і int - класом, що створює цілочисельні об'єкти. type - це просто клас, який створює об'єкти класу. Перевірити можна за допомогою атрибута __class__ . Все, що ви бачите в Python - об'єкти. У тому числі і рядки, числа, класи і функції. Все це об'єкти, і всі вони були створені з класу:
>>> age = 35
>>> age.__class__
<type 'int'>
>>> name = 'bob'
>>> name.__class__
<type 'str'>
>>> def foo(): pass
>>> foo.__class__
<type 'function'>
>>> class Bar(object): pass
>>> b = Bar()
>>> b.__class__
<class '__main__.Bar'>
Цікаве питання: який __class__ у кожного __class__ ?
>>> age.__class__.__class__
<type 'type'>
>>> name.__class__.__class__
<type 'type'>
>>> foo.__class__.__class__
<type 'type'>
>>> b.__class__.__class__
<type 'type'>
Можна зробити висновок, що метаклас створює об'єкти класу. Це можна назвати "фабрикою класів". type - вбудований метаклас, який використовує Python. Також можна створити свій власний метаклас.
Атрибут __metaclass__
При написанні класу можна додати атрибут __metaclass__ :
class Foo(object):
__metaclass__ = something...
[...]
Якщо це зробити, то для створення класу Foo Python буде використовувати метаклас.
Якщо написати class Foo (object), об'єкт класу Foo не відразу створиться в пам'яті.
Python буде шукати __metaclass__ . Як тільки атрибут буде знайдений, він використовується для створення класу Foo. У тому випадку, якщо цього не відбудеться, Python буде використовувати type для створення класу.
Якщо написати:
class Foo(Bar):
pass
Python робить наступне:
Перевірить, чи є атрибут __metaclass__ у класі Foo ? Якщо він є, створить в пам'яті об'єкт класу з ім'ям Foo з використанням того, що знаходиться в __metaclass__ .
Якщо Python раптом не зможе знайти __metaclass__ , він буде шукати цей атрибут на рівні модуля і після цього повторить процедуру. У разі якщо він взагалі не може знайти будь-якого __metaclass__ , Python використовує власний метаклас type , щоб створити об'єкт класу.
Тепер питання: що можна додати в __metaclass__ ?
Відповідь: що може створювати класи.
А що може створити клас? type або його підкласи, а також все, що його використовує.
Призначені для користувача метакласи
Основною метою метакласу є автоматична зміна класу під час його створення. Зазвичай це робиться для API, коли потрібно створити класи, відповідні поточному контексту. Наприклад, ви вирішили, що всі класи в модулі повинні мати свої атрибути, і вони повинні бути записані у верхньому регістрі. Щоб вирішити це завдання, можна задати __metaclass__ на рівні модуля.
Таким чином, потрібно просто повідомити метакласу, що всі атрибути повинні бути в верхньому регістрі. __metaclass__ дійсно може бути будь-яким об'єктом, він не обов'язково повинен бути формальним класом. Отже, почнемо з простого прикладу, з використанням функції.
# the metaclass will automatically get passed the same argument
# that you usually pass to `type`
def upper_attr(future_class_name, future_class_parents, future_class_attr):
"""
Return a class object, with the list of its attribute turned
into uppercase.
"""
# pick up any attribute that doesn't start with '__' and uppercase it
uppercase_attr = {}
for name, val in future_class_attr.items():
if not name.startswith('__'):
uppercase_attr[name.upper()] = val
else:
uppercase_attr[name] = val
# let `type` do the class creation
return type(future_class_name, future_class_parents, uppercase_attr)
__metaclass__ = upper_attr # this will affect all classes in the module
class Foo(): # global __metaclass__ won't work with "object" though
# but we can define __metaclass__ here instead to affect only this class
# and this will work with "object" children
bar = 'bip'
print(hasattr(Foo, 'bar'))
# Out: False
print(hasattr(Foo, 'BAR'))
# Out: True
f = Foo()
print(f.BAR)
# Out: 'bip'
Тепер те ж саме, але з використанням метакласу:
# remember that `type` is actually a class like `str` and `int`
# so you can inherit from it
class UpperAttrMetaclass(type):
# __new__ is the method called before __init__
# it's the method that creates the object and returns it
# while __init__ just initializes the object passed as parameter
# you rarely use __new__, except when you want to control how the object
# is created.
# here the created object is the class, and we want to customize it
# so we override __new__
# you can do some stuff in __init__ too if you wish
# some advanced use involves overriding __call__ as well, but we won't
# see this
def __new__(upperattr_metaclass, future_class_name,
future_class_parents, future_class_attr):
uppercase_attr = {}
for name, val in future_class_attr.items():
if not name.startswith('__'):
uppercase_attr[name.upper()] = val
else:
uppercase_attr[name] = val
return type(future_class_name, future_class_parents, uppercase_attr)
Але це не зовсім ООП, так як type не переважає, а викликається безпосередньо. Давайте реалізуємо це:
class UpperAttrMetaclass(type):
def __new__(upperattr_metaclass, future_class_name,
future_class_parents, future_class_attr):
uppercase_attr = {}
for name, val in future_class_attr.items():
if not name.startswith('__'):
uppercase_attr[name.upper()] = val
else:
uppercase_attr[name] = val
# reuse the type.__new__ method
# this is basic OOP, nothing magic in there
return type.__new__(upperattr_metaclass, future_class_name,
future_class_parents, uppercase_attr)
Швидше за все, ви помітили додатковий аргумент upperattr_metaclass. У ньому немає нічого особливого: цей метод першим аргументом отримує поточний екземпляр. Точно так же, як і self для звичайних методів. Імена аргументів такі довгі для наочності, але для self все імена мають назви звичайної довжини. Тому реальний метаклас буде виглядати так:
class UpperAttrMetaclass(type):
def __new__(cls, clsname, bases, dct):
uppercase_attr = {}
for name, val in dct.items():
if not name.startswith('__'):
uppercase_attr[name.upper()] = val
else:
uppercase_attr[name] = val
return type.__new__(cls, clsname, bases, uppercase_attr)
Використовуючи метод super , можна зробити код більш "чистим":
class UpperAttrMetaclass(type):
def __new__(cls, clsname, bases, dct):
uppercase_attr = {}
for name, val in dct.items():
if not name.startswith('__'):
uppercase_attr[name.upper()] = val
else:
uppercase_attr[name] = val
return super(UpperAttrMetaclass, cls).__new__(cls, clsname, bases, uppercase_attr)
Причина складності коду, що використовує метакласи, полягає не в самих метакласах. Код складний тому, що зазвичай метакласи використовуються для складних завдань, заснованих на спадкуванні, інтроспекції й маніпуляції такими змінними, як __dict__ .
Навіщо використовувати класи метаклас замість функцій?
Є кілька причин для цього:
- Зрозуміліші назви. Коли ви читаєте UpperAttrMetaclass (type) , ви знаєте що буде далі.
- Ви можете використовувати ООП. Метаклас може успадковуватися від метакласу, перевизначати батьківські методи.
- Можна краще структурувати свій код. Навряд чи ви будете використовувати метакласи для чогось простого. Звичайно це більш складні завдання. Можливість створювати кілька методів і групувати їх в один клас дуже корисна, щоб зробити код більш зручним для читання.
- Можете використовувати __new__ , __init__ і __call__ . Це відкриває простір для творчості. Зазвичай все це можна зробити в __new__ , але деяким людям просто зручніше працювати в __init__ .
Навіщо використовувати метакласи?
"Метакласи - це магія, про яку 99% користувачів не варто навіть замислюватися. Якщо вам цікаво, чи потрібні вони вам - тоді точно ні. Люди, яким вони насправді потрібні, знають, навіщо, і що з ними робити."
~ Гуру Python Tim Peters
В основному метакласи використовуються для створення API. Типовим прикладом є Django ORM. Можна написати щось на зразок цього:
class Person(models.Model):
name = models.CharField(max_length=30)
age = models.IntegerField()
Але якщо написати так:
guy = Person(name='bob', age='35')
print(guy.age)
Він не поверне об'єкт IntegerField . Він поверне код int і навіть може взяти його безпосередньо з бази даних.
Висновок
По-перше, класи - це об'єкти, що створюють екземпляри. Класи самі є екземплярами метакласів.
>>> class Foo(object): pass
>>> id(Foo)
142630324
В Python все є об'єктами. Всі вони є або екземплярами класів, або екземплярами метакласів. За винятком type. type - сам собі метаклас. Його неможливо створити в чистому Python, це можна зробити тільки за допомогою невеликого чітерства.
По-друге, метакласи складні. Якщо вам не потрібні складні зміни класу, метакласи використовувати не варто. Просто змінити клас можна двома способами:
- вручну
- декораторами класу
У 99% випадків краще використовувати ці методи, а в 98% зміни класу взагалі не потрібні.
0 комментариев
Добавить комментарий