ホームページ バックエンド開発 Python チュートリアル 使用Python装饰器在Django框架下去除冗余代码的教程

使用Python装饰器在Django框架下去除冗余代码的教程

Jun 06, 2016 am 11:25 AM
python

 Python装饰器是一个消除冗余的强大工具。随着将功能模块化为大小合适的方法,即使是最复杂的工作流,装饰器也能使它变成简洁的功能。

例如让我们看看Django web框架,该框架处理请求的方法接收一个方法对象,返回一个响应对象:
 

def handle_request(request):
  return HttpResponse("Hello, World")
ログイン後にコピー

我最近遇到一个案例,需要编写几个满足下述条件的api方法:

  • 返回json响应
  • 如果是GET请求,那么返回错误码

做为一个注册api端点例子,我将会像这样编写:

def register(request):
  result = None
  # check for post only
  if request.method != 'POST':
    result = {"error": "this method only accepts posts!"}
  else:
    try:
      user = User.objects.create_user(request.POST['username'],
                      request.POST['email'],
                      request.POST['password'])
      # optional fields
      for field in ['first_name', 'last_name']:
        if field in request.POST:
          setattr(user, field, request.POST[field])
      user.save()
      result = {"success": True}
    except KeyError as e:
      result = {"error": str(e) }
  response = HttpResponse(json.dumps(result))
  if "error" in result:
    response.status_code = 500
  return response
ログイン後にコピー

然而这样我将会在每个api方法中编写json响应和错误返回的代码。这将会导致大量的逻辑重复。所以让我们尝试用装饰器实现DRY原则吧。

装饰器简介

如果你不熟悉装饰器,我可以简单解释一下,实际上装饰器就是有效的函数包装器,python解释器加载函数的时候就会执行包装器,包装器可以修改函数的接收参数和返回值。举例来说,如果我想要总是返回比实际返回值大一的整数结果,我可以这样写装饰器:

# a decorator receives the method it's wrapping as a variable 'f'
def increment(f):
  # we use arbitrary args and keywords to
  # ensure we grab all the input arguments.
  def wrapped_f(*args, **kw):
    # note we call f against the variables passed into the wrapper,
    # and cast the result to an int and increment .
    return int(f(*args, **kw)) + 1
  return wrapped_f # the wrapped function gets returned.
ログイン後にコピー

现在我们就可以用@符号和这个装饰器去装饰另外一个函数了:

@increment
def plus(a, b):
  return a + b
 
result = plus(4, 6)
assert(result == 11, "We wrote our decorator wrong!")
ログイン後にコピー

装饰器修改了存在的函数,将装饰器返回的结果赋值给了变量。在这个例子中,'plus'的结果实际指向increment(plus)的结果。

对于非post请求返回错误

现在让我们在一些更有用的场景下应用装饰器。如果在django中接收的不是POST请求,我们用装饰器返回一个错误响应。

def post_only(f):
  """ Ensures a method is post only """
  def wrapped_f(request):
    if request.method != "POST":
      response = HttpResponse(json.dumps(
        {"error": "this method only accepts posts!"}))
      response.status_code = 500
      return response
    return f(request)
  return wrapped_f
ログイン後にコピー

现在我们可以在上述注册api中应用这个装饰器:

@post_only
def register(request):
  result = None
  try:
    user = User.objects.create_user(request.POST['username'],
                    request.POST['email'],
                    request.POST['password'])
    # optional fields
    for field in ['first_name', 'last_name']:
      if field in request.POST:
        setattr(user, field, request.POST[field])
    user.save()
    result = {"success": True}
  except KeyError as e:
    result = {"error": str(e) }
  response = HttpResponse(json.dumps(result))
  if "error" in result:
    response.status_code = 500
  return response
ログイン後にコピー

现在我们就有了一个可以在每个api方法中重用的装饰器。

发送json响应

为了发送json响应(同时处理500状态码),我们可以新建另外一个装饰器:

def json_response(f):
  """ Return the response as json, and return a 500 error code if an error exists """
  def wrapped(*args, **kwargs):
    result = f(*args, **kwargs)
    response = HttpResponse(json.dumps(result))
    if type(result) == dict and 'error' in result:
      response.status_code = 500
return response
ログイン後にコピー

现在我们就可以在原方法中去除json相关的代码,添加一个装饰器做为代替:

post_only
@json_response
def register(request):
  try:
    user = User.objects.create_user(request.POST['username'],
                    request.POST['email'],
                    request.POST['password'])
    # optional fields
    for field in ['first_name', 'last_name']:
      if field in request.POST:
        setattr(user, field, request.POST[field])
    user.save()
    return {"success": True}
  except KeyError as e:
    return {"error": str(e) }
ログイン後にコピー

现在,如果我需要编写新的方法,那么我就可以使用装饰器做冗余的工作。如果我要写登录方法,我只需要写真正相关的代码:

@post_only
@json_response
def login(request):
  if request.user is not None:
    return {"error": "User is already authenticated!"}
  user = auth.authenticate(request.POST['username'], request.POST['password'])
  if user is not None:
    if not user.is_active:
      return {"error": "User is inactive"}
    auth.login(request, user)
    return {"success": True, "id": user.pk}
  else:
    return {"error": "User does not exist with those credentials"}
ログイン後にコピー

BONUS: 参数化你的请求方法

我曾经使用过Tubogears框架,其中请求参数直接解释转递给方法这一点我很喜欢。所以要怎样在Django中模仿这一特性呢?嗯,装饰器就是一种解决方案!

例如:

def parameterize_request(types=("POST",)):
  """
  Parameterize the request instead of parsing the request directly.
  Only the types specified will be added to the query parameters.
 
  e.g. convert a=test&b=cv in request.POST to
  f(a=test, b=cv)
  """
  def wrapper(f):
    def wrapped(request):
      kw = {}
      if "GET" in types:
        for k, v in request.GET.items():
          kw[k] = v
      if "POST" in types:
        for k, v in request.POST.items():
          kw[k] = v
      return f(request, **kw)
    return wrapped
  return wrapper
ログイン後にコピー

注意这是一个参数化装饰器的例子。在这个例子中,函数的结果是实际的装饰器。

现在我就可以用参数化装饰器编写方法了!我甚至可以选择是否允许GET和POST,或者仅仅一种请求参数类型。

@post_only
@json_response
@parameterize_request(["POST"])
def register(request, username, email, password,
       first_name=None, last_name=None):
  user = User.objects.create_user(username, email, password)
  user.first_name=first_name
  user.last_name=last_name
  user.save()
  return {"success": True}
ログイン後にコピー

现在我们有了一个简洁的、易于理解的api。

BONUS #2: 使用functools.wraps保存docstrings和函数名

很不幸,使用装饰器的一个副作用是没有保存方法名(__name__)和docstring(__doc__)值:

def increment(f):
  """ Increment a function result """
  wrapped_f(a, b):
    return f(a, b) + 1
  return wrapped_f
 
@increment
def plus(a, b)
  """ Add two things together """
  return a + b
 
plus.__name__ # this is now 'wrapped_f' instead of 'plus'
plus.__doc__  # this now returns 'Increment a function result' instead of 'Add two things together'
ログイン後にコピー

这将对使用反射的应用造成麻烦,比如Sphinx,一个 自动生成文档的应用。

为了解决这个问题,我们可以使用'wraps'装饰器附加上名字和docstring:

from functools import wraps
 
def increment(f):
  """ Increment a function result """
  @wraps(f)
  wrapped_f(a, b):
    return f(a, b) + 1
  return wrapped_f
 
@increment
def plus(a, b)
  """ Add two things together """
  return a + b
 
plus.__name__ # this returns 'plus'
plus.__doc__  # this returns 'Add two things together'
ログイン後にコピー

BONUS #3: 使用'decorator'装饰器

如果仔细看看上述使用装饰器的方式,在包装器声明和返回的地方也有不少重复。

你可以安装python egg 'decorator',其中包含一个提供装饰器模板的'decorator'装饰器!

使用easy_install:

$ sudo easy_install decorator
ログイン後にコピー

或者Pip:

$ pip install decorator
ログイン後にコピー

然后你可以简单的编写:


from decorator import decorator
 
@decorator
def post_only(f, request):
  """ Ensures a method is post only """
  if request.method != "POST":
    response = HttpResponse(json.dumps(
      {"error": "this method only accepts posts!"}))
    response.status_code = 500
    return response
  return f(request)
ログイン後にコピー

这个装饰器更牛逼的一点是保存了__name__和__doc__的返回值,也就是它封装了 functools.wraps的功能!

このウェブサイトの声明
この記事の内容はネチズンが自主的に寄稿したものであり、著作権は原著者に帰属します。このサイトは、それに相当する法的責任を負いません。盗作または侵害の疑いのあるコンテンツを見つけた場合は、admin@php.cn までご連絡ください。

ホットAIツール

Undresser.AI Undress

Undresser.AI Undress

リアルなヌード写真を作成する AI 搭載アプリ

AI Clothes Remover

AI Clothes Remover

写真から衣服を削除するオンライン AI ツール。

Undress AI Tool

Undress AI Tool

脱衣画像を無料で

Clothoff.io

Clothoff.io

AI衣類リムーバー

AI Hentai Generator

AI Hentai Generator

AIヘンタイを無料で生成します。

ホットツール

メモ帳++7.3.1

メモ帳++7.3.1

使いやすく無料のコードエディター

SublimeText3 中国語版

SublimeText3 中国語版

中国語版、とても使いやすい

ゼンドスタジオ 13.0.1

ゼンドスタジオ 13.0.1

強力な PHP 統合開発環境

ドリームウィーバー CS6

ドリームウィーバー CS6

ビジュアル Web 開発ツール

SublimeText3 Mac版

SublimeText3 Mac版

神レベルのコード編集ソフト(SublimeText3)

携帯電話でXMLをPDFに変換するとき、変換速度は高速ですか? 携帯電話でXMLをPDFに変換するとき、変換速度は高速ですか? Apr 02, 2025 pm 10:09 PM

Mobile XMLからPDFへの速度は、次の要因に依存します。XML構造の複雑さです。モバイルハードウェア構成変換方法(ライブラリ、アルゴリズム)コードの品質最適化方法(効率的なライブラリ、アルゴリズムの最適化、キャッシュデータ、およびマルチスレッドの利用)。全体として、絶対的な答えはなく、特定の状況に従って最適化する必要があります。

携帯電話のXMLファイルをPDFに変換する方法は? 携帯電話のXMLファイルをPDFに変換する方法は? Apr 02, 2025 pm 10:12 PM

単一のアプリケーションで携帯電話でXMLからPDF変換を直接完了することは不可能です。クラウドサービスを使用する必要があります。クラウドサービスは、2つのステップで達成できます。1。XMLをクラウド内のPDFに変換し、2。携帯電話の変換されたPDFファイルにアクセスまたはダウンロードします。

C言語合計の機能は何ですか? C言語合計の機能は何ですか? Apr 03, 2025 pm 02:21 PM

C言語に組み込みの合計機能はないため、自分で書く必要があります。合計は、配列を通過して要素を蓄積することで達成できます。ループバージョン:合計は、ループとアレイの長さを使用して計算されます。ポインターバージョン:ポインターを使用してアレイ要素を指し示し、効率的な合計が自己概要ポインターを通じて達成されます。アレイバージョンを動的に割り当てます:[アレイ]を動的に割り当ててメモリを自分で管理し、メモリの漏れを防ぐために割り当てられたメモリが解放されます。

XMLをPDFに変換できるモバイルアプリはありますか? XMLをPDFに変換できるモバイルアプリはありますか? Apr 02, 2025 pm 09:45 PM

XML構造が柔軟で多様であるため、すべてのXMLファイルをPDFSに変換できるアプリはありません。 XMLのPDFへのコアは、データ構造をページレイアウトに変換することです。これには、XMLの解析とPDFの生成が必要です。一般的な方法には、ElementTreeなどのPythonライブラリを使用してXMLを解析し、ReportLabライブラリを使用してPDFを生成することが含まれます。複雑なXMLの場合、XSLT変換構造を使用する必要がある場合があります。パフォーマンスを最適化するときは、マルチスレッドまたはマルチプロセスの使用を検討し、適切なライブラリを選択します。

推奨されるXMLフォーマットツール 推奨されるXMLフォーマットツール Apr 02, 2025 pm 09:03 PM

XMLフォーマットツールは、読みやすさと理解を向上させるために、ルールに従ってコードを入力できます。ツールを選択するときは、カスタマイズ機能、特別な状況の処理、パフォーマンス、使いやすさに注意してください。一般的に使用されるツールタイプには、オンラインツール、IDEプラグイン、コマンドラインツールが含まれます。

携帯電話でXMLをPDFに変換する方法は? 携帯電話でXMLをPDFに変換する方法は? Apr 02, 2025 pm 10:18 PM

携帯電話でXMLをPDFに直接変換するのは簡単ではありませんが、クラウドサービスの助けを借りて実現できます。軽量モバイルアプリを使用してXMLファイルをアップロードし、生成されたPDFを受信し、クラウドAPIで変換することをお勧めします。クラウドAPIはサーバーレスコンピューティングサービスを使用し、適切なプラットフォームを選択することが重要です。 XMLの解析とPDF生成を処理する際には、複雑さ、エラー処理、セキュリティ、および最適化戦略を考慮する必要があります。プロセス全体では、フロントエンドアプリとバックエンドAPIが連携する必要があり、さまざまなテクノロジーをある程度理解する必要があります。

XMLを写真に変換する方法 XMLを写真に変換する方法 Apr 03, 2025 am 07:39 AM

XMLは、XSLTコンバーターまたは画像ライブラリを使用して画像に変換できます。 XSLTコンバーター:XSLTプロセッサとスタイルシートを使用して、XMLを画像に変換します。画像ライブラリ:PILやImageMagickなどのライブラリを使用して、形状やテキストの描画などのXMLデータから画像を作成します。

XML形式を開く方法 XML形式を開く方法 Apr 02, 2025 pm 09:00 PM

ほとんどのテキストエディターを使用して、XMLファイルを開きます。より直感的なツリーディスプレイが必要な場合は、酸素XMLエディターやXMLSPYなどのXMLエディターを使用できます。プログラムでXMLデータを処理する場合、プログラミング言語(Pythonなど)やXMLライブラリ(XML.ETREE.ELEMENTTREEなど)を使用して解析する必要があります。

See all articles