Doge log

Abby CTO 雑賀 力王のオフィシャルサイトです

namedtupleの話

python2.6から導入されたnamedtupleについてちょっくら書いておくか。

namedtupleって?

namedtupleは名前の通り名前付きでアクセスできるtupleを返す。

>>> from collections import namedtuple
>>> p = namedtuple('Point', 'x y')
>>> p1 = p(11, 22)
>>> p1[0]
11
>>> p1[1]
22
>>> p1.x
11
>>> p1.y
22
>>> p2 = p(x = 33, y = 44)
>>> p2[0]
33
>>> p2[1]
44
>>> p2.x
33
>>> p2.y
44
>>>

とまあこんな感じ。
namedtuple関数に名前とフィールドを渡すと名前付きでアクセスできるものを
作成するものを返す。

仕組み

実際にはnametuple関数はクラスを返している。

>>> namedtuple('Point', 'x y')

このクラスはtupleのサブクラスである。
詳細はverboseオプションで確認できる

>>> namedtuple('Point', 'x y', verbose=True)
class Point(tuple):
'Point(x, y)'

__slots__ = ()

_fields = ('x', 'y')

def __new__(cls, x, y):
return tuple.__new__(cls, (x, y))

@classmethod
def _make(cls, iterable, new=tuple.__new__, len=len):
'Make a new Point object from a sequence or iterable'
result = new(cls, iterable)
if len(result) != 2:
raise TypeError('Expected 2 arguments, got %d' % len(result))
return result

def __repr__(self):
return 'Point(x=%r, y=%r)' % self

def _asdict(t):
'Return a new dict which maps field names to their values'
return {'x': t[0], 'y': t[1]}

def _replace(self, **kwds):
'Return a new Point object replacing specified fields with new values'
result = self._make(map(kwds.pop, ('x', 'y'), self))
if kwds:
raise ValueError('Got unexpected field names: %r' % kwds.keys())
return result

def __getnewargs__(self):
return tuple(self)

x = property(itemgetter(0))
y = property(itemgetter(1))


>>>

とまあどんなクラスを生成したか見る事ができる。

で気になったのはこいつの作り方。正直この手のコードが標準ライブラリに
入るとはあんまり思ってなかったんだが。

    numfields = len(field_names)
    argtxt = repr(field_names).replace("'", "")[1:-1]   # tuple repr without parens or quotes
    reprtxt = ', '.join('%s=%%r' % name for name in field_names)
    dicttxt = ', '.join('%r: t[%d]' % (name, pos) for pos, name in enumerate(field_names))
    template = '''class %(typename)s(tuple):
        '%(typename)s(%(argtxt)s)' \n
        __slots__ = () \n
        _fields = %(field_names)r \n
        def __new__(cls, %(argtxt)s):
            return tuple.__new__(cls, (%(argtxt)s)) \n
        @classmethod
        def _make(cls, iterable, new=tuple.__new__, len=len):
            'Make a new %(typename)s object from a sequence or iterable'
            result = new(cls, iterable)
            if len(result) != %(numfields)d:
                raise TypeError('Expected %(numfields)d arguments, got %%d' %% len(result))
            return result \n
        def __repr__(self):
            return '%(typename)s(%(reprtxt)s)' %% self \n
        def _asdict(t):
            'Return a new dict which maps field names to their values'
            return {%(dicttxt)s} \n
        def _replace(self, **kwds):
            'Return a new %(typename)s object replacing specified fields with new values'
            result = self._make(map(kwds.pop, %(field_names)r, self))
            if kwds:
                raise ValueError('Got unexpected field names: %%r' %% kwds.keys())
            return result \n
        def __getnewargs__(self):
            return tuple(self) \n\n''' % locals()
    for i, name in enumerate(field_names):
        template += '        %s = property(itemgetter(%d))\n' % (name, i)
    if verbose:
        print template

    # Execute the template string in a temporary namespace and
    # support tracing utilities by setting a value for frame.f_globals['__name__']
    namespace = dict(itemgetter=_itemgetter, __name__='namedtuple_%s' % typename)
    try:
        exec template in namespace
    except SyntaxError, e:
        raise SyntaxError(e.message + ':\n' + template)
    result = namespace[typename]
    # For pickling to work, the __module__ variable needs to be set to the frame
    # where the named tuple is created.  Bypass this step in enviroments where
    # sys._getframe is not defined (Jython for example).
    if hasattr(_sys, '_getframe'):
        result.__module__ = _sys._getframe(1).f_globals['__name__']

    return result

とまあ定番のexecとかsys._getframeとかやってますな。
これはなんだか黒魔術的な気がするんだけどどうなんだろう?
明示的な文化を持つPythonでは受け入れられない気がしてたのだが、最近では
緩くなったのかも知れないという話でした。
(namedtupleの実装は自動生成しまくりの真っ黒いメタクソプログラミングしたい人は参考にするといいかも知れない。)

まあでも便利は便利よね。
例えばフィールドの定義がだるいのでこんな感じでクラス作るとか

class Point(namedtuple('Point', 'x y')):
...

DBからのデータをクラスにMappingするのとかにも重宝する。

EmployeeRecord = namedtuple('EmployeeRecord', 'name, age, title, department, paygrade')

import csv
for emp in map(EmployeeRecord._make, csv.reader(open("employees.csv", "rb"))):
    print emp.name, emp.title

selfの話

メソッドのself (2)について少し書いてみる。

pythonの不満点でよく上がる定番の話は

  1. self地獄
  2. インデント地獄
  3. GIL外せよ!

なんだけどそのうちのひとつであるselfの話。
まあ死ぬほど聞く話ではあるけど。

多分和訳より原文の方を読むと掴めるんじゃないかな。

decoratorがなぜ問題になるか?って部分だけど。
decoratorってある意味ブラックボックスでなんでもできちゃうので自動でboundさせるのむずくね?みたいな話なのかなと。

def deco(fun):
    def meth(self, arg):
        self.val = "decorator"
        return self.val
    return meth

class C(object):

    @deco
    def meth(self, arg):
        self.val = arg 
        return self.val

c = C() 
print(c.meth(1))
print(c.val)

渡したfuncを無視するなんてあんまし書かないコードではあるが。
(wrapするケースは多いよね)

selfなしを考える。
decorator内で関数を作ってそいつをメソッドのように振る舞わせる時にselfってどこから持ってこよう?
selfを無くす方向で書くと

def deco(fun):
    def meth(arg):
        #このselfって??  
        self.val = "decorator"
        return self.val
    return meth

class C(object):

    @deco
    def meth(arg):
        self.val = arg 
        return self.val

c = C() 
print(c.meth(1))
print(c.val)

将来的にあるクラスのメソッドになりえるものを関数として定義しちゃうケースだとself何を指すのか?
関数でself???関数だよ?
ということになっちゃうよね?って話。
selfがあるからこそpythonのメソッドのbound、unboundがぬるく適当でも動いてくれる。
だからこそdecoratorみたいな関数を返す関数でクラスのメソッドを拡張できるわけだな。

pythonのメソッドのbound、unboundみたいな話は前にも書いたけど以下の書籍に割と書いてあるので
読むといいと思う。

Pythonクィックリファレンス

Pythonクィックリファレンス

追記
selflessをmetaclassでやっちゃう話があったので紹介。自分のAttribute舐めてまわるというよくあるコード。
Meta-classes Made Easy
selfless-pythonは死亡してる模様です。。。