最近覚えた git の黒魔術

とりあえずメモ

コミット履歴をまとめたりなかったことにしたりする

$ git rebase -i ${REV}  # ${REV} にはHEAD^ とか HEAD^^^ とかを指定する

↑と合わせて rebase 中に止めたいとき

$ git rebase --abort

最後のコミットメッセージを修正

$ git commit --amend

コミットの付け替え

$ git cherry-pick ${COMMIT_HASH}

操作履歴を見る

$ git reflog

あと、黒魔術じゃないけど git-meld がマジで便利

Mercurial を Python から使ってみる

Mercurialリポジトリをプログラムから弄りたくなったので適当にやってみた。

やりたかったのは

  • 現在のリポジトリのヘッドにあるファイルの最終更新日一覧取得
  • マルチヘッドは考慮しない

ってこと。

ソースの方が早そう。

ソース

gist から

嫌い!

twitter 上で Python の += の挙動がキモイよねという話が一部で盛り上がっていた。
発端は多分このつぶやき

で、私はこれには大いに賛同する。
そう思う理由としては

  • += したら参照が書き換わるから副作用なんかないだろ
  • そもそも x += y は x = x + y と同じ意味だろ

という二つの感覚から来ていて、 += で副作用が発生するなんて全く思っていなかったという部分がある。
まあ実際は

  • x += y は x = x.__iadd__(y) の糖衣構文なので、 += したら参照を張り替えるし、副作用も起こりうる
  • __iadd__ が定義されていない場合は x = x + y と等価
  • 副作用を起こしているのは処理の効率化のため

ということらしいので、まあそういうもんかと納得しておくところである。

Python おじさんも以下のように言っているし

しかし、感覚としては

  • 副作用起こすくせに参照張り替える
  • 参照張り替えるくせに副作用起こる
  • 型で意味が変わるのはシンプルじゃなくね? simple is better than complex は?

というあたりで綺麗とは思えないし、気持ち悪いと思っている。

少なくとも

  • x += y を呼んだら x.__iadd__(y) を呼び出すだけで参照は変わらない。__iadd__ が実装されていなければ例外を投げる
  • x += y を x = x + y の糖衣構文のみとする

のどちらかであれば綺麗だなと思う。

結局は好みの問題であり、言語仕様に綺麗さを求める潔癖性とか「ぼくのかんがえたさいきょうのぱいそん」みたいな厨二的感覚だろうなと思うので、特に言うことはない。
現実の実装はそうじゃないしね。

まとめ

結局何が言いたいかというと、

  • self 明示大好きだし文法も割とセンス良いし全体的に一貫性があるし Python さん素敵!

という素敵一辺倒だったのが

というところが (super の引数省略も含め) 見つかって嬉しいなあということ。
好き好きだけじゃあだめなんですよ。嫌いなところも見えないとね!

暗黙の…

※4/15 追記

Python3 で導入されたとてもキモイ暗黙の __class__ 変数についてちょっと試した。


やっていること

  • メソッド内で super() を呼ぶ前に self を別の値にする
  • メソッド内で nonlocal __class__ して __class__ を書き換えた後に super() を呼ぶ

結果

$ python3 classtest.py
<class '__main__.Test'>
<class '__main__.Test'>
Traceback (most recent call last):
  File "/home/shoma/source/garbages/python/classtest.py", line 50, in catch
    f()
  File "/home/shoma/source/garbages/python/classtest.py", line 60, in <lambda>
    catch(lambda: a.call_with_otherself())
  File "/home/shoma/source/garbages/python/classtest.py", line 38, in call_with_otherself
    super()
TypeError: super(type, obj): obj must be an instance or subtype of type
called
10
Traceback (most recent call last):
  File "/home/shoma/source/garbages/python/classtest.py", line 50, in catch
    f()
  File "/home/shoma/source/garbages/python/classtest.py", line 65, in <lambda>
    catch(lambda: a.call_super())
  File "/home/shoma/source/garbages/python/classtest.py", line 22, in call_super
    super().test()
SystemError: super(): __class__ is not a type (int)

結果考察

以上から言えること

  • super() はやっぱり super(__class__, self) の省略
  • __class__ がある環境情報は全てのメソッドで同じものを持つ

メタクラスの気になるアレ

ちょっと前に書いた 多重継承を制限してみる - プログラマのネタ帳 で、メタクラスに関してちょっと気になっていたことを検証。
それは、 メタクラスの継承ツリーはどうなるの? ってこと。

これが気になっていたので以下のコードで検証。

結果を簡単にまとめると、メタクラスを適用したクラス同士を多重継承する際は、適用したメタクラス同士が直接の継承関係にないと

<class '__main__.MetaClassAA'>
Traceback (most recent call last):
  File "metameta.py", line 47, in <module>
    class DerivedAB(BaseMetaA, BaseMetaB):
TypeError: Error when calling the metaclass bases
    metaclass conflict: the metaclass of a derived class must be a (non-strict) subclass of the metaclasses of all its bases

なんてエラーが出るということ。
気をつけましょう。

Python3 とクラスと super

Python3 では super() を呼び出す際に引数を省略できる。
例えば以下のように書くと、 super() は super(Derived, self) という呼び出しと等価になるらしい。

class Derived(Base):

    def __init__(self):

        super().__init__()

そもそも明示を是とする Python で引数が省略できてしまうってのは気持ち悪いというのはかなりあるのだけども、どのように解決しているのだろうかなどと気になってしまったので調べてみた。

仕様

この仕様はどこで定義されているのかと PEP を調べてみたら PEP3135 で定義されているらしい。
調べてみた結果とは微妙に違うけどまあそれくらいあるのかも?

実験

引数の所在

そもそも super に渡す引数はどこから取ってくるのか。
第二引数はメソッドの第一引数でいいとして、第一引数の解決をどうやっているのか。
以下のコードで調べてみた。

class Base(object):

    def __init__(self):
        pass


    def test(self, x):
        print('basetest', x)


class Derived(Base):


    def __init__(self):
        super().__init__()


def outertest(self, x):
    print('derivedtest', x)
    super().test(x)


Derived.test = outertest

a = Derived()
a.test(10)

実行してみるとこうなった

$ /usr/local/python3.2/bin/python3 /tmp/aaa.py 
derivedtest 10
Traceback (most recent call last):
  File "/tmp/aaa.py", line 26, in <module>
    a.test(10)
  File "/tmp/aaa.py", line 20, in outertest
    super().test(x)
SystemError: super(): __class__ cell not found

どうやら class 宣言内で定義したメソッドには __class__ という変数があるらしい。
試しに Derived.__init__ に print('__class__:', __class__) を仕込んでみると

__class__: <class '__main__.Derived'>

と表示されたので __class__ という変数がどこかしらのスコープから見えているということらしい。

これで super() に渡しているらしい暗黙の引数の所在がわかった。かも。

定義の所在

さて、 __class__ という変数がどこかしらに存在して、メソッド中から見えているということはわかったので、この変数が「どのタイミングで」「どのスコープに」保持されるかというところを調べてみる。

関数オブジェクトから引数やらの情報を取ってくる。
__class__ が見えているであろう関数を IPython で補完しつつ調べると、 Derived.__init__.__code__.co_freevars にそれっぽいのがあるらしい。

In [13]: Derived.__init__.__code__.co_freevars
Out[13]: ('__class__',)

というわけで co_freevars に含まれているようだ。

co_freevars とは何かというと pythonにおける closure と自由変数 - odz buffer によるとクロージャに含まれる外の関数のスコープの変数であるらしい。

この「環境にある変数」というのがどこで追加されるのかを調べるために色々やってみた。

まず、こんな関数を定義する。

def check__class__(f):

    print('***', f.__name__, 'defined at line', f.__code__.co_firstlineno)

    if '__class__' in f.__code__.co_freevars:
        print('***', f.__name__, id(f), 'has __class__')
    else:
        print('***', f.__name__, id(f), '''doesn't have __class__''')

    return f

これをデコレータとしてメソッドにつけてみる。

こんな感じ。

class Base(object):

    @check__class__
    def __init__(self):
        print('self', self, 'in Base.__init__')
        print('aaaa')

        super().__init__()

    @check__class__
    def test(self, x):

        print('self', self)
        print('__class__', __class__)
        print('basetest', x)



class Derived(Base):

    @check__class__
    def __init__(self):
        print('self', self, 'in Derived.__init__')

        x = 10

        print(locals())

        print('__class__:', __class__)

        print('bbbb')

        super().__init__()


    @check__class__
    def somefunc(self, y):
        pass


    @check__class__
    def somefunc2(self, y):
        super(Derived, self).test(10)

実行したときの標準出力は

*** __init__ defined at line 50
*** __init__ 32934776 has __class__
*** test defined at line 57
*** test 32934912 has __class__
*** __init__ defined at line 68
*** __init__ 32935048 has __class__
*** somefunc defined at line 83
*** somefunc 32935184 doesn't have __class__
*** somefunc2 defined at line 87
*** somefunc2 32935320 has __class__

となっていた。
これを見る限り、メソッド内で super が出てくるような場合は __class__ を持っているのではないか、という予想ができそう。

脇道

ところで、この「クラス定義中のメソッド定義」は __class__ を持っているらしいが、クラス定義が終わる前にはクラスオブジェクトが存在しないはず(Python2では)なので、このデコレータ内での __class__ の値はどうなっているのか。
試しにこんな事をしてみた。

def get__class__(f):

    try:
        cls = f(10)
        print('aaaaaaaaaaa', cls)
    except Exception as e:
        print(e)


# 略

class Derived(Base):

    # 略

    @get__class__
    def somehaveclass(self):
        if 0:
            super()

        return __class__

この結果は

free variable '__class__' referenced before assignment in enclosing scope

だったので、クラス定義が終わる前は __class__ は取れないということらしい。

別の方法

どうやらクラス定義内で __class__ という変数を含む環境を持った関数が定義されるっぽいので、これは type(name, bases, attrs) では同等のことはできないのではないか。
というわけで試してみる。

>>> SomeTest = type('SomeTest', (Base,), {'test': outertest})
>>> b = SomeTest()
>>> SomeTest.test(10)
SystemError: super(): __class__ cell not found

というわけでこの挙動は type の呼び出しだけではエミュレーションできない。
そもそも定義した関数の環境を弄ることができないので、 __class__ を追加するとかいうことはできないのである。

等価な処理を(わざわざ class を使わずに) やるのであれば

def make_class():

    def __init__(self):
        super().__init__()

    def somefunc(self):
        print('test')
        

    __class__ = type('classname', (object,), locals())

    return __class__

C = make_class()
a = C()
a.somefunc()

というようなことになると思われる。
なんとも面倒な話である。

まとめ

  • super() の暗黙の引数は環境にある __class__ と関数の第一引数
  • __class__ は class 定義中で定義した関数であれば環境情報として持っている
  • __class__ はクラス定義が終わるまで使えない
  • type(name, bases, attrs) だけではエミュレーションできない
  • 環境に __class__ があればなんとでもできるので、なんとかできないことはない
  • Python2 だと Unbound Method の第一引数の型をチェックしていたけど、 Python3 だとなくなったっぽい

調べたときに使ったコード片は gist にあげた。

多重継承を制限してみる

昨日のえきぱい

なんてことをつぶやいたので作ってみた。
ソースは gist
多重継承を禁止したい基底クラスのメタクラスに MultipleInheritanceLimitation を指定するだけのお手軽さなのでいいんじゃないかなー。