Javaと Pythonの識別子の仕様を比較する

| コメント(0) | トラックバック(0)

前の記事で,Pythonの識別子のUnicodeの扱いについて取り上げました。では,同じくUnicodeの様々な文字を識別子に使えるJavaとはどのように類似しているか,あるいは違っているでしょうか。

Javaの識別子の仕様

Javaは最初からUnicodeを前提に設計されており,変数名やメソッド名等の識別子にも世界の様々な文字を使えるようになっています。

識別子の仕様は言語仕様Java Language Specification 3.8. Identifiersに見えます。が,読んでみると実は,このセクションにはほとんど中身がなく,実質的にはJava APIのCharacterクラスの2つのstaticメソッド isJavaIdentifierStart(int) および isJavaIdentifierPart(int) にて,識別子に使える文字が規定されています。前者が識別子先頭,後者が2文字目以降です。

前者のメソッドのAPIドキュメントにはこう記されています:

  • isLetter(codePoint) が次を返す: true
  • getType(codePoint) が次を返す: LETTER_NUMBER
  • 参照される文字が通貨記号である ('$' など)
  • 参照文字が連結句読点文字である ('_' など)

一方,後者のメソッドつまり2文字目以降の定義は次のように記されています:

  • 汎用文字である
  • それが通貨記号である ('$' など)
  • それが連結句読点文字である ('_' など)
  • 数字である
  • 数値汎用文字である (ローマ数字文字など)
  • 連結マークである
  • 非スペーシングマークである
  • 文字の isIdentifierIgnorable(codePoint) が true を返す

少々とっつきにくく見えるかもしれませんが,Unicodeの文字カテゴリを前提としているので,その知識があれば「あれのことだな」と思いあたるでしょう。例えば最初の「汎用文字」はカテゴリLo, Luなどをまとめたもの,「数字」はカテゴリNd,「非スペーシングマーク」はMnといった具合です。最後の項目のメソッドは,制御文字の類いかどうかを判定するものです。

Pythonの識別子

Python3では,言語仕様の2.3. Identifiers and keywordsにて識別子の字句構造が定義されています。形式的に定義されているところを引用します:

identifier   ::=  xid_start xid_continue*
id_start     ::=  <all characters in general categories Lu, Ll, Lt, Lm, Lo, Nl, the underscore, and characters with the Other_ID_Start property>
id_continue  ::=  <all characters in id_start, plus characters in the categories Mn, Mc, Nd, Pc and others with the Other_ID_Continue property>
xid_start    ::=  <all characters in id_start whose NFKC normalization is in "id_start xid_continue*">
xid_continue ::=  <all characters in id_continue whose NFKC normalization is in "id_continue*">

Pythonの仕様書では,識別子の仕様は Unicode Standard Annex UAX-31 "Unicode Identifier and Pattern Syntax" に準拠していることが明示されています。これは一般的な言語の識別子の仕様(の雛形)として利用できるものです。Unicode仕様があまりに巨大なので,言語設計者の役に立つようなガイドラインとして作られたものだろうと思います。

実はJavaの方も内容的にはこれに準じているように見えます。ただし仕様書から参照されてはいません。

というわけで,JavaとPythonとで使える文字にあまり違いはありません。通貨記号の扱いが異なるのと,連結句読点(カテゴリPc)かアンダースコアのみかの違い,またUAX-31で互換性のために用意されている "Other_ID_{Start|Continue}" を含むか否かのような細かな出入りはあるようですが,大筋では大体同じものが使えると思ってよさそうです。

正規化の問題

ただし,JavaとPythonの小さくない違いとして,Pythonの方はUnicodeのNFKC正規化にて同一性をチェックされるということがあります。つまり,識別子の文字に,「é」のような文字を含む際に,「e + 合成用アクセント記号」としているか,あるいは合成済みの「é」の符号位置を用いているかという違いの取り扱いです。Pythonではこれらは正規化されて同じものとみなされます。

Javaにはこの規則はありません。Javaは正規化の仕様ができる前のUnicode仕様に基づいて設計されたので,比較的最近Unicode対応したPythonとは事情が異なります。

Javaで以下のようなコードを書いてみると実験できます。

public class JavaIdentifierTest {
    public static void main(String[] args) {
	int é = 0;
	int e\u0301 = 1;
	System.out.println(é);  // -> 0
	System.out.println(e\u0301);  // -> 1
    }
}

コード中のUnicodeエスケープ \u0301 は合成用アキュートアクセントです。JavaのUnicodeエスケープはコンパイルの早い段階で処理されるので,文字列リテラル専用ではなく,このように識別子の一部に用いることもできます。2つ変数を定義しており,どちらも同じ「é」を表しますが,片方は合成済みの符号位置,もう片方は結合文字を使っています。可能であれば,Unicodeエスケープを用いずに「基底文字e + 合成用アキュートアクセント」をエディタから入力してみても良いでしょう。Javaではこの2つの変数は同一視されずに別物として扱われます。

Pythonで同じことを試そうとすると,エスケープでなしに合成列をどうにかしてターミナルにペーストしてやる必要があります。私のMacの環境では,pythonコマンドでインタプリタを起動して,

>>> print('e\u0301')

として出力された é をマウスで選択してコピーすると,「e + 合成用アクセント」としてコピーできるようです (どこかで勝手に正規化されないことを祈りましょう)。確かめるには,

>>> len('é')
2

のように長さが2になっていればOK。これを変数名として,

>>> é = 1

のように変数として用いてみます。一方,キーボードから普通に入力したéは合成済みの符号位置になるので,Macの場合なら入力方式「ABC - 拡張」から Option-e e と打鍵して,

>>> é
1

とすることで,さっき代入した値が出てくることがわかります。つまり,文字éの符号化として合成済みの単一の符号位置を用いても,結合文字を用いて2つの符号位置で符号化しても,識別子としては同一のものとして扱われていることがわかります。

トラックバック(0)

トラックバックURL: http://yanok.net/yanok/mt-tb.cgi/658

コメントする

最近のブログ記事

LGの KF94マスクを買ってみた
マスクの重要性 デルタ株の感染力によって…
『[改訂新版] プログラマのための文字コード技術入門』増刷決定!
拙著『[改訂新版] プログラマのための文…
停電対策として自宅にUPSを導入した話
私は写真撮影が趣味で,撮影した写真は自宅…
Javaと Pythonの識別子の仕様を比較する
前の記事で,Pythonの識別子のUni…
Pythonの識別子における Unicode文字
プログラミング言語Pythonで,変数名…

広告