Pythonチュートリアルを読んで知らなかったことをまとめておいた。

オライリーのPythonチュートリアルを読んで知らなかったことを自分用メモとしてまとめておく。

3.1.1 数値
対話モードでは、最後に表示した式を「_」に代入してある。

>>> price = 1000
>>> tax = 10
>>> price * (tax+100)/100
1100.0
>>> print(_)
1100.0

3.1.2 文字列
raw文字列:エスケープ用の特殊文字\を普通の文字列として認識させることができる。(下の例は\nが改行コードと認識されるかされていないかの違い)

>>> print("C:\some\name")
C:\some
ame
>>> print(r"C:\some\name")
C:\some\name

文字列は*演算子で繰り返すことができる。

>>> 3*"ねる"+"ね"
'ねるねるねるね'

トリプルクォートを使えば文字列を改行できる。

>>> print("""
... 長い文字列は
... トリプルクォートを使えば
... 複数行で記述できる
... """)

長い文字列は
トリプルクォートを使えば
複数行で記述できる

以下に示す他の方法は知ってた。

>>> sql1 = "SELECT "
>>> sql1 += "* "
>>> sql1 += "FROM "
>>> sql1 += "TABLE"
>>> sql2 =(
... "SELECT "
... "* "
... "FROM "
... "TABLE")
>>> print(sql1)
SELECT * FROM TABLE
>>> print(sql2)
SELECT * FROM TABLE

Pythonの文字列はimmutable(変更不能体)
リストは普通に出来るのにね。

>>> word = "python"
>>> word[0] = "c"
Traceback (most recent call last):
 File "<stdin>", line 1, in <module>
TypeError: 'str' object does not support item assignment

4.2 for文
あらゆるシーケンス(リストや文字列)のアイテムに対し、そのシーケンス内の順序で反復をかける。
リストしか使えないと思ってた。

>>> word = "word"
>>> for i in word:
...     print(i)
...
w
o
r
d

4.3 range()関数
range()関数が返すオブジェクトはリストではない。iterable(反復可能体)と言う。
list型にしないことで空間を節約しているらしい。

>>> print(range(5))
range(0, 5)

4.4 break 文と continue 文とループの else 節
for文でもelseを使える。
else説はbreakが起きなかったときに実行される。

>>> for n in range(2, 10):
...     for x in range(2, n):
...         if n % x == 0:
...             print(n, 'equals', x, '*', n//x)
...             break
...     else:
...         # loop fell through without finding a factor
...         print(n, 'is a prime number')
...
2 is a prime number
3 is a prime number
4 equals 2 * 2
5 is a prime number
6 equals 2 * 3
7 is a prime number
8 equals 2 * 4
9 equals 3 * 3

4.6 関数の定義
関数本体の最初の文には文字列リテラル(docstring、関数の説明文)を使うことができる。
見たことはあったけど気にしてなかった。

>>> def add(a,b):
...     "aとbを足し算した結果を返す。"
...     return a + b
...
>>> print(add(1,2))
3

4.7.1 引数のデフォルト値
引数のデフォルト値を決めることができる。
使ったことなかったけど便利そう。

def add(a=1,b=1,c=1):
   return a,b,c,a + b + c

print(add())              #(1, 1, 1, 3)
print(add(3))             #(3, 1, 1, 5)
print(add(b=4))           #(1, 4, 1, 6)
print(add(c=5))           #(1, 1, 5, 7)
print(add(a=3,b=4))       #(3, 4, 1, 8)
print(add(3,4,5))         #(3, 4, 5, 12)

4.7.2 キーワード引数
キーワード引数は「key=value」の順で使う。
使ったことないし、見たこともなかったかも。

def test(a, *arguments, **keywords):
   """
   aは必須の引数
   *argumentsは位置指定型引数
   **keywordsはキーワード引数
   """
   print(a)

   #argumentsはtuple型
   print(arguments)
   for arg in arguments:
       print(arg)
   
   #keywordsはdict型
   print(keywords)
  #キーワード引数の順序を一定のものにしている。
   key_list = sorted(keywords.keys())

   for key in key_list:
       print(key, ":", keywords[key])

test("必須の引数",
   "pythonは",
   "いろいろな機能があって",
   "凄いね!",
   giants = "巨人",
   tigers = "阪神",
   swallows = "ヤクルト")

必須の引数
('pythonは', 'いろいろな機能があって', '凄いね!')
pythonは
いろいろな機能があって
凄いね!
{'giants': '巨人', 'tigers': '阪神', 'swallows': 'ヤクルト'}
giants : 巨人
swallows : ヤクルト
tigers : 阪神

4.7.4 引数リストのアンパック
引数がすでにリストやdict型になっているときは*演算子や**演算子を使って関数をコールすることでリストやdict型からアンパックした引数を渡すことができる。

def c_league(giants,baystars,tigers,carps,dragons,swallows):
   print(giants,baystars,tigers,carps,dragons,swallows)

dict={"giants":"巨人",
   "baystars":"横浜",
   "tigers":"阪神",
   "carps":"広島",
   "dragons":"中日",
   "swallows":"ヤクルト"}
c_league(**dict) #巨人 横浜 阪神 広島 中日 ヤクルト

4.7.5 lambda式
キーワードlambdaを使うと小さな無名関数が書ける。
「lambda a, b: a+b」は二つの引数の和を返す。
ちょっと使い方がわかってません。

def add(n):
   return lambda a : a+n

f=add(1)
print(f(2)) #3

4.7.7 関数注釈
注釈は関数の__annotations__属性にディクショナリとして格納される。
注釈自体は見たことあったけど、格納場所は知らなかった。
確かに、格納場所は必要か。

def f(ham: str, eggs: str = "eggs") -> str:
   print("Annotations:", f.__annotations__)
   print("Arguments:", ham, eggs)
   return ham + " and " + eggs

print(f("spam"))

Annotations: {'ham': <class 'str'>, 'eggs': <class 'str'>, 'return': <class 'str'>}
Arguments: spam eggs
spam and eggs

5.1.2 リストをキューとして使う
キューの実装にはcollections.dequeを使うべきである。
あんまり使う機会なさそうだけど。
とりあえず時間差を体感してみたくて以下のコードで試してみた。
上の実装だと0がもう一個多いとMemoryErrorになってしまったが、
下の実装なら平気だった。

from collections import deque
import time
def que_pop():
   list_a = []
   for m in range(10000000):
       list_a.append(m)
   start_time = time.time()
   result = list_a.pop(0)
   elapsed_time = time.time() - start_time
   return result, elapsed_time

def deque_pop():
   list_2 = deque([])
   for n in range(10000000):
       list_2.append(n)
   start_time2 = time.time()
   result = list_2.popleft()
   elapsed_time = time.time() - start_time2
   return result, elapsed_time
   
print(que_pop())         #(0, 0.00700068473815918)
print(deque_pop())       #(0, 0.0)

5.1.3 リスト内包
リスト内包表記は、括弧の中の 式、 for 句、そして0個以上の for か if 句で構成されます。 リスト内包表記の実行結果は、 for と if 句のコンテキスト中で式を評価した結果からなる新しいリストです。
書き方があまり好きじゃなくて、今まで食わず嫌いで避けてきた技術だけど、今回を機に使いこなせるようになっとこうと。
一応、リスト内包のほうが速いということは聞いたことあったので、ついでに速度計算もした。
コードは以下のサイトを参照にした。Python2.6.6だったのでPython3.8.1で再実装。

import time
# 1. testfunc1: 空リストを用意してappend
def testfunc1(rangelist):
   start_time = time.time()
   templist = []
   for temp in rangelist:
       templist.append(temp)
   elapsed_time = time.time() - start_time
   return elapsed_time

# 2. testfunc2: 1+appendをオブジェクト化       
def testfunc2(rangelist):
   start_time = time.time()
   templist = []
   append = templist.append
   for temp in rangelist:
       append(temp)
   elapsed_time = time.time() - start_time
   return elapsed_time

# 3. testfunc3: リスト内包表記      
def testfunc3(rangelist):
   start_time = time.time()
   templist = [temp for temp in rangelist]
   elapsed_time = time.time() - start_time
   return elapsed_time

def time_test():
   rangelist = range(1,10000000)
   print(testfunc1(rangelist))
   print(testfunc2(rangelist))
   print(testfunc3(rangelist))

time_test()

1.8749825954437256
1.4004216194152832
0.9428277015686035

5.3 タプルとシーケンス
アイテム数が1つのタプルの作り方。

tuple_a = "test",
print(tuple_a) #('test',)

5.4 集合(set)
集合とは重複しない要素を順不同で集めたもの。

a = {"a","b","c","d","e"}
b = set("ace")
print(a)     #{'a', 'b', 'e', 'd', 'c'}
print(b)     #{'a', 'c', 'e'}
print(a-b)   #{'b', 'd'}、print(a+b)はエラーになる。
print(a|b)   #{'b', 'a', 'e', 'd', 'c'}
print(a&b)   #{'a', 'c', 'e'}
print(a^b)   #{'b', 'd'}

5.6 ループのテクニック
シーケンスにループをかけるとき、enumerate()を使うと位置インデックスとそれに対応した値を同時に得ることができる。

for i,v in enumerate(["tic","tac","toe"]):
   print(i,v)

0 tic
1 tac
2 toe

5.7 条件についての補足
a < b == c は「a < b かつ b==c 」を判定する。
a or b or c のようなブール演算子を用いた評価は左から順に行われ、結論が決定した時点で評価をやめる。
下の例では、string2でnullかどうかの判定が結論付けられるので、non_nullの中身はstring2となる。

string1, string2, string3 = '', 'Trondheim', 'Hammer Dance'
non_null = string1 or string2 or string3
if not non_null:
   print("NULL")
else:
   print(non_null)

5.8 シーケンスの比較、その他の型の比較
シーケンスオブジェクトは、同じシーケンス型を持つオブジェクトと比較できる。この比較には辞書型順序を使用する。

>>> "Pascal" < "Python"
True

6.1.1 モジュールをスクリプトとして実行する。
恥ずかしながら、「if __name__ == "__main__"」の意味をちゃんと理解してなかったのでちゃんと動きを知っておこうと思った。
要は、以下のスクリプトがあったとき、

#test_import.py
def name_print():
   print(__name__)

print("これはimportしたら動くよ")
print(__name__)

if __name__ == "__main__":
   name_print()
#test_import2.py
import test_import
print("これは別モジュールを呼び出した後に動くよ")

test_import.pyを実行したときには、__name__=__main__が格納されているけど、test_import2.pyを実行したときにはtest_import.__name__=test_importが格納されている、という違い。以下、実行結果。

C:\pywork\tutorial>python test_import.py
これはimportしたら動くよ
__main__
__main__

C:\pywork\tutorial>python test_import2.py
これはimportしたら動くよ
test_import
これは別モジュールを呼び出した後に動くよ

6.1.2 モジュールの検索パス
書いてあることがいまいちわからなかったので放置。そのうちわかったら追記。

6.1.3 「コンパイル済」Pythonファイル
モジュールの読み込みを高速化するため、Python はコンパイル済みの各モジュールを __pycache__ ディレクトリの module.version.pyc ファイルとしてキャッシュします。
見たことはあった。とりあえず、自分でコンパイルしようと思わない限りは放置で。(2020/1/13追記:これimportすると勝手に作られるのね。)

6.2 標準モジュール
今まで逆引きで求める機能からライブラリを探してきたけど、いずれ標準モジュールを一通りさらっとくくらいはしたほうがいいよな、とは思っている。今やるとは言ってない。

6.3 dir()関数
組込み関数 dir() は、あるモジュールがどんな名前を定義しているか調べるために使われます。 dir() はソートされた文字列のリストを返します。
使う機会あるかな?

6.4 パッケージ
ファイルを含むディレクトリをパッケージをとしてPython に扱わせるには、ファイル __init__.py が必要です。 最も簡単なケースでは __init__.py はただの空ファイルで構いませんが、 __init__.py ではパッケージのための初期化コードを実行したり、__all__ 変数を設定してもかまいません。
こちらのサイトが分かりやすかったのでリンク貼っとく。

6.4.1 パッケージから*をインポート
import 文の使う規約は、パッケージの __init__.py コードに __all__ という名前のリストが定義されていれば、 from package import * が現れたときに import すべきモジュール名のリストとして使う、というものです。
ほーん、って感じ。

6.4.2 パッケージ内の相互参照
絶対インポートを、毎回使っとけばいいんじゃねって感じ。

6.4.3 複数のディレクトリにまたがるパッケージ
パッケージはもう一つ特別な属性として __path__ をサポートしています。この属性は、パッケージの __init__.py 中のコードが実行されるよりも前に、 __init__.py の収められているディレクトリ名の入ったリストになるよう初期化されます。
使うことがあったら意識するでいいや。

7.1 手の込んだ出力フォーマット
repr()とstr()の違い。以下のサイトが分かりやすかったので引用。

以下サンプルコード。

import datetime
today = datetime.date.today()
print(str(today))   #2020-01-13
print(repr(today))  #datetime.date(2020, 1, 13)

出力を見やすくするための工夫もいろいろある。

print("書き方一つ目")
for x in range(1,11):
   print(x,x**2,x**3)

print("書き方二つ目")
for x in range(1,11):
   print(repr(x).rjust(2),repr(x**2).rjust(3),end=" ")
   print(repr(x**3).rjust(4))

print("書き方三つ目")
for x in range(1,11):
   print("{0:2d} {1:3d} {2:4d}".format(x,x**2,x**3))

結果を張り付けるとインデントがずれてしまったので画像で。

スクリーンショット (2)

dictを上手に使えば以下のようなこともできる。

table = {'Sjoerd': 4127, 'Jack': 4098, 'Dcab': 8637678}
print("Jack: {0[Jack]:d}; Sjoerd: {0[Sjoerd]:d}; Dcab: {0[Dcab]:d}".format(table))
#Jack: 4098; Sjoerd: 4127; Dcab: 8637678
print("Jack: {Jack:d}; Sjoerd: {Sjoerd:d}; Dcab: {Dcab:d}".format(**table))
#Jack: 4098; Sjoerd: 4127; Dcab: 8637678

7.1.1 従来形式の文字列フォーマッティング
文字列フォーマッティングには%演算子も使うことができる。

import math
print(math.pi) #3.141592653589793
print("円周率の値はおよそ%5.3fである。"% math.pi) #円周率の値はおよそ3.142である。

7.2 ファイルの読み書き
fileをうまく開くことができなかったので試行錯誤したので記録として残しておく。PythonコードはすべてVScodeで書いて実行しているので、まずは自分の居場所をos.getcwd()で確認。そのあと、開きたいテキストファイルを絶対パスで指定。これで問題なく開けた。ハマった原因は、VScodeのターミナルにおけるカレントディレクトリが自分の想定と違っていて、相対パスでファイルを指定できていなかったこと。最初から絶対パスで指定していればこういうハマり方はしなかったのである意味貴重な体験w

import os
print(os.getcwd()) #C:\Users\[ユーザ名]
file_path = "/pywork/tutorial/test.txt"
f = open(file_path, "r")
print("一回目:{}".format(f.read()))
print("二回目:{}".format(f.read()))
f.close()
#一回目:first line
#second line
#二回目:

read()はEOFまで読み込んでそこで止まるので、もう一度read()しても何も帰ってこない。

7.2.2 構造のあるデータをjsonで保存する
Pythonの文字列以外のデータ構造を文字列表現にコンバートする(シリアライズ)ときにjson.dumps()を使うことができる。コンバートしテキストファイルに書き込むときには、ファイル開いた上で、json.dump()を使うことができる。逆に、テキストファイルの中身をPythonの文字列以外のデータ構造で取り込みたいときに、json.load()で取り込みことができる。

import json
file_path = "/pywork/tutorial/test.json"
x = [1, "simple", "list"]
print(type(x)) #<class 'list'>
x_json = json.dumps(x)
print(type(x_json)) #<class 'str'>
with open(file_path, "r+") as f:
   json.dump(x,f)
   #x_load = json.load(f) #json.decoder.JSONDecodeError

with open(file_path, "r") as f2:
   x_load2 = json.load(f2)
   print(type(x_load2)) #<class 'list'>
   print(x_load2) #[1, 'simple', 'list']

コメントアウトしてあるが、上の例でjson.decoder.JSONDecodeErrorが出て、しばらく原因がわからずはまった。
理由はjson.dump(x,f)を行った時点でEOFになっているので、直後にjson.load()を行うとエラーになってしまう。だからもう一度開き直せば問題なくjson.load()を行うことができる。

8.3 例外の処理
sys.exc_info()は現在処理中の例外を示す 3 個の値のタプルを返します。例外処理中とはexcept節を実行中であるフレームのこと。どのスタックフレームでも、最後に処理した例外の情報のみを参照できる。戻り値のタプルは以下の例を参照の通り、 (type, value, traceback)となっている。
traceback.print_exc()では以下のようにエラーメッセージを出力できる。
raise 例外クラス(メッセージ)で指定した例外を発生させることができる。except節の中で引数のないraiseを用いると、try文の中で発生した例外が再送出できる。
例外とは関係ないが、input("文字列")で文字列を出力しつつ標準入力を要求できる。

import sys
import traceback
try:
   x = int(input("数字を入れてください:"))
except:
   print("予期せぬエラー:",sys.exc_info())
   traceback.print_exc()
   print("例外再生成")
   raise

#結果
数字を入れてください:A
予期せぬエラー: (<class 'ValueError'>, ValueError("invalid literal for int() with base 10: 'A'"), <traceback object at 0x03825888>)
Traceback (most recent call last):
 File "c:/pywork/tutorial/test_exception.py", line 4, in <module>
   x = int(input("数字を入れてください:"))
ValueError: invalid literal for int() with base 10: 'A'
例外再生成
Traceback (most recent call last):
 File "c:/pywork/tutorial/test_exception.py", line 4, in <module>
   x = int(input("数字を入れてください:"))
ValueError: invalid literal for int() with base 10: 'A'

try except文にはオプションでelse節入れられる。else節の位置はすべてのexcept節より後ろでなければならない。else節はtry節が例外を送出しなかったときにのみ実行される。

for i in range(2):
   try:
       if i == 0:
           print("No Error!")
       else:
           raise Exception
   
   except Exception:
       print("Error!")
   
   else:
       print("エラーなしのときだけ実行。")

#結果
No Error!
エラーなしのときだけ実行。
Error!

追加のコードを付け加えるのは try 節よりも else 節の方がよい。 なぜなら、そうすることで try ... except 文で保護されたコードから送出されたもの以外の例外を過って捕捉してしまうという事態を避けられるから。
言われてみれば、特定のエラーに対して処理を中断させず続行させたいときにほかのエラーまで拾ってしまうのは避けたい場合がありそう。

8.5 ユーザ定義例外
プログラム上で新しい例外クラスを作成することで、独自の例外を指定することができる。
今後システムを作るときには使う機会があるかもしれないけど、今まで使ったことなかった。

class MyError(Exception):
   def __init__(self,value):
       self.value = value
   def __str__(self):
       return repr(self.value)

try:
   raise MyError(2*2)
except MyError as me:
   print("My Error occured, value:", me.value)

raise MyError("エラーだよ")

8.6. クリーンアップ動作を定義する
もし try 文の実行中に例外が発生して、その例外がexcept 節によって処理されなければ、 finally 節が実行された後に、その例外が再送出される。
知らなかったので試してみた。

try:
   raise Exception

finally:
   print("ジョブ終了!")
#結果
ジョブ終了!
Traceback (most recent call last):
 File "c:/pywork/tutorial/test_finally.py", line 2, in <module>
   raise Exception
Exception

8.6.1 オブジェクトに定義してあるクリーンアップ動作
with文を使うと、ファイルのようなオブジェクトを、使用後すぐに適切な方法でクリーンアップされることを保証した形で利用できる。
以下、サンプルコード。

with open("/pywork/tutorial/test.txt") as f:
   for line in f:
       print(line,end="")

#結果
first line
second line

print()のキーワード引数にendを指定したのは、改行コードがテキストファイルに含まれているため、デフォルトで改行するprint()をそのまま使うとfirst lineとsecond lineの間に空白行が出力されてしまうから。
with文のクリーンアップ動作はオブジェクトごとの定義によるらしい。
というわけで確かめてみた。

with open("/pywork/tutorial/test.txt") as f:
   print(type(f))
   print(dir(f))
#結果
<class '_io.TextIOWrapper'>
['_CHUNK_SIZE', '__class__', '__del__', '__delattr__', '__dict__', 
'__dir__', '__doc__', '__enter__', '__eq__', '__exit__', '__format__', 
'__ge__', '__getattribute__', '__gt__', '__hash__', '__init__', 
'__init_subclass__', '__iter__', '__le__', '__lt__', '__ne__', '__new__', 
'__next__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', 
'__sizeof__', '__str__', '__subclasshook__', '_checkClosed', 
'_checkReadable', '_checkSeekable', '_checkWritable', '_finalizing', 
'buffer', 'close', 'closed', 'detach', 'encoding', 'errors', 'fileno', 
'flush', 'isatty', 'line_buffering', 'mode', 'name', 'newlines', 'read', 
'readable', 'readline', 'readlines', 'reconfigure', 'seek', 'seekable', 
'tell', 'truncate', 'writable', 'write', 'write_through', 'writelines']

dir()の結果のリストは見やすいようにあえて手動で改行した。
__enter__()と__exit__()が実装されていればwith文を使うことができるらしい。

9.2 Pythonのスコープと名前空間
global 文を使うと、特定の変数がグローバルスコープに存在し、そこで再束縛されることを指示できる。 nonlocal 文は、特定の変数が外側のスコープに存在し、そこで再束縛されることを指示する。
nonlocal文は見たことも使ったこともなかったので、よくわからなかったのでサンプルコードを書いて試してみた。

def local():
   var = "local"
   print(var)

def non_local():
   nonlocal var
   var = "nonlocal"
   print(var)

var = "global"
print(var)
local()
non_local()

#結果
  File "c:/pywork/tutorial/non_local.py", line 6
   nonlocal var
   ^
SyntaxError: no binding for nonlocal 'var' found

SyntaxErrorになってしまったのでその原因調査のために以下のドキュメントを読んだ。

nonlocal 文は、列挙された識別子がグローバルを除く一つ外側のスコープで先に束縛された変数を参照するようにする。今回の例だと、def non_local()の一つ外側のスコープがグローバルなので、nonlocalとしてbindingできる変数が見つからなかったということ。

9.3.1 クラス定義の構文
以下、サンプルコード。

class Test:
   var = ""
   print(dir())

print(dir())

#結果
['__module__', '__qualname__', 'var']
['Test', '__annotations__', '__builtins__', '__cached__', '__doc__', 
'__file__', '__loader__', '__name__', '__package__', '__spec__']

特に意識してなかったけど、class定義の中で実行した関数も実行されるらしい。

9.3.2 クラスオブジェクト
クラスが __init__() メソッドを定義している場合、クラスのインスタンスを生成すると、新しく生成されたクラスインスタンスに対して自動的に __init__() を呼び出します。

class Kishi:
   #すべてのインスタンスに共通な変数を定義
   senkei = ""
   age = 0
   #Javaのコンストラクタのようなもの
   def __init__(self,senkei,age):
       self.senkei = senkei
       self.age = age

Habu = Kishi("居飛車",49)
print(Habu.senkei)
print(Habu.age)

classの中で変数を定義しなくても、__init__()で変数を初期化していれば、インスタンスで使うことができる。両方で定義している場合、__init__()で初期化した値で上書きされる。__init__()はインスタンスを作るときに必ず実行される。

9.3.3 インスタンスオブジェクト
インスタンスオブジェクトが理解できる唯一の操作は、属性の参照で、有効な属性の名前には二種類(データ属性およびメソッド)ある。

class Kishi:
   #すべてのインスタンスに共通な変数を定義
   senkei = ""
   age = 0
   #Javaのコンストラクタのようなもの
   def __init__(self,senkei,age):
       self.senkei = senkei
       self.age = age
   def print_status(self):
       return self.senkei,self.age

Habu = Kishi("居飛車",49)
print(Habu.senkei)
print(Habu.age)
print(Habu.print_status())
print(Kishi.senkei)
print(Kishi.age)
print(Kishi.print_status(Habu))

#結果
居飛車
49
('居飛車', 49)

0
('居飛車', 49)

Kishiクラスの変数とHabuインスタンスの変数は同名ではあるが当然違うものであり、Habu.print_status()とKishi.print_status(Habu)は上記の例では完全に等価であることが確認できた。

9.5 継承
派生クラスは基底クラスのメソッドをオーバーライドできる。

class Kishi:
   def tokui_senpou(self):
       print("居飛車が得意")

class Deshi(Kishi):
   def tokui_senpou(self):
       print("振り飛車が得意")

Tanigawa = Kishi()
Tonari = Deshi()
Tanigawa.tokui_senpou() #居飛車が得意
Tonari.tokui_senpou() #振り飛車が得意

ちなみに、Pythonにオーバーロードはない。
以下サンプルコード。

def overload():
   print("普通の関数")

def overload(word):
   print(word)

#overload() TypeError: overload() missing 1 required positional argument: 'word'
overload("Pythonにはオーバーロードはない")

単純にあと勝ちになる模様。

9.6 プライベート変数
オブジェクトの中からしかアクセス出来ない "プライベート" インスタンス変数は、 Python にはない。

class Sample:
   __privateName = "private"
   publicName = "public"

sample = Sample()
print(dir(sample))
print(sample._Sample__privateName)

#結果
['_Sample__privateName', '__class__', '__delattr__', '__dict__', '__dir__',
 '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', '__gt__',
 '__hash__', '__init__', '__init_subclass__', '__le__', '__lt__',
 '__module__', '__ne__', '__new__', '__reduce__', '__reduce_ex__',
 '__repr__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__',
 '__weakref__', 'publicName']
private

上記の通り、インスタンスからアクセス可能だった。ただ、名前が__privateNameではなく、_Sample__privateNameになっている。これはname mangling(名前マングリング)というサポート機能らしい。_(ClassName)__privateNameという名前に置き換えることで、サブクラスで名前衝突しないようになっている。

9.9 iterator(反復子)
コンテナオブジェクトの多くはfor文でループできる。for文はコンテナオブジェクトにiter()をコールする。
試しにiteratorを作ってビルトイン関数next()を使ってみる。

list = list(range(5))
s = "abc"
iterator1 = iter(list)
iterator2 = iter(s)
print(iterator1)
print(iterator2)
while True:
   try:
       element = next(iterator1)
   except Exception as e:
       print(type(e))
       break
   else:
       print(element)

#結果
<list_iterator object at 0x02BE3208>
<str_iterator object at 0x02BE3238>
0
1
2
3
4
<class 'StopIteration'>

ドキュメントの内容と微妙に違い、iteratorを表示させると単にiteratorではなくlist_iteratorやstr_iteratorと使い分けて表示されるらしい。
iteratorのふるまいを自作クラスに追加することもできる。

class Reverse:
   def __init__(self,data):
       self.data = data
       self.index = len(data)
   def __iter__(self):
       return self
   def __next__(self):
       if self.index == 0:
           raise StopIteration
       self.index = self.index -1
       return self.data[self.index]

rev = Reverse("abc")
print(rev.data)
for char in rev:
   print(char)

#結果
abc
c
b
a

revインスタンスを作った段階では、rev.dataに"abc"が代入されるだけだが、for文でiter()とnext()がコールされるので上記の結果が得られるということがわかる。

9.10 ジェネレーター
上の例で得られて結果を別の方法で得たいとき、ジェネレーターを使えばより簡単に実装できる。
yieldによってgenerator(ジェネレータ)を作ることができるが、generatorは__iter__()と__next__()を自動的に生成する。

def reverse(data):
   for index in range(len(data)-1,-1,-1):
       yield data[index]

rev = reverse("abc")
print(rev)

for char in rev:
   print(char)

#結果
<generator object reverse at 0x00802D80>
c
b
a

ちなみにyieldをreturnに変えると結果はcだけとなる。理由はreturnだと関数を抜けてしまうから。

9.11 ジェネレーター式
リスト内包の[]を()に変えればgenerator式になる。

list = [i**2 for i in range(10)]
generator = (i**2 for i in range(10))
print(list)
print(generator)
print(sum([i**2 for i in range(10)]))
print(sum(i**2 for i in range(10)))

#結果
[0, 1, 4, 9, 16, 25, 36, 49, 64, 81]
<generator object <genexpr> at 0x02E72DB8>
285
285

sum値の結果はリスト内包を使った場合とgenerator式を使った場合で同じだが、メモリ使用はgenerator式のほうが少ないらしい。

以下、書き足す予定。(2020/1/25)

この記事が気に入ったらサポートをしてみませんか?