SE(たぶん)の雑感記

一応SEやっている筆者の思ったことを書き連ねます。会計学もやってたので、両方を生かした記事を書きたいと考えています。 でもテーマが定まってない感がすごい。

C#erがPythonを勉強したので、違いについて比較しながら述べてみる その3 型とか

はい。その2を書いたのが250日前、という感じですが、唐突にその3が始まります。

hiroronn.hatenablog.jp

hiroronn.hatenablog.jp

Twitterで、

Pythonの型について、C#等との比較で知りたい

というお話があったので、経験した中で書いていきます。
間違っていたらごめんなさい。

メモリ管理

メモリ管理 — Python 3.6.4 ドキュメント

C#、というか.NETでは、メモリをヒープ、スタック、静的領域と分けます。
が、PythonではPythonの領域としてはヒープを使う、とのことです。
ただ、CythonならC言語に、Iron Pythonなら.NETに依存する形で、メモリ管理はなされるように感じています。

まあ、普通に使う分には気にしなくて良いことかと思います。

詳細は、リンクをご覧いただきたいところです。

数値型、参照型

これらの型ですが、C#と同じく、objectを継承します。
Javaのように、「基本データ型」扱いとなったりもしません。
普通に、intがメソッドを持っていたりします。

ただ、数値型、文字型、タプルは、値変更不可となり、特殊なオブジェクトな扱いは受けます。

数値型

4. 組み込み型 — Python 3.6.4 ドキュメント

Pythonの数値と呼ばれるものですが…

型(省略形) 意味
int 整数
float 浮動小数点数
complex 複素数

3つしかありません。

C#だと、整数だけ*1でも

型(省略形) 意味
byte 8ビット整数
short 16ビット整数
int 32ビット整数
long 64ビット整数

のように、4つもあります。

これは、どこに書いてあったか忘れたのですが、理由を意訳すると

CPU都合で数値型を分けるのって、プログラマー的には面倒じゃね?

という感じだったと思います。

なお、intの最大値は以下の文で取得できます。

import sys
print(sys.maxsize)

私の環境(Windows10 64bit)だと、

9223372036854775807*2

となります。

また、10進数を扱うためにdecimalというモジュールがあるため、金額計算等を行う場合は、そちらを使うべきかと思われます。ここはC#等でも一緒です。

9.4. decimal — 十進固定及び浮動小数点数の算術演算 — Python 3.6.4 ドキュメント

型の解釈

さて、静的型付き言語であるC#と、動的型付き言語であるPythonの、最も違うと思われる部分です。

まず、一般的には、

種類 意味
静的型付き言語 コンパイル時に型が決定される
動的型付き言語 実行時に型が決定される

という違いが、両者にはあります。

C#でいう

public int Plus(int x, int y) {
    return x + y;
}

は、Python

def plus(x, y):
    return x + y

と同じに見えます。

しかし、Pythonでは、

plus(2, 3) # 結果は「5」
plus("a", "b")  #結果は「"ab"」

のように、処理ができれば型は関係ないです。

今は加算演算を例*3に出しましたが、メソッド呼び出しでも同様です。

型推論

C#でいうところのvarを、Pythonではどう扱うのか、です。

C#型推論*4は、あくまで「型をわざわざ指定しなくて良い」だけの機能で、結局はコンパイル時に型は決まります。
そのため、Visual Studioの型ヘルプを出すと、型が推論できない場合*5を除いて、型名が表示されます。

f:id:hiroronn:20180328235041p:plain

これは、メソッドの結果が、どういう型で返ってくるか決まっているため、当然の帰結です。

Pythonは、あるメソッドが複数の型を返す可能性があります*6
有名なところでは、pandasで列指定で返す際、1列ならSeries、複数列ならDataFrameが返ってくるという動きが良い例です。

import pandas as pd
import numpy as np

# 元になるDataFrame
df = pd.DataFrame(np.random.random([100, 3]), columns=['foo', 'bar', 'baz'])

one = df["foo"]
two = df[["foo", "bar"]]

print(type(one))
print(type(two))

実行結果は、

f:id:hiroronn:20180329001344p:plain

のようになります。

まあ、上のような定義はC#でも可能*7ですが、Pythonオーバーロードができないので、引数の型可変、戻り値の型可変という定義になります。

Pythonの型アノテーション

Python3.5から、型アノテーションという機能が追加されました。

def plus(x: int, y: int) -> int:
    return x + y

Pythonでも、上のように書くことで、型ヒントを出せます。
しかし、動作時は無視されるため、別にstr型であっても呼び出せます。

まあ、Python自体は動的言語で、その挙動は邪魔しないけど、利用者には型を意識させたい、という場合に使えます。

使ってみて思うこと

C#みたいに、静的な「比較的堅い」言語を使った後に、Pythonのように「柔らかい言語」を使うと、特にメソッドの戻り値の型が分からない場合に、混乱するように思います。
pandasを使っていて、そこが一番混乱しました。

しかし、逆に言えば、そこまで型を意識しなくても作れるということでもあります。

利点となるか欠点となるかは…用途と状況次第ですね。

なお、動的であるが故に、単体テストの重要性は非常に高いといえます。

おわりに

継承とか、ディスクリプタとか絡めて、Pythonの面白い型機能を述べるのもよいと思っていますが、そのあたりはまとめるのも(私からしたら)難しい印象です。
インターフェイスに近い用法などもあるので、そのような部分も、また別の記事で述べたいと思います。

*1:uintのように、正の値しか取らないものは省略

*2:922京3372兆0368億5477万5807

*3:Pythonは、演算子オーバーロードをサポートしており、結果的にメソッド呼び出しとして解釈される

*4:Java10でも搭載されたそうな

*5:その場合はコンパイルエラーとなる

*6:そもそも、何の型で返すのか、決める必要が無い

*7:オーバーロードを使う