# None ってなに?
None
には2つの使われ方があります。
1つ目は、未定義であること。
2つ目は、存在しないこと。
C 言語の NULL
や Java, JavaScript で言われる null
は Python では None
が該当します。
None (opens new window)
値の非存在を表すのに頻繁に用いられます。
# 1. 未定義を表す None
「未定義」という難しい言葉を使ってしまいましたが、 「いるのかいないのか わからない 」ということです。
- 関数の返り値 その1
- 関数の返り値 その2
- 関数の引数
# (1) 関数の返り値 その1
Wikipedia によるとモナコ公国には、首都が無いそうです。
ちなみに、このページのトップ画像はモナコ公国の写真です。
ここで国名をキーにして、首都名を値に持つ capitals
という辞書を作りました。
#
# 対話モード >>> に
# コピペで実行できます。
#
capitals = {
'日本': '東京',
'中国': '北京',
'アメリカ': 'ワシントンDC',
'フランス': 'パリ',
'ロシア': 'モスクワ',
# 'モナコ公国': ... <--- 首都が未定義の国
}
#
# 添字表記 subscription
#
print(capitals['日本'])
print(capitals['中国']))
print(capitals['アメリカ'])
print(capitals['フランス'])
print(capitals['ロシア'])
print(capitals['モナコ公国']) # <--- KeyError になります。
#
# get メソッド
#
print(capitals.get('日本'))
print(capitals.get('中国'))
print(capitals.get('アメリカ'))
print(capitals.get('フランス'))
print(capitals.get('ロシア'))
print(capitals.get('モナコ公国'))
添字表記 capitals['モナコ公国']
だと KeyError
で弾き返されます。
>>> print(capitals['ナウル共和国'])
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
KeyError: 'モナコ公国'
>>>
例外を返されてしまうと途中でプログラムがストップしてしまって使い物になりません。 try 文を使えば例外が起こってもプログラムを続行できる様にできますが、いまは置いておきます。
そこで get
メソッドを使います。
get
メソッドを使うと None
を返してくれます。
この None
は未定義を表す None
です。
「モナコ公国の首都は未定義ですよ」という、そういう意味です。
>>> print(capitals.get('モナコ公国'))
None
>>>
# (2) 関数の返り値 その2
処理をするだけで、値を返さない関数をつくることがあります。
そのような時には return
文を使うだけの関数または return
文を使わない関数を書くことがあります。
#
# 対話モード >>> に
# コピペで実行できます。
#
# return 文は書くけど、返す値は書かない
def f():
return
# return 文を書かない
def g():
pass # pass 文は後述します。
print(f())
print(g())
関数 f, g はともに None を返します。
>>> print(f())
None
>>> print(g())
None
>>>
ここで見たきたことは 「関数 f, g の返り値は未定義ですよ」ということです。
なんで要らないのにわざわざ None
を返しているかというと、
Python の関数は値を返さない作りになっているからです。
とりあえず None
を返しておくか、という流れになっています。
# (3) 関数の引数
関数の引数の規定値に None
が代入されていた場合、
その None
は未定義をあらわしています。
def f(x=None):
# x が未定義なら
if x is None:
return 0
else:
return x
f()
>>> f()
0
>>>
標準ライブラリ bisect には insort_left
という関数があります。
insort_left
関数は、ソートされたリストに、
ソートされた状態を保ったまま新しい要素を追加することができます。
#
# 対話モード >>> に
# コピペで実行できます。
#
from bisect import insort_left
lst = [3, 4, 7, 9, 11, 19]
insort_left(lst, 17)
lst
>>> lst
[3, 4, 7, 9, 11, 17, 19] # <--- 挿入後もソートされたまま
>>>
ここで insort_left
のソースコードを見てみます。
lo, hi というオプショナル引数を取ります。
hi の規定値は「未定義を表す None
」です。
def insort_left(a, x, lo=0, hi=None):
...
#
# bisect_left 関数を呼び出しています。
#
lo = bisect_left(a, x, lo, hi)
a.insert(lo, x)
def bisect_left(a, x, lo=0, hi=None):
...
#
# hi が未定義なら
#
if hi is None:
hi = len(a)
...
ここでは未定義の None
が使用されているところを示しました。
コードを見ると「ウワッ」って思いますが、原理はとても単純です。
詳細は以下のリンク先にまとめました。
ワクワクアカデミーの動画が大変わかりやすいです笑
また PEP 8 によると None
の比較には
==
ではなく is
を使ってくださいとのことです。
def bisect_left(a, x, lo=0, hi=None):
...
# NG
if hi == None:
hi = len(a)
...
def bisect_left(a, x, lo=0, hi=None):
...
# OK
if hi is None:
hi = len(a)
...
None
のようなシングルトンと比較をする場合は、 常にis
かis not
を使うべきです。 絶対に等値演算子を使わないでください。
Python コードのスタイルガイド - PEP 8 (opens new window)
なぜ ==
がいけないのかについては、以下の記事で確認します。
また、なぜ、未定義の引数に None
を使っているのでしょうか?
自分は、書籍 Effective Python
項目 20 「動的なデフォルト引数を指定するときには
None とドキュメンテーション文字列を使う」で知りました。
# ◯ まとめ
ここまで以下のように見てきました。
- 関数の返り値 その1
- 関数の返り値 その2
- 関数の引数
WARNING
ここから先はクラス定義文 class を使ったことがある方向けの文章です。 無ければ読まなくても大丈夫です。
# 3. 存在しないことを表す None
class
を使ったユーザ定義クラスを代入する変数に None
が
代入された場合、その None はオブジェクトが存在しないことを表すために
None が使われています。
class Boy:
def __init__(self):
self.girl_friend = None # 彼女は存在しない
class Girl:
def __init__(self):
self.boy_friend = None
boy = BoyFriend()
girl = GirlFriend()
if not boy.girl_friend:
print('彼女はいません。')
boy.girl_friend = girl
girl.boy_friend = boy
if boy.girl_friend:
print('彼女はいます。')
# ◯ 違いはあるの?
「未定義を表す None」と「存在しないことを表す None」との違いはなんでしょうか? 両方ともたんに「無い」ことを表しているだけでは無いでしょうか?
上の例では彼女は未定義ではないのです、「存在しない」のです。 「未定義」だったら存在するかもしれないみたいな意味合いがあります。 厳密な違いがあるわけではなく、あまり深くとらえず、 キチガイがなんかのたまいてるなという温かい目で流し読みしてください。
意味合いとしては、
「未定義を表す None」は、あまり参照して欲しく無い場合が多いです。
参照して欲しくないとは、
参照されたら例外を投げてほしい、例外を raise
して欲しい
ということです。
# (1) 関数の返り値 その1
辞書の例はどうでしょうか。
それを get メソッドを使って例外が発生していませんが、
本体、添字表記 capitals['モナコ公国']
で参照した場合は、
KeyError
が返されます。
# (2) 関数の引数 その2
返り値を返さない関数が返す None
は、どうでしょうか。
例えば、いま返り値のない関数を作られたとします。
その作られた関数を使ってキチガイのワイが、クライアント側で
返り値の None
を使って何かを計算させようとしたら、どうでしょうか?
そんなことはさせたくないですよね。
やっぱり本来は例外で弾き返して欲しいところです。
Python の関数は、必ず返り値を返さないといけない仕様になっています。
本来はなにも返したくはないのですが、仕方がないので None
を返しています。
# (3) 関数の引数
これについては次章で見ていきます。
if hi is None
で参照されているのではないでしょうか?
def bisect_right(a, x, lo=0, hi=None):
...
if hi is None: # hi が未定義なら
hi = len(a)
...
これはちょっと苦しいのですが、
未定義を表す None
は、普通には参照して欲しくありません。
なので比較演算子 is
を使って外します。
詳細は次章で見ていきます。
# 3. 未定義を表す None が起こす問題
# (1) 空文字 ""
と None
の違いってなに?
teratail で見つけたのですが、この質問、実はナイスでグレートな質問です。 なぜなら「null 安全」の理解に繋がるからです。
空文字 ""
と None
は、両方とも同じ「無い」ことを表しています。
では、空文字 ""
と None
の違いはなんでしょうか?2つあります。
1つ目は、使い方として""
は空文字を None
は未定義であることを表すのに使います。
2つ目は、""
には str
という型の情報がわかるのに対して、None
には型の情報がありません。
自分が書いた関数で未定義を表すために None
を返していまうと
ちょっとした注意が必要になります。
# (2) 問題
変数に代入されたオブジェクトが「無い」ことを表すとき
if 文はそのオブジェクトを Flase
と評価します。
i = 0
if not i:
i = i + 1
print(i) # 2
lst = []
if not lst:
lst.append('apple') # ['apple']
ここで問題なのが「未定義」を表すために
None
を使われると、if 文が上手く動作しなくなることがあることです。
i = None # 未定義
if not i:
i = i + 1
print(i)
...
Traceback (most recent call last):
File "<stdin>", line 2, in <module>
TypeError: unsupported operand type(s) for +: 'NoneType' and 'int'
>>>
lst = None # 未定義
if not lst:
lst.append('apple')
...
Traceback (most recent call last):
File "<stdin>", line 2, in <module>
AttributeError: 'NoneType' object has no attribute 'append'
>>>
# (3) 解決策 - if 文と is 比較演算子
未定義を表す None
を外すには比較演算子 is
を使います。
i = None # 未定義
if i is None:
i = 0
if not a:
i = i + 1
print(i) # 2
lst = None # 未定義
if lst is None:
lst = []
if not lst:
lst.append('apple') # ['apple']
# ◯ まとめ - PEP 8
「未定義を表す None」 がはいるかもしれない変数 x
には if x
を使うことができません。
以下 PEP 8 の文章を引用します。
また if x is not None と書くことを意図して if x と書かないように気をつけてください。 -- 例えば、デフォルトとして None が設定されている変数または引数に、別の値が設定されているか確認するときです。 その別の値というのは、ブール演算においては偽と判定される可能性のあるコンテナ型をもつ値かもしれません。
Also, beware of writing if x when you really mean if x is not None -- e.g. when testing whether a variable or argument that defaults to None was set to some other value. The other value might have a type (such as a container) that could be false in a boolean context!プログラミングに関する推奨事項 (opens new window)
Programming Recommendations - PEP 8
WARNING
ここからは、重箱の隅をつつく話をします。
全然知らなくても大丈夫です。
他言語である JavaScript には未定義を表す undefined
と存在しないことを表す null
があるので、
これを見ていきたいと思います。
そこから Kotlin の「null 安全」について、考えていきたいと思います。
# 4. null と undefined
「無いことを表す None」と「未定義であることを表す None」の違いを明確にするために、 すこし視点を変えて JavaScript に着目して見たいと思います。
JavaScript では値が存在しないことを表すために null
があります。
null という値は、オブジェクトの値が存在しないことを表します。
null - MDN web docs (opens new window)
JavaScript では未定義の変数や属性も、参照することができます。
このとき undefined
が代入されます。
undefined
は文字通り未定義の値を意味しています。
まだ値が代入されていない変数は undefined 型となります。
undefined - MDN web docs (opens new window)
# (1) JavaScript のコードの実行の仕方
Chrome などのブラウザには、デバッガツールがついています。 簡単にコピペで実行できる環境を手に入れられます。
# Step 1. Chrome を開きます。
# Step 2. Chrome のデベロッパツールを開きます
Windows の場合は Cmd
+ Shift
+ j
で開きます。
macOS の場合は Cmd
+ Option
+ j
で開きます。
# Step 3. Console タブを選択します。
# (2) undefined の意味は?
関数の未定義の引数
function f(x){
console.log(x)
}
f()
> f()
undefined <--- 関数定義文の返り値(関数 f が定義された時に返されています。)
undefined <--- return のない関数 f の返り値
>
return 文のない関数の返り値
function f(){
}
f()
> f()
undefined
>
未定義のプロパティを参照する。
object = {}
object.property
> obj.property
undefined
>
# (3) undefined と null の違いは?
null
と undefined
はとてもよく似ています。
そのため null
と undefined
の違いを理解している人はそんなに多くないそうです。
自分も上で2種類の None
の使い方が... とか言っていたのですが、
正直どう使い分けていいのか見当がつきません。
Intent to stop using null in my JS code - sindresorhus/meta (opens new window)
みんな null と undefined を一貫性や互換性なく使っている。
Everyone uses null and undefined inconsistently and interchangeably.誰もいつどちらを使うべきか知らない。
Nobody really knows when to use which.
この記事は以下のツイートから知ることができました。 ありがとうございます。
# (4) undefined のデメリット
「静かに失敗する」というのは例外が投げられず undefined
が代入されて、
そのまま失敗した箇所を通り過ぎてしまうことです。
JavaScript では、デバッグするときに、これが地味に面倒です。
Python でも None を返されると辛いです。 以下の teratail の質問は、None が返されてハマっていた方の質問です。 「試したこと」の一覧を見てください。めちゃくちゃハマっているのがわかります。 僕も同じことでハマっていました。
# (5) undefined の起源は?
本来、例外を投げ返せば undefined
は不要なはずです。
で、なぜこんなややこしいものができてしまったかというと、
当初、JavaScript に例外がなかったからだそうです。
本来、例外を投げたかったけど、仕方なく undefined
を返すようにしたそうです。
最初のバージョンの JavaScript は例外機構を持っていませんでした。 これがなぜ暗黙的に変換し、例外が投げられることなく静かに失敗するか の理由です。
The first version of JavaScript did not have exception handling, which is why JavaScript so often converts automatically [2] and/or fails silently (tweet).
JavaScript history: undefined (opens new window)
そのまま null
を代入すればいいのかなと思ったのですが、
null
だと 0
に変換されてしまうため、新しく作ったそうです。
null
は 0
ではないけど...
> null == 0
false
> null === 0
false
>
0
であるかのように計算できてしまう
> null + 1
1
> null * 1
0
>
ちなみに undefined
で計算すると NaN
が返されます。
> undefined + 1
NaN
>
Java には null がありますが参照型に対してだけです。 動的型付けの JavaScript では、 初期化されていない値は参照型でないか、 あるいは 0 に変換されないものでなければなりませんでした。
Java has null but only for reference types. With untyped JS, the uninitialized value should not be reference-y or convert to 0.
BrendanEich@5 May 2013 (opens new window)
なるほど、それならゼロ除算をすると例外ではなく
undefeind
を返すんやろ(´・ω・`) と思いました?
> // Infinity が返されます笑
> 1 / 0
Infinity
>
数学的にはゼロ除算は未定義なので、個人的には undefined
を使って欲しかったです。
ここで見てきた4つの値は、標準オブジェクトの値プロパティです。 全て網羅してしまいました笑
- Infinity
- NaN
- undefined
- null
# 5. null 安全
None
を返さないと思っていた関数あるいはメソッドが、
ひっそりと None
を返してくると面倒です。
開発している時に気付ければいいですが、
開発している時に気付けず、運用する時になってバグが発生すると悲惨です。
# ◯ Java
Optional がなかった頃は Java の記事になってしまいますが、これら記事を読むと雰囲気がわかりやすかったです。
NullPointerException
が発生した時に「開発者の注意不足で発生した。 しっかりチェックして再発防止に努める。」などと言わされたり(やりもしないのに)、 責める要因に使われるのを見ると虫酸が走る。
nullが嫌い - 日々常々 (opens new window)
多くのJavaプログラマーが血と汗と涙とともに、Null参照を回避する方法を身体で学習することになります。
null安全とはJavaプログラマが血と汗と涙を流さなくてすむ理由 (opens new window)
ちなみに null チェックとは以下のよう、
値を代入したあと if(str1 != null)
みたいなコードを書いて、
null が入っているか入っていないかチェックするようなコードを指すようです。
String str1 = "hoge";
if (str1 != null) {
System.out.println(str1);
}
Java 8 になると Optional が使えるようになり、 以下のようなコードが書けるようになりました。
Optional<String> str2 = Optional.ofNullable("hoge");
str2.ifPresent(x -> System.out.println(x));
# ◯ Kotlin
最近、生まれた言語では未定義を表す null がはいるかもしれない変数と、はいらない変数を分けているようです。 以下は Kotlin の例です。
//
// 誤って null を代入しても
// コンパイラが弾いてくれます。
//
fun main(){
var s: String = "abc"
// null を代入しようとすると
// "コンパイルした段階で" 弾かれます。
// Null can not be a value of non-null type string
s = null
println(s.length)
}
//
// null を代入するには ? を使います。
//
fun main(){
var string: String? = 'Hello, wolrd!'
string = func()
// !! は null じゃないよと宣言しています。
println(string!!.length)
// !! がない場合...
// println(string.length)
//
// コンパイルで弾かれてしまいます。
// Only safe (?.) or non-null asserted (!!.) calls
// are allowed on a nullable receiver of type String?
}
fun func(): String? {
// ^^^^^^^
// null を返すかもしれない関数という宣言
return "你好,世界!"
}
上記のコードは、以下のサイトでコピペで動作確認ができます。 上段のコードは、コンパイルエラーで弾かれます。
上記のコードは、以下のページからコードをお借りしました。
?
と !!
の挙動は以下の記事に詳しいです。
null がはいるかもしれないバグを事前にコンパイラで弾くことを 「null 安全」と表現されているのを見かけます。 「null 安全」という言葉は、もともとは Kotlin 由来らしいです。
null 安全 (または null 安全性 )は null safety の訳語です。 null safety という用語は主に Kotlin コミュニティで使われているもので、 言語によって
Optional
やOption
,Maybe
, nullable type などの型で実現できる、 null が原因で実行時エラーを起こさない性質のことを表します。 そのような性質を表す言葉として null 安全 はぴったりだと思うので、 本投稿では null 安全 という呼び方を採用します。
null安全でない言語は、もはやレガシー言語だ - Qiita (opens new window)
# ◯ Python
しかし、これは静的型付け言語での対策で。 動的型付け言語である Python にはこのような解決策を取れません。 もし Python で null 安全を実現しようと思ったら、 次のように引数チェックを行ったりしていかないといけません。
def f(x):
# x は None を許容しない
# 例外を明示的に投げる
if x is None:
raise ValueError('x is not defined.')
...
def g(y):
# y は None を許容する
# 初期値を与える
if y is None:
y = 0
...
これは明確に動的言語の欠点だと思います。
静的言語の場合、型を宣言すればいいだけです。
例えば Kotlin では ?
などをつけるだけで済みます。
もし、バグがあても実行する前にわかります。
しかし、動的言語の場合、型を判別するための長いコードを書く必要があります。
動的言語しか使ったことのない人のコメントが面白い。 「テストで発見できるから大丈夫」という発想は 「残業や休日出勤でカバーできるから大丈夫」と同じ。 属人性に頼らざるをえない動的言語はもはやブラック企業。
megmin1 氏のコメント (opens new window)
Effective Python の項目 20 は、さらっと書かれていますが、 実際には null 安全について述べられていて、結構、重い内容だったという訳です。
こうして型を明示して見るとわかるのは null 安全とは、 ある変数に想定していたのとこは異なる型がはいってしまう問題です。 初期の Java などの静的型付け言語でさえ、見落としていました。
null すなわち Python では言えば None を、 事前に型を明示して宣言したどの変数にでも、 null 代入できるようにしてしまったのは、 型の情報が欠落してしまうので、あまり良い設計ではなかったりもしたのかなと思います。
しかし、リンク先の記事の人が言ってくれる通り null がない世界なんて考えられないですし、 Optional を明示できるようにすれば良いというだけの話ではありますが。
null の問題はプログラマの責任ですが、 それでも考案者 アントニー・ホーア (opens new window) 自身が言ってくれるなら、 過去の過ちも、仕方ないものかと、ちょっと安心してしまいます。
# 6. まとめ
ここまで以下のような流れで見てきました。
以下の3つがポイントかなと思います。
- 値が存在しないことを表す None と未定義を表すために使われている None を区別する。
- 値が存在しないことを確認するためには if not obj 値が未定義であることを確認するには if obj is None を使う。
- if obj: という書き方は、変数 obj に None が代入されないと分かっている時にしか使えない。
値が未定義であることを表すために None
を使うことを見てきました。
0
も無いことを表しますが、
None
を使った場合は型が失われて、色々と問題を引き起こすことについて考えてきました。
なんだか前にはよくわかんなかった神ってやつの存在も 近頃はなんとなく解る気がしてきたんだ。 もしかしたらだけどさ、数字のゼロに似た概念なんじゃないかなって。 要するに体系を体系足らしめるために要請される意味の不在を否定する記号なんだよ。 そのアナログなのが神で、デジタルなのがゼロ。どうかな? でね、僕たちって基本的な構造がデジタルなわけじゃない。 だから僕たちがいくら情報を集積していっても今のところゴーストは宿らない。 でも基本がアナログベースなバトーさんたちは 電脳化したり義体化してデジタルな要素を増やしていっても ゴーストが損なわれることはない。 しかもゴーストがあるから死ぬこともできる。 いいよねぇ。ねぇねぇ、ゴーストがあるってどんな気分?
攻殻機動隊 第15話 機械たちの時間 MACHINES DESIRANTES