metaclass
などの漠然とした知識/利用はあったけど具体的にどこまでの話なのか分からず。Metaprogramming is the writing of computer programs with the ability to treat programs as their data. It means that a program could be designed to read, generate, analyse or transform other programs, and even modify itself while running. In some cases, this allows programmers to minimize the number of lines of code to express a solution (hence reducing development time) or it gives programs greater flexibility to efficiently handle new situations without recompilation.
メタプログラミングとは、データとしてプログラム自体を処理できるプログラムを記述することです。プログラムからプログラムを、読んだり、分析したり、他のプログラムに変換したり、さらに実行時に自らのプログラムを変更することを意味します。これは、いくつかのケースで、プログラマに最小限のコードで、解決策を記述できるようにしてくれます。 (つまり開発時間の短縮につながる) または、プログラムを再コンパイルする必要なしに、柔軟に新しい状況に対応できるようにしてくれます。
type
クラス … 全てのクラスの雛形type
にオブジェクトを渡すと型が帰ってきますが関数ではありません。type
クラス のインスタンス1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 | import inspect
print(inspect.isclass(object)) # => True
print(isinstance(object, type)) # => True
print(inspect.isclass(type)) # => True
print(isinstance(type, object)) # => True
class Spam: pass
print(inspect.isclass(Spam)) # => True
print(isinstance(Spam, type)) # => True
print(isinstance(Spam, object)) # => True
spam = Spam()
print(inspect.isclass(spam)) # => False
print(isinstance(spam, type)) # => False
print(isinstance(spam, Spam)) # => True
print(isinstance(spam, object)) # => True
print(isinstance(type, type)) # => True
|
type
について詳しく見る1 2 3 | class type(name, bases, dict)
# refs http://docs.python.jp/3/library/functions.html#type
|
name
… クラス名bases
… 継承元の親クラス(tuple)dict
… 名前空間 => 属性(メソッド/プロパティ)を管理する辞書type
から動的にクラスが生成可能self
を受け取る関数であればいい1 2 3 4 5 6 7 8 9 | # 前のスライドのクラス定義と同義
def hello(self):
print('Hello! My name is {}'.format(self.name))
# type を call することで クラスが生成できる
Spam = type('Spam', (), dict(name='tell-k', hello=hello))
spam = Spam()
spam.hello() # => 'Hello! My name is tell-k'
|
type
のインスタンス1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 | print(isinstance(str, type)) # => True
print(isinstance(int, type)) # => True
print(isinstance(float, type)) # => True
print(isinstance(dict, type)) # => True
print(isinstance(list, type)) # => True
print(isinstance(tuple, type)) # => True
print(isinstance(set, type)) # => True
print(isinstance(types.FunctionType, type)) # => True
print(isinstance(types.MethodType, type)) # => True
print(isinstance(None, type)) # => True
# 組み込みの名前を持たない型(functionとか) 全てtypesに定義してある
# refs http://docs.python.jp/3/library/types.html
# type 自体も typeのインスタンス
print(isinstance(type, type)) # => True
|
type
のインスタンスtype
により 動的に定義することが可能type
のインスタンスである1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | # python --
# 動的にメソッド実行 --
str_obj = '1,2,3'
getattr(str_obj, 'split')(',') # => ["1", "2", "3"]
# = str_obj.split(',') と等価
# 動的にメソッド定義 --
class Spam: pass
def hello(self): # selfを受け取る関数
print('Hello')
Spam.hello = hello # クラスにアサインするだけ
spam = Spam()
spam.hello() # => Hello
|
method_missing
というメソッドがクラスに備わっています。method_missing
によって 該当のないメソッドの呼び出し に応答する1 2 3 4 5 6 7 8 9 10 | # ruby --
class Spam
def method_missing(name, *args, &block)
args[0].reverse # 文字列を反転する
end
end
spam = Spam.new()
# 存在しないメソッド「ghost_reverse」をcall -> method_missing 実行
p spam.ghost_reverse('spam') # => 'maps'
|
__getattr__
という スペシャルメソッド が利用できる__getattr__
は 該当のない 属性のアクセス時 に呼ばれるmethod_missing
はメソッドが呼び出されて初めて実行されます1 2 3 4 5 6 7 8 9 10 11 | # python --
class Spam:
def __getattr__(self, name):
def _reverse(*args):
return args[0][::-1]
return _reverse
spam = Spam()
# spam.ghost_reverse にアクセス -> _reverse 関数がreturn -> _reverse関数をcall
print(spam.ghost_reverse('spam')) # => 'maps'
|
1 2 3 4 5 6 | # python --
api = bitly.BitLy(API_USER, API_KEY)
res = api.shorten(longUrl='http://github.com/larsks')
print res['http://github.com/larsks']['shortUrl']
# 実は shorten というメソッドは存在しない
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 | # python -- 注: python2 で書かれてます
def __getattr__ (self, func):
def _ (**kwargs):
# self.api_url + func
# 'http://api.bit.ly/v3' + 'shorten' => 'http://api.bit.ly/v3/shorten'
url = '/'.join([self.api_url, func])
# kwargs -> longUrl はクエリパラメータとしてそのまま渡す
query_string = self._build_query_string(kwargs)
fd = urllib.urlopen(url, query_string)
res = json.loads(fd.read())
if res['status_code'] != 200:
raise APIError(res['status_code'], res['status_txt'], res)
elif not 'data' in res:
raise APIError(-1, 'Unexpected response from bit.ly.', res)
return res['data']
return _
# refs https://github.com/hellp/bitlyapi/blob/master/bitlyapi/bitly.py
|
1 2 3 4 5 6 7 8 9 10 | # ruby --
spam1 = Spam.new()
spam2 = Spam.new()
def spam1.bye
p 'ByeBye'
end
spam1.bye() # => 'ByeBye'
spam2.bye() # => NoMethodError
|
# python --
class Spam:
def hello(self):
print('Hello')
s = Spam()
print(Spam.hello) # => <function Spam.hello at 0x1083388c8>
print(s.hello) # => <bound method Spam.hello of <__main__.Spam object at 0x1083349b0>>
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | # python --
def bye(self):
print('ByeBye')
spam = Spam()
spam.bye = bye
spam.bye() # => TypeError: bye() missing 1 required positional argument: 'self'
# Bound Method を作るためにMethodTypeを利用する
from types import MethodType
# 2016/09/25 修正 MethodTypeの第2引数はinstanceを渡すの正しいので修正
# spam.bye = MethodType(bye, Spam) <- Spamクラスではなくspamを渡すのが正しい
spam.bye = MethodType(bye, spam)
spam.bye() # => "ByeBye"
|
unittest.mock.patch
等) でおなじみ1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | # spam.py ----
def hello():
return 'Hello! Spam'
# ham.py ----
import spam
def patch_hello():
return 'HamHamHamHam!'
spam.hello = patch_hello # helloを差し替える
# main.py -----
import ham # パッチがあたる
import spam
spam.hello() # => 'HamHamHamHam!'
|
with
を 利用するなりして影響範囲を限定的にするのが良い1 2 3 4 5 6 7 8 9 10 11 12 | # python --
class PatchHello:
def __enter__(self):
self.original_hello = spam.hello # オリジナルを保存
spam.hello = patch_hello # 差し替え
return self
def __exit__(self, exec_type, exec_value, traceback):
spam.hello = self.original_hello # オジリナルを復元
with ham.PatchHello():
print(spam.hello()) # => 'HamHamHamHam!'
spam.hello() # => 'Hello! Spam'
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | # python --
from gevent import monkey
monkey.patch_all()
# いろんな標準パッケージ/モジュールにパッチが当たる
# patch_os
# patch_time
# patch_thread
# patch_sys
# patch_socket
# patch_select
# patch_ssl
# patch_subprocess
# patch_signal
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 | pathc_module('os')
def patch_module(name, items=None):
# 1. 「gevent.os」 をimport ---
gevent_module = getattr(__import__('gevent.' + name), name)
module_name = getattr(gevent_module, '__target__', name)
# 2. 標準の「os」をimport ---
module = __import__(module_name)
if items is None:
# 3. 「gevent.os.__implements__」 パッチ対象を取得(gevent.os.fork) ---
items = getattr(gevent_module, '__implements__', None)
if items is None:
raise AttributeError('%r does not have __implements__' % gevent_module)
for attr in items:
# 4. 「gevent.os.fork」 -> 「os.fork」 にセット
patch_item(module, attr, getattr(gevent_module, attr))
# path_itemではオリジナルが保存されてるので後で戻すこと可能
return module
# refs https://github.com/gevent/gevent/blob/master/src/gevent/monkey.py#L151
|
type
が metaclass
として設定されてますtype
以外のクラスに差し替え可能abc.ABCMeta
などがあります。hello
メソッド を追加する Metaclass を作成metaclass
を指定するだけです。1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 | # python --
class HelloMeta(type):
def __new__(cls, name, bases, attrs):
def _hello(self):
return print('My name is {}.'.format(self.name)
# 名前空間(クラス辞書) にhelloメソッドをセット
attrs['hello'] = _hello
return super().__new__(cls, name, bases, attrs)
class Spam(metaclass=HelloMeta): # <= metaclasss を指定
name = 'Spam'
spam = Spam()
spam.hello() # => "My name is Spam"
|
__prepare__
が使えますdict
から OrderedDict
に変えるなど。View
というクラスがあるView.methods
= アクセスを許可するHTTPメソッドのリストが指定可能MethodView
は 実装されたメソッド名から自動的に methods
という属性を設定してくれる1 2 3 4 5 6 7 8 9 10 11 | # python --
from flask.views import MethodView
class GetAndPostMethodView(MethodView):
def get(self): # GETアクセスが可能になる
def post(self): # POSTアクセスが可能になる
# 定義したメソッド名が「methods」として自動で登録される
GetAndPostMethodView.methods # => ['GET', 'POST']
|
methods
に自動的にセット1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 | # python --
class MethodViewType(type):
def __new__(cls, name, bases, d):
# d には post , get などのメソッドがセットされている
rv = type.__new__(cls, name, bases, d)
if 'methods' not in d:
methods = set(rv.methods or [])
for key in d:
# メソッド が HTTPメソッド と同名であれば 自動で登録
if key in http_method_funcs:
methods.add(key.upper())
if methods:
rv.methods = sorted(methods)
return rv
class MethodView(with_metaclass(MethodViewType, View)):
...
# refs https://github.com/pallets/flask/blob/master/flask/views.py#L105
|
1 2 3 4 5 6 7 8 9 10 11 | def hello(func):
def inner()
ret = func()
return 'Hello! My name is {}'.format(ret)
return inner
@hello
def spam():
return 'spam'
spam() # => 'Hello! My name is spam'
|
__eq__
を実装が必要__lt__, __le__, __gt__, __ge__
の どれか一つ の実装が必要1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 | import functools
@functools.total_ordering
class Person:
def __init__(self, score):
self.score = score
def __eq__(self, other):
return self.score == other.score
def __lt__(self, other):
return self.score < other.score
p1 = Person(2)
print(p1 == Person(2)) # __eq__ 実装
print(p1 < Person(3)) # __lt__ 実装
# 残りのメソッド群を自動実装 ---
print(p1 > Person(1)) # __gt__ 自動実装
print(p1 <= Person(2)) # __le__ 自動実装
print(p1 >= Person(2)) # __ge__ 自動実装
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 | # 実装された __lt__ を使って __gt__ の比較を実現するメソッド
def _gt_from_lt(self, other, NotImplemented=NotImplemented):
op_result = self.__lt__(other)
if op_result is NotImplemented:
return op_result
return not op_result and self != other
def total_ordering(cls):
roots = [op for op in _convert if getattr(cls, op, None) is not getattr(object, op, None)]
if not roots:
raise ValueError('must define at least one ordering operation: < > <= >=')
root = max(roots)
# [('__gt__', _gt_from_lt), ('__le__', _le_from_lt), ('__ge__', _ge_from_lt)]
for opname, opfunc in _convert[root]:
if opname not in roots:
opfunc.__name__ = opname
setattr(cls, opname, opfunc) # クラスにメソッドを動的に定義
return cls
|
__get__
, __set__
の最低限両方実装が必要__get__
のみ実装が必要1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 | class PowDescritor:
def __get__(self, obj, type=None):
# # obj(= instance)が渡ってこない時はクラス属性として呼ばれている
if not obj:
return self
return getattr(obj, '_score') ** 2
def __set__(self, obj, value):
setattr(obj, '_score', value)
def __delete__(self, obj):
if hasattr(obj, '_score'):
del obj._value
class Spam:
score = PowDescritor()
spam = Spam()
spam.score = 2
print(spam.score) # => 4
spam.score = 3
print(spam.score) # => 9
del spam.score
print(spam.score) # => AttributeError
|
__getattribute__
が呼ばれます__getattribute__
は下記の順番でデータを取得しようとします。1. データディスクリプタからデータを取得
2. 属性辞書からデータを取得
3. 非データディスクリプタからデータを取得
1 2 3 4 5 6 7 8 9 10 11 12 | from datetime import datetime
from pyramid.decorator import reify
class Spam:
@reify
def now(self):
return datetime.now()
spam = Spam()
spam.now # nowメソッド実行
spam.now # nowメソッドの実行はスキップ、キャッシュされた結果が手にはいる
|
reify
は 非データディスクリプタ = つまり属性アクセスの優先順位が一番低い1 2 3 4 5 6 7 8 9 10 11 12 | class reify(object):
...
def __get__(self, inst, objtype=None):
if inst is None:
return self
val = self.wrapped(inst)
# メソッドの実行結果をダイレクトに属性辞書にセット
setattr(inst, self.wrapped.__name__, val)
return val
# refs https://github.com/Pylons/pyramid/blob/master/pyramid/decorator.py#L39
|
==
, >
, >=
, <
, <=
), 算術演算子(+
, -
, /
, *
, %
, //
, **
),+
」演算子で加算できるようにする1 2 3 4 5 6 7 8 9 | class Spam:
def __init__(self, value):
self.value = value
def __add__(self, other):
self.value += other.value
return self
s = Spam(1) + Spam(1) # プロパティvalue同士を加算
print(s.value) # => 2
|
Spam.value
に加算できるようにしたい1 2 3 4 5 6 7 8 9 10 11 12 | class Spam:
def __init__(self, value):
self.value = value
def __add__(self, other):
# value の有無で加算対象を変える other.value or other
self.value += getattr(other, 'value', other)
return self
(Spam(1) + Spam(1)).value # => 2
(Spam(1) + 1).value # => 2
1 + Spam(1) # TypeError: unsupported operand type(s) for +: 'int' and 'Spam'
|
int
の __add__
が実行されるのでエラー__radd__
メソッドを定義すると、右を左に渡すように向きが変わる1 2 3 4 5 6 7 8 9 10 | class Spam:
def __init__(self, value):
self.value = value
def __add__(self, other):
self.value += getattr(other, 'value', other)
return self
def __radd__(self, other):
return self.__add__(other)
(1 + Spam(1)).value # => 2
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 | from sqlalchemy import Column, Integer
Base = declarative_base()
Base.query = db_session.query_property()
class User(Base):
__tablename__ = 'users'
id = Column(Integer, primary_key=True)
print(User.query.filter(User.id == 1)) # User.id == 1 の結果が boolじゃない
# SELECT users.id AS users_id FROM users WHERE users.id = :id_1
print(User.id == 1) # => users.id = :id_1
print(User.id > 1) # => users.id > :id_1
print(User.id >= 1) # => users.id >= :id_1
print(User.id < 1) # => users.id < :id_1
print(User.id <= 1) # => users.id <= :id_1
print(-User.id) # => -users.id
print(~User.id) # => NOT users.id
print((User.id == 1) | (User.id == 1)) # => users.id = :id_1 OR users.id = :id_2
print((User.id == 1) & (User.id == 1)) # => users.id = :id_1 AND users.id = :id_2
|
Column
クラスに演算子をOverloadする実装がされています。1 2 3 4 5 6 7 8 9 10 11 12 | class ColumnOperators(Operators):
def __eq__(self, other):
"""Implement the ``==`` operator.
In a column context, produces the clause ``a = b``.
If the target is ``None``, produces ``a IS NULL``.
"""
return self.operate(eq, other)
# refs: https://github.com/zzzeek/sqlalchemy/blob/master/lib/sqlalchemy/sql/operators.py#L235
|
eval
は単一の式を評価, exec
は複数の文を評価1 2 3 4 5 6 7 8 9 10 11 12 13 14 | # eval ---
spam = 1
ham = 2
egg = eval('spam + ham')
print(egg) # => 3
# exec ---
code = """
spam = 1
ham = 2
egg = spam + ham
"""
exec(code)
print(egg) # => 3
|
1 2 3 4 5 6 7 8 9 10 | # python --
from collections import namedtuple
Person = namedtuple('Person', ('first', 'last'))
p1 = Person(first='spam', last='ham')
print(p1.first) # => spam
print(p1.last) # => ham
print(type(Person)) # => <class 'type'>
print(type(p1)) # => <class '__main__.Person'>
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 | # collections/__init__.py --
# (注) 大分端折ってます
_class_template = """\
from builtins import property as _property, tuple as _tuple
from operator import itemgetter as _itemgetter
from collections import OrderedDict
class {typename}(tuple):
'{typename}({arg_list})'
__slots__ = ()
_fields = {field_names!r}
def __new__(_cls, {arg_list}):
'Create new instance of {typename}({arg_list})'
return _tuple.__new__(_cls, ({arg_list}))
"""
namespace = dict(__name__='namedtuple_%s' % typename)
exec(class_definition, namespace)
|
types
に モジュール ための型( types.ModuleType
)が存在する1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | import types
import sys
spam_module = types.ModuleType('spam', 'dynamic generated module')
spam_class = """
class Spam:
def hello(self):
print('Hello')
"""
exec(spam_class, spam_module.__dict__) # spamモジュールの名前空間に所属させる
sys.modules['spam'] = spam_module
import spam
s = spam.Spam()
s.hello() # => 'Hello'
|
with
/for
/decorator
を駆使してDSLを実現するテクニックが説明されていますast
モジュールを使ってDSLを実現with
に関しては過去にPEPで提案済み。だが却下。1 2 3 4 5 6 7 8 | # python --
spam = 'spam'
with ham('ham1'):
spam = 'ham' # 必ず実行 ブロックの中身はスキップ不可
with ham('ham2'):
print(spam) # => 'ham' すぐ上のwithが影響してる
|
1 2 3 4 5 6 7 8 | # ruby --
class String
def hello
'Hello! String is ' + self
end
end
p 'Spam'.hello() # => "Hello! String is Spam"
|
1 2 3 4 5 | # python --
def hello(self):
return 'Hello! String is' + self
str.hello = hello # => TypeError: can't set attributes of built-in/extension type 'str'
|
1 2 3 4 5 6 7 | class MyStr(str):
def hello(self):
return 'Hello! String is ' + self
spam = MyStr('Spam')
print(spam.hello()) # => "Hello! String is Spam"
|
ctypes
モジュールを使って、組み込み型にもパッチを当てることが可能ctypes
モジュールにある Python C API の機能を利用して実現1 2 3 4 5 6 7 | from forbiddenfruit import curse
def hello(self):
return 'Hello! String is ' + self
curse(str, 'hello', hello)
print('Spam'.hello()) # => "Hello! String is Spam"
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | import ast
source = """
class Spam:
def __init__(self, name):
self.name = name
def hello(self):
print('Hello {}'.format(self.name))
"""
tree = ast.parse(source)
ast.dump(tree)
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 | Module(
body=[
ClassDef(name='Spam', bases=[], keywords=[], body=[
FunctionDef(
name='__init__',
args=arguments( args = [ arg(arg='self', annotation=None), arg(arg='name', annotation=None) ], vararg=None,
kwonlyargs=[], kw_defaults=[], kwarg=None, defaults=[]
),
body=[Assign(targets=[Attribute(value=Name(id='self', ctx=Load()), attr='name', ctx=Store() ], value=Name(id='name', ctx=Load()))
],
decorator_list=[],
returns=None
),
FunctionDef(
name='hello',
# ~ 省略 ~
],
decorator_list=[]
)
]
)
|
ast.NodeTransformer
を利用してコードを書き換える事ができるprint
を pprint
に変えたい1 2 3 4 5 6 7 8 9 10 11 12 13 | # python --
source = """
data = [
{ 'name': 'Spam', 'value': 1, },
{ 'name': 'Ham', 'value': 2, },
{ 'name': 'Egg', 'value': 3, }
]
print(data)
"""
# printすると一行見づらい
# [{'name': 'Spam', 'value': 1}, {'name': 'Ham', 'value': 2}, {'name': 'Egg', 'value': 3}]
# print(data) => pprint(data) に変えたい
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 | # python --
import ast
from pprint import pprint
class PPrintTransformer(ast.NodeTransformer):
def visit_Name(self, node):
if node.id == 'print':
name = ast.Name(id='pprint', ctx=ast.Load())
return ast.copy_location(name, node)
return node
tree = ast.parse(source)
code = compile(PPrintTransformer().visit(tree), '<string>', 'exec')
exec(code)
# => print が pprintに変わった結果が表示される
# [{'name': 'Spam', 'value': 1},
# {'name': 'Ham', 'value': 2},
# {'name': 'Egg', 'value': 3}]
|
import
したら、 print
を pprint
に置き換える1 2 3 4 5 6 7 | # print_data.py --
data = [
{ 'name': 'Spam', 'value': 1, },
{ 'name': 'Ham', 'value': 2, },
{ 'name': 'Egg', 'value': 3, }
]
print(data) # pprintに変える
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 | import sys
class MyImportHook:
def find_module(self, mod_name, path=None):
# 1. print_data というモジュールだけ return self => self.load_moduleに続く
if mod_name == 'print_data':
return self
def load_module(self, mod_name):
src = mod_name.replace('.', '/') + '.py' # 2. 対象のソースファイルを読み込む
with open(src) as fp:
src_code = fp.read()
src_code = 'from pprint import pprint\n' + src_code # 3. pprintをimportする一文を追加
tree = ast.parse(src_code) # 4. astでパースしてcompile
new_code = compile(PPrintTransformer().visit(tree), '<string>', 'exec')
new_mod = types.ModuleType(mod_name) # 5. 新しく「print_data」モジュールを作る
exec(new_code, new_mod.__dict__) # 6. モジュールの名前空間に、書き換えたコードを当てはめる
sys.modules[mod_name] = new_mod
return new_mod
|
1 2 3 4 5 6 7 | sys.meta_path.insert(0, MyImportHook())
import print_data
# pprintに書き換わった結果が表示
# [{'name': 'Spam', 'value': 1},
# {'name': 'Ham', 'value': 2},
# {'name': 'Egg', 'value': 3}]
|
既定のコードを置き換えるルールやパターンを作ることで簡潔な表現やコードの再利用性をもたらす.
Pythonのコアコミッター Tim Peters のありがたいお言葉
Metaclasses are deeper magic than 99% of users should ever worry about. If you wonder whether you need them, you don’t (the people who actually need them know with certainty that they need them, and don’t need an explanation about why).
メタクラスは、ユーザーの99%の人が考える以上に奥が深い魔法です。 必要かどうかを悩むようなものは必要ないのです (本当にそれを必要としている人は、それが必要であることを確信しており、なぜ必要なのかなど説明の必要はありません)。
– Python Guru Tim Peters”
https://www.ibm.com/developerworks/jp/linux/library/l-pymeta/