Python シーケンスの基本

Intermediate

This tutorial is from open-source community. Access the source code

はじめに

Python のシーケンスは、順序付きのアイテムのコレクションです。整数でインデックス付けされます。

これは Guided Lab です。学習と実践を支援するためのステップバイステップの指示を提供します。各ステップを完了し、実践的な経験を積むために、指示に注意深く従ってください。過去のデータによると、この 中級 レベルの実験の完了率は 80%です。学習者から 100% の好評価を得ています。

シーケンスデータ型

Python には 3 つの「シーケンス」データ型があります。

  • 文字列:'Hello'。文字列は文字のシーケンスです。
  • リスト:[1, 4, 5]
  • タプル:('GOOG', 100, 490.1)

すべてのシーケンスは順序付きで、整数でインデックス付けされ、長さを持っています。

a = 'Hello'               ## 文字列
b = [1, 4, 5]             ## リスト
c = ('GOOG', 100, 490.1)  ## タプル

## インデックス付けされた順序
a[0]                      ## 'H'
b[-1]                     ## 5
c[1]                      ## 100

## シーケンスの長さ
len(a)                    ## 5
len(b)                    ## 3
len(c)                    ## 3

シーケンスは複製できます:s * n

>>> a = 'Hello'
>>> a * 3
'HelloHelloHello'
>>> b = [1, 2, 3]
>>> b * 2
[1, 2, 3, 1, 2, 3]
>>>

同じ型のシーケンスは連結できます:s + t

>>> a = (1, 2, 3)
>>> b = (4, 5)
>>> a + b
(1, 2, 3, 4, 5)
>>>
>>> c = [1, 5]
>>> a + c
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: can only concatenate tuple (not "list") to tuple

スライシング

スライシングとは、シーケンスから部分シーケンスを取り出すことを意味します。構文は s[start:end] です。ここで、startend は、取得したい部分シーケンスのインデックスです。

a = [0,1,2,3,4,5,6,7,8]

a[2:5]    ## [2,3,4]
a[-5:]    ## [4,5,6,7,8]
a[:3]     ## [0,1,2]
  • インデックス startend は整数でなければなりません。
  • スライスは、終了値を含みません。数学の半開区間のようなものです。
  • インデックスが省略された場合、それぞれリストの先頭または末尾がデフォルト値となります。

スライスの再代入

リストでは、スライスを再代入したり削除したりできます。

## 再代入
a = [0,1,2,3,4,5,6,7,8]
a[2:4] = [10,11,12]       ## [0,1,10,11,12,4,5,6,7,8]

注:再代入するスライスは同じ長さである必要はありません。

## 削除
a = [0,1,2,3,4,5,6,7,8]
del a[2:4]                ## [0,1,4,5,6,7,8]

シーケンスの縮約

シーケンスを単一の値に縮約するための一般的な関数がいくつかあります。

>>> s = [1, 2, 3, 4]
>>> sum(s)
10
>>> min(s)
1
>>> max(s)
4
>>> t = ['Hello', 'World']
>>> max(t)
'World'
>>>

シーケンスの反復処理

for ループは、シーケンスの要素を反復処理します。

>>> s = [1, 4, 9, 16]
>>> for i in s:
...     print(i)
...
1
4
9
16
>>>

ループの各反復では、新しい項目が取得されます。この新しい値は、反復変数に格納されます。この例では、反復変数は x です。

for x in s:         ## `x` は反復変数
  ...statements

各反復で、反復変数の前の値は上書きされます(もしあれば)。ループが終了した後、変数は最後の値を保持します。

break 文

break 文を使うと、ループを早期に抜けることができます。

for name in namelist:
    if name == 'Jake':
        break
  ...
  ...
statements

break 文が実行されると、ループを抜けて次の statements に進みます。break 文は最内側のループにのみ適用されます。このループが別のループ内にある場合、外側のループを抜けることはありません。

continue 文

1 つの要素をスキップして次の要素に移動するには、continue 文を使用します。

for line in lines:
    if line == '\n':    ## 空行をスキップする
        continue
    ## その他の文
 ...

これは、現在の項目が関心対象外である場合や、処理で無視する必要がある場合に便利です。

整数のループ処理

カウントする必要がある場合は、range() を使用します。

for i in range(100):
    ## i = 0,1,...,99

構文は range([start,] end [,step]) です。

for i in range(100):
    ## i = 0,1,...,99
for j in range(10,20):
    ## j = 10,11,..., 19
for k in range(10,50,2):
    ## k = 10,12,...,48
    ## 2 ずつカウントする方法に注意してください。1 ではありません。
  • 終了値は含まれません。スライスの動作と同じです。
  • start は省略可能で、デフォルトは 0 です。
  • step は省略可能で、デフォルトは 1 です。
  • range() は必要に応じて値を計算します。実際に大きな数の範囲を格納するわけではありません。

enumerate() 関数

enumerate 関数は、反復処理に追加のカウンタ値を付加します。

names = ['Elwood', 'Jake', 'Curtis']
for i, name in enumerate(names):
    ## i = 0 のとき name = 'Elwood' でループ
    ## i = 1 のとき name = 'Jake'
    ## i = 2 のとき name = 'Curtis'

一般的な形式は enumerate(sequence [, start = 0]) です。start は省略可能です。enumerate() を使用する良い例は、ファイルを読み取りながら行番号を追跡することです。

with open(filename) as f:
    for lineno, line in enumerate(f, start=1):
     ...

結局のところ、enumerate は以下のようなショートカットに過ぎません。

i = 0
for x in s:
    statements
    i += 1

enumerate を使用すると、入力が少なくなり、わずかに高速に実行されます。

タプルと for 文

複数の反復変数を使って反復処理を行うことができます。

points = [
  (1, 4),(10, 40),(23, 14),(5, 6),(7, 8)
]
for x, y in points:
    ## x = 1, y = 4 のときにループ
    ##            x = 10, y = 40
    ##            x = 23, y = 14
    ##           ...

複数の変数を使用する場合、各タプルは一連の反復変数に「展開」されます。変数の数は各タプルの要素数と一致する必要があります。

zip() 関数

zip 関数は複数のシーケンスを取り、それらを結合するイテレータを作成します。

columns = ['name','shares', 'price']
values = ['GOOG', 100, 490.1 ]
pairs = zip(columns, values)
## ('name', 'GOOG'), ('shares',100), ('price',490.1)

結果を得るには反復処理が必要です。前述のように、複数の変数を使ってタプルを展開することができます。

for column, value in pairs:
...

zip の一般的な使い方は、辞書を構築するためのキー/値のペアを作成することです。

d = dict(zip(columns, values))

演習 2.13: カウント

いくつかの基本的なカウントの例を試してみましょう。

>>> for n in range(10):            ## 0 から 9 までカウント
        print(n, end=' ')

0 1 2 3 4 5 6 7 8 9
>>> for n in range(10,0,-1):       ## 10 から 1 までカウント
        print(n, end=' ')

10 9 8 7 6 5 4 3 2 1
>>> for n in range(0,10,2):        ## 0、2、...、8 までカウント
        print(n, end=' ')

0 2 4 6 8
>>>

演習 2.14: さらなるシーケンス操作

シーケンスの縮約操作のいくつかを対話的に試してみましょう。

>>> data = [4, 9, 1, 25, 16, 100, 49]
>>> min(data)
1
>>> max(data)
100
>>> sum(data)
204
>>>

データをループ処理してみましょう。

>>> for x in data:
        print(x)

4
9
...
>>> for n, x in enumerate(data):
        print(n, x)

0 4
1 9
2 1
...
>>>

時々、初心者が見苦しいコード断片の中で for 文、len()、および range() が使われることがあります。それは錆びた C プログラムの奥深くから現れたかのようなものです。

>>> for n in range(len(data)):
        print(data[n])

4
9
1
...
>>>

そんなことはしないでください!それを読むだけで誰の目も痛くなりますし、メモリの効率が悪く、処理速度も非常に遅くなります。データを反復処理したい場合は、通常の for ループを使ってください。何らかの理由でインデックスが必要な場合は、enumerate() を使ってください。

演習 2.15: enumerate() の実用的な例

ファイル missing.csv には株式ポートフォリオのデータが含まれていますが、一部の行に欠損データがあります。enumerate() を使って、pcost.py プログラムを修正して、入力が不適切な場合に警告メッセージとともに行番号を表示するようにしましょう。

>>> cost = portfolio_cost('/home/labex/project/missing.csv')
Row 4: Couldn't convert: ['MSFT', '', '51.23']
Row 7: Couldn't convert: ['IBM', '', '70.44']
>>>

これを行うには、コードのいくつかの部分を変更する必要があります。

...
for rowno, row in enumerate(rows, start=1):
    try:
     ...
    except ValueError:
        print(f'Row {rowno}: Bad row: {row}')

演習 2.16: zip() 関数の使用

ファイル portfolio.csv の最初の行には列ヘッダが含まれています。これまでのコードでは、それらを捨ててきました。

>>> f = open('/home/labex/project/portfolio.csv')
>>> rows = csv.reader(f)
>>> headers = next(rows)
>>> headers
['name','shares', 'price']
>>>

しかし、ヘッダを何か役に立つことに使えないでしょうか?ここで zip() 関数が登場します。まず、ファイルのヘッダとデータの 1 行をペアにするためにこれを試してみましょう。

>>> row = next(rows)
>>> row
['AA', '100', '32.20']
>>> list(zip(headers, row))
[ ('name', 'AA'), ('shares', '100'), ('price', '32.20') ]
>>>

zip() が列ヘッダと列値をどのようにペアにしたかに注目してください。ここでは結果をリストに変換するために list() を使っています。これにより結果を見ることができます。通常、zip() はイテレータを作成し、for ループで消費する必要があります。

このペアリングは辞書を作成するための中間ステップです。次にこれを試してみましょう。

>>> record = dict(zip(headers, row))
>>> record
{'price': '32.20', 'name': 'AA','shares': '100'}
>>>

この変換は、多くのデータファイルを処理する際に知っておく最も便利なトリックの 1 つです。たとえば、pcost.py プログラムをさまざまな入力ファイルで動作させたいが、名前、株数、価格が表示される実際の列番号を考慮しない場合があります。

pcost.pyportfolio_cost() 関数を次のように変更します。

## pcost.py

def portfolio_cost(filename):
  ...
        for rowno, row in enumerate(rows, start=1):
            record = dict(zip(headers, row))
            try:
                nshares = int(record['shares'])
                price = float(record['price'])
                total_cost += nshares * price
            ## これは上の int() と float() 変換のエラーをキャッチします
            except ValueError:
                print(f'Row {rowno}: Bad row: {row}')
      ...

次に、まったく異なるデータファイル portfoliodate.csv で関数を試してみましょう。このファイルは次のようになっています。

name,date,time,shares,price
"AA","6/11/2007","9:50am",100,32.20
"IBM","5/13/2007","4:20pm",50,91.10
"CAT","9/23/2006","1:30pm",150,83.44
"MSFT","5/17/2007","10:30am",200,51.23
"GE","2/1/2006","10:45am",95,40.37
"MSFT","10/31/2006","12:05pm",50,65.10
"IBM","7/9/2006","3:15pm",100,70.44
>>> portfolio_cost('/home/labex/project/portfoliodate.csv')
44671.15
>>>

正しく行えば、データファイルの列形式が以前とまったく異なっていてもプログラムがまだ機能することがわかります。すごいですね!

ここで行われた変更は微妙ですが、重要です。portfolio_cost() が単一の固定ファイル形式を読み取るようにハードコードされていたのではなく、新しいバージョンは任意の CSV ファイルを読み取り、その中から関心のある値を抽出します。ファイルに必要な列があれば、コードは機能します。

セクション 2.3 で書いた report.py プログラムを変更して、同じ手法を使って列ヘッダを抽出するようにします。

report.py プログラムを portfoliodate.csv ファイルで実行して、以前と同じ結果が得られることを確認してみましょう。

演習 2.17: 辞書の逆転

辞書はキーを値にマッピングします。たとえば、株価の辞書です。

>>> prices = {
        'GOOG' : 490.1,
        'AA' : 23.45,
        'IBM' : 91.1,
        'MSFT' : 34.23
    }
>>>

items() メソッドを使うと、(キー, 値) のペアを取得できます。

>>> prices.items()
dict_items([('GOOG', 490.1), ('AA', 23.45), ('IBM', 91.1), ('MSFT', 34.23)])
>>>

しかし、代わりに (値, キー) のペアのリストを取得したい場合はどうでしょうか。ヒント:zip() を使います。

>>> pricelist = list(zip(prices.values(),prices.keys()))
>>> pricelist
[(490.1, 'GOOG'), (23.45, 'AA'), (91.1, 'IBM'), (34.23, 'MSFT')]
>>>

なぜこのようにするのでしょうか?一つの理由は、辞書データに対して特定の種類のデータ処理を行うことができるようになるからです。

>>> min(pricelist)
(23.45, 'AA')
>>> max(pricelist)
(490.1, 'GOOG')
>>> sorted(pricelist)
[(23.45, 'AA'), (34.23, 'MSFT'), (91.1, 'IBM'), (490.1, 'GOOG')]
>>>

これはまた、タプルの重要な特徴を示しています。比較で使用される場合、タプルは最初の要素から順に要素ごとに比較されます。文字列が文字ごとに比較されるのと同様です。

zip() は、異なる場所からのデータをペアにする必要があるような状況でよく使用されます。たとえば、列名と列値をペアにして名前付き値の辞書を作成する場合です。

zip() はペアに限定されるものではありません。たとえば、任意の数の入力リストと一緒に使用できます。

>>> a = [1, 2, 3, 4]
>>> b = ['w', 'x', 'y', 'z']
>>> c = [0.2, 0.4, 0.6, 0.8]
>>> list(zip(a, b, c))
[(1, 'w', 0.2), (2, 'x', 0.4), (3, 'y', 0.6), (4, 'z', 0.8))]
>>>

また、最短の入力シーケンスが使い果たされると zip() は停止することに注意してください。

>>> a = [1, 2, 3, 4, 5, 6]
>>> b = ['x', 'y', 'z']
>>> list(zip(a,b))
[(1, 'x'), (2, 'y'), (3, 'z')]
>>>

まとめ

おめでとうございます!あなたはシーケンスの実験を完了しました。あなたのスキルを向上させるために、LabEx でさらに多くの実験を行って練習してください。