daisychaining Python/Django定制装饰器

2024-06-26 02:24:21 发布

您现在位置:Python中文网/ 问答频道 /正文

菊花链Python/Django定制装饰器的风格好吗?并传递不同于收到的论点?在

我的许多Django视图函数都是从完全相同的代码开始的:

@login_required
def myView(request, myObjectID):
    try:
        myObj = MyObject.objects.get(pk=myObjectID)
    except:
        return myErrorPage(request)       

    try:
        requester = Profile.objects.get(user=request.user)
    except:
        return myErrorPage(request)

    # Do Something interesting with requester and myObj here

仅供参考,这是网址.py文件看起来像:

^{pr2}$

在许多不同的视图函数中重复相同的代码一点也不枯燥。我想通过创建一个decorator来改进它,它将为我做重复性的工作,并使新的视图功能更干净,看起来像这样:

@login_required
@my_decorator
def myView(request, requester, myObj):        
    # Do Something interesting with requester and myObj here

我的问题是:

  1. 这是有效的吗?款式好吗?请注意,我将更改myView()函数的签名。我觉得有点奇怪,也有点冒险。但我不知道为什么
  2. 如果我创建多个这样的decorator,这些decorator执行一些公共函数,但是每个decorator调用的参数与decorator接收到的参数不同,那么我是否可以将它们串在一起?在
  3. 如果上面的#1和#2是可以的,那么向这个myView的用户指明他们应该传递的参数集是什么(因为仅仅查看函数定义中的参数不再是真正有效的)的最佳方法是什么

Tags: django函数代码视图参数requestdefrequired
3条回答

1)是的,连锁装饰商是有效的,因为其他答案已经指出。好的风格是主观的,但我个人认为它会使你的代码更难被别人阅读。熟悉Django但不熟悉您的应用程序的人在处理代码时需要在头脑中保留额外的上下文。我认为遵守框架约定是非常重要的,这样可以使代码尽可能的可维护。在

2)答案是肯定的,从技术上讲,将不同的参数传递给包装好的函数是可以的,但是请考虑一个简单的代码示例来说明这是如何工作的:

def decorator1(func):
    def wrapper1(a1):
        a2 = "hello from decorator 1"
        func(a1, a2)
    return wrapper1

def decorator2(func):
    def wrapper2(a1, a2):
        a3 = "hello from decorator 2"
        func(a1, a2, a3)
    return wrapper2

@decorator1
@decorator2
def my_func(a1, a2, a3):
    print a1, a2, a3

my_func("who's there?")

# Prints:
# who's there?
# hello from decorator 1
# hello from decorator2

在我看来,任何读到这篇文章的人都必须是一个脑力劳动者,才能在decorator堆栈的每个级别上保持方法签名的上下文。在

3)我将使用基于类的视图并重写dispatch()方法来设置实例变量,如下所示:

^{pr2}$

dispatch方法调用您的get()/post()方法。来自django文档:

The as_view entry point creates an instance of your class and calls its dispatch() method. dispatch looks at the request to determine whether it is a GET, POST, etc, and relays the request to a matching method if one is defined

然后可以在get()和/或post()视图方法中访问这些实例变量。这种方法的优点是可以将其提取到基类中,并在任意数量的视图子类中使用它。它在IDE中的可追溯性也要高得多,因为这是标准继承。在

一个get()请求的示例:

class MyView(View):
    def get(self, request, id):
        print 'requester is {}'.format(self.requester)

这是一个非常有趣的问题!Another one has already been answered in depth on the basic usage of decorators。但它并没有提供关于修改论点的更多见解

可堆叠装饰器

在另一个问题上,您可以找到一个堆叠装饰器的例子,下面的解释隐藏在非常详细的答案中:

Yes, that’s all, it’s that simple. @decorator is just a shortcut to:

another_stand_alone_function = my_shiny_new_decorator(another_stand_alone_function)

这就是魔法。Aspython documentation声明:decorator是返回另一个函数的函数。在

这意味着您可以:

from functools import wraps

def decorator1(f):
    @wraps(f)
    def wrapper(*args, **kwargs):
        do_something()
        f(*args, **kwargs)
    return wrapper


def decorator2(f):
    @wraps(f)
    def wrapper(*args, **kwargs):
        do_something_else()
        f(*args, **kwargs)
    return wrapper

@decorator1
@decorator2
def myfunc(n):
    print "."*n

#is equivalent to 

def myfunc(n):
    print "."*n
myfunc = decorator1(decorator2(myfunc))

装饰者不是装饰者

Python装饰器可能会让那些学习OOP的开发人员感到困惑,因为GoF已经用了一半的字典来命名修复这种语言失败的模式,而实际上是设计模式商店。在

GoF的decorator是他们所装饰的组件(接口)的子类,因此与该组件的任何其他子类共享这个接口。在

Python修饰符是返回函数(or classes)的函数。在

功能一直下降

python decorator是返回函数的函数,任何函数。在

大多数decorator都是为了扩展decorated函数而设计的,而不会妨碍它的预期行为。它们是在GoF对Decorator模式的定义之后形成的,Decorator模式描述了一种在保持对象接口的同时扩展对象的方法。在

但是GoF的Decorator是一个模式,而python的Decorator是一个特性。在

Python decorator是函数,这些函数期望返回函数(当提供函数时)。在

适配器

让我们再来一个GoF模式:Adapter

An adapter helps two incompatible interfaces to work together. This is the real world definition for an adapter.

[An Object] adapter contains an instance of the class it wraps. In this situation, the adapter makes calls to the instance of the wrapped object.

以一个对象为例,比如一个调度器,他将调用一个接受一些已定义参数的函数,另一个调用一个执行该任务但提供另一组参数的函数。第二个函数的参数可以从第一个函数的参数中导出。在

一个函数(在python中是一类对象)将获取第一个参数并派生它们来调用第二个并返回从其结果派生的值的函数将是适配器。在

为传递的函数返回适配器的函数将是适配器工厂。在

Python装饰器是返回函数的函数。包括适配器。在

^{pr2}$

哦,我知道你在那里做了什么…风格不错吗?在

我会说,见鬼,又是一个内置模式!但您必须忘记GoF装饰器,只需记住python装饰器是返回函数的函数。因此,您处理的接口是包装器函数的接口,而不是修饰的接口。在

一旦你装饰了一个函数,装饰者就定义了契约,要么告诉它保留了修饰函数的接口,要么将其抽象出来。你不再调用这个修饰函数了,它甚至很难尝试,你调用包装器。在

首先,这段代码:

try:
    myObj = MyObject.objects.get(pk=myObjectID)
except:
    return myErrorPage(request)

可替换为:

^{pr2}$

这同样适用于您拥有的第二个代码块。在

这本身就让它更优雅了。在

如果您想进一步实现自己的decorator,最好的办法是将@login_required子类化。如果你传递不同的论点或者不想这样做,那么你确实可以自己做一个装饰器,这是正确的。在

相关问题 更多 >