Pythonのマングリングについて

Pythonのマングリングとは?

Pythonではクラス内に __で始まり、 __ で終わらない名前をマングリングします。
マングリングとは例えば

class T:
   def foo(self):
     self.__bar = "bar"
     print(self.__bar)
     print(self._T__bar)

T().foo()

としたときに、barが2回出力されます。Pythonではクラス内で定義された __barを特別扱いして self._T__bar とすることでprivateメンバ変数に見せかける仕組みのようです。もちろんクラス内からは self.__bar でアクセス出来ます。

ルールは簡単。__ がついて __ で終わらない変数を _クラス名__変数名 にするだけです。

マングリングの闇

import編

さて、上の例だとselfの変数にマングリングが行われましたが、実はマングリングはクラス内で定義されたもの 全て に対して行われます。

$ ls | grep test
__test2
_T__test1

というディレクトリがあった場合、

class T:
   import __test1
   import __test2

を実行すると

Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "<stdin>", line 3, in T
ImportError: No module named '_T__test2'

こうなるわけです。
import __test1 はマングリングされて _T__test1 名前空間パッケージを見に行くため正常にimportされます。
import __test2 はマングリングされて _T__test2 を見に行きますが、__test2 は見ないため ImportError となります。
これバグではとちょっと思ったり…

import __test1 を逆アセンブルします。

  3           8 LOAD_CONST               1 (0)
             10 LOAD_CONST               2 (None)
             12 IMPORT_NAME              3 (_T__test1)
             14 STORE_NAME               3 (_T__test1)
             16 LOAD_CONST               2 (None)
             18 RETURN_VALUE

つまり、 import __test1 はコンパイル時に import _T__test1 にされるのです。

exec編

class Foo:
    def __init__(self):
        exec("self.__foo = 2")
    def bar(self):
        print(self.__foo)

Foo().bar()

を実行すると

Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "<stdin>", line 5, in bar
AttributeError: 'Foo' object has no attribute '_Foo__foo'

となるわけです。つまり exec はマングリングされないんですね。マングリングはスクリプトのコンパイル時に行われるので exec だとマングリングされません。

クラススコープについて

import ですでにやりましたが、クラススコープの識別子も変換しないと、メソッドから参照できなくなるのでとにかく変換します。

class A:
  import sys as __sys
  print(__sys)
  print(_A__sys)
  def f(self):
    print(self.__sys)
     print(self._A__sys)

A().f()

<module 'sys' (built-in)>
<module 'sys' (built-in)>
<module 'sys' (built-in)>
<module 'sys' (built-in)>

となります。

何でもかんでも変換する

マングリングに例外はありません。識別子は全部変換されます。

class A:
  def x(self):
    __y = 100
    print(dir())

A().x()

['_A__y', 'self']

となります。

class A:
   def f(self):
      import sys
      sys.__omg = 100

A().f()

import sys

sys._A__omg
100

もはや意味がわかりませんね。

global編

当然globalもです。

class A:
  global __x
  __x = 100

_A__x

100

oh…

引数編

class A:
  def f(self):
    def a(__arg):
      print(dir())
      a(1)

A().f()

['_A__arg']

(‘A`)

最後に

マングリングの仕様について、如何だったでしょうか?こんな知識嫌がらせにしか使えないですが、もし遭遇した場合には役に立つことを願います。
私はcafebabepyの実装にこれを盛り込まないといけないので憂鬱です。。

Pythonのsuperについて

Pythonのsuperクラスについてpyhack Slackでatsuoishimotoに教えて貰いました。

superは第2引数を省略することができます。何に使うのでしょうか?

>>> class A: pass
...
>>> super(A)
<super: <class 'A'>, NULL>

これは unbound super と言います。使い方は

class A(object):
    def method(self):
        print('A')

class B(A):
    def method(self):
        print('B')
        self.__super.method()

B._B__super = super(B)

B().method()

のように使うみたいですがどんなシーンで使うのかまったく想像できません。
B._B__super = super(B) はBクラスのprivateインスタンスメンバに代入しているので超絶hackしてますね。。。

super(B).__get__(B()).method()

ならわかりやすいですね。

ではsuper(A, B)は何に使うのでしょうか?

class A:
    @classmethod
    def f(cls):
        print('A')

class B(A):
    @classmethod
    def f(cls):
        super(B, cls).f()

単純に classmethod の場合に使うようですね。
ということはsuperクラスのデスクリプタはどうなっているんでしょうか?

>>> class B(A): pass
...
>>> super(B).__init__
<method-wrapper '__init__' of super object at 0x7fc8c12e3b08>
>>> super(B, B()).__init__
<method-wrapper '__init__' of B object at 0x7fc8c13953c8>

unbound superのget()は普通のsuper()のインスタンスを返します。
bounded superは引数を無視します。

>>> super(B, B()).__get__(123).__init__
<method-wrapper '__init__' of B object at 0x7fc8c12defd0>

Python考古学ですね。

Thanks atsuoishimoto!!

Homebrewからインストールしたnodebrewの初期構築でハマったところ

Homebrew、便利ですよね。nodebrew、多分必要ですよね?
$ brew install nodebrew
しましょう。んでnodebrewでNode.jsを入れようとしたら…


$ nodebrew install-binary v6.1.0
Fetching: https://nodejs.org/dist/v6.1.0/node-v6.1.0-darwin-x64.tar.gz
Warning: Failed to create the file
Warning: /Users/xxx/.nodebrew/src/v6.1.0/node-v6.1.0-darwin-x64.tar.gz:
Warning: No such file or directory

えっ…

原因は.nodebrew/srcディレクトリがないことですね。なので
$ mkdir -p ~/.nodebrew/src

っとして


$ nodebrew install-binary v6.1.0
Fetching: https://nodejs.org/dist/v6.1.0/node-v6.1.0-darwin-x64.tar.gz
######################################################################## 100.0%
Installed successfully

はい。

GradleでただのJavaプロジェクトでもprovidedCompileを使う方法

build.gradleで
apply plugin: 'war'
等としていると、dependenciesprovidedCompileが使えますが、例えば
apply plugin: 'java'
だけだとprovidedCompileは使えません。そこで以下のようにすれば使えるようになります。


configurations {
    providedCompile
}

sourceSets {
    main.compileClasspath += configurations.providedCompile
    test.compileClasspath += configurations.providedCompile
    test.runtimeClasspath += configurations.providedCompile
}

ただし、マルチプロジェクト等でmasterに上記設定をしてサブプロジェクトで
apply plugin: 'war'
としていた場合、すでにprovidedCompileを登録しているため、エラーが発生します。
そのため、以下のようにすればエラーが無くなります。が、これでいいんだろうか?


sourceSets {
    main.compileClasspath -= configurations.providedCompile
    test.compileClasspath -= configurations.providedCompile
    test.runtimeClasspath -= configurations.providedCompile
}

configurations.remove(configurations.providedCompile)

apply plugin: 'war'

ようは初期状態に戻してあとはwarプラグインに任せましょうってことで。

MavenプロジェクトでJettyのServletTesterやHttpTesterを利用する場合

Mavenを利用していてJetty 9.3.7.v20160115でServletTesterやHttpTesterをテストに利用する場合、pom.xmlを

<dependency>
  <groupId>org.eclipse.jetty</groupId>
  <artifactId>jetty-servlet</artifactId>
  <version>9.3.7.v20160115</version>
  <scope>test</scope>
</dependency>

ではなく、

<dependency>
  <groupId>org.eclipse.jetty</groupId>
  <artifactId>jetty-servlet</artifactId>
  <version>9.3.7.v20160115</version>
  <classifier>tests</classifier>
  <scope>test</scope>
</dependency>

といったように<classifier>tests</classifier>を入れる必要があります。いれないとMavenがjetty-http:testsを落としてきてくれなく、利用できません。小一時間ハマりました。

ReadyNAS 104(多分102も)単体でGoogle Driveと同期する方法

背景

私は家のNASにReadyNAS 104を使ってます。3TBx4のRAID-5を組んでおおむね満足していたのですが、本機のみでのクラウド連携ができないといった問題があります。(ReadyNASのクラウドやDropboxはあるみたいですがバックアップ用途っぽい?)

MacでGoogle Driveアプリを使おうとしたもののSMBには対応しておらず使えず…
Insyncを使ってMacからNAS上のディレクトリを同期していました。つまりMacがサーバーになっていて常時起動状態で半年運用していました。
しかし最近たまたまネットを見ていると、Raspberry PiでNASを構築してしかもInsyncを使ってGoogle Driveを同期すると行った方法があるようです!
ReadyNASでもできないかさっそく試してみることにしました。

結論から言えば成功です。まだ細かい確認まではしていませんが、同期は行われています。
続きを読む “ReadyNAS 104(多分102も)単体でGoogle Driveと同期する方法”

株式会社ビープラウドへ遊びに行ってきました

2015/3/13に株式会社ビープラウドへ遊びに行ってきました。

Python mini hack-a-thon 雪山合宿 2015@aodag先生にビープラウドへ遊びに来ないかと誘われていましたが、2月は仕事が忙しく休みがとれませんでした。その後@aodag先生からSlackで連絡があり、いつの間にかshachiku groupが出来ていました。(名前が…)

3月に有休が何とか取れる見込みがたったので3/13に遊びに行きました。

続きを読む “株式会社ビープラウドへ遊びに行ってきました”