Декораторы 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.