読者です 読者をやめる 読者になる 読者になる

はてブロ@ama-ch

https://twitter.com/ama_ch

今日のPython

Program Python

オブジェクトのコピーを作る

p.125

lista = [1, 2, 3]
listc = lista[:]

このようにスライスを使って代入すると、オブジェクトのコピーができる。


動作確認してみよう!

>>> lista = [1, 2, 3]
>>> listb = lista 
>>> listc = lista[:]
>>> lista, listb, listc
([1, 2, 3], [1, 2, 3], [1, 2, 3])
>>> lista.append(4)
>>> lista, listb, listc
([1, 2, 3, 4], [1, 2, 3, 4], [1, 2, 3])

うん、listcは違うオブジェクトになっていますね。


辞書などのスライスを使えないものはcopyモジュールのcopy()関数というものを使う。
p.125

>>> import copy
>>> listd = copy.copy(lista) #lista[:]と同じ
>>> listd
[1, 2, 3, 4]

あぁ、モジュールって
インポートする→モジュール名.関数名 って感じで使うのか。
copy関数は要素のリファレンスのみをコピーするための関数。オブジェクト内部までたどってコピーを行うdeepcopy()というものもある。

関数の引数とリファレンス

p.126

>>> def reftest(alist):
...     alist.append('new string')
... 
>>> list = [1, 2, 3]
>>> reftest(list)
>>> list
[1, 2, 3, 'new string']

引数のデフォルト値とリファレンス

引数に対するデフォルト値の代入は一度しか行われない!
p.127

>>> def deftest(deflist=[1, 2, 3]):  #デフォルト値を設定
...     deflist.append('da-!')       #引数に要素を追加
...     print deflist
... 
>>> deftest()   #引数を指定しない→ここでのみ設定される
[1, 2, 3, 'da-!']
>>> deftest()   #再度指定せず呼び出し
[1, 2, 3, 'da-!', 'da-!']
>>> deftest()
[1, 2, 3, 'da-!', 'da-!', 'da-!']
>>> deftest([1, 2, 3])   #引数を指定
[1, 2, 3, 'da-!']
>>> deftest()
[1, 2, 3, 'da-!', 'da-!', 'da-!', 'da-!']

デフォルト値の設定は一度しか行われないので、最初にdeftest()を呼び出して設定された値はそのまま保持される。
デフォルト値で設定されるオブジェクトは何度呼び出しても変わらないということかな。扱いに注意が必要だ。

数値の操作

p.129

>>> hex(1023)  #10進数の数値を16進数の「文字列」に変換
'0x3ff'
>>> int("0xff",16)  #16進数の「文字列」を10進数の数値に変換
255

int()関数は、int("数値", 基数)という形で使う。


p.130 16進数、8進数、2進数の扱い

種類 リテラル 数値を文字列に変換 文字列を数値に変換
16進数 0x1abf hex(1234) int("0x1abf", 16)
8進数 0123 oct(0123) int("0123", 8)
2進数 なし なし int("101010", 2)

エスケープシーケンス一覧

p.132

エスケープシーケンス 説明
\n 改行
\r 改行(CR)
\t 水平タブ
\f 改ページ(ライン・フィード)
\' シングルクォーテーション
\" ダブルクォーテーション
\\ バックスラッシュ
\x61 8バイトの16進数に対応する8ビット文字
\u3042 16ビットの16進数に対応するUnicode文字、「0x」は不要
\0 Null文字

raw文字列

p.132
ファイルパスの区切り文字など、バックスラッシュ自体を文字列として定義したい場合は、
r"C:\path\to\file"
のように記述する。ユニコード文字列のraw文字列を定義するにはrを「ur」に。

文字列メソッドを活用する

p.134 .(ドット)で区切られた商品名、金額、購入日を空白で繋ぎ直しスペースで区切る astring.py

#!/usr/bin/env

import sys

def add_tax(astring):
    items = astring.split('.')  #文字列を.で区切りリストにする
    price = int(items[1]) * 1.05
    items[1] = str(int(price))
    return " ".join(items)  #リストを空白で区切り連結する

print add_tax(sys.argv[1])

実行結果

$ python astring.py Goods.1000.2006/05/27
Goods 1050 2006/05/27

文字列メソッドsplit()は、文字列.split('区切り文字')と書く。文字列を区切り文字で区切り、リストに格納する。区切り文字を省略すると、スペースやタブの空白文字列が区切り文字となる。
同じく文字列メソッドjoin()は、"区切り文字".join(文字列リスト)と書き、リストの要素を区切り文字で繋ぎ出力する。

文字列で利用できるメソッド一覧

p.135

メソッド 説明
S.find(sub(検索したい文字列)[,start(開始idx)[,end(終了idx)]]) S内の文字列を検索し、最初に見つかった位置を0から始まるインデックスとして返す。見つからなかった場合は-1を返す。オプションで検索範囲を指定できる。文字列の右から検索するrfind()メソッドも存在する。
S.index(sub(検索したい文字列)[,start(開始idx)[,end(終了idx)]]) find()と同じような動作をするが、見つからなかった場合はValueErrorという例外を発生する。rindex()も存在する。
S.endswith(suffix(検索したい文字列)[,start(開始idx)[,end(終了idx)]]) 文字列Sが検索したい文字列で終わっているときにTrueを返し、そうでないときにFalseを返す。
S.startswith(suffix(検索したい文字列)[,start(開始idx)[,end(終了idx)]]) 文字列Sが検索したい文字列で始まっているときにTrueを返し、そうでないときにFalseを返す。
S.split([sep(区切り文字列)[,maxsplit(分割数)]]) 文字列Sを区切り文字列で区切り、リストを作って返す。
S.join(seq(シーケンス)) シーケンス中の文字列要素を、Sで連結する。
S.strip([chars(削除する文字列)]) 文字列の先頭および末尾から文字列を削除する。引数を指定しないと、空白文字を削除する。
S.upper() 文字列を英字大文字に変換する
S.lower() 文字列を英字小文字に変換する
S.ljust(width(幅)[, fillchar(埋め草文字列)]) 文字列Sを幅の値を考慮して左寄せする。同様にrjust()は右寄せを、center()は中央寄せを行う。

文字列フォーマット

文字列フォーマットを使うことで、文字列に数値などを差し込むことができる。Cライクな書き方ですね。
p.138

>>> template = u"%sの%d月の平均気温:約%d度"
>>> print template % (u"北京", 6, 24)
北京の6月の平均気温:約24

u"%sの%d月の平均気温:約%d度" % (u"北京", 6, 24)
のように書く。


p.139 よく使われる文字列フォーマットのコード一覧

コード 対応する型
%s 文字列(数値なども文字列に変換する)
%d 10進数の整数
%f 10進数の浮動小数点数
%x 16進数、%Xとすると英字を大文字として表示する
%o 8進数
%% %のエスケープ文字


精度や桁数を指定するには、
%[フラグ][桁数][.精度]コード
と書く。


p.140 フラグ

フラグ 説明
0 桁数に満たない場合0で埋める
- 左寄せにする
+ 数値の先頭に符号をつける
>>> "%+010.2f" % 100.12345  #符号つき、10桁で精度は小数点以下2桁を0詰めで表示
'+000100.12'


これで以前奇麗に表示できなかった九九の表が出力できるぞ!

>>> for a in range(1, 10):
...     for b in range(1, 10):
...             print "%3d" % a*b,  #a*bを3桁で表示する
...     print ""
... 
  1   1  1   1  1  1   1  1  1  1   1  1  1  1  1   1  1  1  1  1  1   1  1  1  1  1  1  1   1  1  1  1  1  1  1  1   1  1  1  1  1  1  1  1  1 
  2   2  2   2  2  2   2  2  2  2   2  2  2  2  2   2  2  2  2  2  2   2  2  2  2  2  2  2   2  2  2  2  2  2  2  2   2  2  2  2  2  2  2  2  2 
  3   3  3   3  3  3   3  3  3  3   3  3  3  3  3   3  3  3  3  3  3   3  3  3  3  3  3  3   3  3  3  3  3  3  3  3   3  3  3  3  3  3  3  3  3 
  4   4  4   4  4  4   4  4  4  4   4  4  4  4  4   4  4  4  4  4  4   4  4  4  4  4  4  4   4  4  4  4  4  4  4  4   4  4  4  4  4  4  4  4  4 
  5   5  5   5  5  5   5  5  5  5   5  5  5  5  5   5  5  5  5  5  5   5  5  5  5  5  5  5   5  5  5  5  5  5  5  5   5  5  5  5  5  5  5  5  5 
  6   6  6   6  6  6   6  6  6  6   6  6  6  6  6   6  6  6  6  6  6   6  6  6  6  6  6  6   6  6  6  6  6  6  6  6   6  6  6  6  6  6  6  6  6 
  7   7  7   7  7  7   7  7  7  7   7  7  7  7  7   7  7  7  7  7  7   7  7  7  7  7  7  7   7  7  7  7  7  7  7  7   7  7  7  7  7  7  7  7  7 
  8   8  8   8  8  8   8  8  8  8   8  8  8  8  8   8  8  8  8  8  8   8  8  8  8  8  8  8   8  8  8  8  8  8  8  8   8  8  8  8  8  8  8  8  8 
  9   9  9   9  9  9   9  9  9  9   9  9  9  9  9   9  9  9  9  9  9   9  9  9  9  9  9  9   9  9  9  9  9  9  9  9   9  9  9  9  9  9  9  9  9

な、なんじゃこりゃあ!
なんか文字列として認識されてますね?「a*b(数値)を3桁で表示」じゃなくて、「a(文字列)をb回出力する」って解釈されてる。どう考えても文字列です。


キャストしてみたらいいんかな

>>> for a in range(1, 10):
...     for b in range(1, 10):
...             print "%3d" % int(a*b),  #キャストしてみる
...     print ""
... 
  1   2   3   4   5   6   7   8   9 
  2   4   6   8  10  12  14  16  18 
  3   6   9  12  15  18  21  24  27 
  4   8  12  16  20  24  28  32  36 
  5  10  15  20  25  30  35  40  45 
  6  12  18  24  30  36  42  48  54 
  7  14  21  28  35  42  49  56  63 
  8  16  24  32  40  48  56  64  72 
  9  18  27  36  45  54  63  72  81

おーできた!でもいまいち仕組みが把握しきれない。おぼえとこ。

辞書のキーを指定した埋め込み

辞書のキーを利用してフォーマットを作ることができます。
p.141 dicformat.py

#!/usr/bin/env
# -*- coding: utf-8 -*-

#辞書を定義する
namelist = {"name":"Amano", "nickname":"ama-ch"}

#フォーマット文字列を定義する
formatname = "僕の名前は%(name)sです。%(nickname)sって呼んでね!"

#辞書を使って差し込む
print formatname % namelist

実行結果

$ python dicformat.py
僕の名前はAmanoです。ama-chって呼んでね!

ソース2行目の
# -*- coding: utf-8 -*-
を入れないとうまく動かなかった。エンコードのセットをしないとダメみたい。


これを利用してみんなに自己紹介してもらえます!
selfintroductions.py

#!/usr/bin/env
# -*- coding: utf-8 -*-

#辞書のタプルを定義する
namelist = ({"name":"Amano", "nickname":"ama-ch"}, 
            {"name":"Yamada", "nickname":"yama-ch"},
            {"name":"Suzuki", "nickname":"suzu-ch"},
            {"name":"Kumazawa", "nickname":"kuma-ch"},
            {"name":"Morita", "nickname":"mori-ch"},
            {"name":"Ikeda", "nickname":"ike-ch"})

#フォーマット文字列を定義する
formatname = "僕の名前は%(name)sです。%(nickname)sって呼んでね!"

#辞書を使って差し込む
for person in namelist:
    print formatname % person

実行結果

$ python selfintroductions.py
僕の名前はAmanoです。ama-chって呼んでね!
僕の名前はYamadaです。yama-chって呼んでね!
僕の名前はSuzukiです。suzu-chって呼んでね!
僕の名前はKumazawaです。kuma-chって呼んでね!
僕の名前はMoritaです。mori-chって呼んでね!
僕の名前はIkedaです。ike-chって呼んでね!

辞書のタプルを定義するのが面倒くさいです!もっと楽に書けないかしら。

リスト、タプルを操作する

スライスのステップ数

p.142

>>> list = [1, 2, 3, 4, 5, 6]
>>> list[::2]  #2個おきに取り出す
[1, 3, 5]
>>> list[::3]  #3個おきに取り出す
[1, 4]

スライスには3つ目のパラメータが指定できて、その値は「n個おきに」という意味をもつ。

スライスを使った要素の代入と削除

p.142

>>> list = [1, 2, 3, 4, 5]
>>> list[2:4] = ["three", "four"]  #リストの3〜4個目の要素に代入
>>> list
[1, 2, 'three', 'four', 5]
>>> list[2:4] = ["three", "four", "FIVE"]  #スライスの範囲を超えた数の要素も代入できちゃう
>>> list
[1, 2, 'three', 'four', 'FIVE', 5]
>>> del list[2:5]  #リストの3〜5個目を削除する
>>> list
[1, 2, 5]
アンパック代入

p.143

>>> a = 1
>>> b = 2
>>> a, b = b, a  #アンパック代入
>>> a, b
(2, 1)

カンマで区切り複数の要素を並べて、一括で代入をするのがアンパック代入。
これを利用すると簡単に要素の交換ができます!

ソート順をコントロールする

比較を行う関数をsort()関数への引数として指定すると、ソート順をコントロールできる。
p.145

>>> def cmp_lower(a, b):
...     return cmp(a.lower(), b.lower())
... 
>>> list = ["abc", "def", "BCD", "EFG"]
>>> list.sort()  #普通のソート
>>> list
['BCD', 'EFG', 'abc', 'def']
>>> list.sort(cmp_lower)  #大文字小文字を同一視してソート
>>> list
['abc', 'BCD', 'def', 'EFG']

cmp()は組み込み関数で、

  • 「要素1<要素2」のときは-1を返す
  • 「要素1==要素2」のときは0を返す
  • 「要素1>要素2」のときは1を返す

という性質をもつ。
このソースでは英字の大文字小文字を同一視して、アルファベット順になるようにソート順をコントロールした。
つまり、「2つの引数を与えて、1 or 0 or -1を返す関数」を定義すれば、sort()に放り込んで結果をコントロールできるってこと?あーでも比較だから同値はなくても成り立つか。1 or -1を返す関数を作ればいけそう。


実験実験!

#!/usr/bin/env
# -*- coding: utf-8 -*-

# find_a.py
# リスト中の'a'or'A'を含む文字列を先頭に持ってくるようにソートする。
# また、aを含む位置により結果を調整する。
def find_a(str1, str2):
    flg1, flg2 = str1.lower().find('a'), str2.lower().find('a')
    if flg1 >= 0 and flg2 >= 0:
        if flg1 < flg2:
            return -1
        elif flg1 == flg2:
            return 0
        else:
            return 1
    elif flg1 >= 0 and flg2 < 0:
        return -1
    elif flg2 >= 0 and flg1 < 0:
        return 1
    else:
        return 1

list =  ["bcd", "BCD", "abc", "ABC", "CBA", "bac"]
print "     処理前    ", list
list.sort()
print " 普通にソート  ", list
list.sort(find_a)
print "aの位置でソート", list

実行結果

$ python find_a.py
     処理前     ['bcd', 'BCD', 'abc', 'ABC', 'CBA', 'bac']
 普通にソート   ['ABC', 'BCD', 'CBA', 'abc', 'bac', 'bcd']
aの位置でソート ['ABC', 'abc', 'bac', 'CBA', 'BCD', 'bcd']

できるもんだな!
もっと簡潔に書けそうだけど・・・