目次
対象読者
- 日常的に複数言語を業務で扱っている方
想像ついた方もいるでしょう…何が原因か
外部キーが見つからない原因は、Python ではクラス定義の「順番」が結果に影響することです。
Java のようにコンパイラがシンボル解決をまとめて行う言語と違い、Python は 上から順番に実行 されます。したがって「後で定義するはずのクラス」を先に参照すると、その場で NameError や Django の設定時エラーになります。
Python(Django)での誤った例と正しい例
まず、実際に私が書いてしまった誤った例です。Todo クラスが User を参照しているのに、User がまだ定義されていません。
# 誤った順番
class Todo(models.Model):
user = models.ForeignKey(User, on_delete=models.CASCADE)
title = models.CharField(max_length=200)
class User(models.Model):
name = models.CharField(max_length=100)
Python ではこの段階で NameError: name 'User' is not defined となり、Django もマイグレーションを作れません。解決策は 2 つあります。
# その1: 参照先クラスを先に書く
class User(models.Model):
name = models.CharField(max_length=100)
class Todo(models.Model):
user = models.ForeignKey(User, on_delete=models.CASCADE)
title = models.CharField(max_length=200)
# その2: 文字列で遅延評価する(モデルの順番に依存しない書き方)
class Todo(models.Model):
user = models.ForeignKey('User', on_delete=models.CASCADE)
title = models.CharField(max_length=200)
class User(models.Model):
name = models.CharField(max_length=100)
文字列指定にすると Django が設定ロード時に解決してくれるため、クラス定義の順序に影響されません。
Java(Spring)なら動いてしまう典型例
一方 Java(特に Spring Data JPA)では、同じファイル内でクラスの順番を入れ替えても問題無く動きます。コンパイル時に全クラスを解決できるためです。
// Spring のエンティティ例(順番に依存しない)
@Entity
@Table(name = "todos")
public class Todo {
@Id @GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@ManyToOne
@JoinColumn(name = "user_id")
private User owner;
private String title;
// getter/setter など省略
}
@Entity
@Table(name = "users")
public class User {
@Id @GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String name;
@OneToMany(mappedBy = "owner")
private List<Todo> todos = new ArrayList<>();
// getter/setter など省略
}
「Java では問題ない書き方」がそのまま Python に持ち込めないのは、このコンパイル方式の違いが原因です。
様々な言語を扱うからこその学び
- 注意: Python は逐次評価、Java はコンパイル済みシンボル解決
言語によって解釈タイミングが違うため、同じつもりで書いたコードが別の結果を招きます。 - 対策: Django では参照先を定義順に気を配るか、文字列参照を積極的に使う
文字列参照は公式ドキュメントでも推奨されており、循環参照を避ける際にも便利です。 - 複数言語を扱っていると、思わぬ所で「常識」がぶつかります。
今回は外部キー定義でつまずきましたが、こうした違いに気づけるのもいろんな言語開発の面白さだと感じました。
以上のように、Python(Django)ではクラスの定義順が重要であり、Java の感覚のまま書くと外部キー周りでエラーに繋がります。複数言語を扱うときは、小さな違いでも意識しておくと同じミスを繰り返さずに済みます。