初めに
この記事は、Python その2 Advent Calendar 2015の9日目の記事です。
書こうと思ったわけ
僕がPython初めて書いたのが4年前でとてもよい言語だと思い、簡単なコードなどはいつもPythonで書いていました。
初めに書いたのはPython2.7でしたが、調べていたらPython3なるものがあり、新しいもの好きだったので本気で学ぼうとし始めた時はPython3.3を使ってました。
なので、2系の話などはほとんど知らず、わざわざ古い(といっても当時は2系がメインでしたが)ものを学ぶ必要もないと思ってました。
が。
2系のライブラリが3系に対応していなかったり、2系で書かれていて3系に自分で置き換えなければならないなど色々問題がありました。
とまぁ、とにかく2系と3系の違いを学習ついでにまとめて見ようと思った次第です。
(すでに3系がでてから7年位経っているのは置いといて・・・)
(さらに、他の素晴らしい方々が素晴らしい記事を書いていらっしゃるのも置いといて・・・(T_T))
本題に入る前に・・
- 書き始めた際はPython2からPython3.5までの経過を書こうと思ったのですが、Python3.0まででかなりの長さになったのでPython3.0からPython3.5までの経過は別記事に書きます。
- コードを実行したバージョンは左上に書いてあります。
- Pythonの知識がある程度ある方向けになっています。ちょろっと触ったことある方なら多分大丈夫!
- コードの中に突如出てくるアンダーバーは一つ前の実行結果が格納されています。
>>> 1+1
2
>>> _
2
>>> 2+3
5
>>> _ -3
2
>>> type(_)
<class 'int'>
>>> _
<class 'int'>
それでは、結構長いですが(笑)
頑張ってきましょう!
Python2 -> Python3.0
printが文から関数に変更
>>> print 'Hello'
Hello
>>> print('Hello')
Hello
Python3での定義はこんな感じらしいです。
def print(*args, sep=' ', end='\n', file=None)
タプルを表示させようとした時とか困るのかな。
>>> print ('item1', 'item2')
('item1', 'item2')
>>> print ('item1', 'item2')
item1 item2
>>> print(('item1', 'item2'), )
('item1', 'item2')
リストの代わりにViewsやイテレータを返す
Viewsってナンノコッチャ。
まぁ、結果を見ればわかるでしょう。
dictのメソッド、keys, items, valuesはViewsを返す
>>> d = {"key1": "value1"}
>>> d.keys()
['key1']
>>> type(_)
<type 'list'>
>>> d.values()
['value1']
>>> d.items()
[('key1', 'value1')]
>>> d = {"key1": "value1"}
>>> d.keys()
<dict_keys object at 0x7f0da5268e18>
>>> type(_)
<class 'dict_keys'>
>>> d.values()
<dict_values object at 0x7f0da5268e18>
>>> d.items()
<dict_items object at 0x7f0da3a44638>
Python2では全てリストで、Python3ではdict_keys
などのオブジェクトとして返ってきてますね。
これはイテレート可能なオブジェクトで、これをViewsと呼んでいるのでしょう。
Python3でリストとして欲しければlist()
でラップしましょう。
>>> list(d.keys())
['key1']
dictのメソッド、iterkeys, iteritems, itervaluesの廃止
>>> d.iterkeys()
<dictionary-keyiterator object at 0x7f5b586155d0>
>>> type(_)
<type 'dictionary-keyiterator'>
>>> d.iterkeys()
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
AttributeError: 'dict' object has no attribute 'iterkeys'
まー当たり前ですよね。keysがイテレーター返すんですから。
map, filter関数がイテレーターを返す
>>> f1 = lambda x : x + 1
>>> f2 = lambda x : x%2 == 0
>>> l = [1, 2, 3]
>>> map(f1, l)
[2, 3, 4]
>>> type(_)
<type 'list'>
>>> filter(f2, l)
[2]
>>> type(_)
<type 'list'>
>>> iter_check = lambda obj : hasattr(obj, '__iter__') and hasattr(obj, '__next__')
>>> isIterable = lambda obj : hasattr(obj, '__iter__')
>>> f1 = lambda x : x + 1
>>> f2 = lambda x : x%2 == 0
>>> l = [1, 2, 3]
>>> map(f1, l)
<map object at 0x7f0da5261950>
>>> map_obj = _
>>> type(map_obj)
<class 'map'>
>>> iter_check(map_obj)
True
>>>
>>> filter(f2, l)
<filter object at 0x7f0da52619d0>
>>> filter_obj = _
>>> type(filter_obj)
<class 'filter'>
>>> iter_check(filter_obj)
True
lambdaの所の内容はあまり気にしなくて大丈夫です(笑)
イテレート可能なオブジェクトなのかイテレーターなのかなどをチェックする関数です。
Python2ではリスト、Python3ではイテレーターが返ってきてますね。
Python3のrange関数はイテレータオブジェクトを返す(Python2でのxrange)。xrangeは廃止
>>> iter_check = lambda obj : hasattr(obj, '__iter__') and hasattr(obj, '__next__')
>>> isIterable = lambda obj : hasattr(obj, '__iter__')
>>> type(range(1,10))
<type 'list'>
>>> type(xrange(1,10))
<type 'xrange'>
>>> iter_check(xrange(1,10))
False
>>> isIterable(xrange(1,10))
True
>>> type(range(1,10))
<class 'range'>
>>> type(iter(range(1,10)))
<class 'range_iterator'>
>>> xrange(1,10)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
NameError: name 'xrange' is not defined
Python3ではrangeがリストではなくイテレート可能なオブジェクトを返しています。
そしてxrangeはお亡くなりになりました。チーン
zip関数はイテレーターを返す
>>> zip([1,2,3], [2,3,4])
[(1, 2), (2, 3), (3, 4)]
>>> type(_)
<type 'list'>
>>> zip([1,2,3], [2,3,4])
<zip object at 0x7f0da3a40cf8>
>>> iter_check(_)
True
Python3ではイテレーターを返していますね。
順序比較
比較演算子(<, >, <=, >=)は、そのオペランドが自然な順序付けを持たない場合TypeErrorを送出する
>>> 1 < 'a'
True
>>> 0 > None
True
>>> len <= len
True
>>> None < None
False
>>> 1 < 'a'
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: unorderable types: int() < str()
>>> 0 > None
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: unorderable types: int() > NoneType()
>>> len <= len
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: unorderable types: builtin_function_or_method() <= builtin_function_or_method()
>>> None < None
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: unorderable types: NoneType() < NoneType()
これ、自分初めて知りました。Python2ひどいですね・・・。
ですがPython3ではしっかり例外を投げてくれるので安心ですね!
sorted関数とlistのsortメソッドは比較関数を与えるcmp引数を取らなくなった
Python2での定義は以下の通り
sorted(iterable[, cmp[, key[, reverse]]])
s.sort([cmp[, key[, reverse]]])
Python3での定義は以下の通り
sorted(iterable[, key][, reverse])
sort(*, key=None, reverse=None)
cmp引数、なくなってますね。
また、Python3でのlistのsortメソッドの引数はキーワード引数で渡す必要があります。
cmp関数は廃止され、__cmp__
メソッドはサポートされない
これは例を出すとかではないですよねー。
ソートには__lt__
メソッドを使用してくれとのことです。
整数について
基本的にlongはintに改名された(が、古いlong型とほぼ同様に振る舞う)
>>> type(10**100)
<type 'long'>
>>> type(10**100)
<class 'int'>
Pythonではint型として扱われていますね。
1/2のような式はfloatを型を返す。少数点以下切捨て場合は1//2のような式にすること
>>> 1/2
0
>>> type(1/2)
<type 'int'>
>>> type(1/2)
<class 'float'>
>>> 1//2
0
>>> type(1//2)
<class 'int'>
Python3で除算を行いintで返したい場合は//演算子を使いましょう。
整数の上限がなくなったためsys.maxint定数は削除された
>>> sys.maxint
9223372036854775807
>>> sys.maxint
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
AttributeError: 'module' object has no attribute 'maxint'
削除されてますね。
long整数のreprメソッドは末尾にLをつけない
>>> 10**20
100000000000000000000L
>>> 10**20
100000000000000000000
Python3ではLが末尾についていませんね。
8進数リテラルが0720のような表記から0o720というような表記に変更
>>> 0720
464
>>> 0o720
464
>>> 0720
File "<stdin>", line 1
0720
^
SyntaxError: invalid token
>>> 0o720
464
これは0xや0bなどの表記に統一された形でしょうか。
ユニコードと8bitの代わりにテキストとデータへ
全てのテキストはUnicodeに
タイトル通り。
これによって以前までのユニコードテキストのリテラルu"..."
は使用できなくなった。
>>> u"unicode"
u'unicode'
>>> u"unicode"
File "<stdin>", line 1
u"unicode"
^
SyntaxError: invalid syntax
テキストはstr型、dataはbytes型
>>> type("test")
<class 'str'>
>>> type(b"test")
<class 'bytes'>
バイナリデータのリテラルにはb"..."
を使用する。
テキストとdataを混ぜるとTypeErrorを送出する
>>> "str" + b"bytes"
'strbytes'
>>> "str" + b"bytes"
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: Can't convert 'bytes' object to str implicitly
Python2まじかよ・・(2回目)
こんなの発狂ものですね。。Python3エラい!
テキストとdata間の明示的変換
>>> "str".encode()
b'str'
>>> bytes("str", "utf-8")
b'str'
>>>
>>> b"bytes".decode()
'bytes'
>>> str(b"bytes", "utf-8")
'bytes'
str, bytes間の変換ですね。
まぁ、Pythonでのencode、decodeの記事は山ほどありますからそちらを参考に・・・。
raw文字列内でもバックスラッシュはそのままの文字で解釈される
>>> print(ur"\u20ac")
€
>>> print(r"\u20ac")
\u20ac
Python2まじかよ、とまでは言いませんが、おいっ!
って感じですね。raw文字列のいみねーじゃん!
まぁ、こういう挙動をして欲しい場合もあるかもしれませんがPython3の挙動のほうがいいですよねー。
basestringという抽象クラスが削除
>>> str.__base__
<type 'basestring'>
>>> bytes.__base__
<type 'basestring'>
>>> str.__base__
<class 'object'>
>>> bytes.__base__
<class 'object'>
Python2ではbasestringという抽象クラスがあったのですね。それを継承してstr、bytes型ができてたということですね。
Python3では直接それぞれのクラスが定義されていますね。
構文の変更の概要
関数アノテーションの追加
>>> def f(a: "int_value", b: "int_value default value one" = 1) -> "added_value":
... return a+b
...
>>> f.__annotations__
{'a': 'int_value', 'b': 'int_value default value one', 'return': 'added_value'}
関数の引数や返り値にアノテーションを追加することができます。
上の例では文字列だけですが、式を書くことができるので、1+1
やint
などを書くことができます。
>>> i=2
>>> j=3
>>> def f(a: i*j, b: i+j) -> 1+1:
... return a+b
...
>>> f.__annotations__
{'a': 6, 'b': 5, 'return': 2}
>>> i=10
>>> j=10
>>> f.__annotations__
{'a': 6, 'b': 5, 'return': 2}
式は関数定義時に評価されます。当たり前っちゃ当たり前ですね。。
キーワードのみの引数
>>> def f(a, *, b):
... return a+b
...
>>> def g(*, a, b):
... return a*b
...
>>> f(1, 2)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: f() takes exactly 1 positional argument (2 given)
>>> f(1, b=2)
3
>>> g(1, b=2)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: g() takes exactly 0 non-keyword positional arguments (1 given)
>>> g(a=1, b=2)
2
>>> g(b=1, a=2)
2
アスタリスク*
を使用してそれ以降に列挙される引数をキーワード引数限定とすることができます。
これ日本語の文献すっごく少なくって最初見た時は随分と困りました(笑)
クラス定義の基底クラスのリストの後にキーワード引数を渡せる
>>> class MClass(type):
... def __new__(cls, name, bases, namespace, **kwds):
... result = type.__new__(cls, name, bases, dict(namespace))
... result.members = tuple(namespace)
... return result
... def __init__(self, *args, **kargs):
... print("args: " + str(args))
... print("kargs: " + str(kargs))
...
>>> class A(object, metaclass=MClass, a=1, b=2, c=3):
... def one(self): pass
...
args: ('A', (<class 'object'>,), {'__module__': '__main__', 'one': <function one at 0x7f62d071c408>})
kargs: {'a': 1, 'c': 3, 'b': 2}
これ、内容が少しヘビーですね・・・。
ともかく基底クラスの後にキーワード引数を受け取ることができます。
ただし、typeではキーワード引数を受け取ることができないので、受け取れるようなmetaclassを自作しなければいけませんが。。(MClassがそれです)
nonlocal文
>>> def counter(init=0):
... def cntup():
... nonlocal init
... init+=1
... return init
... return cntup
...
>>> c = counter()
>>> c()
1
>>> c()
2
>>> c()
3
nonlocal文は指定された識別子を一つ上のスコープにある変数を参照できるようにします。
要はクロージャーですかね。
例ではcounter関数の引数initをnonlocal文で参照していますね。
Python2ではlistなどを用いてゴニョゴニョしなければできなかったのですが、Python3では簡潔に書けますね。
(参照するだけであればnonlocalやPython2でのlistなどは必要ない。外側のスコープの変数に束縛を行う際に必要)
>>> c.__closure__
(<cell at 0x7f62d071d5c8: int object at 0x7a3e20>,)
>>> c.__closure__[0].cell_contents
3
ついでにこのような形でクロージャーを参照できます。
拡張Iterable Unpacking
>>> a, *rest, b = range(5)
>>> a
0
>>> rest
[1, 2, 3]
>>> b
4
restにaとbにUnpack後、余ったものをリストとしてUnpackされる。
このrestは空リストとしてUnpackされることもある。
>>> a, *rest, b = range(2)
>>> a
0
>>> rest
[]
>>> b
1
これはすごい・・・。
変更された構文
例外のキャッチの構文
>>> try:
... raise Exception("test")
... except Exception, e:
... print "catch exception!"
...
catch exception!
>>> try:
... raise Exception("test")
... except Exception as e:
... print("catch exception!")
...
catch exception!
カンマからasキーワードに変更されていますね。
新たなraise文のシンタックス
>>> try:
... raise Exception("test")
... except Exception as e:
... raise RuntimeError("catch exception!") from e
...
Traceback (most recent call last):
File "<stdin>", line 2, in <module>
Exception: test
The above exception was the direct cause of the following exception:
Traceback (most recent call last):
File "<stdin>", line 4, in <module>
RuntimeError: catch exception!
from節が追加され、例外の連鎖を表現できるようになりました。
また、except節で例外が送出された場合も暗黙的に働くそうですよ。
>>> try:
... 1/0
... except Exception as e:
... raise RuntimeError("raise exception.")
...
Traceback (most recent call last):
File "<stdin>", line 2, in <module>
ZeroDivisionError: int division or modulo by zero
During handling of the above exception, another exception occurred:
Traceback (most recent call last):
File "<stdin>", line 4, in <module>
RuntimeError: raise exception.
True, Falseが予約語に
>>> True = 0
>>> False = 1
>>> True
0
>>> False
1
>>> True and True
0
>>> True=0
File "<stdin>", line 1
SyntaxError: assignment to keyword
>>> False=1
File "<stdin>", line 1
SyntaxError: assignment to keyword
恐怖!!
ってかまぁTrue,Falseに代入したり、普通は!しないですけどね。
メタクラスのシンタックスの変更
>>> class M(type):
... pass
...
>>> class C:
... __metaclass__ = M
...
>>> C.__class__
<class '__main__.M'>
>>> class M(type):
... pass
...
>>> class C(metaclass=M):
... pass
...
>>> C.__class__
<class '__main__.M'>
Python2では__metaclass__変数、Python3では基底クラスを指定するとこにキーワード引数として渡します。
Python3ではPython2のような手段はサポートしていません。
ここで初めて知ったのが、Python2ではclassでobjectを明示的に継承しないといけなかったのですが、メタクラスを利用することでobjectを継承したクラスができることでした。なるほど!
>>> class A:
... pass
...
>>> A.__bases__
()
>>> class C:
... __metaclass__ = type
...
>>> C.__bases__
(<type 'object'>,)
ellipsisはどこででも原始的な式として使用できる
>>> ...
Ellipsis
>>> def e():
... ...
省略。(まじで言うことがない・・・)
削除された操作
削除されたものなのでさーっといきます。
タプル引数のアンパック
>>> def foo(a, (b,c)):
... return a+b+c
...
>>> t = 2,3
>>> foo(1, t)
6
>>> def foo(a, (b,c)):
File "<stdin>", line 1
def foo(a, (b,c)):
^
SyntaxError: invalid syntax
読みづらいですねぇ・・・。
Python3では1行目から怒られます。
バッククオートについて
>>> i = 123
>>> `i`
'123'
>>> i=123
>>> `i`
File "<stdin>", line 1
`i`
^
SyntaxError: invalid syntax
どうやら__repr__メソッドが呼ばれていたようです。
execについて
>>> exec "print('python2')"
python2
>>> exec("print('python2')")
python2
>>> exec "print('python3')"
File "<stdin>", line 1
exec "print('python3')"
^
SyntaxError: invalid syntax
>>> exec("print('python3')")
python3
Python2ではexec文でした。関数表記も大丈夫です。
Python3では予約語ではなくなり、関数として残りました。
<>演算子
>>> 1 <> 2
True
>>> 1 <> 1
False
>>> 1 <> 2
File "<stdin>", line 1
1 <> 2
^
SyntaxError: invalid syntax
!=でいいじゃないですかねぇ。
リテラル周り
>>> u"python2"
u'python2'
>>> 100L
100L
>>> u"python3"
File "<stdin>", line 1
u"python3"
^
SyntaxError: invalid syntax
>>> 100L
File "<stdin>", line 1
100L
^
SyntaxError: invalid syntax
上の方で既に述べている内容ですね。
トップレベル以外でのfrom module import *について
>>> from sys import *
>>> def f():
... from os import *
...
<stdin>:1: SyntaxWarning: import * only allowed at module level
>>> from sys import *
>>> def f():
... from os import *
...
File "<stdin>", line 1
SyntaxError: import * only allowed at module level
WarningからErrorになってますね。なんでPython2はWarningで止めたんだろうか・・・
その他の変更
演算子と特殊メソッド
!=
は==
と逆の結果を返す(==
がNotImplementedを返さない場合は)
>>> class C:
... def __init__(self, a):
... self.a = a
...
... def __eq__(self, other):
... return self.a == other.a
...
>>> a = C(1)
>>> b = C(1)
>>> c = C(2)
>>> a == b
True
>>> a == c
False
>>> a != b
True
>>> a != c
True
>>> class C:
... def __init__(self, a):
... self.a = a
...
... def __eq__(self, other):
... return self.a == other.a
...
>>> a = C(1)
>>> b = C(1)
>>> c = C(2)
>>> a == b
True
>>> a != b
False
>>> a == c
False
>>> a != c
True
これちょっと意外でした。
Python2の!=
は__ne__()
を実装していないのでオブジェクトが同一のものかを比較しています。
a, b, cはどれも別物なので結果がTrueになっています。
自分はてっきりAttributeErrorが出るのかと思ってました・・・。少し考えれば分かる話でしたね。
unbound methodsからfunction objectへ
>>> class C:
... def f(self):
... pass
...
>>> C.f
<unbound method C.f>
>>> class C:
... def f(self):
... pass
...
>>> C.f
<function f at 0x100a35270>
はい。Python3がただの関数として扱われています。
unbound methodはインスタンスに紐付いていないメソッドということでしょう。
Python3のほうが単純明快で分かりやすいですね
__getslice__()
系のメソッドは削除され、__getitem__()
系のメソッドにsliceオブジェクトが渡される
>>> class C:
... def __getslice__(self, i, j):
... print i, j
... raise NotImplementedError()
...
>>> a = C()
>>> a[0:2]
0 2
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "<stdin>", line 4, in __getslice__
NotImplementedError
>>> class C:
... def __getitem__(self, obj):
... print(obj)
... print(type(obj))
... raise NotImplementedError()
...
>>> a = C()
>>> a[0:2]
slice(0, 2, None)
<class 'slice'>
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "<stdin>", line 5, in __getitem__
NotImplementedError
- getslice
- setslice
- delslice
が
- getitem
- setitem
- delitem
を代わりに呼ぶようになり、スライスオブジェクトが渡されるように変わりました。
例では実装をしていませんが、引数にスライスオブジェクトが渡されているのがわかりますね。
next()
メソッドは__next__()
に改名された
>>> class C:
... def __iter__(self):
... return self
...
... def next(self):
... return 0
...
>>> a = C()
>>> i = iter(a)
>>> next(i)
0
>>> i.next()
0
>>> class C:
... def __iter__(self):
... return self
...
... def __next__(self):
... return 0
...
>>> a = C()
>>> i = iter(a)
>>> next(a)
0
>>> i.__next__()
0
next()
から__next__()
が呼ばれるように変わっていますね。
__oct__()
と__hex__()
は削除され、__index__()
を使用すること
>>> class C:
... def __oct__(self):
... return "07"
...
... def __hex__(self):
... return "0x7"
...
>>> a = C()
>>> hex(a)
'0x7'
>>> oct(a)
'07'
>>> class C:
... def __index__(self):
... return 7
...
>>> a = C()
>>> hex(a)
'0x7'
>>> oct(a)
'0o7'
__oct__()
、__hex__()
は数値を文字列に変換して返すメソッドなのですが、__index__()
は数値を返すだけです。後はPythonが良しなに変換してくれます。
Python3素晴らしい〜。
(例は定数返してますがご勘弁を・・・)
__nonzero__()
は__bool__()
に改名された
>>> class C:
... def __nonzero__(self):
... return True
...
>>> a = C()
>>> bool(a)
True
>>> class C:
... def __bool__(self):
... return True
...
>>> a = C()
>>> bool(a)
True
__nonzero__()
ってどういう流れで名前が決まったんですかね・・・。
Python3では__bool__()
が呼ばれています。分かりやすい。
Builtins
super()
の追加
>>> class SuperClass:
... pass
...
>>> class SubClass(SuperClass):
... def findSuperClass(self):
... print("call super(): ", super())
... print("call super(SubClass, self)", super(SubClass, self))
...
>>> i = SubClass()
>>> i.findSuperClass()
call super(): <super: <class 'SubClass'>, <SubClass object>>
call super(SubClass, self) <super: <class 'SubClass'>, <SubClass object>>
super()
は引数無しで呼び出すことができ、適切なクラスとインスタンスを自動的に選んでくれます。
引数がある時と処理は特には変わりません。
raw_input()
がinput()
に改名
>>> input()
test
'test'
input()
は標準入力から1行読み取り、改行を除いた文字列を返します。
Python2でのinput()
を実行したい場合はeval(input())
で実現できます。
まとめ
ぶっちゃけPython3では
- printが関数に
- keys, items, valuesメソッドはイテレート可能なオブジェクトを返す
- 文字列の標準がUnicodeになり型が
str
- xrangeがrangeに
辺りを覚えておけば殆ど支障はないと思います。
また、この記事で省いた変更点がいくつかあるのですが、ニッチな内容であまり重要でない気がしたものを全体の1割ほど削っています。知りたい方はWhat's New in Python3.0読んでください(丸投げ)。
終わりに
記事を書いてて思ったことですが、Python2で罠にハマりそうな点がいくつかあってびっくりしました。
自分でやり始めたのがPython3からで良かったです(笑)
そしてまだPython2から乗り換えてない方は便利にこれを機会に覚えていただいてPython3に乗り換えると幸せなPythonライフが待っているのではないでしょうか(笑)
(Python2から3とかもう随分たっててまとめ系の記事は沢山ありますが・・・)
冒頭でも書きましたが、Python3.0からPython3.5までの経過は別記事で書こうと思います。
Python3.0からPython3.5までの経過はPython その2 Advent Calendar の16日にのせるつもりです。
また、変な点や間違い、こうした方が良いなどありましたらコメントくださるとありがたいです!
明日は空きで、11日は@FGtatsuroです!
それでは良きPython3ライフを!!