Open10

Python知見まとめ

snowindysnowindy

Python その1: Python の class について

classtype を継承している

>>> class X:
...     pass
... 
>>> type(X)
<class 'type'>

type はインスタンスが class になる メタクラス である。よって、以下の2つのクラス定義は同値である。

>>> class Sample:
...     a = 0
... 
>>> Sample = type('Sample', (type,), dict(a=0))
>>> Sample
<class '__main__.Sample'>

また、typeはobjectを継承している。

snowindysnowindy

Python その2:int strのメソッドの謎挙動?

int型のインスタンス (ex. 5)は文法エラーになるが、変数に代入すればエラーにならない。なぜ?

>>> -5.__abs__()
  File "<stdin>", line 1
    -5.__abs__()
       ^
SyntaxError: invalid syntax
>>> b = -5
>>> b.__abs__()
5

これだけなら参照からしかメソッドアクセスできないのかなとか思うが、str型は普通にエラーにならずメソッド実行できてしまうから不思議。

>>> 'aa'.__len__()
2

追記

intのあとのdotが小数点だとPythonが勘違いして怒られているだけなので、上記の思考は勘違いだった。
参考:https://stackoverflow.com/questions/11802214/integer-literal-is-an-object-in-python

snowindysnowindy

Python その3:関数とメソッドのちがい;実は同じもの

まずはこれを見てほしい。

>>> class X:
...     def y(self):
...         return 1
...
>>> ins = X()
>>> ins.y()
1
>>> X.y()
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: y() missing 1 required positional argument: 'self'
>>> X.y(X())
1

メソッドはインスタンスにしないと実行できないとどっかのブログで言っていたが半分嘘で、クラスに属する関数でもあるため普通に実行できてしまう。ただ、引数の違いが存在する。

続きを見ていこう。

>>> ins.y
<bound method X.y of <__main__.X object at 0x7fd6203f0d90>>
>>> X.y
<function X.y at 0x7fd61ebcbc10>

インスタンスから呼び出すのとクラスから呼び出すので属性が変わっている。前者はメソッド、後者は関数だ。
この差が第一引数に自動的にselfを代入してくれるかどうかの違いのようだ。
つまり、Pythonの内部で、メソッドが呼び出されると自動的にメソッドの元になっている関数を呼び出し(ins.y.__func__ == X.y)、selfをつけて実行するのだ。

公式ドキュメントより:

呼び出された時、引数リストに self 引数が追加されます。
m.__func__ はそのメソッドを実装している関数です。
m(arg-1, arg-2, ..., arg-n) の呼び出しは、 m.__func__(m.__self__, arg-1, arg-2, ..., arg-n) の呼び出しと完全に等価です。
https://docs.python.org/ja/3/library/stdtypes.html#methods

公式ドキュメントの
https://docs.python.org/ja/3/tutorial/classes.html#method-objects
をみるとより理解が深まることでしょう。

特にこの部分が詳しいです。

インスタンスの非データ属性が参照されたときは、そのインスタンスのクラスが検索されます。その名前が有効なクラス属性を表している関数オブジェクトなら、インスタンスオブジェクトと見つかった関数オブジェクト (へのポインタ) を抽象オブジェクト、すなわちメソッドオブジェクトにパックして作成します。メソッドオブジェクトが引数リストと共に呼び出されるとき、インスタンスオブジェクトと渡された引数リストから新しい引数リストを作成して、元の関数オブジェクトを新しい引数リストで呼び出します。

snowindysnowindy

Python その4:多重継承

Pythonには多重継承が実装されている。
多重継承には実装上の問題点やダイヤモンド継承などの問題もはらんでいるため、DjangoのようにMixinやInterface的な使い方をして、ダイヤモンド継承をしないように工夫しているものが多いように見受けられる。

ダイヤモンド継承のなにが良くないかはこの記事が詳しかった。

それを踏まえて、Pythonの継承順をみてみると

class DerivedClassName(Base1, Base2, Base3):
    pass

上の場合、DerivedClassNameのメソッドが呼ばれた場合、まずはBase1に該当メソッドが無いかを確認しに行き、次にBase1のスーパークラスを順々に参照しに行く。それで見つからなければ、Base2、Base3と参照しに行く。

ただ、実際はダイアモンド継承がほぼ常に発生するため動的な探索が行われる。ここは super() の挙動をより深く見る必要がありそうだ。

https://docs.python.org/ja/3/tutorial/classes.html#multiple-inheritance

super()の挙動はこれが詳しいらしい。重そう。気が向いたらみます。
https://www.python.org/download/releases/2.3/mro/

snowindysnowindy

Python その5:プライベート変数について

いいやつがあったのでそのまま記事貼ります
https://qiita.com/mounntainn/items/e3fb1a5757c9cf7ded63

つまり、

class ClassName:
    int __varName

ClassName()._ClassName__varName

になるということ

Pythonにはプライベート変数はないけど名前修飾で意図的な隠蔽はできるよ、という話

snowindysnowindy

Python その6:イテレータ

Pythonのforはiter()を呼び出している。これはnext() functionから呼ばれるiterator.__next__()
を持つものならどれでも使える
StopIteration例外が出るまで動く
自身をIterateしたいなら__next____iter__を実装すれば良い

公式から引用:インデックスと逆の方向にyieldするReverse classを実装している

class Reverse:
    """Iterator for looping over a sequence backwards."""
    def __init__(self, data):
        self.data = data
        self.index = len(data)

    def __iter__(self):
        return self

    def __next__(self):
        if self.index == 0:
            raise StopIteration
        self.index = self.index - 1
        return self.data[self.index]

>>> rev = Reverse('spam')
>>> iter(rev)
<__main__.Reverse object at 0x00A1DB50>
>>>for char in rev:
...    print(char)

m
a
p
s

ジェネレータ(yield)を使えばこの実装は一瞬でできる。

snowindysnowindy

Python その7:CPythonのメソッド表示(__repr__)あれこれ

>>> class Foo(object):
...     def __init__(self, val):
...         self.val = val
...     def __eq__(self, other):
...         return self.val == other.val
...
>>> Foo(42).__eq__
<bound method Foo.__eq__ of <__main__.Foo object at 0x7fd620475340>>
>>> Foo(42).__ne__
<method-wrapper '__ne__' of Foo object at 0x7fd6203f0d90>
>>> int.__repr__
<slot wrapper '__repr__' of 'int' objects>

これらの bound method method-wrapper slot wrapper とは何だ?と思ったわけです。
slot wrapper Cで実装された関数。クラスオブジェクトの関数(function)としてふるまう。
method-wrapper Cで実装された関数。 bound methodとしてふるまう。

bound methodについて:公式ドキュメントより

インスタンスを通してメソッド (クラスの名前空間内で定義された関数) にアクセスすると、特殊なオブジェクトが得られます。それは束縛メソッド (bound method) オブジェクトで、インスタンスメソッド (instance method) とも呼ばれます。

クラス関数については https://zenn.dev/link/comments/35180fc5822276 を参照されたし。

snowindysnowindy

Python その9:hashtable(辞書)について

pythonのhashtableである辞書はアクセスがO(1)なので優秀。
in 演算子がvalue ではなく key をみてるので注意.

>>> a = {1 : 2, 3 : 4, 5 : 6}
>>> 1 in a
True
>>> 2 in a
False
snowindysnowindy

Python その10 : 便利なものたち

これまでは概念的なものが多かったが、ここで使える?リポジトリや関数を紹介。

collections

deque

双方向リスト

Counter

SQLのorder_by. さらに、辞書のキーが無くても初期値を設定してくれて便利(defaultdictでいいけどね)

defaultdict

キーの初期化を自動的に行なってくれる。intなら賞味Counterでいい。それ以外なら結構使えるかも。defaultdict[list]で二次元配列の初期化が可能だし

OrderedDict

順序突き合わせの等価演算をしてくれる。iterationやspace efficiencyはそんなに重要にされてない
popitemはindexの最初からもO(1)で取れる。

namedtuple

tupleを名前付きで保存できる。宣言が楽なstructみたいな感じかな。

Point = (namedtuple('Point', ['x', 'y', 'z'])
p = Point(11, 33, 55)
p.x

heapq

heapの解説
heapq.heapify がこのスクラップの build_min_heap,
heapq.heappopmin_heapify & pop
に相当する。

itertools

無限イテレータやイテレータセレクタなど。

product

デカルト積を提供。

product(range(n), range(m)) 
returns 
[0, 0], [0, 1], [0, 2]...[n-1, m-1]