菊花链Python/Django自定义装饰器

Saq*_*Ali 5 python django python-decorators

菊花链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
Run Code Online (Sandbox Code Playgroud)

仅供参考,这是urls.py文件中的相应条目:

url(r'^object/(?P<myObjectID>\d+)/?$', views.myView, ),
Run Code Online (Sandbox Code Playgroud)

在许多不同的视图函数中重复相同的代码根本不是DRY.我想通过创建一个装饰器来改进它,它将为我做这个重复的工作,并使新的视图功能更清洁,看起来像这样:

@login_required
@my_decorator
def myView(request, requester, myObj):        
    # Do Something interesting with requester and myObj here
Run Code Online (Sandbox Code Playgroud)

所以这是我的问题:

  1. 这是有效的吗?这是好风格吗?请注意,我将更改myView()函数的签名.这对我来说有点奇怪和危险.但我不确定为什么
  2. 如果我创建多个这样的装饰器来执行一些常见的功能但是每个调用包装函数的参数与装饰器接收的参数不同,那么将菊花链接在一起是否可以?
  3. 如果对上面的#1和#2没问题,那么向这个myView的用户指出他们应该传递的参数集的最佳方法是什么(因为只是查看函数定义中的参数不再是真有效)

uba*_*dub 5

首先,这段代码:

try:
    myObj = MyObject.objects.get(pk=myObjectID)
except:
    return myErrorPage(request)
Run Code Online (Sandbox Code Playgroud)

可以替换为:

from django.shortcuts import get_object_or_404
myObj = get_object_or_404(MyObject, pk=myObjectID)
Run Code Online (Sandbox Code Playgroud)

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

这本身就使这更加优雅。

如果您想进一步实现自己的装饰器,最好的办法是将@login_required 子类化。如果您传递不同的参数或不想这样做,那么您确实可以制作自己的装饰器,这不会错。


dde*_*eny 5

这是一个非常有趣的问题!另外一个已经对装饰器的基本用法进行了深入的回答.但它没有提供很多关于修改参数的见解

可堆叠装饰器

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

是的,就是这样,就这么简单.@decorator只是一个快捷方式:

another_stand_alone_function = my_shiny_new_decorator(another_stand_alone_function)
Run Code Online (Sandbox Code Playgroud)

这就是魔术.正如python文档所述:装饰器是一个返回另一个函数的函数.

这意味着你可以这样做:

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))
Run Code Online (Sandbox Code Playgroud)

装饰者不是装饰者

对于使用GoF语言学习OOP的开发人员来说,Python装饰者可能会感到困惑 已经使用了一半的字典来命名修复语言失败的模式 是事实上的设计模式商店.

GoF的装饰器是它们正在装饰的组件(接口)的子类,因此与该组件的任何其他子类共享此接口.

Python装饰器是返回函数(或类)的函数.

功能一直向下

python decorator是一个返回函数,任何函数的函数.

大多数装饰器都是为了扩展装饰功能而设计的,而不会妨碍它的预期行为.它们是在GoF定义的Decorator模式之后形成的,它描述了一种在保持对象界面的同时扩展对象的方法.

但是GoF的Decorator是一个模式,而python的装饰器是一个特征.

Python装饰器是函数,这些函数有望返回函数(当提供函数时).

适配器

让我们采取另一种GoF模式:适配器

适配器可帮助两个不兼容的接口协同工作.这是适配器的真实世界定义.

[对象]适配器包含它包装的类的实例.在这种情况下,适配器调用包装对象的实例.

以一个对象为例 - 比如一个调度程序,它会调用一个接受一些已定义参数的函数,并接受一个可以完成这项工作但提供另一组参数的函数.第二个函数的参数可以从第一个函数的参数中导出.

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

为其传递的函数返回适配器的函数将是适配器工厂.

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

def my_adapter(f):
    def wrapper(*args, **kwargs):
        newargs, newkwargs = adapt(args, kwargs)
        return f(*newargs, **newkwargs)

@my_adapter # This is the contract provider
def myfunc(*args, **kwargs):
    return something()
Run Code Online (Sandbox Code Playgroud)

噢,我看到你在那里做了什么......这是好风格吗?

我会说,地狱是的,还有另一种内置模式!但是你必须忘记GoF装饰器并且只记得python装饰器是返回函数的函数.因此,您正在处理的接口是包装器功能之一,而不是装饰功能.

一旦你装饰了一个函数,装饰器就会定义契约,要么告诉它保持装饰函数的界面,要么将它抽象出来.你不再调用那个装饰函数了,尝试它甚至很棘手,你调用包装器.


spi*_*lok 2

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
Run Code Online (Sandbox Code Playgroud)

在我看来,任何阅读本文的人都需要成为一名心理体操运动员,以保持装饰器堆栈每个级别的方法签名的上下文。

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

class MyView(View):
    @method_decorator(login_required)
    def dispatch(self, *args, **kwargs):
        self.myObj = ...
        self.requester = ...
        return super(MyView, self).dispatch(*args, **kwargs)
Run Code Online (Sandbox Code Playgroud)

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

as_view 入口点创建类的实例并调用其dispatch() 方法。调度查看请求以确定它是否是 GET、POST 等,如果定义了,则将请求转发到匹配的方法

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

请求的示例get()如下:

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