はじめに
関数は以前から紹介されていますが、それが実際にどのように機能するかについては、より深いレベルでの詳細はほとんど提供されていません。このセクションでは、いくつかの空白を埋め、呼び出し規約、スコープ規則などについて議論します。
This tutorial is from open-source community. Access the source code
💡 このチュートリアルは英語版からAIによって翻訳されています。原文を確認するには、 ここをクリックしてください
関数は以前から紹介されていますが、それが実際にどのように機能するかについては、より深いレベルでの詳細はほとんど提供されていません。このセクションでは、いくつかの空白を埋め、呼び出し規約、スコープ規則などについて議論します。
この関数を考えてみましょう。
def read_prices(filename, debug):
...
位置引数を使って関数を呼び出すことができます。
prices = read_prices('prices.csv', True)
または、キーワード引数を使って関数を呼び出すこともできます。
prices = read_prices(filename='prices.csv', debug=True)
時には、引数を省略可能にしたい場合があります。その場合は、関数定義でデフォルト値を割り当てます。
def read_prices(filename, debug=False):
...
デフォルト値が割り当てられている場合、その引数は関数呼び出しで省略可能です。
d = read_prices('prices.csv')
e = read_prices('prices.dat', True)
注: デフォルト値のある引数は、引数リストの末尾に配置する必要があります (すべての必須引数は先頭に配置します)。
これらの2つの異なる呼び出しスタイルを比較してみましょう。
parse_data(data, False, True) #?????
parse_data(data, ignore_errors=True)
parse_data(data, debug=True)
parse_data(data, debug=True, ignore_errors=True)
ほとんどの場合、キーワード引数はコードの明確さを向上させます。特に、フラグとして機能する引数や省略可能な機能に関連する引数の場合です。
関数の引数には常に短くて意味のある名前を付けましょう。
関数を使用する人は、キーワード呼び出しスタイルを使用したい場合があります。
d = read_prices('prices.csv', debug=True)
Pythonの開発ツールは、ヘルプ機能やドキュメントに名前を表示します。
return
文は値を返します。
def square(x):
return x * x
返却値が指定されていない場合、または return
が欠けている場合、None
が返されます。
def bar(x):
statements
return
a = bar(4) ## a = None
## または
def foo(x):
statements ## return がない
b = foo(4) ## b = None
関数は1つの値のみを返すことができます。ただし、関数はタプルで値を返すことで複数の値を返すことができます。
def divide(a,b):
q = a // b ## 商
r = a % b ## 余り
return q, r ## タプルを返す
使用例:
x, y = divide(37,5) ## x = 7, y = 2
x = divide(37, 5) ## x = (7, 2)
プログラムは変数に値を割り当てます。
x = value ## グローバル変数
def foo():
y = value ## ローカル変数
変数の割り当ては関数定義の外と内で行われます。関数定義の外で定義された変数は「グローバル」です。関数内の変数は「ローカル」です。
関数内で割り当てられた変数は非公開です。
def read_portfolio(filename):
portfolio = []
for line in open(filename):
fields = line.split(',')
s = (fields[0], int(fields[1]), float(fields[2]))
portfolio.append(s)
return portfolio
この例では、filename
、portfolio
、line
、fields
、およびs
はローカル変数です。これらの変数は関数呼び出しの後に保持されたり、アクセス可能ではありません。
>>> stocks = read_portfolio('portfolio.csv')
>>> fields
Traceback (most recent call last):
File "<stdin>", line 1, in?
NameError: name 'fields' is not defined
>>>
ローカル変数は他の場所で見つかる変数とも競合しません。
関数は、同じファイル内で定義されたグローバル変数の値に自由にアクセスできます。
name = 'Dave'
def greeting():
print('Hello', name) ## `name` グローバル変数を使用
ただし、関数はグローバル変数を変更できません。
name = 'Dave'
def spam():
name = 'Guido'
spam()
print(name) ## 'Dave' と表示されます
覚えておいてください: 関数内のすべての代入はローカルです。
グローバル変数を変更する必要がある場合は、そのように宣言する必要があります。
name = 'Dave'
def spam():
global name
name = 'Guido' ## 上のグローバルな name を変更
global
宣言はその使用の前に表示される必要があり、対応する変数は関数と同じファイル内に存在する必要があります。これを見てきたことから、これは不適切な形式であると考えられます。実際、できるだけ global
を避けるようにしてください。関数が関数の外のある種の状態を変更する必要がある場合、代わりにクラスを使用する方が良いです(後ほど詳しく説明します)。
関数を呼び出すとき、引数の変数は渡された値を参照する名前です。これらの値はコピーではありません。可変データ型(たとえば、リスト、辞書)が渡された場合、それらは「その場で」変更できます。
def foo(items):
items.append(42) ## 入力オブジェクトを変更
a = [1, 2, 3]
foo(a)
print(a) ## [1, 2, 3, 42]
重要なポイント: 関数は入力引数のコピーを受け取りません。
値を変更することと変数名を再代入することの微妙な違いを理解しておくことを確認しましょう。
def foo(items):
items.append(42) ## 入力オブジェクトを変更
a = [1, 2, 3]
foo(a)
print(a) ## [1, 2, 3, 42]
## VS
def bar(items):
items = [4,5,6] ## ローカルの `items` 変数を別のオブジェクトに参照させるように変更
b = [1, 2, 3]
bar(b)
print(b) ## [1, 2, 3]
メモ: 変数の代入は決してメモリを上書きしません。名前は単に新しい値にバインドされるだけです。
このセットの演習では、おそらくこのコースで最も強力で難しい部分を実装します。たくさんのステップがあり、過去の演習の多くの概念が一度にまとめられています。最終的なソリューションはコードで約25行程度ですが、ゆっくりと時間をかけて、各部分を理解していることを確認してください。
report.py
プログラムの中心的な部分はCSVファイルの読み取りに焦点を当てています。たとえば、read_portfolio()
関数はポートフォリオデータの行が含まれるファイルを読み取り、read_prices()
関数は価格データの行が含まれるファイルを読み取ります。これらの関数の両方には、多くの低レベルの「面倒な」部分と同様の機能があります。たとえば、両方ともファイルを開き、csv
モジュールでラップし、さまざまなフィールドを新しい型に変換します。
本当に多くのファイル解析を行う場合、おそらくこれらの一部を整理し、より汎用的なものにする必要があります。それが私たちの目標です。
この演習は、fileparse.py
と呼ばれるファイルを開いて始めます。ここで私たちが作業を行う場所です。
まずは、CSVファイルを辞書のリストに読み込む問題に焦点を当てましょう。fileparse_3.3.py
ファイルに、次のような関数を定義します。
## fileparse_3.3.py
import csv
def parse_csv(filename):
'''
CSVファイルをレコードのリストに解析する
'''
with open(filename) as f:
rows = csv.reader(f)
## ファイルのヘッダーを読む
headers = next(rows)
records = []
for row in rows:
if not row: ## データのない行をスキップ
continue
record = dict(zip(headers, row))
records.append(record)
return records
この関数は、ファイルを開く詳細、csv
モジュールでラップすること、空行を無視することなどの詳細を隠しながら、CSVファイルを辞書のリストに読み込みます。
試してみましょう。
ヒント: python3 -i fileparse_3.3.py
。
>>> portfolio = parse_csv('/home/labex/project/portfolio.csv')
>>> portfolio
[{'name': 'AA','shares': '100', 'price': '32.20'}, {'name': 'IBM','shares': '50', 'price': '91.10'}, {'name': 'CAT','shares': '150', 'price': '83.44'}, {'name': 'MSFT','shares': '200', 'price': '51.23'}, {'name': 'GE','shares': '95', 'price': '40.37'}, {'name': 'MSFT','shares': '50', 'price': '65.10'}, {'name': 'IBM','shares': '100', 'price': '70.44'}]
>>>
これは良いですが、すべてが文字列として表されているため、データを使って何か有益な計算を行うことはできません。これをすぐに修正しますが、それに引き続き作り上げていきましょう。
多くの場合、CSVファイルから選択された特定の列のみに興味があり、すべてのデータには興味がありません。parse_csv()
関数を次のように修正して、ユーザーが指定できる列を選択できるようにしましょう。
>>> ## すべてのデータを読む
>>> portfolio = parse_csv('/home/labex/project/portfolio.csv')
>>> portfolio
[{'name': 'AA','shares': '100', 'price': '32.20'}, {'name': 'IBM','shares': '50', 'price': '91.10'}, {'name': 'CAT','shares': '150', 'price': '83.44'}, {'name': 'MSFT','shares': '200', 'price': '51.23'}, {'name': 'GE','shares': '95', 'price': '40.37'}, {'name': 'MSFT','shares': '50', 'price': '65.10'}, {'name': 'IBM','shares': '100', 'price': '70.44'}]
>>> ## 一部のデータのみを読む
>>> shares_held = parse_csv('/home/labex/project/portfolio.csv', select=['name','shares'])
>>> shares_held
[{'name': 'AA','shares': '100'}, {'name': 'IBM','shares': '50'}, {'name': 'CAT','shares': '150'}, {'name': 'MSFT','shares': '200'}, {'name': 'GE','shares': '95'}, {'name': 'MSFT','shares': '50'}, {'name': 'IBM','shares': '100'}]
>>>
演習2.23で列選択機能の例が示されています。ただし、実装方法の1つは次の通りです。
## fileparse_3.4.py
import csv
def parse_csv(filename, select=None):
'''
CSVファイルをレコードのリストに解析する
'''
with open(filename) as f:
rows = csv.reader(f)
## ファイルのヘッダーを読む
headers = next(rows)
## 列選択機能が指定されている場合、指定された列のインデックスを見つけます。
## 結果の辞書に使用するヘッダーのセットも絞り込みます。
if select:
indices = [headers.index(colname) for colname in select]
headers = select
else:
indices = []
records = []
for row in rows:
if not row: ## データのない行をスキップ
continue
## 特定の列が選択されている場合、行をフィルタリングします
if indices:
row = [ row[index] for index in indices ]
## 辞書を作成
record = dict(zip(headers, row))
records.append(record)
return records
この部分にはいくつかのポイントがあります。おそらく最も重要なのは、列選択を行インデックスにマッピングすることです。たとえば、入力ファイルに次のようなヘッダーがあったとしましょう。
>>> headers = ['name', 'date', 'time','shares', 'price']
>>>
次に、選択された列が次のようであったとします。
>>> select = ['name','shares']
>>>
適切な選択を行うには、選択された列名をファイル内の列インデックスにマッピングする必要があります。このステップが行っているのがこれです。
>>> indices = [headers.index(colname) for colname in select ]
>>> indices
[0, 3]
>>>
言い換えると、"name" は列0で、"shares" は列3です。ファイルからデータの行を読むとき、インデックスを使ってそれをフィルタリングします。
>>> row = ['AA', '6/11/2007', '9:50am', '100', '32.20' ]
>>> row = [ row[index] for index in indices ]
>>> row
['AA', '100']
>>>
/home/labex/project/fileparse_3.5.py
ディレクトリ内の parse_csv()
関数を修正して、返されるデータに対して任意で型変換を適用できるようにしましょう。たとえば:
>>> portfolio = parse_csv('/home/labex/project/portfolio.csv', types=[str, int, float])
>>> portfolio
[{'name': 'AA','shares': 100, 'price': 32.2}, {'name': 'IBM','shares': 50, 'price': 91.1}, {'name': 'CAT','shares': 150, 'price': 83.44}, {'name': 'MSFT','shares': 200, 'price': 51.23}, {'name': 'GE','shares': 95, 'price': 40.37}, {'name': 'MSFT','shares': 50, 'price': 65.1}, {'name': 'IBM','shares': 100, 'price': 70.44}]
>>> shares_held = parse_csv('/home/labex/project/portfolio.csv', select=['name','shares'], types=[str, int])
>>> shares_held
[{'name': 'AA','shares': 100}, {'name': 'IBM','shares': 50}, {'name': 'CAT','shares': 150}, {'name': 'MSFT','shares': 200}, {'name': 'GE','shares': 95}, {'name': 'MSFT','shares': 50}, {'name': 'IBM','shares': 100}]
>>>
演習2.24で既にこれを検討しています。コードの次のフラグメントをソリューションに挿入する必要があります。
...
if types:
row = [func(val) for func, val in zip(types, row) ]
...
一部のCSVファイルにはヘッダー情報が含まれていません。たとえば、prices.csv
ファイルは次のようになっています。
"AA",9.22
"AXP",24.85
"BA",44.85
"BAC",11.27
...
/home/labex/project/fileparse_3.6.py
の parse_csv()
関数を修正して、このようなファイルに対応できるようにしましょう。代わりにタプルのリストを作成します。たとえば:
>>> prices = parse_csv('/home/labex/project/prices.csv', types=[str,float], has_headers=False)
>>> prices
[('AA', 9.22), ('AXP', 24.85), ('BA', 44.85), ('BAC', 11.27), ('C', 3.72), ('CAT', 35.46), ('CVX', 66.67), ('DD', 28.47), ('DIS', 24.22), ('GE', 13.48), ('GM', 0.75), ('HD', 23.16), ('HPQ', 34.35), ('IBM', 106.28), ('INTC', 15.72), ('JNJ', 55.16), ('JPM', 36.9), ('KFT', 26.11), ('KO', 49.16), ('MCD', 58.99), ('MMM', 57.1), ('MRK', 27.58), ('MSFT', 20.89), ('PFE', 15.19), ('PG', 51.94), ('T', 24.79), ('UTX', 52.61), ('VZ', 29.26), ('WMT', 49.74), ('XOM', 69.35)]
>>>
この変更を行うには、コードを修正して最初のデータ行をヘッダー行として解釈しないようにします。また、キーとして使用する列名がなくなったため、辞書を作成しないようにする必要があります。
CSVファイルは非常に一般的ですが、タブやスペースなど、異なる列区切り文字を使用するファイルに遭遇する可能性もあります。たとえば、portfolio.dat
ファイルは次のようになっています。
name shares price
"AA" 100 32.20
"IBM" 50 91.10
"CAT" 150 83.44
"MSFT" 200 51.23
"GE" 95 40.37
"MSFT" 50 65.10
"IBM" 100 70.44
csv.reader()
関数を使用すると、次のように異なる列区切り文字を指定できます。
rows = csv.reader(f, delimiter=' ')
/home/labex/project/fileparse_3.7.py
の parse_csv()
関数を修正して、区切り文字の変更も可能にしましょう。
たとえば:
>>> portfolio = parse_csv('/home/labex/project/portfolio.dat', types=[str, int, float], delimiter=' ')
>>> portfolio
[{'name': 'AA','shares': 100, 'price': 32.2}, {'name': 'IBM','shares': 50, 'price': 91.1}, {'name': 'CAT','shares': 150, 'price': 83.44}, {'name': 'MSFT','shares': 200, 'price': 51.23}, {'name': 'GE','shares': 95, 'price': 40.37}, {'name': 'MSFT','shares': 50, 'price': 65.1}, {'name': 'IBM','shares': 100, 'price': 70.44}]
>>>
ここまでたどり着けたなら、本当に便利な素敵なライブラリ関数を作成したことになります。この関数を使えば、ファイルの内部構造やcsv
モジュールの詳細についてあまり心配することなく、任意のCSVファイルを解析し、関心のある列を選択し、型変換を行うことができます。
おめでとうございます!あなたは「関数に関するさらなる学習」の実験を完了しました。あなたのスキルを向上させるために、LabExでさらに多くの実験を行って練習することができます。