Python 装饰器与 CLOS 的“around”方法比较
我想起了我在使用CLOS(通用Lisp对象系统)时的一些事情,想问个抽象的问题。
我想补充一下问题,以便更清楚:
在我看来,Python中的装饰器有点像CLOS中的“around”方法。
根据我记得的,CLOS中的“around”方法是一个可以包裹同名的主要方法/函数的函数。它还可以在子类之间上下遍历。 这里有一些语法(我刚从书里找的)。
所有这些方法 这 都是在一个类里面:
(defmethod helloworld ()
(format t "Hello World"))
还有“before”和“after”方法 (我提到这个是为了完整性):
(defmethod helloworld :before ()
(format t "I'm executing before the primary-method"))
(defmethod helloworld :after ()
(format t "I'm executing after the primary-method"))
最后是“around”方法 (注意这个方法看起来像个装饰器):
(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还很陌生,想看看装饰器是怎么回事。它们似乎有点松散,因为装饰器可以是一个完全外部的函数,但它能操作调用信息中的内容,甚至修改被调用对象的实例和类变量,而且它似乎也能执行“around”方法的角色。希望有人能帮我解释一下装饰器和around方法之间的关系。我觉得会有人很乐意来做这个。
对我来说,CLOS强大的地方在于你可以使用这些方法进行多重继承。这样,一个类可以由包含不同功能和属性的超类组成,这些超类可以自行处理。因此,一个超类上的“around”方法可能会终止流程(如果没有执行“call-next-method”),就像装饰器的工作方式一样。那么这和装饰器是一样的吗,还是不同的?在“around”方法中,你传入的是相同的参数,但在装饰器中,你传入的是被增强的“函数”。但结果是一样的吗?
非常感谢!也许有人可以在Python中展示与上述内容最接近的实现。
所以问题不在于如何在Python中实现CLOS的方法,而是展示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方法将不会调用三角形的主要方法。
换句话说,render方法的调用链是(a)renderable :around,(b)三角形主要方法,(c)finish renderable :around。如果三角形有一个:after方法,它会在主要方法之后被调用,然后around方法会结束。
我理解使用继承与考虑更新设计模式的困难,但在这里我试图将我的CLOS知识结合起来。如果有一个设计模式与装饰器匹配(比“装饰器”设计模式更准确),那也很值得理解。
结论
我正在逐渐掌握装饰器。但我想展示我在尝试模拟CLOS方法遍历时的进展。大家都激励我去尝试,因为我有这本书,而且我记得得还不错。感谢大家的建议,它们都是拼图的一部分。在实现单个装饰器的实际结构方面,Will做得很接近,这为动态方法查找提供了帮助(见下文)。我创建了一个单一的装饰器,能够实现我想要的功能,并且可以在任何类上操作。我相信它可以更简洁,但它只查找一个超类,并且处理around方法的方式有点奇怪,但它确实有效。
'''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的调用方式,也激发一些关于如何改进这个装饰器的想法,或者如何批评我尝试这样做。:-)
5 个回答
受到最初问题和各种草稿的启发,我实现了一个类似CLOS的辅助方法的Python模块,这些方法包括在某些操作之前、之后或周围执行其他代码。
可以查看这里: http://code.activestate.com/recipes/577859-clos-like-aroundbeforeafter-auxiliary-methods/
我使用了一些Python的基本特性来实现这个模块:
- 类和函数的装饰器,
- 类的继承,还有
super()
这个内置函数, - 私有名称混淆(这样用户就不需要重复输入类名了)。
你可以实现一个类似的东西。Will的思路是对的,但看起来“call-next-method”在使用“around”时非常重要,可以这样实现:
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()
这样做的结果和你的输出完全一样,结构也相似。只要注意你给函数加装饰的顺序就可以了。
这里有一个稍微好一点的实现(现在使用了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版本那么优雅(而且我可能漏掉了一些功能),但看起来能满足你的需求。