induction in metaclasses

50
Induction in metaclasses

Upload: ionel-marie-cristian

Post on 06-Aug-2015

15 views

Category:

Software


0 download

TRANSCRIPT

Page 1: Induction in metaclasses

Induction in metaclasses

Page 2: Induction in metaclasses
Page 3: Induction in metaclasses

Induction in metaclasses

MET

A CLASSIonel Cristian Mărieș — Python / OSS enthusiast

blog.ionelmc.rogithub.com/ionelmc

Page 4: Induction in metaclasses
Page 5: Induction in metaclasses

Why?Lots of unwarranted fear ... ʺitʹs magicʺ, ʺthey are badʺ etcThey present lots of interesting opportunities for:

Reducing boilerplate.

Nicer APIs

Especially useful in designs that rely on subclassing (subclasses will use the samemetaclass).

They have a tax:

Introspection issues, unhappy linters.

Need to understand them to use effectively. Because some interactions are not explicit.

Page 6: Induction in metaclasses
Page 7: Induction in metaclasses

Few basic thingsObjects allow overriding certain object interfaces and operations

Like the function call operator:>>> class Funky:...     def __call__(self):...         print("Look at me, I work like a function!")>>> f = Funky()>>> f()Look at me, I work like a function!

Or a�ribute access:>>> class MagicHat:...     def __getattr__(self, name):...         return "Imagine a %a ..." % name

>>> hat = MagicHat()>>> hat.rabbit"Imagine a 'rabbit' ..."

Page 8: Induction in metaclasses
Page 9: Induction in metaclasses

But there are some caveatsMagic methods are looked up (resolved) on the class, not on the instance. [1]>>> class Simple:...     pass>>> s = Simple()

Doesnʹt work:>>> s.__call__ = lambda self: "x">>> s()Traceback (most recent call last):  ...TypeError: 'Simple' object is not callable

Aha! It is resolved on the class:>>> Simple.__call__ = lambda self: "x">>> s()'x'

[1] Exceptions: Python 2 old‑style objects and few special‑cased methods.

Page 10: Induction in metaclasses
Page 11: Induction in metaclasses

Constructors and initialisersInstance creation is just a magic method you can override:>>> class WithConstructor(object):...     def __new__(cls, value):...         print("Creating the instance")...         return super().__new__(cls)......     def __init__(self, value):...         print("Initialising the instance")...         self.value = value

>>> WithConstructor(1)Creating the instanceInitialising the instance<__main__.WithConstructor object at 0x...>

Note that if  __new__  returns a different type of object then a different __init__  would be called (from the other typeʹs class).

Page 12: Induction in metaclasses
Page 13: Induction in metaclasses

Types and instances

A class is just an instance of something else, a metaclass.

Page 14: Induction in metaclasses
Page 15: Induction in metaclasses

You can have a custom metaclassSo, if a class is just an instance, we can customise the creation process:>>> class Meta(type):...     pass>>> class Complex(metaclass=Meta):...     pass>>> Complex()<__main__.Complex object at 0x...>

Normally, the metaclass should be a subclass of  type . More on this later.

Page 16: Induction in metaclasses
Page 17: Induction in metaclasses

The instantiation dance in detail

Remember that:

__init__  does not create instances,  __new__  does.

Magic methods are resolved on the metaclass.  Complex()  is equivalent to Meta.__call__ .

Page 18: Induction in metaclasses
Page 19: Induction in metaclasses

The full interface__prepare__(mcs, name, bases, **kwargs)  ‑ New in Python 3, returns thenamespace dictionary

__new__(mcs, name, bases, attrs, **kwargs)  ‑ Returns an instance ofMeta

__init__(cls, name, bases, attrs, **kwargs)  ‑ Runs initialisation code,typeʹs __init__ doesnʹt do anything

kwargs: only in Python 3, example:

class Class(object, metaclass=Meta, a=1, b=2, c=3):    pass

__call__  ‑ Returns and instance of Complex (which in turn is instance of Meta).Because magic methods are resolved on the class.

Page 20: Induction in metaclasses
Page 21: Induction in metaclasses

Syntactic sugar>>> class Simple(Base):...     foo = 'bar'

Is equivalent to:>>> Simple = type('Simple', (Base, ), {'foo': 'bar'})

~

>>> class Simple(Base, metaclass=Meta):...     foo = 'bar'

Is equivalent to:>>> Simple = Meta('Simple', (Base, ), {'foo': 'bar'})

Page 22: Induction in metaclasses
Page 23: Induction in metaclasses

Going back to the type of the metaclassNormally you inherit from type because it implements all the necessary interfacefor a well functioning class.

But you can use just as well a plain callable.

And let horrible stuff like this happen:>>> class NotAnymore(metaclass=print):...     passNotAnymore () {'__qualname__': 'NotAnymore', '__module__': '__main__'}

>>> repr(NotAnymore)'None'

Page 24: Induction in metaclasses
Page 25: Induction in metaclasses

Why can the metaclass be any callable

Page 26: Induction in metaclasses
Page 27: Induction in metaclasses

Why can the metaclass be any callable

Page 28: Induction in metaclasses
Page 29: Induction in metaclasses

Useless tricks>>> type(type) is typeTrue

But how about:>>> class mutable(type):  # dummy type, so the instance has a __dict__...     pass              # (mutable in other words)...>>> class typeish(type, metaclass=mutable):...     pass...>>> typeish.__class__ = typeish>>> print(type(typeish) is typeish)True

Page 30: Induction in metaclasses
Page 31: Induction in metaclasses

New in Python 3: __prepare__Allows users to customize the class creation before the body of the class isexecuted.

Basically allows you to return a different object as the namespace (instead of adict).

What can you do with it? Lots of interesting stuff:

Single dispatch

Duplicate validators

Field order aware objects

Page 32: Induction in metaclasses
Page 33: Induction in metaclasses

Disallow overrides with __prepare__>>> class StrictDict(dict):...     def __setitem__(self, name, value):...         if name in self:...             raise RuntimeError('You already defined %r!' % name)...         super().__setitem__(name, value)...

>>> class StrictMeta(type):...     def __prepare__(name, bases):...         return StrictDict()...

>>> class Strict(metaclass=StrictMeta):...     a = 1...     a = 2  # Ooops. Will ever anyone notice this?Traceback (most recent call last):  ...RuntimeError: You already defined 'a'!

Page 34: Induction in metaclasses
Page 35: Induction in metaclasses

Single dispatch with __prepare__ ﴾1/3﴿Suppose we have this code:>>> class Int(int):...     def __repr__(self):...         return "Int(%d)" % self......     @dispatching.dispatch...     def __add__(self, other:int):...         return Int(int.__add__(self, other))......     @__add__.dispatch...     def __add__(self, other:str):...         return Int(int.__add__(self, int(other)))

>>> i = Int(5)>>> i + 1Int(6)>>> i + "2"Int(7)

Page 36: Induction in metaclasses
Page 37: Induction in metaclasses

Single dispatch with __prepare__ ﴾2/3﴿We can make it seamless using  __prepare__ :>>> class Int(int, metaclass=SingleDispatchMeta):...     def __repr__(self):...         return "Int(%d)" % self......     def __add__(self, other:int):...         return Int(int.__add__(self, other))......     def __add__(self, other:str):...         return Int(int.__add__(self, int(other)))

>>> i = Int(5)>>> i + 1Int(6)>>> i + "2"Int(7)

Page 38: Induction in metaclasses
Page 39: Induction in metaclasses

Single dispatch with __prepare__ ﴾3/3﴿>>> import dispatching

>>> class SingleDispatchCollector(dict):...     def __setitem__(self, name, value):...         if callable(value):...             if name in self:...                 self[name].dispatch(value)...             else:...                 super().__setitem__(name,...                                     dispatching.dispatch(value))...         else:...             super().__setitem__(name, value)

>>> class SingleDispatchMeta(type):...     def __prepare__(name, bases):...         return SingleDispatchCollector()

Page 40: Induction in metaclasses
Page 41: Induction in metaclasses

What else can you do with metaclassesDocstring fixers

DSLs, Models

Class or usage validators

Subclass registry systems

All sorts of behavior changing

Warning/deprecation systems

Automatic function decoration

Page 42: Induction in metaclasses
Page 43: Induction in metaclasses

Metaclasses vs class decoratorsA class decorator takes a class as input and, hopefully, returns a class.

The decorator usually returns the same class after making some changes to it (eg:monkey‑patching).

Disadvantages of a decorator:

Lack of flexibility. They are equivalent to a  __init__  method in themetaclass.

When accessing methods through the class or instance they become boundfunctions (which is not the same thing as the original function).Solution is to pull them out of  __dict__ .

It becomes boilerplate.

Doesnʹt work well with subclassing.

Page 44: Induction in metaclasses
Page 45: Induction in metaclasses

Known usesPythonʹs abc

Django

SQLAlchemy

fields$ pip install fields>>> from fields import Fields>>> class Pair(Fields.a.b):...     pass...>>> Pair(1, 2)Pair(a=1, b=2)

Page 46: Induction in metaclasses
Page 47: Induction in metaclasses

Where do we draw the line?Metaclasses have some disadvantages. What is a good tradeoff?

Whatʹs worse, boilerplate or implicit behavior?

Two important things to consider:

Is the implicit behavior intuitive?

Does the abstraction leak?

Page 48: Induction in metaclasses
Page 49: Induction in metaclasses

And last ....Q: What does the metaclass say to the class?

A: Youʹre  __new__  to me.

Page 50: Induction in metaclasses