Python-декораторы по сравнению с CLOS-методом "вокруг" - PullRequest
10 голосов
/ 09 сентября 2010

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

Я уточняю вопрос для уточнения:

Мне кажется, что декоратор Python - это что-то вроде "обходного" метода в CLOS.

Из того, что я помню, "обходной" метод в 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"))

И, наконец, метод округления (обратите внимание, что этот метод выглядит как декоратор) :

(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 и пытаюсь понять, как вписываются декораторы. Они кажутся немного более свободными в том, что декоратор может быть полностью внешней функцией, которая все же имеет возможность манипулировать информацией внутри вызывающей информации и даже изменять переменные экземпляра и класса вызываемого объекта, и, кроме того, кажется, что он предопределяет роль метода округления, как показано здесь. Но я надеялся, что кто-нибудь сможет объяснить связь между декораторами и методами . Я думал, что кому-то действительно понравится возможность сделать это.

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

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

Таким образом, проблема не в реализации методов CLOS в Python, а в том, чтобы показать, насколько Python приближается к этой системе питонским способом. Или показывает, как 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 ...

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

Другими словами, вызывающая цепочка метода рендеринга: (a) рендеринг: вокруг, (b) первичный треугольник, (c) конец рендеринга: вокруг. Если бы у треугольника был метод: after, он был бы вызван после primary, а затем завершился бы метод 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's 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, а также рождает идеи о том, как улучшить этот декоратор, или как проклясть меня, даже если я попытаюсь это сделать. :-)

Ответы [ 4 ]

2 голосов
/ 26 августа 2011

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

См .: http://code.activestate.com/recipes/577859-clos-like-aroundbeforeafter-auxiliary-methods/

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

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

Вы могли бы реализовать нечто подобное.Уилл был на правильном пути, но кажется, что «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()

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

2 голосов
/ 09 сентября 2010

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

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 (и мне не хватает некоторых его функций), но, похоже, он делает то, что вы хотите.

0 голосов
/ 09 сентября 2010

Я не уверен, что очень хорошо понимаю :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 's MRO ( порядок разрешения метода ).

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

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

  1. смотрит на MRO self
  2. находит следующий класс после cls
  3. вызывает метод helloworld этого класса

Итак:

foo.helloworld()

приводит к

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

Подробнее о супер и MRO см. Здесь отличная статья Шалабха Чатурведи.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...