LoginSignup
7
6

More than 5 years have passed since last update.

JavaおじさんがPythonを使えるようになるまでの全記録(その16)

Posted at

はじめに

ご無沙汰です。Javaおじさんです。

5年ぶりに履歴書書いて面接行ったり、東京行ってVTuberのなり方を勉強しに行ったりして更新の間が開いてしまいました。

いやあVTuber、いろんな技術が融合しててすごい面白いねあれ。
まあさすがにバ美肉する気まではないけれどもwケモナー系ならまだワンチャンあるような気もしているw(ナイナイ

あ、それで面接の結果、某社で社員としてお仕事させていただくことになりました。
正式契約になって社名出していいってなったら出します。

Djangoのビルトインのクエリー式(その1)

で、前回でDjangoのチュートリアルを終えたんだけど、チュートリアルの中で「排他制御に興味あるやつぁここ見ろ」ってところが全然日本語化されてなくてしかもネット上に解説記事が全然ないので、とりあえず超訳してみた。

F()式

F()オブジェクトは、モデルフィールドまたは注釈付き列の値を表します。 モデルフィールドの値を参照し、実際にデータベースからPythonメモリにデータを引き出さずに、モデルフィールド値を参照してデータベース操作を実行することができます。

代わりに、DjangoはF()オブジェクトを使用して、データベースレベルで必要な操作を記述するSQL式を生成します。

これは例を通して理解するのが一番簡単です。 通常、次のようなコードが書かれます。

# Tintinさんが新しいニュース記事をファイルしました!
reporter = Reporters.objects.get(name='Tintin')
reporter.stories_filed += 1
reporter.save()

(訳注:要するにModelであるReporterのnameフィールドを使ってインスタンスを復元して、そのインスタンスの持つstories_filedに1を足してからDBに返すコードを考えるのだけど、日本人的にはなんでよりによってTinTinやねんとは思わざるを得ないw
普通にSmithとかPetersonとかでいいじゃないの。これのせいで最初目が滑ってしょうがなかったわ)

ここではreporter.stories_filedの値をデータベースからメモリに引き出してきてPythonの演算子を使って処理を行い、データベースに書き戻しています。しかし、こう書く代わりに我々はこんな風に書くことも可能です。

from django.db.models import F

reporter = Reporters.objects.get(name='Tintin')
reporter.stories_filed = F('stories_filed') + 1
reporter.save()

reporter.stories_filed = F('stories_filed') + 1 lというコードは、通常のPythonの値をインスタンスの属性に割り当てているように見えますが、実際にはデータベースへの操作を記述するSQL構造体になっています。
DjangoはFクラスのインスタンスに遭遇すると通常のPythonの演算子をオーバーライドしてカプセル化されたSQL表現を作り出します。この場合は、データベースにreporter.stories_filedで表されるデータベースフィールドをインクリメントするよう指示します。
reporter.stories_filedの値が何であっても、過去どうであったとしても、Pythonはそれについて知ることは決してありません。データベースによって完全に処理されます。
Pythonが行うことは、DjangoのF()クラスを使用して、フィールドを参照して操作を記述するSQL構文を作成することだけです。

この方法で保存された新しい値にアクセスするには、(以下のように)オブジェクトを再ロードする必要があります:

reporter = Reporters.objects.get(pk=reporter.pk)
# Or, more succinctly:
reporter.refresh_from_db()

上記のように単一インスタンスの操作で使用されるだけでなく、F()はオブジェクトインスタンスのQuerySetでupdate()とともに使用できます。 これにより、上記で使用したget()とsave()という2つのクエリが1つに減ります:

reporter = Reporters.objects.filter(name='Tintin')
reporter.update(stories_filed=F('stories_filed') + 1)

update()を使用して、複数のオブジェクトのフィールド値をインクリメントすることもできます。これは、すべてのデータをデータベースからPythonに引き出してループを回しそれぞれのフィールド値をインクリメントした後それぞれをデータベースに書き戻すよりも非常に速く処理できるでしょう:

Reporter.objects.all().update(stories_filed=F('stories_filed') + 1)

したがって、F()は次のようにパフォーマンスを向上させることができます。

  • Pythonではなくデータベースを使って仕事をする
  • いくつかの操作が必要とするクエリの数を減らす

F()を使って競合状態を防ぐには

F()のもう1つの有益な点は、Pythonではなくデータベースでフィールドの値を更新することで、競合状態を回避できることです。
上記の最初の例で2つのPythonスレッドがコードを実行すると、一方のスレッドは、他方のスレッドがデータベースから値を取得した後に、フィールドの値を取り出し、インクリメントして保存できます。 2番目のスレッドが保存する値は、元の値に基づいています。 最初のスレッドの作業は単純に失われます。
データベースがフィールドの更新を担当する場合、プロセスはより堅牢です。save()またはupdate()が実行されたときにデータベースのフィールドの値に基づいてフィールドを更新するので、むしろインスタンスが取得されたときの値に基づいて更新するよりも堅牢になります。

なるほどねー。
Pythonのコード上で排他制御を行うのではなくRDBに排他制御を丸投げしようとそういうことなのね。

ちなみにFクラスのソースは公開されている。多分これだけ見てもわかんないと思うけど一応FクラスとスーパークラスであるCombinableだけ抜き出すとこんな感じ。Combinableで演算子のオーバーライドをやっててSQLの組み立てをやるのがFっていう割り当てなのかな。

class Combinable:
    """
    Provide the ability to combine one or two objects with
    some connector. For example F('foo') + F('bar').
    """

    # Arithmetic connectors
    ADD = '+'
    SUB = '-'
    MUL = '*'
    DIV = '/'
    POW = '^'
    # The following is a quoted % operator - it is quoted because it can be
    # used in strings that also have parameter substitution.
    MOD = '%%'

    # Bitwise operators - note that these are generated by .bitand()
    # and .bitor(), the '&' and '|' are reserved for boolean operator
    # usage.
    BITAND = '&'
    BITOR = '|'
    BITLEFTSHIFT = '<<'
    BITRIGHTSHIFT = '>>'

    def _combine(self, other, connector, reversed):
        if not hasattr(other, 'resolve_expression'):
            # everything must be resolvable to an expression
            if isinstance(other, datetime.timedelta):
                other = DurationValue(other, output_field=fields.DurationField())
            else:
                other = Value(other)

        if reversed:
            return CombinedExpression(other, connector, self)
        return CombinedExpression(self, connector, other)

    #############
    # OPERATORS #
    #############

    def __neg__(self):
        return self._combine(-1, self.MUL, False)

    def __add__(self, other):
        return self._combine(other, self.ADD, False)

    def __sub__(self, other):
        return self._combine(other, self.SUB, False)

    def __mul__(self, other):
        return self._combine(other, self.MUL, False)

    def __truediv__(self, other):
        return self._combine(other, self.DIV, False)

    def __mod__(self, other):
        return self._combine(other, self.MOD, False)

    def __pow__(self, other):
        return self._combine(other, self.POW, False)

    def __and__(self, other):
        raise NotImplementedError(
            "Use .bitand() and .bitor() for bitwise logical operations."
        )

    def bitand(self, other):
        return self._combine(other, self.BITAND, False)

    def bitleftshift(self, other):
        return self._combine(other, self.BITLEFTSHIFT, False)

    def bitrightshift(self, other):
        return self._combine(other, self.BITRIGHTSHIFT, False)

    def __or__(self, other):
        raise NotImplementedError(
            "Use .bitand() and .bitor() for bitwise logical operations."
        )

    def bitor(self, other):
        return self._combine(other, self.BITOR, False)

    def __radd__(self, other):
        return self._combine(other, self.ADD, True)

    def __rsub__(self, other):
        return self._combine(other, self.SUB, True)

    def __rmul__(self, other):
        return self._combine(other, self.MUL, True)

    def __rtruediv__(self, other):
        return self._combine(other, self.DIV, True)

    def __rmod__(self, other):
        return self._combine(other, self.MOD, True)

    def __rpow__(self, other):
        return self._combine(other, self.POW, True)

    def __rand__(self, other):
        raise NotImplementedError(
            "Use .bitand() and .bitor() for bitwise logical operations."
        )

    def __ror__(self, other):
        raise NotImplementedError(
            "Use .bitand() and .bitor() for bitwise logical operations."
        )

@deconstructible
class F(Combinable):
    """An object capable of resolving references to existing query objects."""
    # Can the expression be used in a WHERE clause?
    filterable = True

    def __init__(self, name):
        """
        Arguments:
         * name: the name of the field this expression references
        """
        self.name = name

    def __repr__(self):
        return "{}({})".format(self.__class__.__name__, self.name)

    def resolve_expression(self, query=None, allow_joins=True, reuse=None, summarize=False, for_save=False):
        return query.resolve_ref(self.name, allow_joins, reuse, summarize)

    def asc(self, **kwargs):
        return OrderBy(self, **kwargs)

    def desc(self, **kwargs):
        return OrderBy(self, descending=True, **kwargs)

    def __eq__(self, other):
        return self.__class__ == other.__class__ and self.name == other.name

    def __hash__(self):
        return hash(self.name)

とりあえず眠いので今夜はここまで。

7
6
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
7
6