DA研の技術ブログ

October 25th, 2023

Pythonのオブジェクト

Python のオブジェクト

オブジェクトとは

Hello World とは、プログラミング言語の世界における伝統的な初歩的なプログラムです。多くの Python 入門者はまずインタプリタを起動し次のように入力することで、この世界に足を踏み入れてきたはずです。

>>> 'Hello World'
'Hello World'

非常に初歩的なプログラムですが、このプログラムはオブジェクト指向プログラミングの世界におけるオブジェクトの概念を理解する上で重要なプログラムです。

一見すると(echo コマンドのように)インタプリタが呼応して'Hello World'という文字列を返しているだけのように見えます。しかし事態はそう単純な話ではありません。この裏側でインタプリタは、'Hello World'という値をもつ文字列型のオブジェクトを生成した後、そのオブジェクトの文字列表現を標準出力しており、我々はその結果を目にしています。

Python では、全ての要素がオブジェクトとして表現されます。上でみた文字列や整数、浮動小数点数、リスト、辞書、関数、クラス、例外、モジュールなどなど全てがオブジェクトです。

オブジェクトとは値(value)と型(type)、識別子(id)を持つ透明な箱のようなものです。

透明であるため、我々は自由に箱の中身を覗くことができます。

---------------
|             |
|             |
|    value    |
|             |
| type        |
---------------

オブジェクトは通常リテラル1を用いて生成します。

>>> 'Hello World'
'Hello World'
---------------
|             |
|             |
| Hello World |
|             |
| type: str   |
---------------
>>> 1991
1991
---------------
|             |
|             |
|    1991     |
|             |
| type: int   |
---------------

オブジェクトの型を確認するには、組み込み関数 type() を利用します。

>>> type('Hello World')
<class 'str'>
>>> type(1991)
<class 'int'>
>>> type(type)
<class 'type'>

また、組み込み関数 id() はオブジェクトの識別子を返します。

>>> id('Hello World')
4379060208
>>> id(1991)
4372760080

識別子はオブジェクトに対して一意に割り当てられ、実行中は変更されることはありません。

加えて識別子はメモリ上でのオブジェクトの位置を表します。そのため ctypes.cast を利用して、直接取得したメモリアドレスへのアクセスすることも可能です。

>>> import ctypes
>>> obj = 'Hello World'
>>> ctypes.cast(id(obj), ctypes.py_object).value
'Hello World'

⚠ Warning
Python では直接的なメモリアクセスは推奨されません。上のコードを書くようなことはまずありません。

変数は箱ではない

特定の文脈では変数に対して「値を格納する箱」のように説明される場合がありますが、こと Python の文脈においては、このイメージは不正確です。

>>> var = 1991
>>> var
1991

正しくは、変数はオブジェクトに紐づいたタグのようなものです。

x 誤ったイメージ: varという名前の箱に1991という値が入っている
----------------
|              |
|              |
|     1991     |
|              |
|  var         |
----------------

o 正しいイメージ: 名前varは数値オブジェクト1991を指している
----------------
|              |
|              |
|     1991     |
|              |
| type: int    |-----[var]
----------------

変数を格納する箱と表現した場合、以下のコードの説明がつきません。

>>> a = [1, 2, 3]
>>> b = a
>>> a += [4]
>>> b
[1, 2, 3, 4]  # expected [1, 2, 3]

より正確に表現すると、変数はオブジェクトに対する参照です。作ったオブジェクトがメモリの波に飲み込まれれる前に、変数を通してオブジェクトへの参照を保持する、つまり繋がりを持たせることで、その後もオブジェクトを利用できるようにしているのです。

ℹ Note
例えば C++や Rust などのように、変数を値を格納する箱と表現できる言語も存在します。。これらの言語では変数はメモリ上の特定の位置を表します。(Rust のプリミティブ型はこの限りではありません)


前節で、オブジェクトが保存されているメモリアドレスへ直接アクセスする方法を紹介しました。

>>> import ctypes
>>> obj = 'Hello World'
>>> id(obj)
4379060208
>>> ctypes.cast(4379060208, ctypes.py_object).value
'Hello World'

上記の例では obj という変数を利用してオブジェクトへの参照を保持していますが、以下のようにオブジェクトへの参照が保持されず、オブジェクトへ到達不可能になると、オブジェクトはガベージコレクト(後述)され、メモリから消去されます。

解放されたオブジェクトのメモリを参照しても期待される出力は得られません。

>>> import ctypes
>>> id('Hello World')
4379060208
>>> ctypes.cast(4379060208, ctypes.py_object).value
b'e eej¡jFdS'

2 行目でリテラルを利用して'Hello World'という値をもつ文字列オブジェクトが生成されましたが、直前の例と異なり参照が保持されないため、以降このオブジェクトに対して操作することは不可能です。オブジェクトはガベージコレクトされ、メモリが解放されたため、元々の位置にあったデータは上書きされています。

ガベージコレクションと参照カウント

Python では、オブジェクトが不要になった時点で自動的にメモリを解放します。この仕組みをガベージコレクションと呼びます。

正確には、オブジェクトへ到達不可能になった時点で、そのオブジェクトはガベージコレクションの対象となります。

到達可能性の判断には、参照カウントという概念が利用されます。一般的に、オブジェクトへの参照が増えると参照カウントが増え、参照が減ると参照カウントが減ります。参照カウントの値が 0 になった時点で、そのオブジェクトはガベージコレクションの対象となるわけです。

sys.getrefcount 関数を利用すると、オブジェクトへの参照カウントを取得できます。

>>> import sys
>>> obj = 'Hello World'
>>> sys.getrefcount(obj)
2
>>> obj2 = obj
>>> sys.getrefcount(obj)
3
>>> obj2 = None
>>> sys.getrefcount(obj)
2

コード 4 行目にて、文字列オブジェクト'Hello World'に対して新たな参照 obj2 を作成したので、参照カウントが 1 増えたことが確認できます。またコード 6 行目で obj2 は別のオブジェクト None を参照するようになったため、オブジェクトへの参照カウントは 1 減ることを確認してください。

ℹ Note
最初の sys.getrefcount(obj)の返り値が 2 であったことに驚いた方も多いでしょう。これは、sys.getrefcount()関数の引数に渡したオブジェクトへの参照が関数内部で一時的に作成されるためです。

参照カウントが 0 になった時点で、オブジェクトはガベージコレクションの対象となります。実際にガベージコレクトされるタイミングは、Python インタプリタの実装に依存しますが、 weakref.finalize を利用すると、ガベージコレクト時に呼び出されるコールバック関数を登録できます。

>>> import weakref
>>> obj = {1, 2, 3}
>>> weakref.finalize(obj, lambda: print('garbage collected!'))
>>> obj = None
garbage collected!

3 行目で、集合オブジェクト3に対してガベージコレクト時のコールバック関数が登録されました。その後、4 行目で3への参照を失ったことでガベージコレクトされ、コールバック関数が実行されました。

また参照カウントのみが到達可能性の判定に使われるわけではなく、1 以上の参照カウントがあってもそれが循環参照であり、どのみち到達出来ない場合はガベージコレクトの対象になります。

>>> import weakref
>>> class A:
...     def __init__(self):
...         self.b = None
...
>>> class B:
...     def __init__(self):
...         self.a = None
...
>>> obj1 = A()
>>> obj2 = B()
>>> obj1.b = obj2
>>> obj2.a = obj1
>>> weakref.finalize(obj1, lambda: print('garbage collected!'))
>>> obj1 = obj2 = None
garbage collected!

obj1 = obj2 = None によって、変数 obj1obj2 が参照するそれぞれのオブジェクト A,B への参照がなくなり、参照カウントが 1 減ります。ですがまだ A オブジェクトの b 属性が B オブジェクトを参照しており、また B オブジェクトの a 属性が A オブジェクトを参照しているため、参照カウントは 0 になりません。

結果を見るに、参照カウントを残しながらもオブジェクトはガベージコレクトされました。これは、A オブジェクトと B オブジェクトが循環参照しているため、どちらも到達可能性がないと判断されたためです。

参考

Footnotes

  1. リテラルとは、組み込み型のオブジェクトを生成するための記法です。例えばクォーテーション''やダブルクォーテーション""で任意の unicode 文字を囲むと文字列オブジェクトが生成されます。 また 0 以外から始まる数字を並べると整数型オブジェクトが生成されます。

もっと読む

October 25th, 2023

Google Colaboratory入門

入門:Google Colaboratory を利用して Python のコードを実行する

Google アカウントの生成

既にアカウントを持っている人はこの手順を踏む必要はありません

Google アカウントを持っていない人は、こちらから自分用アカウントを作成してください

アカウントを持っているか分からない人も、同様に、上記のリンクに飛んでもらって、下の方にスクロールしてもらうと、アカウントの存在確認方法が載っています

Google Colaboratory へアクセス

まず、こちらのリンクにアクセスしてください

Chrome(Google 系のブラウザ)の場合

既に、Google アカウントにログインしている状態で、Chrome ブラウザにログインされている場合は、以下のような表示になると思います。(下のような画面にならない場合は、Chrome ブラウザ以外の方を参照してみてください)

こちらのような表示になっている場合は、右下の「New notebook」(日本語表記だと「ノートブックを新規作成」のような文章)をクリックすることで、下に示すような Python を書くためのスクリプトが作られます。また、一度操作したスクリプトは、上の写真の部分で参照できます。

それ以外のブラウザの場合

Chrome ブラウザ以外で初めて開く場合や、Google アカウントにログインしていない状態で Chrome ブラウザで開いた場合は、以下のような画面が出てくると思います。

こちらの画面が表示されたら、右上の「ログイン」ボタンを押してください。

すると、Google アカウントにログインする多面の画面が表示されるので、前のフェーズで作成したアカウント、もしくはすでに持っている Google アカウントの情報に従って、入力を進めてください。Google アカウントのログインが終わると、以下のような画面が出てくると思います。

こちらの画面にある、「ノートブックを新規作成」(英語版だと「New notebook」)を押していただくと、Python を実行するためのスクリプトが表示されます。

Colab 上でのコードの書き方と実行方法

以降、英語表記ですが、ボタンの場所等は日本語と英語で変わらないと思いますので、参考にしてください

コードの書き方と、新しいセルの追加方法

スクリプトを作成したときに、横長の何か(以降、セルと言います)が表示されていると思いますが、そこがコードを書くためのスペースです。このセルに、実行したい Python のコードを記述してください(特にない場合は、print(‘Hello’)と書いてください)

また、新しいセルを追加したい場合は画面左上の「+ Code」をクリックしてください

コードの実行方法

コードを書けたら、Shift キーと Enter キーを同時に押すと、既述したコードが実行され、また、新しいセルが追加されます

セルを新しく追加せずに実行したい時は Ctr キーと Enter キーを同時に押すと、セルを追加せずに実行できます

Appendix

一度実行したセルの結果は以降実行するセルの結果に影響する

一度、実行したセルは、セルの上下は関係なく、反映されます。例えば、以下の例では、上のセルで、x に 1 を代入してから、次に、別のセルで print(x)を実行すると、1 が出力されます

また、セルの上下関係を変えても、変数への代入を先に実行してから、print のセルを実行すると、同じ挙動を示します(この書き方は好ましく、エラーの元になり得るので、避けるようにするべきです)

実行の状態を初期化

どのセルも実行されていない状態に戻すためには、Ctr キーと M を同時に押した後、「、」を押すと実行可能です。クリック操作で行うには、Runtime をクリックし、Restart runtime をクリックすることで出来ます。どのセルを実行しているのか分からなくなったら、一度これを実行し、上から順番にセルを実行してみましょう

一度開いたことがあるファイルを、再度開く方法

  1. https://colab.research.google.com/ から開く方法 こちらは、Google Colaboratory から開く方法です。一番最初に開いた時の画面ですが、こちらに、依然開いたファイルが表示されるようになっています
  2. Google drive から開く方法 今回出て来たスクリプトたちが保存される場所は Google Drive になっています。正確には、マイドライブ配下にある、Colab Notebooks というディレクトリ(フォルダー)に保存されるようになっています。ここまでの手順を踏んだ人は恐らく、1 つ以上のファイルがそこにあると思いますが、スクリプトを作成したときに、左上に表示されるタイトルがファイル名に対応しています(下記の例だと、Untitled1.ipynb)

こちらのファイルを開くことで、Google colaboratory でスクリプトを操作することが可能です

質問等あれば直接 Twitter で DM ください

もっと読む

October 24th, 2023

ブログで利用可能なMarkdown記法

Availabel notation in MDX

Enjoy your markdown:)

level 1 heading

level 2 heading

level 3 heading

level 4 heading

level 5 heading
level 6 heading

Lists

Unordered list

  • item 1
  • item 2
  • item 3

Ordered list

  1. item 1
  2. item 2
  3. item 3

Links

Link to google

Images

Image of Random

Blockquote

This is a blockquote

This is a nested blockquote

Code

const foo = "bar";

Inline code

const foo = 'bar';

Horizontal rule


Table

SyntaxDescription
HeaderTitle
ParagraphText

Emphasis

Bold

bold text

Italic

italicized text

Bold and Italic

bold and italicized text

Strikethrough

The world is flat.

Task list

  • Write the press release
  • Update the website
  • Contact the media

Footnotes

Here's a simple footnote,1 and here's a longer one.2

Math

Inline math: $x^2$

Block math:

$$ x^2 $$

Footnotes

  1. This is the first footnote.

  2. Here's one with multiple paragraphs and code.

    Indent paragraphs to include them in the footnote.

    But seems to not be supported in MDX

もっと読む