2021年7月22日木曜日

【和訳】Django Rest Framework チュートリアル1:シリアル化

【和訳】Django Rest Framework クイックスタート

【和訳】Django Rest Framework チュートリアル1:シリアル化

【和訳】Django Rest Framework チュートリアル2:リクエストとレスポンス

【和訳】Django Rest Framework チュートリアル3:クラスベースのビュー

【和訳】Django Rest Framework チュートリアル4:認証とアクセス許可

【和訳】Django Rest Framework チュートリアル5:リレーションとハイパーリンクされたAPI

【和訳】Django Rest Framework チュートリアル6:ビューセットとルーター 


チュートリアル1:シリアル化


前書き

本チュートリアルでは、WebAPIを強調表示する簡単なコードスニペット(テキスト装飾サービス)の作成について説明します。
その過程で、RESTフレームワークを構成するさまざまなコンポーネントを紹介していき、すべてがどのように組み合わさっているのか、あなたが包括的に理解できるようにします。

チュートリアルはかなり詳細なので、始める前にクッキーとお気に入りのビールを1杯用意するのがいいでしょう。
おおまかな概要を知りたい場合は、代わりに
クイックスタートご覧ください


:このチュートリアルのコードは、GitHubencode / rest-framework-tutorialリポジトリにあります。
完成した実装は、テスト用のサンドボックスバージョンとして、
ここから入手できます



新しい環境のセットアップ

まず初めにvenvを使用して新しい仮想環境を作成します
こうしておけば、今回作成するappのパッケージ構成が、あなたの既存のプロジェクトとは別になり、切り分けられた状態で保たれます。

python3 -m venv env
source env/bin/activate

仮想環境内にいるので、必要なパッケージをインストールします。

pip install django
pip install djangorestframework
pip install pygments  # ハイライト表示用にこのパッケージを使用します。

注:仮想環境を終了したい時は、deactivateと入力するだけです。
詳細については、venvのドキュメントを参照してください


入門

さて、コーディングを始める準備ができました。
まず、新規プロジェクト
tutorialを作成し、作成したtutorialフォルダに移動します。

cd ~
django-admin startproject tutorial
cd tutorial

次に、snippetsアプリを作成します。

python manage.py startapp snippets

続いて、tutorial/settings.pyファイルのINSTALLED_APPSに、snippetsアプリとrest_frameworkアプリを追加します。
以下のように編集しましょう

INSTALLED_APPS = [
    ...
    'rest_framework',
    'snippets.apps.SnippetsConfig',
]

これで準備ができました。


使用するモデルの作成

このチュートリアルでは、コードスニペットを格納するために使用するシンプルSnippetモデルの作成から始めます。
snippets/models.pyファイルを編集していきましょう
 注:コメントは優れたプログラミング手法の一つです。このチュートリアルのリポジトリバージョン中にはコメントが掲載されていますが、ここではコード自体に焦点を当てるため、    省略しています。

from django.db import models
from pygments.lexers import get_all_lexers
from pygments.styles import get_all_styles

LEXERS = [item for item in get_all_lexers() if item[1]]
LANGUAGE_CHOICES = sorted([(item[1][0], item[0]) for item in LEXERS])
STYLE_CHOICES = sorted([(item, item) for item in get_all_styles()])


class Snippet(models.Model):
    created = models.DateTimeField(auto_now_add=True)
    title = models.CharField(max_length=100, blank=True, default='')
    code = models.TextField()
    linenos = models.BooleanField(default=False)
    language = models.CharField(choices=LANGUAGE_CHOICES, default='python', max_length=100)
    style = models.CharField(choices=STYLE_CHOICES, default='friendly', max_length=100)

    class Meta:
        ordering = ['created']

また、スニペットモデル用にmigrateを実行し、データベースを同期します。

python manage.py makemigrations snippets
python manage.py migrate


Serializerクラスの作成

Web APIを使い始めるために最初に必要なことは、スニペットインスタンスをjsonなどの表現にシリアル化および逆シリアル化する方法を提供することです

 ●シリアル化:スニペットインスタンス→json

 ●逆シリアル化:スニペットインスタンス←json

これは、Djangoのフォームと非常によく似た動作をするシリアライザーを宣言すれば実現できます。snippetsディレクトリにserializers.pyファイルを作成し以下を追加します。

from rest_framework import serializers
from snippets.models import Snippet, LANGUAGE_CHOICES, STYLE_CHOICES


class SnippetSerializer(serializers.Serializer):
    id = serializers.IntegerField(read_only=True)
    title = serializers.CharField(required=False, allow_blank=True, max_length=100)
    code = serializers.CharField(style={'base_template': 'textarea.html'})
    linenos = serializers.BooleanField(required=False)
    language = serializers.ChoiceField(choices=LANGUAGE_CHOICES, default='python')
    style = serializers.ChoiceField(choices=STYLE_CHOICES, default='friendly')

    def create(self, validated_data):
        """
        検証済みデータを渡すと、`Snippet` インスタンスを新規作成して返す。
        """
        return Snippet.objects.create(**validated_data)

    def update(self, instance, validated_data):
        """
        検証済みデータを与えると、既存の `Snippet` インスタンスを更新して返す。
        """
        instance.title = validated_data.get('title', instance.title)
        instance.code = validated_data.get('code', instance.code)
        instance.linenos = validated_data.get('linenos', instance.linenos)
        instance.language = validated_data.get('language', instance.language)
        instance.style = validated_data.get('style', instance.style)
        instance.save()
        return instance

シリアライザークラスの最初のプロパティの部分は、シリアル化/逆シリアル化されるフィールドを定義します。
create()メソッド、update()メソッドは、serializer.save()を呼び出した時に、インスタンスが作成されたり、更新される手順を定義します。

シリアライザークラスは、DjangoのFormクラスに非常に似ており、requiredmax_lengthdefaultのように、各フィールドに対する検証フラグを備えています。

これらのフィールドフラグは、HTMLにレンダリングする場合など、特定の状況で、シリアライザーの表示方法を制御することもできます。
上記の
{'base_template': 'textarea.html'}フラグは、DjangoFormクラスでwidget=widgets.Textarea使用するのと同じです。これは、チュートリアルの後半で説明するように、ブラウザで閲覧可能なAPIの表示方法を制御する場合に特に役立ちます。

後で説明するようにModelSerializerクラスを使用することで、時間を大幅に節約することもできますが、今のところは、あえてシリアライザーを明示的に定義しておきます。


シリアライザーの操作

先に進む前に、新しいSerializerクラスの使い方について理解しておきます。
Djangoシェルを起動しましょう。

python manage.py shell

さて、モジュールのインポートをいくつか済ませて、コードスニペットをいくつか作成しましょう。

from snippets.models import Snippet
from snippets.serializers import SnippetSerializer
from rest_framework.renderers import JSONRenderer
from rest_framework.parsers import JSONParser

snippet = Snippet(code='foo = "bar"\n')
snippet.save()

snippet = Snippet(code='print("hello, world")\n')
snippet.save()

これで、操作できるスニペットインスタンスが2つあります。
これらのインスタンスのうち1つをシリアル化する方法を見てみましょう。

serializer = SnippetSerializer(snippet)
serializer.data
# {'id': 2, 'title': '', 'code': 'print("hello, world")\n', 'linenos': False, 'language': 'python', 'style': 'friendly'}

まず、モデルインスタンス(snippet)をPythonネイティブデータ型に変換しました。
 ●Serializer(MODEL_INSTANCE):

  snippet(モデルインスタンス)→Pythonネイティブデータ型(ディクショナリ)

そして、シリアル化プロセスを完了するために、データをjsonレンダリングします
 ●JsonRenderer().render(TARGET_DICT):

  Pythonネイティブデータ型(ディクショナリ)→JSON

content = JSONRenderer().render(serializer.data)
content
# b'{"id": 2, "title": "", "code": "print(\\"hello, world\\")\\n", "linenos": false, "language": "python", "style": "friendly"}'

デシリアライズも同様です。
まず、ストリームをPythonネイティブデータ型(ディクショナリ)に解析します。
 ●JSONParser().parse(io.BytesIO(TARGET_JSON)):

  JSON→Pythonネイティブデータ型(ディクショナリ)

import io

stream = io.BytesIO(content)
data = JSONParser().parse(stream)

...次に、これらのネイティブデータ型を、データを含んだオブジェクトインスタンスに復元します。

 ●Serializer(data=TARGET_DICT):

  Pythonネイティブデータ型(ディクショナリ)→モデルインスタンス(snippet)

serializer = SnippetSerializer(data=data)
serializer.is_valid()
# True
serializer.validated_data
# OrderedDict([('title', ''), ('code', 'print("hello, world")\n'), ('linenos', False), ('language', 'python'), ('style', 'friendly')])
serializer.save()
# <Snippet: Snippet object>

APIがフォームの操作にどれほど似ているかに注目してください。
シリアライザーを使用するビューを書き始めると、類似性がさらに明らかになるはずです。

モデルインスタンスの代わりにクエリセットをシリアル化することもできます。これを行うmany=Trueには、シリアライザー引数にフラグを追加するだけです。(複数モデルインスタンスの一括シリアライズ)

serializer = SnippetSerializer(Snippet.objects.all(), many=True)
serializer.data
# [OrderedDict([('id', 1), ('title', ''), ('code', 'foo = "bar"\n'), ('linenos', False), ('language', 'python'), ('style', 'friendly')]), OrderedDict([('id', 2), ('title', ''), ('code', 'print("hello, world")\n'), ('linenos', False), ('language', 'python'), ('style', 'friendly')]), OrderedDict([('id', 3), ('title', ''), ('code', 'print("hello, world")'), ('linenos', False), ('language', 'python'), ('style', 'friendly')])]


ModelSerializersの使用

私たちのSnippetSerializerクラスには、Snippetモデルに含まれている多くの情報が重複しています。
コードをもう少し簡潔にできれば素晴らしいですね。

DjangoがFormクラスとModelFormクラスの両方を提供するのと同じように、RESTフレームワークにはSerializerクラスとModelSerializerクラスの両方を提供しています

ModelSerializerクラスを使用してシリアライザーをリファクタリングする方法を見てみましょう
snippets/serializers.pyファイルを再度開き、SnippetSerializerクラスを次のように置き換えます。

class SnippetSerializer(serializers.ModelSerializer):
    class Meta:
        model = Snippet
        fields = ['id', 'title', 'code', 'linenos', 'language', 'style']

また、シリアライザーが持つ優れた特性の1つは、シリアライザーインスタンスの表現をプリントすることで、そのインスタンスのすべてのフィールドを検査できることです。
Djangoシェルを開き、
python manage.py shellで以下のコードを試してください。

from snippets.serializers import SnippetSerializer
serializer = SnippetSerializer()
print(repr(serializer))
# SnippetSerializer():
#    id = IntegerField(label='ID', read_only=True)
#    title = CharField(allow_blank=True, max_length=100, required=False)
#    code = CharField(style={'base_template': 'textarea.html'})
#    linenos = BooleanField(required=False)
#    language = ChoiceField(choices=[('Clipper', 'FoxPro'), ('Cucumber', 'Gherkin'), ('RobotFramework', 'RobotFramework'), ('abap', 'ABAP'), ('ada', 'Ada')...
#    style = ChoiceField(choices=[('autumn', 'autumn'), ('borland', 'borland'), ('bw', 'bw'), ('colorful', 'colorful')...

ModelSerializerクラスは魔法のようなことは特に何もしないことを覚えておくことが重要です。
ModelSerializerクラスはシリアライザークラスを作成するためのショートカットにすぎません。

  • 自動的に決定されたフィールドのセット。
  • create()およびupdate()メソッドの単純なデフォルトの実装


シリアライザーを使用して通常のDjangoビューを作成する

新しいSerializerクラスを使用していくつかのAPIビューを作成する方法を見てみましょう。現時点では、RESTフレームワークの他の機能は使用せず、ビューを通常のDjangoビューとして記述します。

snippets/views.pyファイルを編集し、以下を追加します。

from django.http import HttpResponse, JsonResponse
from django.views.decorators.csrf import csrf_exempt
from rest_framework.parsers import JSONParser
from snippets.models import Snippet
from snippets.serializers import SnippetSerializer

APIのルートは、既存のすべてのスニペットの一覧表示、またはスニペットの新規作成をサポートするビューになります。

@csrf_exempt
def snippet_list(request):
    """
    全コードスニペットの一覧表示 と 新規作成(ListとCreate)
    """
    if request.method == 'GET':
        snippets = Snippet.objects.all()
        serializer = SnippetSerializer(snippets, many=True)
        return JsonResponse(serializer.data, safe=False)

    elif request.method == 'POST':
        data = JSONParser().parse(request)
        serializer = SnippetSerializer(data=data)
        if serializer.is_valid():
            serializer.save()
            return JsonResponse(serializer.data, status=201)
        return JsonResponse(serializer.errors, status=400)

CSRFトークンを持たないクライアントからこのビューにPOSTできるようにするため、ビューをcsrf_exemptとしてマークする必要があることに注意してください。

これは通常あなたが実行したいことではないでしょう、そして、RESTフレームワークビューは実際にはこれよりも賢明な動作を行いますが、今は私たちの目的のためにあえてこうしてます。

また、個々のスニペットに対応し、スニペットを取得、更新、または削除するために使用できるビューも必要です。

@csrf_exempt
def snippet_detail(request, pk):
    """
    個別コードスニペットの表示、更新、削除
    """
    try:
        snippet = Snippet.objects.get(pk=pk)
    except Snippet.DoesNotExist:
        return HttpResponse(status=404)

    if request.method == 'GET':
        serializer = SnippetSerializer(snippet)
        return JsonResponse(serializer.data)

    elif request.method == 'PUT':
        data = JSONParser().parse(request)
        serializer = SnippetSerializer(snippet, data=data)
        if serializer.is_valid():
            serializer.save()
            return JsonResponse(serializer.data)
        return JsonResponse(serializer.errors, status=400)

    elif request.method == 'DELETE':
        snippet.delete()
        return HttpResponse(status=204)

最後に、これらのビューを接続する必要があります。snippets/urls.pyファイルを作成します。

from django.urls import path
from snippets import views

urlpatterns = [
    path('snippets/', views.snippet_list),
    path('snippets/<int:pk>/', views.snippet_detail),
]

またスニペットアプリのURLを含めるために、tutorial/urls.pyファイルにルートurlconfを接続する必要があります。

from django.urls import path, include

urlpatterns = [
    path('', include('snippets.urls')),
]

現在、適切な処理を施していないエッジケース(問題がある場合)がいくつかあることには注意する必要があります。例えば、不正なjson形式でデータを送信した場合、やビューの処理が未定義のメソッドを使用したリクエストが行われた場合、500の「サーバーエラー」応答が返されます。それでも一応、動作はします。


WebAPIでの最初の試みのテスト

これで、スニペットを提供するサンプルサーバーを起動できます。

シェルを終了します

quit()

そしてDjangoの開発サーバーを起動します。

python manage.py runserver

Validating models...

0 errors found
Django version 1.11, using settings 'tutorial.settings'
Development server is running at http://127.0.0.1:8000/
Quit the server with CONTROL-C.

別のターミナルウィンドウで、サーバーをテストできます。

curlまたはhttpieを使用してAPIをテストできますHttpieは、Pythonで記述されたユーザーフレンドリーなhttpクライアントです。それをインストールしましょう。

pipを使用してhttpieをインストールできます。

pip install httpie

最後に、すべてのスニペットのリストを取得できます。

http http://127.0.0.1:8000/snippets/

HTTP/1.1 200 OK
...
[
  {
    "id": 1,
    "title": "",
    "code": "foo = \"bar\"\n",
    "linenos": false,
    "language": "python",
    "style": "friendly"
  },
  {
    "id": 2,
    "title": "",
    "code": "print(\"hello, world\")\n",
    "linenos": false,
    "language": "python",
    "style": "friendly"
  }
]

または、IDを参照して特定のスニペットを取得できます。

http http://127.0.0.1:8000/snippets/2/

HTTP/1.1 200 OK
...
{
  "id": 2,
  "title": "",
  "code": "print(\"hello, world\")\n",
  "linenos": false,
  "language": "python",
  "style": "friendly"
}

同様に、WebブラウザでこれらのURLにアクセスすると、同じjsonを表示できます。


これからどうするか?

これまでのところ順調に進んでいます。
DjangoのFormsAPIと非常によく似たシリアル化APIと、いくつかの通常のDjangoビューがあります。

私たちのAPIビューは、jsonレスポンスを提供する以外に、現時点では特に特別なことは何もしていません
また、クリーンアップしたいエラー処理のエッジケースがいくつかありますが、このWebAPIは動作しています。

チュートリアルのパート2でこれらの問題を改善する方法を説明します

【和訳】Django Rest framework クイックスタート

【和訳】Django Rest Framework クイックスタート

【和訳】Django Rest Framework チュートリアル1:シリアル化

【和訳】Django Rest Framework チュートリアル2:リクエストとレスポンス

【和訳】Django Rest Framework チュートリアル3:クラスベースのビュー

【和訳】Django Rest Framework チュートリアル4:認証とアクセス許可

【和訳】Django Rest Framework チュートリアル5:リレーションとハイパーリンクされたAPI

【和訳】Django Rest Framework チュートリアル6:ビューセットとルーター


クイックスタート

DjangoのAdminユーザーがシステム内のUserとGroupを表示・編集できるように、単純なAPIを作成します。


プロジェクトの作成

tutorialという名前でDjangoプロジェクトを新規作成し、quickstartと言う名前で新規appを作成します。

# プロジェクトディレクトリを作成
mkdir tutorial
cd tutorial

# 仮想環境の構築(ローカルでパッケージの依存性を分離するため)
python3 -m venv env
source env/bin/activate  # Windows では、`env\Scripts\activate`をタイプする

# pipで仮想環境内にDjangoとDjango REST frameworkをインストールする
pip install django
pip install djangorestframework

# プロジェクトとアプリを作成する
django-admin startproject tutorial .  # 最後のドットを忘れずに
cd tutorial
django-admin startapp quickstart
cd ..

プロジェクトのファイル構成は次のようになります。

$ pwd
<some path>/tutorial
$ find .
.
./manage.py
./tutorial
./tutorial/__init__.py
./tutorial/quickstart
./tutorial/quickstart/__init__.py
./tutorial/quickstart/admin.py
./tutorial/quickstart/apps.py
./tutorial/quickstart/migrations
./tutorial/quickstart/migrations/__init__.py
./tutorial/quickstart/models.py
./tutorial/quickstart/tests.py
./tutorial/quickstart/views.py
./tutorial/settings.py
./tutorial/urls.py
./tutorial/wsgi.py

アプリケーションがプロジェクトディレクトリ内に作成されているのは不思議に感じるかもしれません。プロジェクトの名前空間を使用すれば、外部モジュールとの名前の衝突を回避できます(本チュートリアルでは説明しません)。

次に、データベースの初回同期を行います。

python manage.py migrate

名前がadmin、パスワードがpassword123スーパーユーザーを作成します
本チュートリアルの後半では、このユーザーでログインします。

python manage.py createsuperuser --email admin@example.com --username admin

データベースを設定し、最初のユーザーが作成され、準備が整ったら、アプリのディレクトリを開き、コーディングを行っていきましょう。


シリアライザー

最初に、いくつかのシリアライザーを定義します。
tutorial/quickstart/serializers.pyという名前で新規モジュールを作成しましょう
これは、データを表現するために使用します。

from django.contrib.auth.models import User, Group
from rest_framework import serializers


class UserSerializer(serializers.HyperlinkedModelSerializer):
    class Meta:
        model = User
        fields = ['url', 'username', 'email', 'groups']


class GroupSerializer(serializers.HyperlinkedModelSerializer):
    class Meta:
        model = Group
        fields = ['url', 'name']

ここではHyperlinkedModelSerializerを用いて、ハイパーリンクのリレーションを使用しています。
主キーその他のさまざまなリレーションを使用することもできます。
ですが、ハイパーリンクは優れたRESTful設計なので、本チュートリアルではハイパーリンクのリレーションを使用します。


ビュー

続いて、いくつかのビューを書きましょう。
tutorial/quickstart/views.pyを開いて、次の内容を入力します。

from django.contrib.auth.models import User, Group
from rest_framework import viewsets
from rest_framework import permissions
from tutorial.quickstart.serializers import UserSerializer, GroupSerializer


class UserViewSet(viewsets.ModelViewSet):
    """
    APIのエンドポイント:userの表示と編集を許可する
    """
    queryset = User.objects.all().order_by('-date_joined')
    serializer_class = UserSerializer
    permission_classes = [permissions.IsAuthenticated]


class GroupViewSet(viewsets.ModelViewSet):
    """
    APIのエンドポイント:groupの表示と編集を許可する
    """
    queryset = Group.objects.all()
    serializer_class = GroupSerializer
    permission_classes = [permissions.IsAuthenticated]

ここでは、複数のビューを作成するのではなく、すべての共通動作をViewSetsと呼ばれるクラスにひとまとめにします。

もちろん、必要に応じて、各動作を個別のビューに簡単に分割することもできます。
ですが、ビューセットを使用すると、ビューのロジックが適切に整理され、非常に簡潔になります。


URL

では、APIのURLを紐づけしましょう。
tutorial/urls.pyに以下を記述します。

from django.urls import include, path
from rest_framework import routers
from tutorial.quickstart import views

router = routers.DefaultRouter()
router.register(r'users', views.UserViewSet)
router.register(r'groups', views.GroupViewSet)

# 自動URLルーティングを用いて、APIを紐づけしましょう。
# そして、APIをブラウザから閲覧できるようにするため、ログインURLをincludeします。
urlpatterns = [
    path('', include(router.urls)),
    path('api-auth/', include('rest_framework.urls', namespace='rest_framework'))
]

ここでは、ビューの代わりにビューセットを使用しているので、ビューセットをルータークラスに登録するだけで、APIのURLconfが自動的に生成されます。

繰り返しになりますが、API URLを個別に制御したい場合は、通常のクラスベースのビューを使用し、URLconfを明示的に記述するだけです。

最後に、ここでは、ブラウザから閲覧可能なAPIで使用するために、デフォルトのログインビューとログアウトビューを含んでいます。これは必須項目ではありませんが、あなたがAPIでの認証を必要としており、ブラウザから閲覧可能なAPIを使用したい場合に役立ちます。


ページネーション

ページネーションを使用すると、ページごとに返されるオブジェクトの数を制御できます。
有効にするには、
tutorial/settings.pyに以下の内容を追加します。

REST_FRAMEWORK = {
    'DEFAULT_PAGINATION_CLASS': 'rest_framework.pagination.PageNumberPagination',
    'PAGE_SIZE': 10
}


設定

tutorial/settings.pyINSTALLED_APPS'rest_framework'を追加します。

INSTALLED_APPS = [
    ...
    'rest_framework',
]

以上で完成です。



APIのテスト

これで構築したAPIをテストする準備が整いました。
コマンドラインからサーバーを起動してみましょう。

python manage.py runserver

APIには、curlのようなコマンドラインツールを用いてアクセスできます。

bash: curl -H 'Accept: application/json; indent=4' -u admin:password123 http://127.0.0.1:8000/users/
{
    "count": 2,
    "next": null,
    "previous": null,
    "results": [
        {
            "email": "admin@example.com",
            "groups": [],
            "url": "http://127.0.0.1:8000/users/1/",
            "username": "admin"
        },
        {
            "email": "tom@example.com",
            "groups": [],
            "url": "http://127.0.0.1:8000/users/2/",
            "username": "tom"
        }
    ]
}

あるいは、httpieコマンドラインツールを使用しても構いません。

bash: http -a admin:password123 http://127.0.0.1:8000/users/

HTTP/1.1 200 OK
...
{
    "count": 2,
    "next": null,
    "previous": null,
    "results": [
        {
            "email": "admin@example.com",
            "groups": [],
            "url": "http://localhost:8000/users/1/",
            "username": "paul"
        },
        {
            "email": "tom@example.com",
            "groups": [],
            "url": "http://127.0.0.1:8000/users/2/",
            "username": "tom"
        }
    ]
}

あるいは、ブラウザから直接、http://127.0.0.1:8000/users/にアクセスすることもできます。

クイックスタート画像

ブラウザからアクセスする場合は、右上隅のボタンを使用してログインしてください。

素晴らしい。簡単でしたね!

RESTフレームワークがどのように組み合わさっているか、もっと深く理解したいならチュートリアルに進むか、APIガイド参照を開始してください

2017年9月18日月曜日

Pythonのプロパティについて

Pythonのプロパティについて調べてみた

Pythonでは、クラス作成時にプロパティを作れる。
まず、プロパティを作らずに、普通にクラスを作ると、



>>>class Position(object):
    def __init__(self, x):
        self.x = x
   

>>> p = Position(2)
>>> p
<__main__ .position="" 0x02cfa870="" at="" object="">
>>> p.x
2

となる。
これに、@propertyをつけてみる、

class Position(object):
    def __init__(self, x):
        self.x = x
    @property
    def x(self):
        return self.x 

とすると、

>>> p = Position(2)
Traceback (most recent call last):
  File "", line 1, in 
    p = Position(2)
  File "", line 3, in __init__
    self.x = x
AttributeError: can't set attribute


となり、インスタンスを作成しようとするとアトリビュートエラーになる。
これは、@propertyデコレータをつけたメソッド(この場合はx)は、ゲッター、セッター、ディレーターがフックされるからである。
メソッドxに@propertyデコレータをつけたことで、インスタンス作成時に__init__が呼ばれ、その中でself.xへの代入を行われる。
この時、フックされたセッターを呼び出そうとするが、セッターが存在しないので、アトリビュートエラーが発生する。
そこで、次のようにセッターを作ってみると、

>>> class Position(object):
 def __init__(self, x):
  self.x = x
 @property
 def x(self):
  return self.x
 @x.setter
 def x(self, x):
  self.x = x
  
>>> p = Position(2)
Traceback (most recent call last):
  File "", line 1, in 
    p = Position(2)
  File "", line 3, in __init__
    self.x = x
  File "", line 10, in x
    self.x = x
  File "", line 10, in x
    self.x = x
  File "", line 10, in x
    self.x = x
  [Previous line repeated 491 more times]
RecursionError: maximum recursion depth exceeded while calling a Python object


となり、今度は再帰呼び出しの上限でエラーになる。
これは、Positionインスタンスに値をセットする際に、__initi__中のself.xへの代入時に、xへのsetterが呼び出される。
そのsetter中で、さらにx.setterが呼び出されて・・・と、再帰呼び出しになってしまうためである。
なので、代入先の名前をself.xからself._xへと変更してやるとうまくいく。

>>> class Position(object):
 def __init__(self, x):
  self._x = x
 @property
 def x(self):
  return self._x
 @x.setter
 def x(self, x):
  self._x = x

  
>>> p = Position(2)
>>> p
<__main__ .position="" 0x02cfa650="" at="" object="">
>>> p.x
2
>>> p._x
2
>>> p.x = 10
>>> p.x
10

こうしてやると、うまくいく。
なお、上記のように、インスタンス中のプロパティを直接呼び出したり代入すことも出来る。
@propertyを使うメリットは、インスタンスのプロパティの参照時、代入時、削除時の挙動を変更することである。
また、次に示すが、プロパティへのアクセスは、p.getx()のようなメソッド呼び出しだと括弧が必要だが、@propertyを使うと、p.xとなり見やすくなる。
次のようにゲッター、セッター、 ディレーターを普通のメソッドとして実装すると、


>>> class Position(object):
        def __init__(self, x):
            self._x = x
        def setx(self, x):
            self._x = x
        def getx(self):
            return self._x
        def delx(self):
            del self._x

>>> p = Position(2)
>>> p.getx()
2
>>> p._x
2
>>> p.getx()
2
>>> p.setx(10)
>>> p.getx()
10
>>> p.delx()
>>> p.getx()
Traceback (most recent call last):
  File "<pyshell#252>", line 1, in <module>
    p.getx()
  File "<pyshell#244>", line 7, in getx
    return self._x
AttributeError: 'Position' object has no attribute '_x'

また、プロパティに変更があったとき、保持する値や他の値も自動的に更新したい時などにも使える。

>>> class Square(object):
 def __init__(self, x):
  self._x = x * x
 @property
 def x(self):
  return self._x
 @x.setter
 def x(self, x):
  self._x = x * x

  
>>> s = Square(4)
>>> s
<__main__ .square="" 0x02d6d390="" at="" object="">
>>> s.x
16
>>> s.x = 5
>>> s.x
25

【和訳】Django Rest Framework 目次

目次 【和訳】Django Rest Framework クイックスタート 【和訳】Django Rest Framework チュートリアル1:シリアル化 【和訳】Django Rest Framework チュートリアル2:リクエストとレスポンス 【和訳】Django Res...