疑念は探究の動機であり、探究の唯一の目的は信念の確定である。

数学・論理学・哲学・語学のことを書きたいと思います。どんなことでも何かコメントいただけるとうれしいです。特に、勉学のことで間違いなどあったらご指摘いただけると幸いです。 よろしくお願いします。くりぃむのラジオを聴くこととパワポケ2と日向坂46が人生の唯一の楽しみです。

Macで別バージョンのPythonの仮想環境を構築する方法

概要

Macにおいて仮想環境を作成して、そこにすでにインストールされているバージョンとは異なるPythonを構築する方法をまとめる。

: 自分用のためここに記載されたこと以外のことを設定しなければならない可能性がある。

前提条件

  • Macであること。
  • コマンドpyenvがインストールされていること。

やりたいこと

  • 現状自分のMacには3.9.23.10.7のバージョンのPythonがインストールされている。
  • やりたいことは特定のディレクトリに仮想環境を作成して、そこに新しいバージョンのPython(3.11.3)にしたい。

  • before:

  • after:

方法

STEP 1 pyenvをアップデートする

  • 最新のPythonのバージョンをインストールしたいので、pyenvをアップデートします。pyenvには直接アップデートのコマンドがありませんので、次のコマンドを実行します。
cd ~/.pyenv/plugins/python-build/../.. && git pull && cd -

STEP 2 インストールしたいバージョンを確認する

  • インストールしたいPythonのバージョンを次のコマンドで確認します。
Available versions:
  2.1.3
  2.2.3
  2.3.7
  2.4.0
  2.4.1

(略)

  3.8.16
  3.9.0
  3.9-dev
  3.9.1
  3.9.2
  3.9.4
  3.9.5
  3.9.6
  3.9.7
  3.9.8
  3.9.9
  3.9.10
  3.9.11
  3.9.12
  3.9.13
  3.9.14
  3.9.15
  3.9.16
  3.10.0
  3.10-dev
  3.10.1
  3.10.2
  3.10.3
  3.10.4
  3.10.5
  3.10.6
  3.10.7
  3.10.8
  3.10.9
  3.10.10
  3.10.11
  3.11.0
  3.11-dev
  3.11.1
  3.11.2
  3.11.3
  3.12.0a7
  3.12-dev
  activepython-2.7.14
  activepython-3.5.4
  
  (略)

  stackless-3.4.7
  stackless-3.5.4
  stackless-3.7.5

STEP 3 インストールしたいバージョンをインストールする

  • 確認後に次のコマンドでインストールします。インストールするまで少し時間がかかります(1分ぐらい)。
pyenv install <version>

今回はpyenv install 3.11.3です。

STEP 4 インストールしたことを確認する

  • 実際うまく期待通りのバージョンがインストールされたかどうかを次のコマンドで確認します。問題なく3.11.3が追加されています。
pyenv versions

* system (set by /Users/yoheiwatanabe/.pyenv/version)
  3.9.2
  3.10.7
  3.11.3

  • ここでは設定はsystemが設定されています(*が目印)。

STEP 5 仮想環境に設定したいバージョンに変更する

  • 仮想環境に設定したいバージョンに次のように変更します。
pyenv local 3.11.3
  • pyenv versionsで確認すると*3.11.3に移動しています。

STEP 6 仮想環境を設定する

  • 設定したいディレクトリ配下に移動して、仮想環境を次のように設定します。今回は仮想環境名を.venvとしていますが、それは任意で問題ありません。
python -m venv .venv

STEP 7 仮想環境に入る/出る

  • 仮想環境にはsource .venv/bin/activateで入ります(アクティベイトします)。
  • バージョンは期待通り3.11.3となっています。
  • 仮想環境から出る場合(デアクティベイト)はdeactivateを実行します。







僕から以上

Django REST Frameworkのモデルシリアライザーのフィールド名をハイフンに変更する方法

概要

Django REST Frameworkのモデルシリアライザー(serializers.ModelSerializer)のフィールド名にあるアンダースコア(_)をハイフン(-)に変更する方法をまとめる。



要点

モデルシリアライザーにハイフンを利用したい場合、シリアライザーのクラスMetaを次のように修正してください。

    class Meta:
        """Meta"""
        model = MODEL
        fields = ['hyphen-field']
        extra_kwargs = {
            'hyphen-field': {
                'source': 'model_field'
            }
        }



したいこと

  • 書店のデータベースを作成します(Store)
  • 登録されている書店を取得するAPIを作成します(GET: http://127.0.0.1:8000/api/stores/)
  • クエリがついていない場合は、すべての書店情報が取得されます。
  • クエリ(store-name)がついている場合は、クエリのバリデーションをおこない問題なければ、それを含む書店名の書店情報が取得されます。

背景

どうやらAPIのクエリはアンダースコアよりハイフンのほうが良いとのことです。しかしDjangoのモデルのフィールドはアンダースコアで記載されています。したがってクエリのバリデーションをおこなうモデルシリアライザーを作成したい場合、アンダースコアからハイフンに変更したいと思いました。

ソースコード

モデル

from django.db import models


class Store(models.Model):
    """Store model"""
    store_name = models.CharField(max_length=100)
    address = models.CharField(max_length=300)


リアライザー

from rest_framework import serializers
from backend.books.models.store import Store

class StoreSerializer(serializers.ModelSerializer):
    """Store serializer"""

    class Meta:
        """Meta"""
        model = Store
        fields = '__all__'


class StoreQuerySerializer(serializers.ModelSerializer):
    """Store query serializer"""

    class Meta:
        """Meta"""
        model = Store
        fields = ['store-name']
        extra_kwargs = {
            'store-name': {
                'source': 'store_name'
            }
        }


ビュー

from rest_framework import generics
from rest_framework.response import Response
from backend.books.serializer import StoreQuerySerializer, StoreSerializer


class StoreList(generics.ListAPIView):
    """Store list"""
    permission_classes = []
    serializer_class = StoreSerializer
    queryset = Store.objects.all()

    def list(self, request, *args, **kwargs):
        if not request.query_params:
            return super().list(request, *args, **kwargs)
        query_serializer = StoreQuerySerializer(data=request.query_params)
        query_serializer.is_valid(raise_exception=True)
        store_name = query_serializer.validated_data['store_name']
        queryset = Store.objects.filter(store_name__contains=store_name)
        return Response(data=self.get_serializer(queryset, many=True).data)


ソースコードについていくつか指摘をしたいと思います。

  • 最初のif文はクエリが設定されいるかどうかを確認しています。何もなければ通常のlist関数が実行されて、すべてのデータを取得します。
  • そうでない場合、つまりクエリが設定されている場合、StoreQuerySerializerでクエリのバリデーションをおこないます。query_serializer.is_valid(raise_exception=True)により、不適切なクエリの場合、エラーが返ります。
  • 適切なクエリが設定されている場合、バリデーションを通過します。そのときquery_serializer.validated_data['store_name']store-nameのクエリがあります。
  • あとはクエリに設定されたもの(store-name)を含むデータを絞り込んで返します。


URL

from django.urls import path, include
from rest_framework.routers import DefaultRouter
from backend.books.views import StoreList

router = DefaultRouter()

urlpatterns = [
    path('stores/', StoreList.as_view()),
    path('', include(router.urls)),
]



実行

データベース

id store_name address
1 岩波書店 東京都千代田区一ツ橋2丁目5番5号
2 講談社 東京都文京区音羽 2-12-21
3 幻冬舎 東京都渋谷区千駄ケ谷四丁目9番7号
4 中央公論新社 東京都千代田区大手町1-7-1 読売新聞ビル19階
5 筑摩書房 東京都台東区蔵前2-5-3
6 みすず書房 東京都文京区本郷2-20-7



結果

クエリなし

期待値はすべてのStoreデータが取得されることです。

  • メソッド: GET
  • リソース: http://127.0.0.1:8000/api/stores/

[
    {
        "id": 1,
        "store_name": "岩波書店",
        "address": "東京都千代田区一ツ橋2丁目5番5号"
    },
    {
        "id": 2,
        "store_name": "講談社",
        "address": "東京都文京区音羽 2-12-21"
    },
    {
        "id": 3,
        "store_name": "幻冬舎",
        "address": "東京都渋谷区千駄ケ谷四丁目9番7号"
    },
    {
        "id": 4,
        "store_name": "中央公論新社",
        "address": "東京都千代田区大手町1-7-1 読売新聞ビル19階"
    },
    {
        "id": 5,
        "store_name": "筑摩書房",
        "address": "東京都台東区蔵前2-5-3"
    },
    {
        "id": 6,
        "store_name": "みすず書房",
        "address": "東京都文京区本郷2-20-7"
    }
]


クエリあり

成功ケース

  • メソッド: GET
  • リソース: http://127.0.0.1:8000/api/stores/
  • クエリ: store-name=書房

[
    {
        "id": 5,
        "store_name": "筑摩書房",
        "address": "東京都台東区蔵前2-5-3"
    },
    {
        "id": 6,
        "store_name": "みすず書房",
        "address": "東京都文京区本郷2-20-7"
    }
]


失敗ケース

クエリにアンダースコアがついている場合は、エラーとなります。

  • メソッド: GET
  • リソース: http://127.0.0.1:8000/api/stores/
  • クエリ: store_name=岩波書店

{
    "store-name": [
        "この項目は必須です。"
    ]
}

GitHub - YoheiWatanabe/recording-books at test-serializer-convert-underscore-into-hyphen







僕から以上

Djangoのモデルでコメントの返信数を取得する方法

概要

SNSやコメントの返信において、返信数を取得する方法を提示する。


ソースコード

  • モデル
"""Comment Model"""

from django.db import models

class Comment(models.Model):
    """Comment model"""
    text = models.CharField(max_length=2000)
    reply_to = models.ForeignKey(to='self', on_delete=models.DO_NOTHING, null=True, blank=True, default=None)

    def __str__(self) -> str:
        return self.text



class CommentSerializer(serializers.ModelSerializer):
    """Comment serializer"""

    class Meta:
        """Meta"""
        model = Comment
        fields = '__all__'
    
    def to_representation(self, instance):
        """Representation"""
        response = super().to_representation(instance)
        response['reply_count'] = instance.number_of_replies if instance.number_of_replies else 0
        return response



  • ビュー
class CommentList(generics.ListAPIView):
    """Comment view"""
    permission_classes = []
    serializer_class = CommentSerializer
    queryset = Comment.objects.all()

    def get_queryset(self):
        """Get query set"""
        subquery = Comment.objects.filter(reply_to=OuterRef('id')).values('reply_to').annotate(count=Count('reply_to')).values('count')
        return Comment.objects.annotate(number_of_replies=Subquery(subquery))



  • URL
from django.urls import path, include
from rest_framework.routers import DefaultRouter
from backend.books.views import CommentList

router = DefaultRouter()

urlpatterns = [
    path('comments/', CommentList.as_view()),
    path('', include(router.urls)),
]





データ

結果

[
    {
        "id": 1,
        "text": "This is a test.",
        "reply_to": null,
        "reply_count": 1
    },
    {
        "id": 2,
        "text": "This is a reply test.",
        "reply_to": 1,
        "reply_count": 0
    },
    {
        "id": 3,
        "text": "That is a test.",
        "reply_to": null,
        "reply_count": 0
    },
    {
        "id": 4,
        "text": "Who are you?",
        "reply_to": null,
        "reply_count": 3
    },
    {
        "id": 5,
        "text": "I'm a superman.",
        "reply_to": 4,
        "reply_count": 0
    },
    {
        "id": 6,
        "text": "I'm a mathematician.",
        "reply_to": 4,
        "reply_count": 0
    },
    {
        "id": 7,
        "text": "I'm an entertainer.",
        "reply_to": 4,
        "reply_count": 0
    }
]

GitHub - YoheiWatanabe/recording-books at test-comment-reply

解説

SNSの投稿やコメントには返信機能があります。それを実現するモデルの作成および返信数を取得する方法を解説します。

モデル

返信機能のあるコメントモデルCommentを作成します。簡単のために他のフィールドは除いています。

  • backend/backend/books/models/comment.py
from django.db import models

class Comment(models.Model):
    """Comment model"""
    text = models.CharField(max_length=2000)
    reply_to = models.ForeignKey(to='self',
                      on_delete=models.DO_NOTHING,
                      null=True,
                      blank=True,
                      default=None)

フィールドreply_toは返信(リプライ)を実装するためのものです。自分自身のidと紐付けます。そのためにForeign Keyにto='self'を設定します。

このフィールドはコメントが返信なのかどうかを表しています。普通のコメントの場合はnullとなり、返信のコメントの場合は返信しているIDが設定されます。

on_deleteはここでは返信されている元のコメントがなくなった場合どうなるかということです。on_deleteはとりあえずDO_NOTHINGにしています。元のコメントがなくなってもそのままにしています。CASCADEは元のコメントが削除されると、その返信も削除されるので問題があります。またPROTECTは返信がない限り、元のコメントが削除できなくなるので、それも不都合です。SET_DEFAULTnullとなってしまうので、返信が通常のコメントになってしまいこれもおかしくなります。結局DO_NOTHINGにしています。

ビュー

今回の肝はビュー(view)です。返信数を取得するためには愚直にやる方法もありますが、それだと大量のSQLを実行することになります(N + 1問題)。ですので、サブクエリを使って一発で実行します。

  • backend/backend/books/views.py
from django.db.models import OuterRef, Subquery, Count
from rest_framework import generics
from backend.books.serializer import CommentSerializer
from backend.books.models.comment import Comment

class CommentList(generics.ListAPIView):
    """Comment view"""
    permission_classes = []
    serializer_class = CommentSerializer
    queryset = Comment.objects.all()

    def get_queryset(self):
        """Get query set"""
        subquery = Comment.objects
        .filter(reply_to=OuterRef('id'))
        .values('reply_to')
        .annotate(count=Count('reply_to'))
        .values('count')
        
         return Comment.objects
          .annotate(number_of_replies=Subquery(subquery))

今回はgenerics.ListAPIViewを使っていますが、他のViewでも問題ありません。

解説をしたいのですが、まだ「これでうまくいった」ということしか言えず、ちゃんと理解していません...。

annotateがあるので、このクエリセットはnumber_of_repliesというフィールドがあります。

リアライザー

  • backend/backend/books/serializer.py
from rest_framework import serializers
from backend.books.models.comment import Comment

class CommentSerializer(serializers.ModelSerializer):
    """Comment serializer"""

    class Meta:
        """Meta"""
        model = Comment
        fields = '__all__'
    
    def to_representation(self, instance):
        """Representation"""
        response = super().to_representation(instance)
        response['reply_count'] = instance
                                   .number_of_replies
                                   if instance
                                   .number_of_replies
                                   else 0
        return response

to_representationを使って戻り値を修正しています。新たに返信数reply_countを追加しています。返信がない場合(単なるコメントの場合)、instance.number_of_repliesNoneとなります。このときは返信数を0にするために、三値演算子(if...else)を使っています。

URL

  • backend/backend/books/urls.py
from django.urls import path, include
from rest_framework.routers import DefaultRouter
from backend.books.views import CommentList

router = DefaultRouter()

urlpatterns = [
    path('comments/', CommentList.as_view()),
    path('', include(router.urls)),
]

コメントAPIを実行するためにURLを設定します。

アドミン

  • backend/backend/books/admin.py
from django.contrib import admin
from backend.books.models.comment import Comment

class CommentAdmin(admin.ModelAdmin):
    """Display Comment model"""
    model = Comment
    list_display = ('id', 'text', 'reply_to')

admin.site.register(Comment, CommentAdmin)

管理者画面でコメントのデータベース操作するための設定です。モデルのreply_toblank=Trueとなっているのは管理者画面から普通のコメントを作成できるようにするためです。もしもblank=Trueとなっていない場合、reply_toが空のままだとエラーが生じます。





僕から以上

Django モデルのフィールドのnullとblankについて

概要

Djangoモデルのフィールドにはnullblankのオプションがある。nullはデータベースに関するオプションであり、blankはアプリケーションのバリデーションに関するオプションである。

オプションそれぞれの場合について、どのような動作をするのか確認する。また、Djano REST Framework(DRF)の場合、APIを実行するとどのようになるのかも確認する。

要約

  • 文字列のフィールド場合(CharField, TextField)、空文字可能にするためには原則blank=Trueのみでいい。
  • それ以外のフィールドの場合(e.g.BooleanField, DateTimeField)、空データを可能にするためにはblank=Truenull=Trueが必要となる。
  • フォームでのバリデーションは不要だが、デフォルトが決まっている場合、models.BooleanField(blank=True, default=False)とすればよい。
  • blank=Falseかつnull=Trueはほぼ使われない。
  • DRFにおいてblankまたはnullの少なくとも1つがTrueである場合、APIのリクエストボディはバリデーションされない。

前提知識

  • 必須: Pythonの使い方を知っている。
  • 必須: Djangoの使い方を知っている。
  • 任意: Django REST Frameworkを知っている。
  • 要約
  • 前提知識
  • nullとblankについて
    • 1. null=Falseかつblank=False(デフォルト)の場合
    • 2. null=Trueかつblank=Falseの場合
    • 3. null=Falseかつblank=Trueの場合
    • 4. null=Trueかつblank=Trueの場合
  • 文字型フィールドの問題点
  • 実行結果
    • 管理者画面
    • API
  • まとめ
  • 参考文献
続きを読む

書評: ダニエル・C・デネット『ダーウィンの危険な思想 生命の意味と進化』

一言

何かすごいことが書かれているかのように醸し出しているが、あまりにも冗長すぎて挫折した退屈でつまらない本

概要

  • まだない。

感想

最初の3章ぐらいまではまだおもしろかったが、結局諦めた。理由は端的につまらない。そして結局何が言いたいのかわからないということである。

「自然選択(進化)はアルゴリズムである」や「生物学はエンジニアリングである」等のスローガンばかりが目立つだけで、具体的に何か言っているかというとそうでもない。

「なるほどね。『進化はアルゴリズム』なんだ。じゃあ、遺伝的アルゴリズムとかどうなの?そういうのを哲学的に分析したり批判したりしているの?」と思っても、そんなことは一切ない。もっとも遺伝的アルゴリズムのことは記載されているが(ch.8, sec.5)、だからと言ってそれを発見したエピソードがあるだけで特に分析や応用なんかはない。なんだこれ。

読んだ感じだと、「進化はアルゴリズムである」の意味はせいぜい「自然選択にはスカイフック(超自然的な跳躍)など一切なく、クレーン(現実的な漸進的なプロセス)の連続だ」ということにすぎない。それをなんだかすごいことを言っているかのように偽装している。

よくこんなつまらない鈍器を書いたものだ。訳者たちもよく訳したなと驚きに尽きる。正直最後の訳者の解説を読んで、あとはつまみ食いすればいいと思う。

デネットドーキンスやピンカーなどの現代の有名な知識人の一人である。だから一応どんな人なのか知るためにデネットの著作を読む。だがデネットの本は全て分厚い。直観ポンプの本(『思考の技法』)は一つのテーマを議論しているというよりも、さまざまな思考ツールを紹介しているものだから、まだ読みやすい。けれども他の本はもう読まない。

(未完)

書評: 岩田温著『政治学者、ユーチューバーになる』

著者がユーチューバーになった理由。その存在意義。その可能性を語る。

概要

まず本書『政治学者、ユーチューバーになる』の内容をまとめる。次に先月おこなわれた出版記念講演会に評者は参加したので、それを記載する。最後に本書の内容について批評する。この批判は当初講演会時に著者本人に質問したかったことであるが、結局諸事情により言えなかったことである。

  • 概要
  • 要約
  • 講演会
  • 批評
    • 1. 池上彰批判について
    • 2. 教育は親と教員のどちらの考えを優先すべきか
    • 3. 在宅登校の可能性について
  • おわりに
続きを読む

Vuetify: v-btnの文字を改行する方法

概要
v-btnの文字を改行する方法を示す。


ソースコード

<template>
  <v-app>
  <div>
  <h1>button new line</h1>
  <div class="mt-6 mb-6">
    <v-btn class="new-line-button text-pre-wrap text-left" block>{{textHello}}</v-btn>
  </div>
  <div class="mb-6">
    <v-btn class="new-line-button text-pre-wrap text-left" block>{{textNewLine}}</v-btn>
  </div>
    <div class="mb-8">
      <v-btn class="new-line-button text-pre-wrap text-left">{{textNewLine}}</v-btn>
    </div>
  </div>
  </v-app>
  </template>
  <script>
  
  export default {
    data: () => ({
      textHello: 'Hello,\nWorld!\nFunction, Hello!\nWorld!!!\nFunction,\t Hello,\nWorld!\nFunction, Hello\nWorld!\nFunction, Hello!!\nWorld\nFunction',
      textNewLine: 'これはボタンです。\n改行されます。'
    }),
  };
  </script>
  <style lang="scss" scoped>
  .new-line-button.v-btn {
    height: auto;
    padding: 0.8% 2%;
  }
  </style>


実行結果

詳細

クラス作成

  • 今回はcssのクラスnew-line-buttonを作成しました。これがv-btnにあると改行されます。

ボタンの高さを自動的に変える

  • height: auto;によって、改行数に対して自動的にボタンの幅が変わります。もしこれをなくすと、文字がボタンからはみ出します。

改行の文字について

  • textHellotextNewLinepropsに対して改行がおこなえますが、v-btnに直接書いても改行にはなりません。

つまり次のように書いても改行はされません。

<v-btn class="new-line-button text-pre-wrap text-left">これはボタンです。\n改行されます。</v-btn>

改行した文字を左寄せにする(text-left)

  • text-leftを使うことで、改行した文字を左側に寄せることができます。もしもこれを設定しないと次のように中央揃えに表示されます。

text-pre-wrapを設定する

  • テキストをそのまま表示するtext-pre-wrapやテキストを左側に寄せるtext-leftを自作したclassの中に入れたほうがよりよいのですが、私はわかりませんでしたので、VuetifyにあるText and typographyを使いました。

不具合がある場合

  • 私の場合、上記のコードで特に問題ありませんでしたが、何か不具合やおかしい箇所があれば、さらに次のような.new-line-button.v-btn__contentを追加したほうがいいかもしれません。
.new-line-button.v-btn__content {
   flex: auto;
 }