【廖雪峰python進級ノート】カスタムクラス


1. __str__ および_repr__


クラスのインスタンスをstrにするには、特別な方法__str__()を実装する必要があります.
class Person(object):
    def __init__(self, name, gender):
        self.name = name
        self.gender = gender
    def __str__(self):
        return '(Person: %s, %s)' % (self.name, self.gender)

インタラクティブコマンドラインでprintを使用して試します.
>>> p = Person('Bob', 'male')
>>> print p
(Person: Bob, male)

ただし、変数pを直接叩くと:
>>> p
object at 0x10c941890>

str()は呼び出されないようです.
Pythonは__str__()__repr__()の2つの方法を定義しているため、__str__()はユーザーに表示され、__repr__()は開発者に表示される.
サボる定義__repr__の方法があります.
class Person(object):
    def __init__(self, name, gender):
        self.name = name
        self.gender = gender
    def __str__(self):
        return '(Person: %s, %s)' % (self.name, self.gender)
    __repr__ = __str__

2. __cmp__


int、strなどの内蔵データ型をソートする場合、Pythonのsorted()はデフォルトの比較関数cmpに従ってソートされますが、Studentクラスの のセットをソートする場合は、独自の特別な方法__cmp__()を提供する必要があります.
class Student(object):
    def __init__(self, name, score):
        self.name = name
        self.score = score
    def __str__(self):
        return '(%s: %s)' % (self.name, self.score)
    __repr__ = __str__

    def __cmp__(self, s):
        if self.name < s.name:
            return -1
        elif self.name > s.name:
            return 1
        else:
            return 0

上記のStudentクラスは__cmp__()メソッドを実装し、__cmp__はインスタンス自体selfと入力されたインスタンスsとを比較し、selfが前に並ぶべきであれば−1を返し、sが前に並ぶべきであれば1を返し、両者が等しい場合は0を返す.
Studentクラスはnameでソートされます.
>>> L = [Student('Tim', 99), Student('Bob', 88), Student('Alice', 77)]
>>> print sorted(L)
[(Alice: 77), (Bob: 88), (Tim: 99)]

注意:listにStudentクラスだけが含まれていない場合は、__cmp__がエラーを報告する可能性があります.
L = [Student('Tim', 99), Student('Bob', 88), 100, 'Hello']
print sorted(L)

解決策を考えてください.
例:Studentの__cmp__メソッドを変更して、スコアが高い順に並べ替え、スコアが同じ順に名前で並べ替えてください. はscoreを先に比較し、scoreが等しい場合、nameを比較する必要がある.
参照コード:
class Student(object):

    def __init__(self, name, score):
        self.name = name
        self.score = score

    def __str__(self):
        return '(%s: %s)' % (self.name, self.score)

    __repr__ = __str__

    def __cmp__(self, s):
        if self.score > s.score:
            return -1
        elif self.score == s.score:
            if self.name > s.name:
                return 1
            elif self.name == s.name:
                return 0
            else:
                return -1
        else:
            return 1

L = [Student('Tim', 99), Student('Bob', 88), Student('Alice', 99)]
print sorted(L)
class Student(object):
    def __init__(self, name, score):
        self.name = name
        self.score = score

    def __str__(self):
        return '(%s: %s)' % (self.name, self.score)

    __repr__ = __str__

    def __cmp__(self, s):
        if self.score == s.score:
            return cmp(self.name, s.name)
        return -cmp(self.score, s.score)

L = [Student('Tim', 99), Student('Bob', 88), Student('Alice', 99)]
print sorted(L)

3. __len__


クラスがlistのように表現されている場合、要素がいくつあるかを取得するには、len()関数を使用します.
len()関数を正常に動作させるには、クラスは要素の数を返す特別な方法__len__()を提供する必要があります.
たとえば、Studentsクラスを書いて、名前を伝えます.
class Students(object):
    def __init__(self, *args):
        self.names = args
    def __len__(self):
        return len(self.names)
__len__()メソッドが正しく実装されている限り、studentsインスタンスの「長さ」をlen()関数で返すことができます.
>>> ss = Students('Bob', 'Alice', 'Tim')
>>> print len(ss)
3

例のフィボナッチ数列は0,1,1,2,3,5,8…から構成される.
Fibクラスを作成してください.Fib(10)は数列の上位10要素を表し、print Fib(10)は数列の上位10要素を印刷することができ、len(Fib(10))は数列の個数10を正しく返すことができます. はnumからフィボナッチ数列の最初のN要素を計算する必要がある.
参照コード:
class Fib(object):
    def __init__(self, num):
        a, b, L = 0, 1, []
        for n in range(num):
            L.append(a)
            a, b = b, a + b
        self.numbers = L

    def __str__(self):
        return str(self.numbers)

    __repr__ = __str__

    def __len__(self):
        return len(self.numbers)

f = Fib(10)
print f
print len(f)

4.数学演算


Pythonが提供する基本データ型int,floatは,整数と浮動小数点の4則演算や乗方などの演算を行うことができる.
しかしながら、 はintやfloatに限らず、有理数、行列等であってもよい.
有理数を表すには、Rational で表すことができます.
class Rational(object):
    def __init__(self, p, q):
        self.p = p
        self.q = q

p,qはいずれも整数であり, p/qを表す.
Rationalを+演算するには、__add__を正しく実装する必要があります.
class Rational(object):
    def __init__(self, p, q):
        self.p = p
        self.q = q
    def __add__(self, r):
        return Rational(self.p * r.q + self.q * r.p, self.q * r.q)
    def __str__(self):
        return '%s/%s' % (self.p, self.q)
    __repr__ = __str__

有理数加算を試してみましょう.
>>> r1 = Rational(1, 3)
>>> r2 = Rational(1, 2)
>>> print r1 + r2
5/6

インスタンスRationalクラスは加算は可能ですが、減算、乗算、除算はできません.Rationalクラスを改善し続け、4つの演算を実現してください.
ヒント:減算:__sub__乗算:__mul__除算:__div__演算結果が6/8の場合、表示時に最も簡単な形式3/4にまとめる必要があります.
参照コード:
def gcd(a, b):
    if b == 0:
        return a
    return gcd(b, a % b)

class Rational(object):
    def __init__(self, p, q):
        self.p = p
        self.q = q
    def __add__(self, r):
        return Rational(self.p * r.q + self.q * r.p, self.q * r.q)
    def __sub__(self, r):
        return Rational(self.p * r.q - self.q * r.p, self.q * r.q)
    def __mul__(self, r):
        return Rational(self.p * r.p, self.q * r.q)
    def __div__(self, r):
        return Rational(self.p * r.q, self.q * r.p)
    def __str__(self):
        g = gcd(self.p, self.q)
        return '%s/%s' % (self.p / g, self.q / g)
    __repr__ = __str__

r1 = Rational(1, 2)
r2 = Rational(1, 4)
print r1 + r2
print r1 - r2
print r1 * r2
print r1 / r2

5.タイプ変換


Rationalクラスは有理数演算を実現していますが、結果をintやfloatに変えるにはどうすればいいですか?
整数と浮動小数点数の変換を考察する:
>>> int(12.34)
12
>>> float(12)
12.0

Rationalをintに変更する場合は、次の操作を行います.
r = Rational(12, 5)
n = int(r)

int()関数を正常に動作させるには、特別な方法__int__()を実装する必要があります.
class Rational(object):
    def __init__(self, p, q):
        self.p = p
        self.q = q
    def __int__(self):
        return self.p // self.q

結果は次のとおりです.
>>> print int(Rational(7, 2))
3
>>> print int(Rational(1, 3))
0

同様に、float()関数を正常に動作させるには、特殊な方法__float__()を実現する必要がある.
def __float__(self):
    return float(self.p) / self.q
# 
def __float__(self):
    return 1.0 * self.p / self.q

6. @property


スタディクラスの考察:
class Student(object):
    def __init__(self, name, score):
        self.name = name
        self.score = score

Studioのscroeプロパティを変更する場合は、次のように書きます.
s = Student('Bob', 59)
s.score = 60

しかし、このように書くこともできます.
s.score = 1000

属性に直接値を割り当てると、スコアの有効性がチェックされないことは明らかです.
2つの方法を使用した場合:
class Student(object):
    def __init__(self, name, score):
        self.name = name
        self.__score = score
    def get_score(self):
        return self.__score
    def set_score(self, score):
        if score < 0 or score > 100:
            raise ValueError('invalid score')
        self.__score = score

これでs.set_score(1000)はエラーを報告します.
このget/set手法を用いて1つの属性へのアクセスをカプセル化することは、多くのオブジェクト向けプログラミング言語でよく見られる.
でもs.get_と書きますscore()とs.set_score()はs.scoreを直接書いていません.
両方を完璧にする方法はありますか?—あります.
Pythonは高次関数をサポートするため、関数式プログラミングでは を紹介し、get/setメソッドを装飾器関数で属性呼び出しに「装飾」することができます.
class Student(object):
    def __init__(self, name, score):
        self.name = name
        self.__score = score
    @property
    def score(self):
        return self.__score
    @score.setter
    def score(self, score):
        if score < 0 or score > 100:
            raise ValueError('invalid score')
        self.__score = score

注意:最初のscore(self)はgetメソッド、@propertyで装飾、2番目のscore(self,score)はsetメソッド、@score.setter装飾,@score.setterは前の@property装飾後の副産物です.
プロパティを使用するようにscoreを設定できます.
>>> s = Student('Bob', 59)
>>> s.score = 60
>>> print s.score
60
>>> s.score = 1000
Traceback (most recent call last):
  ...
ValueError: invalid score

scoreに値を割り当てる実際に呼び出されたのはsetメソッドであることを説明します.
インスタンスsetメソッドを定義しないと、プロパティに値を割り当てることができません.この場合、読み取り専用のプロパティを作成できます.
Studentクラスにgradeプロパティを追加し、scoreに基づいてA(>=80)、B、C(<60)を計算してください. @propertyでgradeを修飾するgetメソッドで読み取り専用属性を実現できます.
参照コード:
class Student(object):
    def __init__(self, name, score):
        self.name = name
        self.__score = score
    @property
    def score(self):
        return self.__score
    @score.setter
    def score(self, score):
        if score < 0 or score > 100:
            raise ValueError('invalid score')
        self.__score = score
    @property
    def grade(self):
        if self.score < 60:
        #self.__score < 60  
            return 'C'
        if self.score < 80:
            return 'B'
        return 'A'
s = Student('Bob', 59)
print s.grade
s.score = 60
print s.grade
s.score = 99
print s.grade

7. __slots__


Pythonは動的言語であるため、どのインスタンスも実行期間中に動的に属性を追加できます. に追加する属性、例えば、Studentクラスがname、gender、scoreの3つの属性のみを追加できる場合、Pythonの特殊な__slots__を利用して実現することができる.
名前の通り、__slots__はクラスが許可する属性のリストを指します.
class Student(object):
    __slots__ = ('name', 'gender', 'score')
    def __init__(self, name, gender, score):
        self.name = name
        self.gender = gender
        self.score = score

次に、インスタンスを操作します.
>>> s = Student('Bob', 'male', 59)
>>> s.name = 'Tim' # OK
>>> s.score = 99 # OK
>>> s.grade = 'A'
Traceback (most recent call last):
  ...
AttributeError: 'Student' object has no attribute 'grade'
__slots__の目的は、現在のクラスが持つことができる属性を制限することであり、任意のダイナミックな属性を追加する必要がなければ、__slots__を使用してもメモリを節約できます.

8. __call__


Pythonでは、関数は実際にはオブジェクトです.
>>> f = abs
>>> f.__name__
'abs'
>>> f(-123)
123

fは呼び出すことができるので、fは呼び出すことができるオブジェクトと呼ばれる.
すべての関数は呼び出し可能なオブジェクトです.
1つのクラスインスタンスは、1つの特別な方法 を実装するだけで__call__()になることもできる.
Personクラスを呼び出し可能なオブジェクトにしました.
class Person(object):
    def __init__(self, name, gender):
        self.name = name
        self.gender = gender

    def __call__(self, friend):
        print 'My name is %s...' % self.name
        print 'My friend is %s...' % friend

Personインスタンスを直接呼び出すことができます.
>>> p = Person('Bob', 'male')
>>> p('Tim')
My name is Bob...
My friend is Tim...

p(‘Tim’)を見ると、pが関数なのかクラスインスタンスなのか分からないので、Pythonでは関数もオブジェクトであり、オブジェクトと関数の違いは顕著ではありません.