はじめまして、胡と申します。
普段は言語処理の研究開発に携わっており、Pythonで実験環境を作ったりすることが多いです。
今回は、私が実際に実験でよく使うList型に関する操作メモをシェアしたいと思います。
あれ?PythonのListは思った通りに動かないな!
「ヤバイ、backup用のListも上書きされてしまいます!」と、
自分の甘さに後悔したことがあります。
1 2 |
>>> foo = ['a', 'b', 'c'] >>> foo_back = foo |
この前提で、foo[0]に'd'を与えると、foo_backも['d', 'b', 'c']になってしまいますよね。
これは、Pythonでは、ある変数にList型の変数をassignment(foo_back = foo)するというのは、
単に“=”オペレータ前の変数(foo_back)に
「その後ろの変数(foo)が参照しているlistを参照してください」
と教えただけだからです。
つまり、上述の例では、foo_back = fooによるList中身のコピーができません。
これを解決するためには、
1 2 |
>>> import copy >>> foo_back = copy.copy(foo) |
を使います。
また、以下のコードによるList型データのコピーも可能です。
1 |
>>> foo_back = foo[:] |
Shallow copies of dictionaries can be made using dict.copy(), and of lists by assigning a slice of the entire list, for example, copied_list = original_list[:].
--Python 2.7.9 documentation: copy
ここで“foo[:]”というのは、元々はfoo[n:m]であり、
fooの n から m-1 までの要素をスライスするという意味になります。
全Listのコピーであるため、 n と m はそれぞれ 0 と len(foo) になり、
省略することができます。
このように、Listをコピーすることにより、
fooをどのように変更しても、foo_backへの影響はなくなります。
ただし、上述の方法はいずれも浅いコピー(shallow copy)であり、
[['a', 'b'], 'c', 'd'] のような、nested listになったデータのコピーには対応できません。
1 2 3 4 5 |
>>> foo = [['a', 'b'], 'c', 'd'] >>> foo_back = foo[:] >>> foo[0][1] = 1 >>> print foo_back [['a', 1], 'c', 'd'] |
この場合は、深いコピー(deep copy)を使いましょう。
1 2 3 4 5 6 |
>>> foo = [['a', 'b'], 'c', 'd'] >>> import copy >>> foo_back = copy.deepcopy(foo) >>> foo[0][1] = 1 >>> print foo_back [['a', 'b'], 'c', 'd'] |
参考:Python 2.7.9 documentation: copy
ほかのスクリプト言語と比較するために、JavaScriptとRubyのListコピー法についてもまとめてみました。
Python、JavaScriptとRubyのListコピー法
shallow copy | deep copy | |
Python |
foo_back = copy.copy(foo) 或いは foo_back = foo[:] |
foo_back = copy.deepcopy(foo) |
JavaScript |
var foo_back = jQuery.extend({}, foo);(*) 或いは var foo_back = foo.slice(0, foo.length); 或いは var foo_back = [].concat(foo); |
var foo_back = jQuery.extend(true, {}, foo);(*) |
Ruby |
foo_back = foo.clone 或いは foo_back = foo[0..foo.length] |
foo_back = Marshal.load(Marshal.dump(foo)) |
*jQuery使用。また、取得したfoo_backは辞書型Listになります。
例えばfoo = ["a", "b", "c"]はfoo_back = {0: "a", 1: "b", 2: "c"}になります。
この場合、foo == foo_backはfalseになってしまいますが、
foo[0] == foo_backはtrueであり、つまり要素の取得には支障がありません。
nested listを展開
[('a', 'b', 1), ('c', 'd', 1)]のようなTuple型同士(もしくはList型同士)のnested listを
['a', 'b', 1, 'c', 'd', 1]のような1次元Listに展開するためには、例えば以下のような方法があります。
1 2 3 |
>>> foo = [('a', 'b', 1), ('c', 'd', 1)] >>> list(reduce(lambda x, y: x + y, foo)) ['a', 'b', 1, 'c', 'd', 1] |
ですが、現実の世界ではやはりより複雑な構造があります。
自分の場合、テキストのツリー構造を展開して単語リストに変換することがしばしばあります。
例えば、「貴社の記者が汽車で帰社した」の構文解析結果の一例を以下に示します。
貴社の┐
記者が
|
汽車で┐
帰社した
このツリー構造に対して、単語と単語の関係をTuple型、文節と文節の関係をList型で表現すると、例文を
[[[[(u"貴社", u"の")], (u"記者", u"が")], (u"汽車", u"で")], (u"帰社", u"し", u"た")]
に変換することができます。
このListから、例えば単語頻度などの情報を得るために、
複雑なnested list構造(多次元)を単語List(1次元)に直す必要があります。
また、上述のような複雑なnested list構造を1次元Listに変換するためには、
Python2.x系では、compiler packageというものがあります。
1 2 3 4 |
>>> foo = [[[[(u"貴社", u"の")], (u"記者", u"が")], (u"汽車", u"で")], (u"帰社", u"し", u"た")] >>> from compiler.ast import flatten >>> print ' '.join(flatten(foo)) 貴社 の 記者 が 汽車 で 帰社 し た |
しかし、Python 3.x系になるとcompiler packageがなくなってしまいます。
Deprecated since version 2.6: The compiler package has been removed in Python 3.
--Python 2.7.9 documentation: Python compiler package
この場合は、自分でflatten関数を作らなければなりません。
1 |
>>> flatten = lambda x: [y for ls in x for y in flatten(ls)] if type(x) in (list, tuple) else [x] |
このように展開用の関数を作っておけば、fooの展開が容易にできるようになります。
1 2 |
>>> print ' '.join(flatten(foo)) 貴社 の 記者 が 汽車 で 帰社 し た |
このflatten関数では、再帰的に展開対象nested listにある要素を判断し、
List型もしくはTuple型の場合はそれをさらに展開し、
そうでない場合はそれを出力Listに追加するという仕組みになります。