はじめに
こんにちは!普段はC#
やTypeScript
を使って開発しているエンジニアです。最近、新しい技術スタックに触れる機会があり、Python
の基礎を学んでみました。静的型付け言語に慣れている身からすると、Python
の文法や考え方には多くの驚きがありました。
この記事では、私と同じようにC#
やTypeScript
をバックグラウンドに持つエンジニアや、これからPython
を学ぼうとしている方に向けて、Python
の基礎学習で特に「おっ!」と思った点、C#
やTypeScript
との違いを感じた点を共有したいと思います。Python
のユニークな特徴や面白さが伝われば幸いです。
1. 集合(set)とタプル(tuple)が新しい!
1.1. 集合(set): 数学的な操作が直感的!
Pythonには「集合(set)」という組み込みのデータ型があります。これは数学の集合と同じように、重複しない要素の集まりを扱えます。
# 集合の作成 (重複は自動的に削除される)
my_set = {1, 2, 2, 3, 4, 4, 4}
print(my_set) # 出力: {1, 2, 3, 4}
# 別の集合
another_set = {3, 4, 5, 6}
C#で同様のことをする場合、HashSet<T>を使うのが一般的ですね。
// C#での集合 (HashSet<T>)
var mySet = new HashSet<int> { 1, 2, 2, 3, 4, 4, 4 };
// mySet の内容は { 1, 2, 3, 4 } となる
var anotherSet = new HashSet<int> { 3, 4, 5, 6 };
Pythonのsetで特に驚いたのは、集合演算子が非常に直感的なことです。
- 和集合: | (または union())
- 積集合: & (または intersection())
- 差集合: - (または difference())
# 和集合 (両方に含まれる要素、重複なし)
print(my_set | another_set) # 出力: {1, 2, 3, 4, 5, 6}
# 積集合 (両方に共通する要素)
print(my_set & another_set) # 出力: {3, 4}
# 差集合 (my_set にのみ含まれる要素)
print(my_set - another_set) # 出力: {1, 2}
# 差集合 (another_set にのみ含まれる要素)
print(another_set - my_set) # 出力: {5, 6}
C#でもLINQを使えば同様の操作(Union(), Intersect(), Except())が可能ですが、Pythonの演算子による記述は非常にシンプルで数学的だと感じました。
なぜPythonは集合を標準機能として重視するのか?
Pythonは科学計算、データ分析、アルゴリズム実装など、数学的な概念を扱う場面で広く使われています。そのため、集合のようなデータ構造とその効率的な操作を言語レベルでサポートすることは、これらの分野での生産性を高める上で非常に合理的と言えます。データの重複排除や要素の存在確認、集合間の関係性を扱う際に、setは非常に強力で効率的なツールとなります。
1.2. タプル(tuple): イミュータブルなリスト?
Pythonにはリスト(list)と似た「タプル(tuple)」があります。どちらも複数の要素を順序付けて格納できますが、大きな違いは タプルは作成後に中身を変更できない(イミュータブル※) 点です。
※ イミュータブル(Immutable)とは「不変」という意味で、一度作成したらその状態(値や中身)を変えることができない性質のことです。
# リスト (変更可能)
my_list = [1, 2, 3]
my_list[0] = 100
print(my_list) # 出力: [100, 2, 3]
# タプル (変更不可能)
my_tuple = (1, 2, 3)
# my_tuple[0] = 100 # これは TypeError になる!
print(my_tuple) # 出力: (1, 2, 3)
最初は「リストがあるのになぜタプルが?」と疑問でしたが、以下のような使い分けがあるようです。
- リスト: 要素の追加、削除、変更が頻繁に起こる、要素数が可変な場合に使う。
- タプル: 要素の順序や内容が変わらない、固定的なデータの組(座標、関数の戻り値など)を表す場合に使う。
イミュータブルなので、dictのキーとしても使えます(リストは使えません)。
(25/04/15追記) 修正
ハッシュアブルな値のみを持つタプル であれば、dictのキーとしても使えます。
dictのキーとして使えるのはハッシュアブルな値です。
タプルであってもハッシュアブルでない要素(リストや辞書など)を含んでいるとdictのキーにできません。
>>> x = (1, (2, 3), 4)
>>> y = (1, [2, 3], 4)
>>> z = {}
>>> z[x] = 5
>>> z[y] = 6
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: unhashable type: 'list'
※ (25/04/15追記) @shiracamus(しらかみゅ)さんにコメント欄で教えて頂きました!ありがとうございました!
※ (25/04/15追記) 詳しく調べました。
Pythonの辞書(dict)を理解する:キーの仕組みと「ハッシュ可能(hashable)」の重要性
C#にもタプルはある!
C#にもタプル機能は存在します。主に2種類あります。
System.Tuple (参照型タプル): .NET Framework 4.0から導入されました。参照型であり、要素へのアクセスは Item1, Item2 のようなプロパティ名になります。やや古く、パフォーマンス面で不利な場合があります。
var oldTuple = new Tuple<int, string>(1, "Alice");
Console.WriteLine(oldTuple.Item1); // 1
System.ValueTuple (値型タプル): C# 7.0から導入され、現在主流のタプルです。値型で軽量であり、要素に名前を付けることも可能です。Pythonのタプルに近い感覚で利用できます。
(int Id, string Name) valueTuple = (2, "Bob");
Console.WriteLine(valueTuple.Id); // 2
Console.WriteLine(valueTuple.Name); // Bob
// 要素名なしも可能
var point = (10, 20);
Console.WriteLine(point.Item1); // 10
LINQとタプル
C#のLINQでもタプルは活用されています。例えば、Zipメソッドは2つのシーケンスを組み合わせて、要素ごとのタプルを生成します。
int[] numbers = { 1, 2, 3 };
string[] words = { "one", "two", "three" };
// Zipメソッドは ValueTuple のシーケンスを返す (C# 7.0以降)
var zipped = numbers.Zip(words); // 結果: [(1, "one"), (2, "two"), (3, "three")]
foreach (var (num, word) in zipped)
{
Console.WriteLine($"{num}: {word}");
}
このように、C#でもタプルは複数の値をまとめて扱う便利な機能として定着しています。ただ、Pythonのタプルは後述する関数の多値返却の構文シュガーとして使われるなど、より言語の基本的な部分に組み込まれている印象を受けました。
2. dict(辞書): TypeScriptのオブジェクトに見えるけど?
キーと値のペアを格納するPythonの「dict(辞書)」は、一見TypeScriptのオブジェクトリテラルに似ています。
# Python の dict
person_py = {
"name": "Alice",
"age": 30,
"city": "Tokyo"
}
print(person_py["name"]) # 出力: Alice
TypeScript
// TypeScript のオブジェクト
const personTs = {
name: "Alice",
age: 30,
city: "Tokyo"
};
console.log(personTs.name); // 出力: Alice
console.log(personTs["age"]); // 出力: 30
見た目は似ていますが、いくつか違いがあります。
※ (25/04/15追記) 詳しく調べました。
Pythonの辞書(dict)を理解する:キーの仕組みと「ハッシュ可能(hashable)」の重要性
-
キーの型:
Python
のdict
のキーはハッシュアブル(文字列、数値、タプルなど、変更不可能な型やユーザ定義型のインスタンス)であれば基本的に何でも使えます。TypeScript
のオブジェクトのキーは通常、文字列かシンボルです(数値も使えますが、内部的には文字列に変換されます)。 -
アクセス方法:
TypeScript
ではドット記法 (.
) とブラケット記法 ([]
) の両方が使えますが、Python
の標準のdict
は基本的にブラケット記法 ([]
) を使います。ドット記法 (.
) での値へのアクセスはできません。(※collections.namedtuple
や一部のライブラリが返すオブジェクトなど、属性アクセスが可能な場合もありますが、標準のdict
とは異なります。) -
機能:
Python
のdict
には豊富なメソッド (keys()
,values()
,items()
,get()
,pop()
など)が組み込みで用意されています。TypeScript
のオブジェクトもObject
プロトタイプを通じてメソッドを利用できますが、Python
のdict
はデータ構造としての操作メソッドがより前面に出ている印象です。 -
型:
TypeScript
は静的型付けなので、オブジェクトの構造(プロパティとその型)を定義できます (interface
やtype
)。Python
は動的型付けですが、後述する型ヒントで構造を示すことも可能です。
3. 関数の戻り値が複数!?
Pythonの関数では、return で複数の値をカンマ区切りで指定することで、あたかも複数の値を直接返しているかのように見えます。Python特有のこの簡潔な書き方には驚きました。
def get_point():
x = 10
y = 20
return x, y # ここ!
# 戻り値を複数の変数で受け取る (アンパック)
point_x, point_y = get_point()
print(f"X: {point_x}, Y: {point_y}") # 出力: X: 10, Y: 20
# 実はタプルとして返されている
result = get_point()
print(result) # 出力: (10, 20)
print(type(result)) # 出力: <class 'tuple'>
これは前述の通り、関数がタプルを一つ返していて、それを受け取り側でアンパック(分解して別々の変数に代入)している、という仕組みです。
C#やTypeScriptでこれを実現するには?
既に触れましたが、C#やTypeScriptでも同様のことは可能です。
C#:
ValueTuple (値型タプル): C# 7.0以降、Pythonと非常によく似た構文で複数の値を返せます。
public (int X, int Y) GetPoint()
{
return (10, 20);
}
var (pointX, pointY) = GetPoint();
out パラメータ: 古くからある方法ですが、少し冗長になります。
public void GetPoint(out int x, out int y) { x = 10; y = 20; }
GetPoint(out int pointX, out int pointY);
TypeScript:
配列: 配列リテラルで返し、分割代入で受け取るのが一般的です。
function getPoint(): [number, number] { return [10, 20]; }
const [pointX, pointY] = getPoint();
オブジェクト: オブジェクトリテラルで返す方法もあります。
function getPointObj(): { x: number, y: number } { return { x: 10, y: 20 }; }
const { x: objX, y: objY } = getPointObj();
Pythonの return x, y という書き方は、タプルとアンパックを組み合わせることで非常に簡潔に実現されており、言語の特徴がよく表れている部分だと感じました。
4. インデントがコードブロック!Visual Basicみたい?
C#やTypeScriptではコードのブロック(if文、for文、関数定義など)を {} (中括弧) で囲みますが、Pythonでは**インデント(字下げ)**の深さでブロックを表現します。
# Python の例
def greet(name):
if name:
print(f"Hello, {name}!") # この行は if ブロックの中
print("Nice to meet you!") # この行も if ブロックの中
else:
print("Hello there!") # この行は else ブロックの中
print("Goodbye!") # この行は関数 greet の直下 (if/else の外)
greet("Alice")
これは、インデントを強制することでコードの見た目を統一し、可読性を高めるという思想に基づいています。
確かに、このインデントでブロックを示すスタイルは、昔触ったVisual Basicに似ていると感じました。
コード スニペット
' Visual Basic .NET の例 (参考)
Sub Greet(ByVal name As String)
If Not String.IsNullOrEmpty(name) Then
Console.WriteLine($"Hello, {name}!") ' If ブロックの中
Console.WriteLine("Nice to meet you!") ' If ブロックの中
Else
Console.WriteLine("Hello there!") ' Else ブロックの中
End If ' ブロックの終わりは End If
Console.WriteLine("Goodbye!") ' Sub の直下
End Sub
' --- 呼び出し ---
Greet("Bob")
VBでは End If や Next, End Sub といったキーワードでブロックの終わりを明示しますが、Pythonはインデントが浅くなること自体がブロックの終わりを示します。慣れるまでは少し戸惑いましたが、強制的にコードが綺麗になるのは良い点かもしれません。
5. モジュールはTypeScriptと考え方が似てる?C#のnamespaceとの違いは?
Pythonでは、他のファイルに書かれたコード(関数やクラス)を利用するために import を使います。これはモジュールという単位でコードを分割・再利用する仕組みです。
# my_module.py ファイル
def my_function():
print("This is from my_module!")
# main.py ファイル
import my_module # my_module.py をインポート
my_module.my_function() # モジュール名経由で関数を呼び出す
この import を使って他のファイルの機能を取り込む考え方は、TypeScriptのモジュールシステム(import/export)と非常に似ていると感じました。
// myModule.ts ファイル
export function myFunction(): void {
console.log("This is from myModule!");
}
// main.ts ファイル
import { myFunction } from './myModule'; // ./myModule.ts からインポート
myFunction();
どちらもファイルをモジュール単位として扱い、必要な機能を選択的にインポートする、という点で共通しています。
C#の namespace との違い
C#では namespace を使ってクラスなどを論理的にグループ化し、using でその名前空間を使えるようにします。
// MyLibrary/MyClass.cs
namespace MyLibrary
{
public class MyClass
{
public static void MyMethod()
{
Console.WriteLine("Method from MyLibrary.MyClass");
}
}
}
// Program.cs
using System;
using MyLibrary; // MyLibrary 名前空間を使用
class Program
{
static void Main(string[] args)
{
MyClass.MyMethod(); // 名前空間を指定せずクラス名から使える
}
}
PythonのモジュールやTypeScriptのモジュールが「ファイル」と強く結びついているのに対し、C#の namespace はより「論理的なグループ名」という側面が強いです。1つのファイルに複数の namespace を含めることも、複数のファイルで同じ namespace を使うことも可能です。また、PythonやTypeScriptではインポートしないと基本的に使えませんが、C#では using を書かなくても完全修飾名 (MyLibrary.MyClass.MyMethod()) でアクセスできます。
概念としては似ていますが、ファイルとの結びつきやアクセス方法に違いがあると言えそうです。
6. 型定義がない!? const との違いは?実務ではどうなの?
C#やTypeScript(を静的型付けで使っている場合)に慣れていると、Pythonの変数宣言時に型を書かないスタイルは非常に斬新に感じます。
# Python では型を書かなくても良い (動的型付け)
message = "Hello" # 文字列型
count = 100 # 整数型
is_valid = True # ブール型
# 変数に別の型の値を再代入することも可能
count = "One hundred"
print(count) # 出力: One hundred
(25/04/15追記) Pythonの変数はオブジェクトへの参照を保持するだけの「参照型変数」
※ @shiracamus(しらかみゅ)さんにコメント欄で教えて頂きました!ありがとうございました!
Pythonは動的型付け言語です。これは、変数を使う前に「この変数には整数を入れます」といった型宣言をする必要がなく、変数に値が代入されたときに、その値の種類に応じて実行時に自動的に型が決まることを意味します。
x = 10 # x に整数 10 を代入 -> x は int 型になる
print(type(x)) # <class 'int'>
x = "hello" # 同じ変数 x に文字列 "hello" を代入 -> x は str 型になる
print(type(x)) # <class 'str'>
重要なのは、Pythonでは変数自体が特定の型に縛られているのではなく、 値(オブジェクト)自身が型情報を持っている という点です。type()関数を使うと、変数が現在どの型のオブジェクトを参照しているかを確認できます。
では、変数とは何でしょうか? Pythonの変数は、データ(値)そのものを直接格納しているわけではありません。変数は、メモリ上に存在するオブジェクト(整数 10 や文字列 "hello" など)の場所を示す オブジェクトID(オブジェクト参照値) を保持しています。これは、オブジェクトに付けられた「名札」や「ラベル」のようなものと考えることができます。
a = 100
b = a # b は a と同じオブジェクト(100)を参照する
c = 100 # Pythonの最適化により、c も同じオブジェクト(100)を参照することが多い
print(f"a の ID: {id(a)}")
print(f"b の ID: {id(b)}") # a と同じ ID になる
print(f"c の ID: {id(c)}") # a, b と同じ ID になる可能性が高い
a = 200 # a に新しいオブジェクト(200)を代入(参照先を変更)
print(f"a の ID (変更後): {id(a)}") # 新しい ID になる
print(f"b の ID (変更後): {id(b)}") # b は依然として 100 を参照しているので ID は変わらない
このように、Pythonの変数はオブジェクトへの参照を保持するだけの「 参照型変数 」と見なすことができます。そのため、1つの変数に対して、整数オブジェクトへの参照、文字列オブジェクトへの参照、リストオブジェクトへの参照など、どんな型のオブジェクトへの参照でも代入(再代入)することが可能です。
この「 動的型付け 」と「 すべてのデータがオブジェクトであり、変数はその参照を持つ 」という仕組みが、Pythonの柔軟で書きやすい特徴を支えています。
TypeScript/JavaScriptの const とは違う?
「あれ、でもTypeScriptとかJavaScriptでも const って書くけど、あれは型じゃないの?」と思うかもしれません。
const (JS/TS): これは再代入を禁止する宣言です。変数が指し示す値自体を変更できなくするものであり、「型」を定義するものではありません。let も同様に型定義ではありません。
const message: string = "Hello"; // 型定義 (string) と再代入不可 (const)
// message = "World"; // Error: 再代入不可
let count = 10; // 型推論で number 型になる
count = 20; // OK: let なので再代入可能
// count = "Twenty"; // Error (TypeScriptの場合): 型が違う
Pythonの変数宣言: Pythonでは message = "Hello" のように、単に変数名に値を代入するだけで宣言が完了します。const や let のようなキーワードはありません。型は代入された値によって実行時に自動的に決まります(動的型付け)。
Pythonで型定義はしないの? 実務では?
「型がないと大規模開発とか大変じゃない?」と思うかもしれませんが、Python 3.5以降、型ヒント (Type Hints) という機能が導入されました。
# Python の型ヒント
def add(a: int, b: int) -> int:
return a + b
name: str = "Bob"
age: int = 25
result: int = add(10, 5)
print(result)
この : int や -> int が型ヒントです。注意点として、これはあくまでヒントであり、Pythonの実行自体は型ヒントを無視します(型が違ってもエラーにはなりません)。
では何のためにあるかというと、
可読性の向上: コードを読む人が変数の型を理解しやすくなる。
静的解析ツール: mypy のようなツールを使うと、型ヒントに基づいてコードの型エラーを事前にチェックできる。
IDEの支援: エディタがコード補完やエラー表示をしやすくなる。
というメリットがあります。
型ヒントの推奨について
型ヒントの利用は、Python
の公式ドキュメント(PEP 484 -- Type Hints
)で提案・導入され、その利用は広く推奨されています。また、Google Python Style Guide
のような主要なスタイルガイドでも、型ヒントを使用しています。
そのため、実務のPython
開発、特に複数人での開発や長期的なメンテナンスが必要なプロジェクトでは、型ヒントを記述することが推奨されます。完全に型を書かないスタイルは、小規模なスクリプトや個人開発などに多いかもしれませんが、チーム開発では型ヒントを活用するケースが圧倒的に多いでしょう。
7. and と or がそのまま!
地味な点ですが、論理演算子が and (AND条件) と or (OR条件) なのは、C# (&&, ||) やTypeScript (&&, ||) と比べて非常に読みやすいと感じました。
age = 25
is_student = True
# C# や TypeScript なら if (age < 30 && is_student)
if age < 30 and is_student:
print("学割適用")
# C# や TypeScript なら if (age < 18 || age >= 65)
if age < 18 or age >= 65:
print("入場無料")
英語の文章を読む感覚に近いですね。
まとめ
C#とTypeScriptに慣れた視点からPythonの基礎を学ぶと、文法や設計思想の違いから多くの驚きと発見がありました。
- 数学的な集合演算が直感的な
set
- イミュータブルなシーケンス
tuple
とその活用(多値返却など) -
TypeScript
のオブジェクトに似ているようで異なるdict
- インデントによるブロック定義
-
TypeScript
と似た考え方の モジュールシステム - 型定義を必須としない動的型付けと、それを補う型ヒント
- 読みやすい
and
,or
演算子
特に、動的型付けでありながら型ヒントで静的解析の恩恵も受けられる点は、柔軟性と堅牢性のバランスが取れていると感じました。setやtupleが言語レベルで自然に組み込まれているのも、Pythonがデータ処理や科学技術計算で人気な理由の一端を垣間見た気がします。
まだまだ学ぶべきことは多いですが、Pythonの持つ独特の魅力や簡潔さは非常に面白いと感じています。この記事が、これからPythonを学ぼうとするC#やTypeScriptエンジニアの方の参考になれば嬉しいです。
補足事項:
上記はPython 3.x系を想定しています。
コード例は基本的な動作を示すためのもので、エラーハンドリングなどは省略しています。