Python における値ってなに?




オブジェクトの集まりで表現されるもの










f:id:domodomodomo:20171106122644j:plain




公式マニュアル

実は公式マニュアルでは「値」という言葉が頻繁に使われています。しかし、実際にその「値」がなんであるのかを説明している箇所はありません。この値という概念は何者でしょうか?

オブジェクトのPython ではやや抽象的な概念です: 例えば、オブジェクトの値にアクセスする正統な方法はありません。 また、その全てのデータ属性から構成されるなどの特定の方法で、オブジェクトの値を構築する必要性もありません。

比較演算子は、オブジェクトの値とは何かについての特定の概念を実装しています。 この比較の実装によって、間接的にオブジェクトの値を定義している考えることもできます。
6.10.1. 値の比較

list.remove(x)
リスト中で、 x を持つ最初の要素を削除します。
該当する項目がなければエラーとなります。

従って immutable であること (immutability) は、厳密に言えば "が変更できないこと" と同義ではなく、もう少し複雑です。
So, immutability is not strictly the same as having an unchangeable value, it is more subtle.
3.1. Objects, values and types

None
None は、関数にデフォルト引数が渡されなかったときなどに、 の非存在を表すのに頻繁に用いられます。
3. 組み込み定数 - Python 標準ライブラリ

引数は 値渡し (call by value) で関数に渡されることになります(ここでの 値 (value) とは常にオブジェクトへの参照(reference) をいい、オブジェクトの そのものではありません) 。
4.6. 関数を定義する - Python チュートリアル

すべてのオブジェクトは、同一性 (identity)、型、をもっています。 3. データモデル — Python 3.6.5 ドキュメント


具体例


例 1. int, float クラス

11.0 というオブジェクトは 1 という値を持っています。

>>> 1 == 1.0
True
>>>

すべてのオブジェクトは、同一性 (identity)、型、をもっています。 3. データモデル — Python 3.6.5 ドキュメント

比較演算子は、オブジェクトの値とは何かについての特定の概念を実装しています。 この比較の実装によって、間接的にオブジェクトの値を定義していると考えることもできます。
6.10.1. 値の比較


例えば、1 の値と 1.0 の値は、等しいです。

>>> 1 == 1.0
True
>>>


でも、11.0 は、型が異なりますし、

>>> type(1) == type(1.0)
False
>>> 


それに、11.0 は、属性も異なります。

>>> set(dir(1)) == set(dir(1.0))
False
>>> 


11.0 も、 オブジェクトの集まりです。

>>> # 1 オブジェクト
>>> a = 1
>>>
>>> # a の属性を表示
>>> for e in dir(a): e
... 
'__abs__'
'... 略 ...'
'__xor__'
'bit_length'
'conjugate'
'denominator'
'from_bytes'
'imag'
'numerator'
'real'
'to_bytes'
>>>
>>> # 1 オブジェクト
>>> a = 1
>>>
>>> # 実部
>>> a.real
1
>>> # 虚部
>>> a.imag
0
>>>
>>> # 1.0 オブジェクト
>>> b = 1.0
>>>
>>> for e in dir(b): e
... 
'__abs__'
'... 略 ...'
'__trunc__'
'as_integer_ratio'
'conjugate'
'fromhex'
'hex'
'imag'
'is_integer'
'real'
>>>
>>> # 1.0 オブジェクト
>>> b = 1.0
>>>
>>> # 実部
>>> b.real
1.0
>>> # 虚部
>>> b.imag
0.0
>>>


属性に保持しているオブジェクトは異なりますが、 それぞれのオブジェクトが集まって 1 という値を表現しています。 11.0 というオブジェクトは 1 という値を持っています。

値 (情報工学) - Wikipedia
値は、何らかの式を評価した結果である。式はデータ型を持ち、評価結果は内部的にはビット列になる。データ型が異なれば、同じビット列が異なる値(意味)を持つこともある。例えばあるビット列は整数、浮動小数点数または文字列として解釈されることがある。(ワイの注釈: Python の 1 と 1.0 の例では、ビット列が異なりますが、型が異なれば、同じ値として解釈されることもある例を示しました。)


ここで大事なのはオブジェクトそのものが値を持っているということです。 公式のドキュメントでも "object" を "属性" と誤訳してしまっています(2018/08/15 現在)。 もちろん属性が値を持っていると考えることもできますが、ここでの訳そのものは間違ったものになってしまっています。

Every object has an identity, a type and a value.
3. Data model - The Python Language Reference


自分も最初、これが object ではなくて attribute だったら訳しやすかったのに、この object が値を持つって何だ... と悩みました。誰かが間違えたことを明記することは、よくないことですが、それでも、値という概念は、公式サイトを和訳してる人でさえ、誤訳してしまうくらい、すこし難しい概念ということです。

すべての 属性 は、同一性 (identity)、型、値をもっています。
3. データモデル - Python 言語リファレンス


例 2. Person クラス

また、オブジェクトは属性に値を持っていると考えることもできます。 例えば「人」をクラスにすることを考えます。「人」には属性として「名前」、「年齢」、「性別」があります。

変数 person に束縛されたオブジェクトは、"Gaius Iulius Caesar", 30, False という 3 つの値を持っていると言えます。

class Person(object):
    def __init__(self, name, age, sex):
        self.name = name
        self.age = age
        self.sex = sex

person = Person("Gaius Iulius Caesar", 30, False)


「オブジェクトそのものが1つの値を持っている。」とも「オブジェクトが属性に複数の値を持っている。」とも捉えることもできます。

他のオブジェクトに対する参照をもつオブジェクトもあります; これらは コンテナ (container) と呼ばれます。コンテナオブジェクトの例として、タプル、リスト、および辞書が挙げられます。オブジェクトへの参照自体がコンテナの値の一部です。ほとんどの場合、コンテナの値というと、コンテナに入っているオブジェクトの値のことを指し、それらオブジェクトのアイデンティティではありません;
3.1. オブジェクト、値、および型


例 3. Computer クラス

他にも自分で定義したクラスについて考えてみます。 例えば Computer はCPU, メモリ, SSD と言った部品の集まり、値で構成されています。

メモリはメモリという値として、CPU は CPU という値として、SSDSSD という値として認識することができます。

class Computer(object):
    def __init__(self, cpu,
                 primary_memory, auxiliary_memory):
        self.cpu = cpu
        self.primary_memory = primary_memory
        self.auxiliary_memory = auxiliary_memory


class Cpu(object):
    def __init__(self, clock, core):
        self.clock = clock
        self.core = core


class Memory(object):
    def __init__(self, volume, clock, type_):
        self.volume = volume
        self.clock = clock
        self.type_ = type_


class Ssd(object):
    def __init__(self, volume):
        self.volume = volume


imac = Computer(
    Cpu('3.4GHz', 7),
    Memory('8GB', '2400MHz', 'DDR4'),
    Ssd('1TB'))



◯ オブジェクトと値の関係

オブジェクトは複数の属性を持っていて、その各属性の中に値が1つはいっています。オブジェクトと値の関係は、このように再帰的に定義されていると考えることができます。

object -*-> attribute -1-> value(object)

◯ オブジェクトと値の違い

オブジェクトと値は、おそらく概念的に違います。オブジェクトは、属性を持っています。値は、属性参照せずに取り扱う概念だと思います。

例えば 1 について、考えて見てください。1, 1.0 は、四則演算子や比較演算子を通して、表面的には属性参照することなく扱えるものですし、そもそも属性を意識することさえありません。

最初、なぜこのような値という言葉をわざわざ使っているのか理解できませんでした。正直言っていまもわかっていませんが、確かに 1, "Hello, world!" と言ったリテラルを表現するときには、値と言った言葉があった方がわかりやすいのかなと思ってりもしました。

また 1, "Hello, world!" などのリテラルではなく Computer クラスについても、我々はパソコンを見る時に Memory, CPU, SSD で構成されたものだとは認識しません。1つの Computer という値として認識しています。

このように捉えることは、オブジェクト指向で「メッセージ」を意識してプログラミングをする時に、いくらか役に立つのではないかと考えています。1, "Hello, world!" と言ったリテラルと同じように属性を意識しないで取り扱えるようになり、より自然に近い形でコーディングしやすくなるのではないかなと、感じています。

オブジェクト指向は、基本的に「現実世界をモデリングして、そのモデルのコードを書いたらプログラムができる」ということを目指して作られたものです。(実際にシステムを作るとモデルと現実世界は必ずしも一緒じゃありませんが)ですので、オブジェクト指向を考えたお偉いさんが、現実の世界でおこっているある状態をモデル化するときに「仕事をお願いする」というようなことを「メッセージ」という名前をつけました。
メッセージ脳の作り方 - オブジェクト脳オンライン

もう一度言う。オブジェクトはデータとそれを操作する関数をセットにしたものではない。 オブジェクトはデータエンティティではない。では何か? ... 真のオブジェクト指向の世界では、オブジェクトを生きた有機体のように扱い、 オブジェクトには生まれた日と死ぬ瞬間がある。

また、君が望むなら、アイデンティティや性質を持たせてもいい。 犬にはデータの一部(例えば体重)をくれるよう頼むことができるし、犬はその情報を返してもよい。 ただ、この犬は能動的なコンポーネントであることを忘れてはいけない。 こちらの要求に対し、何をするかは犬が決めるのだ。
Getter/Setterは悪だ。以上。 - To Be Decided


オブジェクトが値を保存する場所

1つのオブジェクトには、いくつもの値がくっついています。あるオブジェクトに値をくっつけるには、次の3通りの方法があります。

# 1. オブジェクトの属性 attribute に代入された値
object.attribute = value

# 2. シーケンス sequence の要素 element
squence[index] = element

# 3.  マッピング mapping の項目 item
mapping[key] = item

1. 属性 attribute に代入されたインスタンスオブジェクト

前述の通り。

2. シーケンス sequence の要素 element

例えばリストのように s[0] という記述でインスタンスオブジェクトを参照できるオブジェクトをシーケンスと読んでいます。 

s[0] は s の i 番目の要素です。s の属性とは違いそうですが、なんとなくこれが、値と絡んでいそうな気がします


s[i]s の 0 から数えて i 番目の要素
4.6.1. 共通のシーケンス演算
>>> # Hello, world! オブジェクト
>>> s = 'Hello, world! '
>>> 
>>> # Hello, world! オブジェクトの値
>>> s[0]
'W'
>>>


要素は属性とは違うやろ。って声が聞こえてきそうですが。シーケンスの要素を使って == を実装したりもできるので、値を構成しうるものとして考えていいのかなと。ちなみに str には callable でない属性、メソッドでない属性が __doc__ しかありません。

>>> for e in dir(s):
...   if not callable(getattr(s, e)):
...     e
... 
'__doc__'
>>>

3. マッピング mapping の要素 element

例えば辞書 dict のように d['key'] という記述でインスタンスオブジェクトを 参照できるオブジェクトをマッピングと読んでいます。 

リテラルはどこにあるんだろう?

答え: immutable なオブジェクトがリテラルだと思われます

再帰的に定義されてるなら、 1 とか 'a' のような実際のデータ、リテラルは、どこに保存されてるのかな?と思いました。

何故なら、属性と値の関係は再帰的に定義されてるなら、どんなに属性を参照し続けても実際のデータ, リテラルが保存されているところに行き着くことができないからです。自己参照してるところを終端として捉えて、リテラルを保存している場所として取り扱っているのかなと思ったりもします。

例えば int の 1 は自分自身 1 が属性に代入されています。

>>> a = 1
>>> 
>>> a.real
1
>>> a.real.real
1
>>>
>>> id(a.real)
4300950496
>>> id(a.real.real)
4300950496
>>> 


str の 'W' には自分自身 'W' が 0 番目の要素に代入されています。

>>> s = 'Welcome to ようこそジャパリパーク! 今日もドッタンバッタン大騒ぎ うー!がぉー! 高らかに笑い笑えば フレンズ喧嘩して すっちゃかめっちゃかしても仲良し けものは居ても のけものは居ない本当の愛はここにあるほら 君も手をつないで大冒険 '
>>> 
>>> s[0]
'W'
>>> s[0][0]
'W'
>>> 
>>> id(s[0])
4473881520
>>> id(s[0][0])
4473881520
>>>


そして、なぜ immutable なオブジェクトが実際のデータを表現していると思ったのかと言うと、immutable なオブジェクトには __dict__ 属性がないからです。

mutable なオブジェクトは Python は x.a と参照された時、内部で x.__dict__['a'] に変換して値を参照しています。

>>> class X:
...   def __init__(self):
...     self.a = 1
... 
>>> 
>>> 
>>> x = X()
>>> x.a
1
>>> x.__dict__['a']
1
>>> 


しかし、immutable なオブジェクトには __dict__ 属性が存在しません。おそらく __dict__ を使わずに属性を固定して(immutable にして)、リテラルを表現しているものと思われます。

>>> a = 100
>>> a.__dict__
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
AttributeError: 'int' object has no attribute '__dict__'
>>>
>>> b = range(10)
>>> b.__dict__
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
AttributeError: 'range' object has no attribute '__dict__'
>>>

◯ str クラスのオブジェクトの構造

Python のテキストデータは str オブジェクト、
すなわち 文字列 として扱われます。
文字列は Unicode コードポイント
イミュータブルなシーケンスです。
4.7. テキストシーケンス型 — str

文字集合では、個々の文字に対して、
文字集合内での符号位置が決められている。
これをコードポイントという。
文字コード考え方から理解する Unicode と UTF-8 の違い


まとめ




値はオブジェクトの集まりで表現されるもの




なんとなく値とは何かについて伝わったでしょうか。マニュアルの解説記事から、値とは何かについて理解を深めて見てください。