Python 3.0 Hacks

第10回 argument"s" clinic

この記事を読むのに必要な時間:およそ 7.5 分

Pythonでありがちな*args/**kwsの使いかた

標準ライブラリの中にあるものをいくつか見ていきましょう。

オブジェクトのメンバを作るのに使う

このケースでは,辞書引数**kwで渡した名前と値の組が,インスタンス変数に代入されます(self.__dict__.updateの部分⁠⁠。

この場合は,インスタンス変数の名前をいちいちタイプしなくてよい,何かをwrapしたりインスタンスのユーザが好きなものを代入できる(下記の例の場合⁠⁠,というメリットがあります。デメリットとしては,初期化時ではなく実行時に問題が顕在化することで,外から渡したキーとインスタンス変数の名前の衝突や,名前が正しくタイプされていなかったために指定した値が処理に渡らない,などのバグを埋め込む可能性があげられます。

リスト8  _threading_local.pyからの抜粋

You can create custom local objects by subclassing the local class:

  >>> class MyLocal(local):
  ...     number = 2
  ...     initialized = False
  ...     def __init__(self, **kw):
  ...         if self.initialized:
  ...             raise SystemError('__init__ called too many times')
  ...         self.initialized = True
  ...         self.__dict__.update(kw)
  ...     def squared(self):
  ...         return self.number ** 2

今度はXMLParserでの例です。辞書kwのkeyをチェックしてインスタンス変数に代入しています。

リスト9 xmllib.pyから抜粋

class XMLParser:
    attributes = {}                     # default, to be overridden
    elements = {}                       # default, to be overridden

    # parsing options, settable using keyword args in __init__
    __accept_unquoted_attributes = 0
    __accept_missing_endtag_name = 0
    __map_case = 0
    __accept_utf8 = 0
    __translate_attribute_references = 1

    # Interface -- initialize and reset this instance
    def __init__(self, **kw):
        self.__fixed = 0
        if 'accept_unquoted_attributes' in kw:
            self.__accept_unquoted_attributes = kw['accept_unquoted_attributes']
        if 'accept_missing_endtag_name' in kw:
            self.__accept_missing_endtag_name = kw['accept_missing_endtag_name']
        if 'map_case' in kw:
            self.__map_case = kw['map_case']
        if 'accept_utf8' in kw:
            self.__accept_utf8 = kw['accept_utf8']
        if 'translate_attribute_references' in kw:
            self.__translate_attribute_references = kw['translate_attribute_references']
        self.reset()

このように,必要とあればキーワード引数のvalueだけでなく,keyも使うことができます。⁠あるkeywordをもった引数を渡したこと」を利用できるのです。

下位にそのまま渡す

前項で説明した「タプル・辞書で受け取る」「タプル・辞書を展開して渡す」を組み合わせて次のように使われています。

リスト10 trace.pyから抜粋


    def runfunc(self, func, *args, **kw):
        result = None
        if not self.donothing:
            sys.settrace(self.globaltrace)
        try:
            result = func(*args, **kw)
        finally:
            if not self.donothing:
                sys.settrace(None)
        return result

decoratorを実装する場合も,decorateされる関数にdecorateされる前と同じように引数を渡すために使用します。

リスト11 test/test_struct.pyから抜粋

def with_warning_restore(func):                                        
    @wraps(func)                                                       
    def decorator(*args, **kw):                                        
        with warnings.catch_warnings():                                
            # We need this function to warn every time, so stick an    
            # unqualifed 'always' at the head of the filter list       
            warnings.simplefilter("always")                            
            warnings.filterwarnings("error", category=DeprecationWarning)
            return func(*args, **kw)                                   
    return decorator

処理が不定数個引数をとるケース

substituteが実装されている時点では不定個の引数です。引数に名前をつけてそのkeyを用いてtemplateへの代入を指定します。

リスト12 http://docs.python.org/library/string.html#template-strings から

>>> from string import Template
>>> s = Template('$who likes $what')
>>> s.substitute(who='tim', what='kung pao')
'tim likes kung pao'

heapqのmerge関数です。sort済みの入力を不定複数受け取り,1つのソート済み出力にマージします。

リスト13 lib/heapq.py

def merge(*iterables):
    '''Merge multiple sorted inputs into a single sorted output.       

    Similar to sorted(itertools.chain(*iterables)) but returns a generator,
    does not pull the data into memory all at once, and assumes that each of
    the input streams is already sorted (smallest to largest).         
    
    >>> list(merge([1,3,5,7], [0,2,4,8], [5,10,15,20], [], [25]))      
    [0, 1, 2, 3, 4, 5, 5, 7, 8, 10, 15, 20, 25]                        
    
    '''
    _heappop, _heapreplace, _StopIteration = heappop, heapreplace, StopIteration
        
    h = []  
    h_append = h.append
    for itnum, it in enumerate(map(iter, iterables)):                  
        try:
            next = it.__next__                                         
            h_append([next(), itnum, next])                            
        except _StopIteration:                                         
            pass                                                       
    heapify(h)
                
    while 1:                                                           
        try:    
            while 1: 
                v, itnum, next = s = h[0]   # raises IndexError when h is empty
                yield v
                s[0] = next()               # raises StopIteration when exhausted
                _heapreplace(h, s)          # restore heap condition   
        except _StopIteration:                                         
            _heappop(h)                     # remove empty iterator    
        except IndexError:                                             
            return

著者プロフィール

保坂範行(ほさかのりゆき)

病気でお休みしていましたが活動再開に向けてウォームアップ中です。Life workと化しつつある趣味のゲーム(backgammon)のためのweb-siteを構築しています。局面のimageを動的に生成して返すサーバをPythonで実装&運用中です。

URLhttp://image.backgammonbase.com/

過去の仕事でいやな思いをした経験からPythonの読みやすさを重視する哲学に深く共感。

個人サイト

URLhttp://www.tonic-water.com/