Pythonの参照渡しと(値渡し)

まずはこちらをご覧ください。


Pythonのサンプルコード書いてみました。

class Test:

    def __init__(self) -> None:
        self.list_ = [1,2,3,4]
        self.num = 100
    
    def get_num(self):
        return self.num
    
    def get_list(self):
        return self.list_

test = Test()

num = test.get_num()
num = 200

# test.numは...?100 or 200?

list_ = test.get_list()
list_.append(700)

# test.list_に700はいる or いない?


...numlist_test.numtest.list_をprintした時、何が出力されると思いますか?

ちなみに私は、

- num = 200、test.num = 100
- list_ = [1,2,3,4,700]、test.list_ = [1,2,3,4]

だと思ってました 👀

はい、これは、間違いですね ☝️


正解は...


# 出力部分の実装
print(f'test.num = {test.num}' + f'\tアドレス:{id(test.num)}')
print(f'num = {num}' + f'\tアドレス:{id(num)}')
print('==================================')
print(f'test.list_ = {test.list_}' + f'\tアドレス:{id(test.list_)}')
print(f'list_ = {list_}' + f'\tアドレス:{id(list_)}')

# 結果

test.num = 100  アドレス:4332965200
num = 200       アドレス:4332968400
==================================
test.list_ = [1, 2, 3, 4, 700]  アドレス:4334289664
list_ = [1, 2, 3, 4, 700]       アドレス:4334289664


id で各変数のアドレス(格納先)も出力させてみました。

こうすると、なんで numlist_ で挙動が違うのかが、わかるかと思います。

リストの場合は、どうも参照渡しになっているのです 👀 !

(調べて分かったのですが、 num も実は参照渡し...後述)

※ アドレスは実行環境によって変わります。

色々調べてみた結果...


Pythonには、値渡しが存在しない 👀 !?
  • さっきは返り値 num に代入していたので気づかなかったのですが、代入しないでアドレスを見ると...一緒でした 👀
  • タイトルの値渡しを( )にしている理由です

      test = Test()
    
      num = test.get_num()
    
      # 結果
      test.num = 100  アドレス:4395519312
      num = 100       アドレス:4395519312
    
    • 値が代入される時に、新しいオブジェクト(格納先)が作られて、そこに代入値が入れられるらしい
      test.num = 100  アドレス:4332965200
      num = 100       アドレス:4332965200
    
      num = 200 の後↓
    
      test.num = 100  アドレス:4332965200
      num = 200       アドレス:4332968400 # アドレス違う
    


型によって挙動が変わるらしい
  • イミュータブルな型 → 代入時に新しいオブジェクトが生成される(結果的に値渡しのような挙動になる)
  • ミュータブルな型 → 参照渡しになるので、元の値も変わってしまう
  • Pythonではどの型がイミュータブルなのかは一覧表↓

Pythonの組み込みデータ型の分類表(ミュータブル等) - ガンマソフト株式会社


Python 3.10ですが、公式ドキュメントにも、ちらっとイミュータブルについて説明が...

3. Data model - Python 3.10.4 documentation

オブジェクトによっては 値 を変更することが可能です。値を変更できるオブジェクトのことを mutable と呼びます。
生成後に値を変更できないオブジェクトのことを immutable と呼びます。

(mutable なオブジェクトへの参照を格納している immutableなコンテナオブジェクトの値は、
その格納しているオブジェクトの値が変化した時に変化しますが、
コンテナがどのオブジェクトを格納しているのかが変化しないのであれば immutable だと考えることができます。
したがって、immutable かどうかは値が変更可能かどうかと完全に一致するわけではありません) 

オブジェクトが mutable かどうかはその型によって決まります。
例えば、数値型、文字列型とタプル型のインスタンスは immutable で、dict や list は mutable です。

※ コンテナ = 任意の型のデータを複数格納できるデータ型(list, tuple, dict, set)

参考

こちらの記事を主に参考にしました!

参照についてもう少し詳しく ~PythonとJavaを例に~ - Qiita

感想

  • Pythonには、値渡しが存在しないというのは知らなかったし、もはや、リストさえも値渡しだと思っていたので、色々間違って理解していたようです。
  • これからリストを返り値で返すときは、参照するだけなら良いですが、変えてしまう可能性がある場合(あまり良くはない)は、新しいリストオブジェクトを作成して返すと事故らなそうですね。

react-to-printで詰まったところmemo

何者?

「Web画面をPDFで印刷したい、PDFで出力したい」をReactで実現してくれるライブラリ

https://www.npmjs.com/package/react-to-print

  • 基本的に↑の例を見れば、サクッとできる。
  • しかもDemoもあるので、参考になる

・・・・が、今回、関数コンポーネントでTypeScriptで書いている場合にサンプルコードだけでは、詰まった部分があったので、メモ

業務で書いたコードを加工してるので、あくまでイメージです。(動作確認してません

同じような境遇になった人の何かのヒントになれば良いなと思います。(あと、将来の自分用に残しておく、多分半年後何も覚えてない。)

(TypeScriptを含め、frontendは全体的に勉強中なので間違えているところもあるかもですが・・・優しい目で見てあげてください 🙇‍♀️ )

ComponentToPrint の部分

import React, { Component, useRef } from "react";

class ComponentToPrint extends Component {
  public render(): JSX.Element {
    return (
      <>
         <SampleComponent1 /> <- こんな感じで、複数のコンポーネントは並べられる
         <SampleComponent2 />
      </>
    );
  }
}

ReactToPrint の部分

  • ReactToPrint と ComponentToPrintの順番はどっちでも動きそうです
  • ボタンを上に置くか、下に置くか見たいなところ
import React, { useRef } from 'react';
import ReactToPrint from 'react-to-print';
import Button from "@material-ui/core/Button";

import { ComponentToPrint } from './ComponentToPrint';

export const Exsample = () => {
  const componentRef = useRef(null);
  return (
    <div>  <- useRefに認識させるために div で囲う必要がある(と理解)
      <ComponentToPrint ref={componentRef} />  <- ref ってやつがクラスコンポーネントじゃないと使えないみたい
      <ReactToPrint
        trigger={() => (
          <Box textAlign="right"> <- material-uiも使えますよ!
            <Button>
              印刷
            </Button>
          </Box>
        )}
        content={() => componentRef.current}
        documentTitle={ここに保存時のファイル名をデフォルトを設定できる}
      />
    </div>
  );
};

使ってみて分かったこと

ComponentToPrint の部分はクラスコンポーネントにする
  • 現状、クラスコンポーネントじゃないとうまく動作しないようです
  • 次のメジャーアップデートで、関数コンポーネントにも対応させるとのコメントも見かけましたので、とりあえずクラスコンポーネントにしましょう
  • FAQにも書いてありますね、抜粋
Can the ComponentToPrint be a functional component?
Officially no, but there are workarounds using the useRef hook.
See #96 and #181 for examples. 
We will officially support this once we release the next major version which will drop React 15 support.

ここら辺のissueにも書いてあるので、ざっと読んでみると良いかと思います。

https://github.com/gregnb/react-to-print/issues/363

頑張れば関数コンポーネントも使えるみたいなコメントもあります↓

https://github.com/gregnb/react-to-print/issues/181#issuecomment-814305408

こちらはHOC(higher-order component; HOC)というReactの応用テクニックらしいですね。

あるコンポーネントを受け取り、新規コンポーネントを返す関数 のことらしいです

https://ja.reactjs.org/docs/higher-order-components.htm

結局分からなかったこと

pageStyle について
  • ReactToPrint に渡すと、PDFのページのmarginとかの設定を一括でできるらしいと使ったが、material-uiのデザインが吹っ飛ぶ(反映されなくなる)ので、結局使えなかった
  • PDFの周囲に空白入れたかったが断念
改行ページ
  • サンプルコードの通りに書いたがうまくいかない(使い方間違えている疑惑)
  • https://www.npmjs.com/package/react-to-print のPage Breaks のところにサンプルコードもあるので、もう一回ちゃんと読めばできそうな気がする(気のせいかも)

pemとp.12は何が違うのか?

(久しぶりの更新になってしまった・・・) 業務をしていてふと、疑問に思ったのでサクッと調べてみた。

証明書の拡張子の種類

以下のサイトの表がわかりやすい。

https://www.nextdoorwith.info/wp/se/infra/ssl-tls-pki/digital-certificate-encoding-extensions-difference/

どうやら、「エンコードの種類」もしくは「証明書 or 鍵なのか」で拡張子を分けるぽい

エンコードで分ける

  • 拡張子の種類としては 「.der」と「.pem」が存在する
  • どちらも「ASN.1」というデータ構造で定義している

「ASN.1」は深くは調べないが、データ構造の表現方法の一種なのだなと理解。

https://e-words.jp/w/ASN.1.html

.derについて

  • DER(Distinguished Encoding Rules)形式
  • 「ASN.1」で表現されたデータを「バイナリデータ化」したもの
  • バイナリデータなので、そのままでは読めない(エディタとかでドラッグしても表示されないっぽい)
  • 1つのファイルに「1つの証明書」しか含められない

.pemについて

  • PEM(Privacy Enhanced Mail)形式
  • 「ASN.1」で表現されたデータを「バイナリデータ化」して、さらに、「Base64」で変換したテキストファイル
[memo]
Base64 = バイナリデータをテキスト(ascii)データに変換する
  • .der形式 から pemファイルが生まれるというわけ🧚‍♀️
  • 「----BEGIN 」で始まる

  • メール送信プロトコルであるSMTPでは、バイナリデータを送ることができないので、メールで証明書を送る手段として生まれたのが「PEM」らしい

  • OpenSSLのデフォルトフォーマットLinuxでよく使われる(今いるチームのシステムでもpemファイル使ってるな〜

  • 拡張子が「.cer」になることもあるらしいが、Linuxでは「.pem」が使われるとのこと

  • .pemファイルはエディタとかで開ける、メモ帳でも開ける、そのまま読める

  • 1つのファイルに「複数の証明書」を含められる → 複数含める場合としては「中間証明書」をくっつける場合 とかがあるらしい

証明書か鍵かで分ける

  • 拡張子の種類としては、「.crt もしくは .cer」、「.key」、「.pfx もしくは .p12」

.crt もしくは .cer

  • 中身が証明書(公開鍵もしくは誰かの公開鍵の証明書)の時に使用される拡張子
  • Windowsでは「.cer」を、LinuxやApahceでは「.crt」を使うことが多い
  • バイナリデータ(DER形式)もしくはテキストデータ(PEM形式)のどちらかの形式になっている

.key

  • 中身が秘密鍵の時に使用される拡張子
  • バイナリデータ(DER形式)もしくはテキストデータ(PEM形式)のどちらかの形式になっている

.pfx もしくは .p12

  • PKCS#12形式(Personal Information Exchange)」と呼ばれる(古い名前として「PFX」とも呼ばれる)
  • 証明書と証明書に紐づく秘密鍵が1つのファイルにまとめられている形式
  • つまり、公開鍵と秘密鍵が一緒に入ってるファイル ☝️
  • このファイルにはパスワードがかけられているので、中身を確認するにはパスワードの入力が必須!
  • バイナリ形式なので、そのまま読めない
  • Windows OSで証明書を使うときは、このフォーマットのことが多いらしい




・・・・・・・ということで、ざっくり調査してみました。

当初のpemとp12の違いは何か理解。

わざわざシステムで使うときにp.12の形式の証明書をpemにしてたのは、中身を確認するためだったのとシステムのOSがLinuxだからなのかな〜という理解 👀

他に参考にしたサイトたち

https://milestone-of-se.nesuke.com/sv-basic/windows-basic/certificate-export-format/

http://tooljp.com/windows/chigai/html/Cipher/PEM-CRT-CER-CERT-KEY-DER-CSR.html

SQLAlchemyでまとめてupsert

きっかけ

SQLAlchmey(ver 1.3)において、まとめてupsert(bulk upsert) するのはどうすればいいのか学んだので、備忘録で書いとこうと思いました。

その前に・・・・

upsertについて

みなさん、upsertって知ってますか?

そんなの基本だと言われるかもしれませんが、なんとなくSQLしてきた人からすると、

selectとinsertとupdateとdeleteくらいしか、実は知らなかったりします。(私だけかな・・・

upsertとは?

upsertとは、対象のデータがDBになければ追加、あれば更新してくれる命令

update と insert を組み合わせて、upsertと呼ばれています

upsertを使うメリット

upsertは select してレコードがあったら、update命令にして、

レコードがなければ、insert命令にするって感じでアプリ側で操作することで実現することも可能

ただし、selectとupdaete もしくは insert の組み合わせでは、

DBに2回SQLの問い合わせが必ず発生する。

それなら1回の問い合わせで済むupsertを使った方が、DBの負担は少ない。

DBへの負荷はなるだけ、少なく済ませるのが、やはり暗黙の了解でしょうか・・・

参考:https://web-academia.org/482/

本題

公式ドキュメントはこちら

https://docs.sqlalchemy.org/en/14/dialects/postgresql.html#insert-on-conflict-upsert

単発、つまり1レコードについての書き方は以下のサイトがわかりやすかったのでこちらを参考にする

https://qiita.com/nsuhara/items/86570f789093222252b1#upsertの実装

上記のサイトをベースに

複数レコードについては、書き方はこんな感じになる(Python)

from sqlalchemy.dialects import postgresql

class FeedbackNew(db.Model):
    __tablename__ = 't_feedback_new'

    id = db.Column(db.Integer, primary_key=True, autoincrement=True)
    title = db.Column(db.String(255), nullable=False)

def bulk_upsert(record_lists) -> None:

    # stmt = ステートメント らしい
    stmt = postgresql.insert(FeedbackNew).values([
      dict(
        id=x.id,
        title=x.title,
      ) for x in record_lists
    ])

    do_stmt = stmt.on_conflict_do_update(
      index_elements=['id'],
      # set_ に渡せるのはdict型のみなので注意
            # 更新対象を渡す
      set_=dict(
          title=stmt.excluded.title,
      ),
    )

    # sessionの渡してるところとかは割愛します
    # とりあえず、どこかで session.executeするのを忘れずに・・・・
    session.execute(do_stmt)

ちなみに・・・

  • これは PostgreSQL ver.9.5以降 の機能らしい。
  • on_conflict_do_update は、SQLAlchemyが提供するメソッド
  • on_conflict_do_update は、PostgreSQL固有のinsert()関数内のON CONFLICT をサポートしている
  • ステートメントは、1つのSQLの命令を指す、(select文, insert文とか)

https://style.potepan.com/articles/26487.html

細かいことは調べてないが、とりあえず、こんな書き方で一気にupsertすることができるらしい 🧚‍♂️

@singledispatchmethodとは

きっかけ

コードレビュー中に見かけたので、調査

2, 3個の個人ブログやらを経由してやっと理解したので、ここでまとめておきたい

あと前提の知識が必要なやつだったので、一気に理解できると嬉しいよねって気持ち

ちなみに @singledispatchmethod を理解するにあたり、以下の順番で理解すると良さそう

  1. ジェネリック関数
  2. singledispatch
  3. singledispatchmethod ←今回学ぶやつ

前置き長いですが、順番に理解する方が意外と近道かもです 🐥

ジェネリック関数

  • 「第一引数の型に応じて、異なる処理を行う関数」

ざっくりイメージとしては

def function(obj):
  if type(obj) == int:
    return print(f'int型が渡されました,obj={obj}')
  
  if type(obj) == str:
    return print(f'string型が渡されました,obj={obj}')

# int型渡す
function(1)

# string型を渡す
function('moji')

===========結果==================
int型が渡されました,obj=1
string型が渡されました,obj=moji

こんな感じで引数の型によって function 関数の挙動が変わる

こういうやつをジェネリック関数 というらしい

もっと詳しくの方は、以下を参考にしてください。

https://retroid2016.com/2018/08/09/【pyhton】ジェネリック関数【備忘録】/

singledispatch

  • Pythonから提供されているライブラリである「functools」の一部

公式ライブラリは以下ですが、まぁ・・・わからなかった

https://docs.python.org/ja/3/library/functools.html#functools.singledispatch

  • ジェネリック関数を定義するためのデコレータ
  • つまり、ジェネリック関数を作りたい!ってなったら、関数の先頭につけるデコレータ
  • クラスの中にある関数(正しくはメソッド と言いますが)ではない、関数用のデコレータ

ジェネリック関数の説明で書いたソースコードを、singledispatch で書き換えてみる

from functools import singledispatch

@singledispatch
def function(obj): <- 元となる関数名
  return print(f'{type(obj)}型が渡されました, obj={obj}')

# int型がきた時の関数の挙動を宣言
# ちなみに関数名が 「 _ 」なのは、何でもいいよって意図らしいです
@function.register  <- 元となる関数名.register で定義する
def _(obj: int):
    return print(f'int型が渡されました,obj={obj}')

# string型が来たときの関数の挙動を宣言
@function.register
def _(obj: str):
    return print(f'string型が渡されました,obj={obj}')

# int型渡す
function(1)

# string型を渡す
function('moji')

# list型を渡す
function([1, 2, 3])

===========結果==================
int型が渡されました,obj=1
string型が渡されました,obj=moji
<class 'list'>型が渡されました, obj=[1, 2, 3]

ちゃんと型によって挙動が変わった・・・!

すごい便利なデコレータですね 👀

では、やっとですが、本来知りたいやついきましょう。

ちなみに、このsingledispatch がバッチリ理解できた人は、もう singledispatchmethod も理解したに等しいですので、ご安心を。。。

singledispatchmethod

  • 同じく、Pythonから提供されているライブラリである「functools」の一部

まぁ、同じく公式ドキュメント難しい

https://docs.python.org/ja/3/library/functools.html#functools.singledispatchmethod

  • singledispatch は関数用でしたが、singledispatchmethod はメソッド用
  • つまり、クラスの中とかで定義されたメソッドをジェネリック関数にしたいとなったら、こっちを使いましょう ということ
  • そして使い方は、singledispatch と一緒です 🙌

singledispatch で書いたソースコードをクラスの中のメソッドにして、singledispatchmethod で書き直してみましょう

from functools import singledispatchmethod

class Func:

  @singledispatchmethod
  def function(self, obj):
    return print(f'{type(obj)}型が渡されました, obj={obj}')

  # int型がきた時の関数の挙動を宣言
  @function.register
  def return_int(self, obj: int): <- 今回はメソッド名をつけてみた
      return print(f'int型が渡されました,obj={obj}')

  @function.register
  def return_str(self, obj: str):
      return print(f'string型が渡されました,obj={obj}')

# Funcクラスを作成
func = Func()

# int型渡す
func.function(1) <- 元の関数名で呼んであげるで良さそう

# string型を渡す
func.function('moji')

# list型を渡す
func.function([1, 2, 3])

===========結果==================
int型が渡されました,obj=1
string型が渡されました,obj=moji
<class 'list'>型が渡されました, obj=[1, 2, 3]

ちなみに他のデコレータと一緒に使いたいってなった時は、以下のようにsingledispatchmethod を一番外にして書いてあげると良いらしい

今回は staticmethod を追加してあげる

=> については以下を見るとわかりやすいかも

https://www.lifewithpython.com/2014/02/python-difference-between-staticmethod-and-classmethod.html

from functools import singledispatchmethod

class Func:
  @singledispatchmethod
  @staticmethod
  def function(obj):
    return print(f'{type(obj)}型が渡されました, obj={obj}')

  # int型がきた時の関数の挙動を宣言
  @function.register
  def return_int(obj: int):
      return print(f'int型が渡されました,obj={obj}')

  @function.register
  def return_str(obj: str):
      return print(f'string型が渡されました,obj={obj}')

# int型渡す
Func.function(1)

# string型を渡す
Func.function('moji')

# list型を渡す
Func.function([1, 2, 3])

ちなみに、今回調べていて、公式ドキュメントを参考に書いてみたら、動かない現象を発見しましたので、一応メモ

以下のコードを動かすと

class Negator:
    @singledispatchmethod
    @classmethod
    def neg(cls, arg):
        raise NotImplementedError("Cannot negate a")

    @neg.register
    @classmethod
    def _(cls, arg: int):
        return -arg

    @neg.register
    @classmethod
    def _(cls, arg: bool):
        return not arg

以下のエラーが出ます

TypeError: Invalid first argument to `register()`: <classmethod object at 0x1043063d0>. Use either `@register(some_class)` or plain `@register` on an annotated function.

↑のコードは、 Python3.8.1 と3.8.3 でしか動作しないコードなのでご注意を・・・

どう修正すればいいのかまでは、調べてないですが・・・

1年前はまだ解決してなさそうですが、今はどうなんでしょう・・・ 🤔

参考:https://stackoverflow.com/questions/62696796/singledispatchmethod-and-class-method-decorators-in-python-3-8

まとめ

こちらのデコレータを使うと、何がいいかってif文が乱立するソースコードではなくなり、可読性があがりそうって感じでしょうかね 👐

今回の調査で参考にしたサイトたち

https://smooth-pudding.hatenablog.com/entry/2021/03/27/145218#class-functoolssingledispatchmethodfunc-ver38

https://marusankakusikaku.jp/python/standard-library/functools/#singledispatch-ジェネリック関数の定義

PydanticのFieldクラスのdeprecated

きっかけ

  • 将来消す可能性がある変数には、 Field(deprecated=True) をつけておいてほしいと言われた
  • イメージとしてはこんな感じ
from pydantic import BaseModel, Field
from typing import Optional

class User(BaseModel):
    id :int
    name: str
    old_field: Optional[str] = Field(deprecated=True) <- これ
    new_field: str
  • deprecated って何と思ったので調べてみた

結論

  • 2021.6現在まだ、 pydantic には実装されていないので、Field(deprecated=True) をつけても無視される(つけたところで何も変わらない)
  • pydanticField クラスはこんな感じで無視されるようになっている

samuelcolvin/pydantic

  • こちらの方がissueで将来的に追加して欲しい機能としてチケットをあげている

Add deprecated marker for fields in a model · Issue #2255 · samuelcolvin/pydantic

有効になるとどうなるのか?

  • ↑のissueによると、deprecated=True をつけると、つけられたフィールドは将来的に削除される可能性があるので、使わないでね みたいなwarningメッセージみたいなものが出るようになるらしい
(原文)
Pydantic Fields should have a boolean option deprecated (default False) which can be used to mark deprecated fields.
Pydantic plugin for Pycharm can also use this field to see if the field should be used any longer, 
and show code hints if someone is trying to interact with deprecated fields.

副作用とは?

巷で話題の 「ワクチンの副作用」 のことではないので、そっちじゃないんですか〜ってなった方は、ここでお戻りください🙇‍♀️

概要

  1. ある機能があるデータを変化させる
  2. 1で変化したデータを使用している処理の結果が変わる

参考:https://gimo.jp/glossary/details/side_effect.html

どういう時に起こるか?

値渡しと参照渡しを例にあげてみた(C言語)

参照渡しでは、関数呼び元のデータを変化させてしまっており、その後の処理の結果を変えてしまっている(副作用がある)

参考:https://chomado.com/programming/call-by-value-or-by-reference/

#include <stdio.h>

void function(int num)
{
    num += 100;
}

void function2(int *pNum)
{
    *pNum += 100;
}

int main()
{
    int number = 1;
    
    /* 値渡し */
    function(number);
    printf("値渡し後\n");
    printf("%d\n", number);
    
    /* 参照渡し */
    function2(&number);
    printf("参照渡し後\n");
    printf("%d\n", number);
}

===== 結果 =========
値渡し後
1
参照渡し後
101  <-  本来であればnumber = 1であるはず
  • この場合は、 function関数の呼び元で宣言されている number の値が、 function関数内で変化させられることはない
  • しかし参照渡しをしている function2 関数内では、呼び元の number が変えられてしまう事象が起きる

以下のサイトに上のコードをコピペして、実行すれば同じ結果が見れる

https://www.tutorialspoint.com/compile_c_online.php

副作用があることによるデメリットとは?

  1. バグが発生しやすい
  2. ソースコードの改修が困難になる
  3. ソースコードが読みづらくなる

副作用を起こさないために。

  • 変数のスコープを小さくする。
  • 外部の変数や状態に依存しない実装を心がける

今、自分が実装しているコードは、自分のコード以外で何か気にしなければいけないのだろうかと一瞬立ち止まって考えてみるのも良いかもですねぇ。

参考:https://boukenki.info/programming-hukusayou-imi/#i-3