Декораторы Python по сравнению с методом CLOS "вокруг"

Я возвращаюсь к своей CLOS (Common Lisp Object System) дням для этого абстрактного вопроса.

Я уточняю вопрос, чтобы уточнить:

Мне кажется, что декоратор Python похож на метод "around" в CLOS.

Из того, что я помню, метод "around" в CLOS - это метод/функция, которая обертывает основной метод/функцию с тем же именем. Он также перемещается вверх и вниз по подклассам. Вот какой-то синтаксис (я просто схватил свою книгу).

Все эти методы Этот будет внутри класса:

(defmethod helloworld ()
  (format t "Hello World"))

Может быть и до и после методов (которые я бросаю для полноты):

(defmethod helloworld :before ()
  (format t "I'm executing before the primary-method"))

(defmethod helloworld :after ()
  (format t "I'm executing after the primary-method"))

И, наконец, метод round (Обратите внимание, что этот метод, казалось, был как декоратор):

(defmethod helloworld :around ()
  (format t "I'm the most specific around method calling next method.")
  (call-next-method)
  (format t "I'm the most specific around method done calling next method."))

Я считаю, что вывод будет:

I'm the most specific around method calling next method. 
I'm executing before the primary-method
Hello World
I'm executing after the primary-method
I'm the most specific around method done calling next method.

Я всегда использовал это понимание классов и их методов как ориентир для разработки кода. И, к сожалению, немногие языки, похоже, заходят так далеко от своей параметризации и мощности метода.

Я новичок в Python и пытаюсь понять, как вписываются декораторы. Они кажутся немного более слабыми, поскольку декоратор может быть полностью внешней функцией, которая все же имеет возможность манипулировать информацией в вызывающей информации и даже изменять переменные экземпляра и класса вызываемого объекта и, кроме того, что он, как представляется, выполняет роль метода round, как показано здесь. Но я надеялся, что кто-то может помочь объяснить отношения между декораторами и методами. Я думал, что кому-то действительно понравится возможность сделать это.

Что делает CLOS мощным для меня, так это то, что вы можете иметь множественное наследование с помощью этих методов. Таким образом, класс может состоять из суперклассов, которые содержат различные функциональные возможности и атрибуты, которые обрабатывают себя. Таким образом, метод round на одном из суперклассов может прекратить поток (если "call-next-method" не выполняется), точно так же, как может работать декоратор. Так это то же самое, что и декоратор, или другое? В методе around вы передаете одни и те же аргументы, но для декоратора вы передаете "функцию" в строгом определении, которое будет дополнено. Но результат тот же?

Спасибо большое! Может быть, кто-то может показать приближение к вышеприведенному в Python. done вызывающий следующий метод.

Таким образом, проблема заключается не в реализации CLOS-методов в Python, а в том, как близко Python добирается до этой системы с помощью pythonic. Или показать, как Python на самом деле лучше этого.

Это больше похоже на пример, о котором я думал:

class shape with attributes position and method area
class renderable with attribute visible and methods render, and render :around
class triangle with superclass shape and renderable attributes p1,p2,p3 and method render and method area
class square ...

Если экземпляр треугольника имеет видимый = false, то render: around не будет вызывать первичный метод треугольника.

Другими словами, вызывающая цепочка метода рендеринга (a) визуализируется: вокруг, (b) треугольник первичный, (c) завершает рендеринг: вокруг. Если у треугольника был метод: after, он будет вызываться после первичного, а затем метод round будет завершен.

Я понимаю трудности использования наследования по сравнению с рассмотрением более новых шаблонов проектирования, но здесь я пытаюсь свести свои знания CLOS. Если есть шаблон дизайна, который соответствует декораторам (точнее, чем шаблон дизайна "декоратор" ), это было бы прекрасно также понять.

Заключение

Я получаю украшение декораторов. Но я хотел представить, где я нахожусь, пытаясь подражать обходу метода CLOS. Все вдохновили меня попробовать, так как у меня есть книга, и я очень хорошо ее помню. Спасибо всем за все замечательные предложения, они все часть головоломки. С точки зрения реализации фактической структуры в одном декораторе, Уилл приблизился, и это помогло продвинуть его вперед с помощью динамического поиска методов (см. Ниже). Я создал один декоратор, который делает то, что я ищу, и могу работать на любом классе. Я уверен, что это может быть более чистым, и есть проблема, что он смотрит только на один суперкласс и делает это со странностями, но он работает.

'''file: cw.py'''
'''This decorator does the job of implementing a CLOS method traversal through superclasses.  It is a very remedial example but it helped me understand the power of decorators.'''
'''Modified based on Richards comments'''
def closwrapper(func): # *args, **kwargs  ?
    def wrapper(self):  #what about superclass traversals???
        name = func.__name__
        # look for the methods of the class 
        before_func = getattr(self, name + "_before", None)
        after_func = getattr(self, name + "_after", None)
        around_func = getattr(self, name + "_around", None)
        sup = super(self.__class__,self)
        #self.__class__.__mro__[1]
        if sup:
            # look for the supermethods of the class (should be recursive)
            super_before_func = getattr(sup,name + "_before", None)
            super_after_func = getattr(sup,name + "_after", None))
            super_around_func = getattr(sup,name + "_around", None))

        ''' This is the wrapper function which upgrades the primary method with any other methods that were found above'''
        ''' The decorator looks up to the superclass for the functions.  Unfortunately, even if the superclass is decorated, it doesn't continue chaining up.  So that a severe limitation of this implementation.'''
        def newfunc():
            gocontinue = True
            supercontinue = True
            if around_func: 
                gocontinue = around_func() 
                if gocontinue and super_around_func:
                  supercontinue = super_around_func()
            if gocontinue and supercontinue:
                if before_func: before_func()
                if super_before_func: super_before_func()
                result = func(self)
                if super_after_func: super_after_func()   
                if after_func: after_func()              
            else:
                result = None
            if gocontinue:
                if super_around_func: super_around_func(direction="out")
            if around_func: around_func(direction='out')
            return result
        return newfunc()

    return wrapper

# Really, the way to do this is to have the decorator end up decorating
# all the methods, the primary and the before and afters.  Now THAT would be a decorator!

class weeclass(object):

    @closwrapper
    def helloworld(self):
        print "Hello Wee World"

    def helloworld_before(self):
        print "Am I really so wee Before?  This method is not called on subclass but should be"



class baseclass(weeclass):
    fooey = 1

    def __init__(self):
        self.calls = 0

    @closwrapper
    def helloworld(self):
        print "Hello World"

    def helloworld_before(self):
        self.fooey += 2
        print "Baseclass Before"

    def helloworld_after(self):
        self.fooey += 2
        print "Baseclass After Fooey Now",self.fooey

    def helloworld_around(self,direction='in'):
        if direction=='in': 
            print "Aound Start"
            if self.fooey < 10:
                return True
            else:
                print ">>FOOEY IS TOO BIG!!!"
                self.fooey = -10
                return False
        #call-next-method
        if not direction=='in': 
            #self.barrey -= 4  #hello??  This should not work!!!  It should croak?
            print "Around End"  



class subclass(baseclass): 
    barrey = 2

    @closwrapper
    def helloworld(self):
        print "Hello Sub World Fooey",self.fooey,"barrey",self.barrey

    def helloworld_before(self):
        self.fooey -= 1
        self.barrey += 5
        print "  Sub Before"

    def helloworld_after(self):
        print "Sub After"

    def helloworld_around(self,direction='in'):
        if direction=='in': 
            print "Sub Around Start"
            if self.barrey > 4:
                print ">>Hey Barrey too big!"
                self.barrey -= 8
                return False
            else:
                return True
        #call-next-method
        if not direction=='in': 
            self.barrey -= 4
            print "Sub Around End"  

Вот результат, чтобы вы могли видеть, что я пытаюсь сделать.

Python 2.6.4 (r264:75706, Mar  1 2010, 12:29:19)  
[GCC 4.2.1 (Apple Inc. build 5646) (dot 1)] on darwin  
Type "help", "copyright", "credits" or "license" for more information.  
>>> import cw  
>>> s= cw.subclass()  
>>> s.helloworld()  
Sub Around Start  
Aound Start  
Sub Before  
Baseclass Before  
Hello Sub World Fooey 2 barrey 7  
Baseclass After Fooey Now 4  
Sub After  
Around End  
Sub Around End  
>>> s.helloworld()  
Sub Around Start
Aound Start  
Sub Before  
Baseclass Before  
Hello Sub World Fooey 5 barrey 8  
Baseclass After Fooey Now 7  
Sub After  
Around End  
Sub Around End  
>>> s.helloworld()  
Sub Around Start  
Aound Start  
Sub Before  
Baseclass Before  
Hello Sub World Fooey 8 barrey 9  
Baseclass After Fooey Now 10  
Sub After  
Around End  
Sub Around End  
>>> s.helloworld()  
Sub Around Start  
>>Hey Barrey too big!  
Sub Around End  
>>> s.helloworld()  
Sub Around Start  
Aound Start  
>>FOOEY IS TOO BIG!!!  
Around End  
Sub Around End  
>>> s.helloworld()  
Sub Around Start  
Aound Start  
Sub Before  
Baseclass Before  
Hello Sub World Fooey -9 barrey -6  
Baseclass After Fooey Now -7  
Sub After  
Around End  
Sub Around End  
>>> s.helloworld()  
Sub Around Start  
Aound Start  
Sub Before  
Baseclass Before  
Hello Sub World Fooey -6 barrey -5  
Baseclass After Fooey Now -4  
Sub After  
Around End  
Sub Around End  
>>> s.helloworld()  
Sub Around Start  
Aound Start  
Sub Before  
Baseclass Before  
Hello Sub World Fooey -3 barrey -4  
Baseclass After Fooey Now -1  
Sub After  
Around End  
Sub Around End  
>>> b = cw.baseclass()  
>>> b.helloworld()  
Aound Start  
Baseclass Before  
Am I really so wee Before?  This method is not called on subclass but should be  
Hello World  
Baseclass After Fooey Now 5  
Around End  
>>> b.helloworld()  
Aound Start  
Baseclass Before  
Am I really so wee Before?  This method is not called on subclass but should be  
Hello World  
Baseclass After Fooey Now 9  
Around End  
>>> b.helloworld()  
Aound Start  
Baseclass Before  
Am I really so wee Before?  This method is not called on subclass but should be  
Hello World  
Baseclass After Fooey Now 13  
Around End  
>>> b.helloworld()  
Aound Start  
>>FOOEY IS TOO BIG!!!  
Around End  
>>> b.helloworld()  
Aound Start  
Baseclass Before  
Am I really so wee Before?  This method is not called on subclass but should be  
Hello World  
Baseclass After Fooey Now -6  
Around End  

Надеюсь, что это создаст некоторое понимание вызова CLOS, а также искрометные идеи о том, как улучшить этот декоратор, или о том, как заставить меня задуматься, даже пытаясь это сделать.:-)

Ответ 1

Здесь быстрая и грязная реализация немного улучшена реализация (теперь с помощью метода around, который, надеюсь, будет в нужных местах), используя декораторы

def hints(before=None, after=None, around=None):
    """A decorator that implements function hints to be run before, after or
    around another function, sort of like in the CLOS."""

    # Make sure all of our hints are callable
    default = lambda *args, **kwargs: None
    before = before if callable(before) else default
    after = after if callable(after) else default
    around = around if callable(around) else default

    # The actual decorator function.  The "real" function to be called will be
    # pased to this as `fn`
    def decorator(fn):

        # The decorated function.  This is where the work is done.  The before
        # and around functions are called, then the "real" function is called
        # and its results are stored, then the around and after functions are
        # called.
        def decorated(*args, **kwargs):
            around(*args, **kwargs)
            before(*args, **kwargs)
            result = fn(*args, **kwargs)
            after(*args, **kwargs)
            around(*args, **kwargs)
            return result
        return decorated
    return decorator

# Shortcuts for defining just one kind of hint
def before(hint):
    return hints(before=hint)

def after(hint):
    return hints(after=hint)

def around(hint):
    return hints(around=hint)


# The actual functions to run before, after, around
def beforefn():
    print 'before'

def afterfn():
    print 'after'

def aroundfn():
    print 'around'


# The function around which the other functions should run
@before(beforefn)
@after(afterfn)
@around(aroundfn)
def fn():
    print 'Hello World!'

# Or use the single @hints decorator
@hints(before=beforefn, after=afterfn, around=aroundfn)
def fn2():
    print 'Goodbye World!'

Вызов fn() приводит к следующему:

>>> fn()
around
before
Hello World!
after
around
>>> fn2()
around
before
Goodbye World!
after
around

Декораторы в этом случае могут быть немного запутанными, потому что в них задействованы две вложенные функции, а не одна вложенная функция, встречающаяся во многих декораторах.

Это может быть не так элегантно, как CLOS-версия (и, возможно, мне не хватает некоторых ее функций), но она, похоже, делает то, что вы хотите.

Ответ 2

Вы можете реализовать что-то подобное. Уилл был на правильном пути, но похоже, что "call-next-method" довольно важен для использования "вокруг", который может быть реализован как таковой:

def around(callback):
  def decorator(fn):
    return lambda *a, **kw: callback(lambda: fn(*a, **kw))
  return decorator

def hello_before(call_next_method):
  print("I'm executing before the primary-method")
  return call_next_method()

def hello_after(call_next_method):
  value = call_next_method()
  print("I'm executing after the primary-method")
  return value


def hello_around(call_next_method):
  print "I'm the most specific around method calling next method."
  value = call_next_method()
  print("I'm the most specific around method done calling next method.")
  return value


@around(hello_around)
@around(hello_after)
@around(hello_before)
def helloworld():
  print("Hello world")

helloworld()

Это дает точно такой же результат, что и ваш, с аналогичными конструкциями. Просто обратите внимание на порядок, в котором вы украшаете функцию.

Ответ 3

Вдохновленный исходным вопросом и всевозможные черновики, я реализовал CLOS-типа вокруг/перед/после вспомогательных методов Python module.

Смотрите: http://code.activestate.com/recipes/577859-clos-like-aroundbeforeafter-auxiliary-methods/

Я сделал это , используя несколько встроенных функций Python:

  • класс и функция декораторы,
  • класс наследование плюс встроенная функция super(),
  • личное имя mangling (чтобы освободить пользователей от необходимости избыточного имени класса перепечатывание).

Ответ 4

Я не уверен, что понимаю :around, :before и :after очень хорошо, но что-то вроде этого, что вы ищете?

class Base(object):
    def helloworld(self):
        print('Hello World')

class After(object):
    def helloworld(self):
        super(After,self).helloworld()
        print('After')        

class Before(object):
    def helloworld(self):
        print('Before')        
        super(Before,self).helloworld()

class Around(object):
    def helloworld(self):
        print('Enter around')
        super(Around,self).helloworld()        
        print('Exit around around')

class Foo(Around,Before,After,Base):
    def helloworld(self):
        super(Foo,self).helloworld()

foo=Foo()

Это foo MRO (порядок разрешения метода).

print([cls.__name__ for cls in foo.__class__.mro()])
# ['Foo', 'Around', 'Before', 'After', 'Base', 'object']

Когда вы говорите super(cls,self).helloworld(), Python

  • смотрит на self MRO
  • находит следующий класс после cls
  • вызывает этот класс helloworld Метод

Итак:

foo.helloworld()

дает

# Enter around
# Before
# Hello World
# After
# Exit around around

Подробнее о супер и MRO см. эту отличную статью от Shalabh Chaturvedi.