Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Craig de Stigter: Intro to Metaclasses

Craig de Stigter: Intro to Metaclasses

= = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = =
Craig de Stigter:
Intro to Metaclasses
= = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = =
@ Kiwi PyCon 2013 - Saturday, 07 Sep 2013 - Track 2
http://nz.pycon.org/

**Audience level**

Intermediate

**Description**

Metaprogramming is a valuable technique for putting complexity where it belongs: behind a clean, friendly API. This talk goes over the basics of metaclasses and introspection in Python, and covers how and when you should use them to make your code more approachable.

**Abstract**

Python's Metaclasses are an "advanced" technique, but once you get started they're surprisingly approachable.

Starting with the basics, we'll:

* figure out what a metaclass does
* point out some existing metaclasses you might already have used
* see where you might use one in your own code.

We'll compare use of a metaclass with some alternatives that don't use metaclasses, to better understand why you'd want to use them.

Then we'll take a look at a couple of use-cases where using a metaclass helps us achieve a much more friendly and intuitive API than we otherwise could.

**YouTube**

http://www.youtube.com/watch?v=OCDbLcCB-uA

New Zealand Python User Group

September 07, 2013
Tweet

More Decks by New Zealand Python User Group

Other Decks in Programming

Transcript

  1. Classes refresher • Every object has a type • ‘type’

    == ‘class’ • IMHO ‘class’ is a stupid word
  2. `type` • `type` is a ‘metaclass’ • A metaclass is

    the type of a type • The `class` statement creates an instance of a metaclass (i.e. a class)
  3. class FormMetaclass(type): def __new__(metaclass, name, bases, attributes): base_fields = [(k,

    v) for (k, v) in attributes.items() if isinstance(v, forms.Field)] attributes = {'base_fields': dict(base_fields)} return type.__new__(metaclass, name, bases, attributes) # Form = FormMetaclass("Form", (object,), {}) class Form(object): __metaclass__ = FormMetaclass
  4. # Form = FormMetaclass("Form", (object,), {}) class Form(object): __metaclass__ =

    FormMetaclass # LoginForm = FormMetaclass("LoginForm", (Form,), {"user": CharField(), "pass": CharField()}) class LoginForm(Form): user = CharField() pass = CharField()
  5. class XmlHandler(Handler): def handle(self, xml): return {"no": "XML is gross.

    Go away"} Handler.register_handler('text/xml', XmlHandler) class JsonHandler(Handler): def handle(self, json): return json.loads(json) Handler.register_handler('application/json', JsonHandler) Death to registries!
  6. class XmlHandler(Handler): accepts = ('text/xml',) def handle(self, xml): return {"no":

    "XML is gross. Go away"} class JsonHandler(Handler): accepts = ('application/json',) def handle(self, json): return json.loads(json) Death to registries!
  7. class HandlerMetaclass(type): def __new__(metaclass, name, bases, attributes): klass = type.__new__(metaclass,

    name, bases, attributes) try: Handler except NameError: # we’re creating Handler itself klass.registry = {} else: # we’re creating a subclass of Handler for content_type in attributes['accepts']: Handler.registry[content_type] = klass return klass
  8. # This is a plain decorator @debug_function def myfunc(): pass

    # This is a decorator *factory* @debug_function(output_to=sys.stdout) def myotherfunc(): pass Decorators are fun
  9. # @debug_function def myfunc(): pass myfunc = debug_function(myfunc) # @debug_function(output_to=sys.stdout)

    def myotherfunc(): pass myotherfunc = debug_function(output_to=sys.stdout)(myotherfunc) Decorators are fun
  10. def debug_function(output_to=None): def dec(func): @wraps(func) def wrapper(*args, **kwargs): output_to.write( "%s

    called. args=%r, kwargs=%r" % (func.__name__, args, kwargs)) return func(*args, **kwargs) return wrapper if callable(output_to): # decorator, called without parentheses. # i.e. @debug_function func, output_to = output_to, sys.stderr return dec(func) else: # decorator factory # i.e. @debug_function() output_to = sys.stderr return dec
  11. class debug_function(NiceDecorator): def __init__(self, func, output_to=sys.stderr): super(debug_function, self).__init__(func) self.output_to =

    output_to def __call__(self, *a, **kw): self.output_to.write( "%s(args=%r, kwargs=%r)\n" % (self.func.__name__, a, kw)) return self.func(*a, **kw) Why not this?
  12. class NiceDecorator(object): "Base class for class-based decorators." __metaclass__ = NiceDecoratorMeta

    def __init__(self, func): self.func = func def __call__(self, *args, **kwargs): return self.func(*args, **kwargs) NiceDecorator
  13. # class MyClass(object): # foo = "bar" MyClass = type('MyClass',

    (object,), {"foo": "bar"}) # a rough approximation of what happens when you create a class: class type(type): @classmethod def __call__(metaclass, name, bases, attributes): cls = type.__new__(metaclass, name, bases, attributes) metaclass.__init__(cls, name, bases, attributes) return cls Making a class
  14. class NiceDecoratorMeta(type): def __call__(self, *args, **kwargs): # `self`: a NiceDecoratorMeta

    *instance*, ie NiceDecorator or a subclass # `args`, `kwargs`: decorator factory arguments, if there are any. if len(args) == 1 and callable(args[0]) and not kwargs: # decorator, i.e. @dec func = args.pop(0) cls = type.__call__(self, func) return cls else: # decorator factory, i.e. @dec() def decorate(func): decorated = type.__call__(self, func, *args, **kwargs) return func(decorated) return decorate The cool stuff
  15. Wrapping up • Metaclasses are just classes • They’re not

    magic. • They’re powerful tools for making great APIs • Use them for good