Sunday, January 6, 2013

Python Decorators

I've now been working in Python for more than a year and we have been doing some pretty crazy stuff, especially around decorators and authorization/permissions implementations.

Admittedly, Python decorators are one of the bits of the language that can be best defined as 'magic' and certainly remains puzzling for someone like me, used to Java strict and (I still believe, much safer) compile-time type-checking.

Anyway, sparked by this very useful article on decorators, I have decided to further explore the topic of class decorators, that in the original article was just mentioned in passing, and found a few twists - the most surprising of which has certainly been finding out that the call sequence, and the type of objects being associated with type names depends on whether the decorator annotation is followed by a parameter list.
It is worth noting that python Mocks use decorators pretty heavily, and it's easy to see why, once you browse the code below: it's heavily commented and should give enough of an idea of what goes on; it can be easily executed by just running
python fun_decorators.py



"""
@copyright: AlertAvert.com (c) 2013. All rights reserved.

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

   http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.

Decorators
==========

Code to try out Python decorators.

Original idea from:
http://www.brianholdefehr.com/decorators-and-functional-python

@author: Marco Massenzio (m.massenzio@gmail.com)
         Created on [2013-01-05]
"""

class DecoratorClass(object):

    def __init__(self, klass=None, **kwargs):
        """This gets called every time the @DecoratorClass annotation
        is encountered.

        @param klass: the type of the class being decorated, it will be 
                passed in if no parameters are passed in the annotation
        @type klass: type
        @param kwargs: a dictionary containing any of the named 
                parameters passed in the decorator's declaration
        @type kwargs: dict
        """

        # If the decorator is declared without parameters, 
        # the class type will be passed in
        self._name = 'default'
        self._klass = None
        if klass:
            print 'No-args decorator for: ', klass.__name__
            self._klass = klass
        else:
            print 'Args declared: ', kwargs
            self._name = kwargs.get('name')

    def __call__(self, *args, **kwargs):
        """This gets invoked every time a decorated class gets created, 
        with the actual arguments in what would appear to look like a 
        constructor call: they do not have to (and in fact, won't) match 
        the actual formal argument list of the
        constructor of the decorated class (which may not even get invoked).

        If the decorator is declared with one or more arguments 
        (see Different class below), then the first item in ``args`` will be the
        **type** of the class being decorated (which
        will not have been passed in to the __init__()).

        This gets invoked immediately after self#__init__() if the decorator is 
        declared with one or more arguments, or when the "constructor" is invoked.
        """
        print 'Decorator __call__ invoked with: ', args, kwargs
        if self._klass:
            print 'Decorated instance: ', self._klass.__name__
            return self._klass(self._name)
        else:
            print 'Setting up klass'
            self._klass = args[0]
            # Here we inject a 'class-level' static method
            self._klass.get_name = self.get_name
        return self._klass

    def get_name(self):
        return self._name


@DecoratorClass
class Decorated(object):
    def __init__(self, name):
        print '>>>>> I am being decorated! <<<<<'
        self.name = name

    def call_me(self, *args):
        print 'I\'m being called: ', self.name
        print 'These are my args: ', args

# Notice how here a 'type' name (Decorated) has been completely 
# 'hijacked' to point to a specific
# instance of a DecoratorClass object (this provides 'closure')

print 'what is Decorated here?', Decorated
# >> what is Decorated here? <__main__.DecoratorClass object at 0x7f34b71308d0>

@DecoratorClass(name='another')
class Different(object):
    def __init__(self):
        # Notice how here we are using a 'static' method 
        # that has been injected by the decorator
        self.name = Different.get_name()

    def method(self, *args):
        """This is the same implementation as Decorated#call_me(), 
        just to show how they behave differently
        """
        print 'I\'m being called: ', self.name
        print 'These are my args: ', args

    def __call__(self, *args, **kwargs):
        return 'Different called with: ', args, kwargs

# Here, Different is whatever DecoratorClass#__call__()  returned: 
# this happens to be what one would expect it to be (a Different class 
# type) but that's only because the code makes it so

print 'what is Different here? ', Different
# >> what is Different here?  

print 'Calling Decorated class constructor'
# Note how the params being passed here have no relation with the 
# constructor argument list and in fact, the name of the 'decorated' 
# class is not even given here, but in the decorator

# Due to the 'magic' of decorators, DecoratorClass#__call__() is
# instead invoked here,
# on the instance that was created at declaration
deco = Decorated(123, "Hello", foo='foo', baz='baz')

# Again, now ``deco`` here happens to be what one would expect it to 
# be (an instance of the Decorated class) but solely because the code 
# makes it so - it could have been anything, really

deco.call_me(1, 2, 3, 1)
# >> I'm being called:  default
# >> These are my args:  (1, 2, 3, 1)

print 'Creating now a Different class:'
# Here the call does actually invoke the constructor (__init__()) 
# for the Different class, and the returned object is again what one 
# would expect it to be (an instance of Different):
diff = Different()

diff.method('quart', 'naught')
# >> I'm being called:  another
# >> These are my args:  ('quart', 'naught')

# Obviously, as the Different#__call__() method is defined, we can call it too:

print diff(1, 2, 3, value='val')
# >> ('Different called with: ', (1, 2, 3), {'value': 'val'})

# If we now create an entirely different instance of a Different object,
# it will still have the same 'static' method injected by the decorator:
another_diff = Different()

print 'My name is:', another_diff.name
# >> My name is: another

1 comment:

  1. đồng tâm
    game mu
    cho thuê nhà trọ
    cho thuê phòng trọ
    nhac san cuc manh
    số điện thoại tư vấn pháp luật miễn phí
    văn phòng luật
    tổng đài tư vấn pháp luật
    dịch vụ thành lập công ty trọn gói

    “Ân, Thái tử điện hạ không nói gì chứ?” Sở Dương vẫn mỉm cười đều đều hỏi tiếp.

    “Ách, không nói gì, không nói gì …A!” Thành Tử Ngang nói được phân nửa thì đột nhiên bừng tỉnh, nghẹn họng nhìn Sở Dương trân trối, thần sắc trong mắt khiếp hãi tựa như đang gặp quỷ.

    “Không nói gì là tốt rồi a …” Sở Dương đứng dậy, gật nhẹ đầu rồi nói: “Kỳ thật ta vẫn hy vọng hắn nói thêm chút gì đó, ha ha ha... Thành đại nhân tuổi đã cao lại cả ngày bôn ba mệt nhọc, không nên quá cực khổ…ha ha. Sớm nghỉ ngơi đi thôi.” Nói đoạn đẩy cửa nghênh ngang rời đi.

    Thành Tử Ngang đầu toát mồ hôi, toàn thân lạnh ngắt.
    Sau khi kết thúc mọi việc tại Bổ Thiên Các, Thành Tử Ngang liền theo mật đạo và dùng tốc độ nhanh nhất mà chạy đến phủ thái tử, sau đó trong khoảng thời gian ngắn nhất báo cáo xong mọi chuyện, rồi lại lấy tốc độ nhanh nhất mà quay trở về. Toàn bộ quá trình thậm chí không tốn đến một khắc đồng hồ!

    Vậy mà Sở Dương lại đoán biết được tất cả, thậm chí còn ở nơi này mà chờ hắn. Cho dù là Thiết Bổ Thiên có mật báo với Sở Dương thì hắn cũng không thể nhanh như vậy!

    ReplyDelete