banner
Zoney

Zoney

一个励志成为Web安全工程师的女青年!

pythonの勉強 4

オブジェクト指向プログラミング#

オブジェクト指向プログラミング(Object Oriented Programming、略称OOP)は、プログラム設計の思想の一つです。OOP ではオブジェクトプログラムの基本単位とし、オブジェクトはデータとそのデータを操作する関数を含みます。

手続き型プログラミングは、コンピュータプログラムを一連の命令の集合、つまり関数の順次実行として捉えます。プログラム設計を簡素化するために、手続き型は関数をさらに小さなサブ関数に分割し、大きな関数を小さな関数に切り分けてシステムの複雑さを軽減します。

一方、オブジェクト指向プログラミングは、コンピュータプログラムをオブジェクトの集合と見なし、各オブジェクトは他のオブジェクトから送信されたメッセージを受信し、それらのメッセージを処理します。コンピュータプログラムの実行は、各オブジェクト間でメッセージが伝達される一連のプロセスです。

Python では、すべてのデータ型はオブジェクトと見なすことができ、もちろんカスタムオブジェクトも定義できます。カスタムオブジェクトのデータ型は、オブジェクト指向におけるクラス(Class)の概念です。

手続き型とオブジェクト指向のプログラムフローの違いを例を挙げて説明します。

学生の成績表を処理すると仮定します。学生の成績を表すために、手続き型のプログラムでは dict を使用して表現できます:

std1 = { 'name': 'Michael', 'score': 98 }
std2 = { 'name': 'Bob', 'score': 81 }

学生の成績を処理するために、関数を使用して実装できます。例えば、学生の成績を印刷する関数:

def print_score(std):
    print('%s: %s' % (std['name'], std['score']))

オブジェクト指向のプログラミング思想を採用する場合、最初に考えるべきはプログラムの実行フローではなく、Student というデータ型がオブジェクトとして扱われるべきであるということです。このオブジェクトは name と score という 2 つの属性(Property)を持ちます。学生の成績を印刷するには、まずその学生に対応するオブジェクトを作成し、その後、オブジェクトに print_score メッセージを送信して、自分のデータを印刷させます。

class Student(object):

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

    def print_score(self):
        print('%s: %s' % (self.name, self.score))

オブジェクトにメッセージを送信することは、実際にはオブジェクトに関連する関数を呼び出すことを意味し、これをオブジェクトのメソッド(Method)と呼びます。オブジェクト指向のプログラムはこのように書かれます:

bart = Student('Bart Simpson', 59)
lisa = Student('Lisa Simpson', 87)
bart.print_score()
lisa.print_score()

オブジェクト指向の設計思想は自然界から来ており、自然界では ** クラス(Class)とインスタンス(Instance)** の概念が非常に自然です。クラスは抽象的な概念であり、例えば私たちが定義したクラス —Student は学生という概念を指し、インスタンス(Instance)は具体的な Student、例えば Bart Simpson や Lisa Simpson です。

したがって、オブジェクト指向の設計思想はクラスを抽象化し、クラスに基づいてインスタンスを作成することです。

オブジェクト指向の抽象度は関数よりも高く、クラスはデータを含むだけでなく、データを操作するメソッドも含みます。

データのカプセル化継承、およびポリモーフィズムは、オブジェクト指向の 3 つの主要な特徴です。

クラスとインスタンス
オブジェクト指向で最も重要な概念はクラス(Class)とインスタンス(Instance)であり、クラスは抽象的なテンプレートであることを常に心に留めておく必要があります。例えば Student クラスは、インスタンスはクラスに基づいて作成された具体的な **「オブジェクト」** であり、各オブジェクトは同じメソッドを持っていますが、それぞれのデータは異なる場合があります。

Student クラスを例にとると、Python ではクラスを定義するのは class キーワードを使用します:

class Student(object):
    pass

class の後にはクラス名が続き、ここでは Student です。クラス名は通常大文字で始まる単語であり、続いて (object) があり、このクラスがどのクラスから継承されているかを示します。通常、適切な継承クラスがない場合は object クラスを使用します。これはすべてのクラスが最終的に継承するクラスです。

Student クラスを定義したら、Student クラスに基づいて Student のインスタンスを作成できます。インスタンスを作成するには ** クラス名 +()** を使用します:

>>> bart = Student()
>>> bart
<__main__.Student object at 0x10a67a590>
>>> Student
<class '__main__.Student'>

変数 bart が指しているのは Student のインスタンスであり、後ろの 0x10a67a590 はメモリアドレスであり、各オブジェクトのアドレスは異なりますが、Student 自体はクラスです。

インスタンス変数に属性を自由にバインドできます。例えば、インスタンス bart に name 属性をバインドします:

>>> bart.name = 'Bart Simpson'
>>> bart.name
'Bart Simpson'

クラスはテンプレートの役割を果たすことができるため、インスタンスを作成する際に、必須の属性を強制的に入力させることができます。特別な__init__メソッドを定義することで、インスタンスを作成する際に name、score などの属性をバインドできます:

class Student(object):

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

注意:特別なメソッド「init」の前後には2 つのアンダースコアがあります!!!
__init__メソッドの最初の引数は常にselfであり、これは作成されたインスタンス自体を示します。したがって、__init__メソッドの内部では、さまざまな属性を self にバインドできます。なぜなら、self は作成されたインスタンス自体を指すからです

__init__メソッドがあると、インスタンスを作成する際に空の引数を渡すことはできず、__init__メソッドに一致する引数を渡す必要がありますが、self は渡す必要はありません。Python インタプリタが自動的にインスタンス変数を渡します:

>>> bart = Student('Bart Simpson', 59)
>>> bart.name
'Bart Simpson'
>>> bart.score
59

通常の関数と比較して、クラス内で定義された関数は 1 つの違いがあります。それは、最初の引数は常にインスタンス変数 self であることです。そして、呼び出すときにこの引数を渡す必要はありません。それ以外は、クラスのメソッドと通常の関数には違いはありません。したがって、デフォルト引数、可変引数、キーワード引数、命名キーワード引数を使用することができます。

データのカプセル化
オブジェクト指向プログラミングの重要な特徴の 1 つはデータのカプセル化です。上記の Student クラスでは、各インスタンスはそれぞれの name と score というデータを持っています。これらのデータにアクセスするために関数を使用できます。例えば、学生の成績を印刷する関数:

>>> def print_score(std):
...     print('%s: %s' % (std.name, std.score))
...
>>> print_score(bart)
Bart Simpson: 59

しかし、Student インスタンス自体がこれらのデータを持っているので、データにアクセスするために外部の関数からアクセスする必要はなく、Student クラスの内部でデータにアクセスする関数を定義することができます。これにより、「データ」がカプセル化されます。これらのカプセル化されたデータの関数は Student クラス自体に関連付けられており、これをクラスのメソッドと呼びます:

class Student(object):

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

    def print_score(self):
        print('%s: %s' % (self.name, self.score))

メソッドを定義するには、最初の引数が self であることを除いて、他は通常の関数と同じです。メソッドを呼び出すには、インスタンス変数の上で直接呼び出すだけで、self を渡す必要はなく、他の引数は通常通り渡します。

こうすることで、外部から Student クラスを見ると、インスタンスを作成する際に name と score を指定する必要があるだけで、印刷する方法はすべて Student クラスの内部で定義されており、これらのデータとロジックが「カプセル化」されているため、呼び出しは簡単ですが、内部実装の詳細を知る必要はありません

カプセル化のもう 1 つの利点は、Student クラスに新しいメソッドを追加できることです。例えば get_grade メソッド:

class Student(object):
    ...

    def get_grade(self):
        if self.score >= 90:
            return 'A'
        elif self.score >= 60:
            return 'B'
        else:
            return 'C'

同様に、get_grade メソッドはインスタンス変数の上で直接呼び出すことができ、内部実装の詳細を知る必要はありません。

小結
1 クラスはインスタンスを作成するためのテンプレートであり、インスタンスは具体的なオブジェクトであり、各インスタンスが持つデータは互いに独立しており、影響を与えません。

2 メソッドはインスタンスにバインドされた関数であり、通常の関数とは異なり、メソッドはインスタンスのデータに直接アクセスできます。

3 インスタンス上でメソッドを呼び出すことによって、オブジェクト内部のデータを直接操作できますが、メソッド内部の実装の詳細を知る必要はありません。

4 静的言語とは異なり、Python ではインスタンス変数に任意のデータをバインドできます。つまり、2 つのインスタンス変数は同じクラスの異なるインスタンスであっても、持っている変数名は異なる可能性があります:

>>> bart = Student('Bart Simpson', 59)
>>> lisa = Student('Lisa Simpson', 87)
>>> bart.age = 8
>>> bart.age
8
>>> lisa.age
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
AttributeError: 'Student' object has no attribute 'age'

アクセス制限
クラス内部には属性とメソッドがあり、外部コードはインスタンス変数のメソッドを直接呼び出すことでデータを操作できます。これにより、内部の複雑なロジックが隠されます。

しかし、前述の Student クラスの定義から見ると、外部コードはインスタンスの name、score 属性を自由に変更できます:

>>> bart = Student('Bart Simpson', 59)
>>> bart.score
59
>>> bart.score = 99
>>> bart.score
99

内部属性が外部からアクセスされないようにするには、属性名の前に2 つのアンダースコア__を付けることができます。Python では、インスタンスの変数名が__で始まる場合、それはプライベート変数(private)となり、内部からのみアクセスでき、外部からはアクセスできません。したがって、Student クラスを次のように変更します:

class Student(object):

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

    def print_score(self):
        print('%s: %s' % (self.__name, self.__score))

変更後、外部コードにとっては何も変わりませんが、インスタンス変数.__name とインスタンス変数.__score には外部からアクセスできなくなります:

>>> bart = Student('Bart Simpson', 59)
>>> bart.__name
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
AttributeError: 'Student' object has no attribute '__name'

これにより、外部コードがオブジェクト内部の状態を自由に変更できないことが保証されます。アクセス制限による保護により、コードはより堅牢になります。

しかし、外部コードが name と score を取得したい場合はどうすればよいでしょうか?Student クラスにget_nameget_scoreというメソッドを追加できます:

class Student(object):
    ...

    def get_name(self):
        return self.__name

    def get_score(self):
        return self.__score

外部コードが score を変更できるようにするには、Student クラスにset_scoreメソッドを追加できます:

class Student(object):
    ...

    def set_score(self, score):
        self.__score = score

あなたは、元々の方法で bart.score = 99 を使っても変更できるのに、なぜわざわざメソッドを定義するのかと疑問に思うかもしれません。それは、メソッド内で引数をチェックし、無効な引数が渡されるのを防ぐことができるからです

class Student(object):
    ...

    def set_score(self, score):
        if 0 <= score <= 100:
            self.__score = score
        else:
            raise ValueError('bad score')

注意すべきは、Python では、変数名が__xxx__のように、つまりダブルアンダースコアで始まり、ダブルアンダースコアで終わるものは特殊変数であり、特殊変数には直接アクセスできます。プライベート変数ではないため、name、__score__のような変数名を使用することはできません。

時には、1 つのアンダースコアで始まるインスタンス変数名(例えば_name)を見かけることがあります。このようなインスタンス変数は外部からアクセス可能ですが、慣習的にこのような変数を見ると、「アクセスできるが、プライベート変数として扱ってほしい、自由にアクセスしないでほしい」という意味です。

ダブルアンダースコアで始まるインスタンス変数は外部からアクセスできないのでしょうか?実際にはそうではありません。__name に直接アクセスできないのは、Python インタプリタが__name 変数を_Student__name に変更するためです。したがって、_Student__name を通じて__name 変数にアクセスすることができます:

>>> bart._Student__name
'Bart Simpson'

しかし、これを行うことは強く推奨されません。なぜなら、異なるバージョンの Python インタプリタが__name を異なる変数名に変更する可能性があるからです。

要するに、Python 自体には悪事を防ぐメカニズムはなく、すべては自己責任です

最後に、以下のような誤った書き方に注意してください:

>>> bart = Student('Bart Simpson', 59)
>>> bart.get_name()
'Bart Simpson'
>>> bart.__name = 'New Name' # __name変数を設定!
>>> bart.__name
'New Name'

表面的には、外部コードが__name 変数を「成功裏に」設定したように見えますが、実際にはこの__name 変数はクラス内部の__name 変数とは異なる変数です!内部の__name 変数は Python インタプリタによって自動的に_Student__name に変更され、外部コードは bart に新しい__name 変数を追加しました。

>>> bart.get_name() # get_name()内部はself.__nameを返します
'Bart Simpson'

継承とポリモーフィズム

OOP プログラム設計では、クラスを定義する際に、既存のクラスから継承することができます。新しいクラスはサブクラス(Subclass)と呼ばれ、継承されるクラスは基底クラス、親クラス、または超クラス(Base class、Super class)と呼ばれます。

例えば、Animal というクラスを作成し、run () メソッドを直接印刷できるようにします:

class Animal(object):
    def run(self):
        print('Animal is running...')

Dog や Cat クラスを作成する必要がある場合は、Animal クラスから直接継承できます:

class Dog(Animal):
    pass

class Cat(Animal):
    pass

Dog にとって、Animal は親クラスです。Animal にとって、Dog は子クラスです。Cat と Dog は似ています。

継承の利点は何でしょうか?最大の利点は、子クラスが親クラスのすべての機能を取得することです。Animal が run () メソッドを実装しているため、Dog と Cat はその子クラスとして、何もせずに自動的に run () メソッドを持っています:

dog = Dog()
dog.run()

cat = Cat()
cat.run()

実行結果は次のようになります:

Animal is running...
Animal is running...

もちろん、子クラスにいくつかのメソッドを追加することもできます。例えば Dog クラス:

class Dog(Animal):

    def run(self):
        print('Dog is running...')

    def eat(self):
        print('Eating meat...')

継承の第 2 の利点は、コードを少し改善する必要があることです。Dog や Cat が run () メソッドを呼び出すと、表示されるのは Animal is running... です。論理的な方法は、それぞれ Dog is running... と Cat is running... を表示することです。したがって、Dog と Cat クラスを次のように改善します:

class Dog(Animal):

    def run(self):
        print('Dog is running...')

class Cat(Animal):

    def run(self):
        print('Cat is running...')

再度実行すると、結果は次のようになります:

Dog is running...
Cat is running...

子クラスと親クラスの両方に同じ run () メソッドが存在する場合、子クラスの run () が親クラスの run () をオーバーライドしたと言います。コードが実行されるとき、常に子クラスの run () が呼び出されます。これにより、継承のもう 1 つの利点であるポリモーフィズムを得ることができます。

ポリモーフィズムとは何かを理解するために、データ型についてもう少し説明する必要があります。クラスを定義すると、実際にはデータ型を定義していることになります。私たちが定義するデータ型は、Python が提供するデータ型(例えば str、list、dict)と何ら変わりありません:

a = list() # aはlist型
b = Animal() # bはAnimal型
c = Dog() # cはDog型

変数が特定の型であるかどうかを判断するには、**isinstance ()** を使用します:

>>> isinstance(a, list)
True
>>> isinstance(b, Animal)
True
>>> isinstance(c, Dog)
True

a、b、c は確かに list、Animal、Dog の 3 つの型に対応しています。
しかし、試してみてください:

>>> isinstance(c, Animal)
True

c は Dog だけでなく、Animal でもあります!

しかし、よく考えてみると、これは理にかなっています。なぜなら、Dog は Animal から継承されているからです。Dog のインスタンス c を作成するとき、c のデータ型が Dog であることは間違いありませんが、c が Animal でもあることは間違いありません。Dog は本来 Animal の一種だからです!

したがって、継承関係において、インスタンスのデータ型が特定の子クラスである場合、そのデータ型は親クラスとしても見なすことができます。しかし、逆は成り立ちません:

>>> b = Animal()
>>> isinstance(b, Dog)
False

Dog は Animal と見なすことができますが、Animal は Dog とは見なすことができません。

ポリモーフィズムの利点を理解するために、もう 1 つの関数を作成する必要があります。この関数は Animal 型の変数を受け取ります:

def run_twice(animal):
    animal.run()
    animal.run()

Animal のインスタンスを渡すと、run_twice () は次のように印刷します:

>>> run_twice(Animal())
Animal is running...
Animal is running...

Dog のインスタンスを渡すと、run_twice () は次のように印刷します:

>>> run_twice(Dog())
Dog is running...
Dog is running...

Cat のインスタンスを渡すと、run_twice () は次のように印刷します:

>>> run_twice(Cat())
Cat is running...
Cat is running...

見たところ、特に意味はありませんが、よく考えてみると、今、Tortoise 型を定義し、Animal から派生させることができます:

class Tortoise(Animal):
    def run(self):
        print('Tortoise is running slowly...')

run_twice () を呼び出すと、Tortoise のインスタンスを渡すと:

>>> run_twice(Tortoise())
Tortoise is running slowly...
Tortoise is running slowly...

Animal の子クラスを追加しても、run_twice () を変更する必要がないことがわかります。実際には、Animal をパラメータとして依存する関数やメソッドは、変更なしで正常に動作します。これがポリモーフィズムの理由です。

ポリモーフィズムの利点は、Dog、Cat、Tortoise などを渡す必要がある場合、Animal 型を受け取るだけで済むということです。なぜなら、Dog、Cat、Tortoise はすべて Animal 型であり、Animal 型に従って操作すればよいからです。Animal 型には run () メソッドがあるため、渡された任意の型が Animal クラスまたはその子クラスであれば、実際の型の run () メソッドが自動的に呼び出されます。これがポリモーフィズムの意味です:

変数に関しては、それが Animal 型であることだけを知っていれば、正確なサブタイプを知る必要はなく、run () メソッドを安心して呼び出すことができます。そして、具体的に呼び出される run () メソッドは、Animal、Dog、Cat、または Tortoise オブジェクトのどれに作用するかは、実行時にそのオブジェクトの正確な型によって決まります。これがポリモーフィズムの真の力です:呼び出し側は呼び出すだけで、詳細には関与せず、新しい Animal の子クラスを追加する際には、run () メソッドが正しく記述されていることを確認するだけで、元のコードがどのように呼び出されるかを気にする必要はありません。これが有名な「オープン・クローズド原則」です:

拡張に対してオープン:Animal の子クラスを追加することを許可します;

変更に対してクローズド:Animal 型に依存する run_twice () などの関数を変更する必要はありません。

継承は階層的に継承することもでき、祖父から父、そして子に至るような関係です。すべてのクラスは最終的に根クラスobjectに遡ることができ、これらの継承関係は逆さまの木のように見えます。

静的言語 vs 動的言語
静的言語(例えば Java)では、Animal 型を渡す必要がある場合、渡すオブジェクトは Animal 型またはその子クラスでなければならず、そうでなければ run () メソッドを呼び出すことができません。

Python のような動的言語では、Animal 型を渡す必要はありません。渡すオブジェクトが run () メソッドを持っていることを保証するだけで済みます

class Timer(object):
    def run(self):
        print('Start...')

これが動的言語の「アヒル型」です。厳密な継承体系を要求せず、オブジェクトが「アヒルのように見え、アヒルのように歩く」限り、アヒルとして見なすことができます。

Python の「ファイルのようなオブジェクト」はアヒル型の一例です。本物のファイルオブジェクトには read () メソッドがあり、その内容を返します。しかし、多くのオブジェクトは read () メソッドを持っている限り、「ファイルのようなオブジェクト」と見なされます。多くの関数が受け取るパラメータは「ファイルのようなオブジェクト」であり、本物のファイルオブジェクトを渡す必要はなく、read () メソッドを実装した任意のオブジェクトを渡すことができます。

小結
1 継承は親クラスのすべての機能を直接取得できるため、ゼロから始める必要がなく、子クラスは自分特有のメソッドを追加するだけで済み、親クラスに適さないメソッドをオーバーライドして再定義することもできます。

2 動的言語のアヒル型の特徴により、継承は静的言語のように必須ではありません。

オブジェクト情報の取得
オブジェクトの参照を取得したとき、そのオブジェクトがどのような型で、どのようなメソッドを持っているかをどうやって知るのでしょうか?

**type ()** を使用してオブジェクトの型を判断します。基本型はすべて type () で判断できます:

>>> type(123)
<class 'int'>
>>> type('str')
<class 'str'>
>>> type(None)
<type(None) 'NoneType'>

変数が関数やクラスを指している場合も type () で判断できます:

>>> type(abs)
<class 'builtin_function_or_method'>
>>> type(a)
<class '__main__.Animal'>

しかし、type () 関数が返すのはどの型でしょうか?それは対応するクラスの型です。

if 文で判断する場合、2 つの変数の型が同じかどうかを比較する必要があります:

>>> type(123)==type(456)
True
>>> type(123)==int
True
>>> type('abc')==type('123')
True
>>> type('abc')==str
True
>>> type('abc')==type(123)
False

基本データ型を判断する場合は、直接 int や str などを書くことができますが、オブジェクトが関数であるかどうかを判断するには、types モジュールに定義された定数を使用できます:

>>> import types
>>> def fn():
...     pass
...
>>> type(fn)==types.FunctionType
True
>>> type(abs)==types.BuiltinFunctionType
True
>>> type(lambda x: x)==types.LambdaType
True
>>> type((x for x in range(10)))==types.GeneratorType
True

isinstance () を使用する
クラスの継承関係において、type () を使用するのは非常に不便です。クラスの型を判断するには isinstance () 関数を使用します。

前回の例を振り返ると、継承関係は次のようになります:

object -> Animal -> Dog -> Husky

isinstance () は、オブジェクトが特定の型であるかどうかを教えてくれます。まず 3 つの型のオブジェクトを作成します:

>>> a = Animal()
>>> d = Dog()
>>> h = Husky()

次に、判断します:

>>> isinstance(h, Husky)
True

問題ありません。なぜなら、h 変数は Husky オブジェクトを指しているからです。

次に判断します:

>>> isinstance(h, Dog)
True

h は Husky 型ですが、Husky は Dog から継承されているため、h は Dog 型でもあります。言い換えれば、isinstance () はオブジェクトがその型自体であるか、またはその型の親の継承チェーンにあるかどうかを判断します。

したがって、h は Animal 型でもあることが確実です:

>>> isinstance(h, Animal)
True

同様に、実際の型が Dog である d も Animal 型です:

>>> isinstance(d, Dog) and isinstance(d, Animal)
True

しかし、d は Husky 型ではありません:

>>> isinstance(d, Husky)
False

type () で判断できる基本型も isinstance () で判断できます:

>>> isinstance('a', str)
True
>>> isinstance(123, int)
True
>>> isinstance(b'a', bytes)
True

また、変数が特定の型のいずれかであるかどうかを判断することもできます。例えば、以下のコードは list または tuple であるかどうかを判断できます:

>>> isinstance([1, 2, 3], (list, tuple))
True
>>> isinstance((1, 2, 3), (list, tuple))
True

常に isinstance () を使用して型を判断し、指定された型とそのサブクラスを「一網打尽」にすることができます。

dir () を使用する
オブジェクトのすべての属性とメソッドを取得するには、dir () 関数を使用します。これは文字列のリストを含むリストを返します。例えば、str オブジェクトのすべての属性とメソッドを取得します:

>>> dir('ABC')
['__add__', '__class__',..., '__subclasshook__', 'capitalize', 'casefold',..., 'zfill']

__xxx__のような属性とメソッドは Python で特別な用途があります。例えば、len__メソッドは長さを返します。Python では、**len () 関数を呼び出してオブジェクトの長さを取得しようとすると、実際には len () 関数内部でそのオブジェクトの__len() メソッドが自動的に呼び出されます **。したがって、以下のコードは等価です:

>>> len('ABC')
3
>>> 'ABC'.__len__()
3

私たちが作成したクラスも、len (myObj) を使用したい場合は、len() メソッドを自分で定義する必要があります:

>>> class MyDog(object):
...     def __len__(self):
...         return 100
...
>>> dog = MyDog()
>>> len(dog)
100

残りは普通の属性やメソッドです。例えば、lower () は小文字の文字列を返します:

>>> 'ABC'.lower()
'abc'

属性とメソッドをリストするだけでは不十分で、getattr()setattr()、および **hasattr ()** を組み合わせることで、オブジェクトの状態を直接操作できます:

>>> class MyObject(object):
...     def __init__(self):
...         self.x = 9
...     def power(self):
...         return self.x * self.x
...
>>> obj = MyObject()

次に、このオブジェクトの属性をテストできます:

>>> hasattr(obj, 'x') # 属性'x'はありますか?
True
>>> obj.x
9
>>> hasattr(obj, 'y') # 属性'y'はありますか?
False
>>> setattr(obj, 'y', 19) # 属性'y'を設定
>>> hasattr(obj, 'y') # 属性'y'はありますか?
True
>>> getattr(obj, 'y') # 属性'y'を取得
19
>>> obj.y # 属性'y'を取得
19

存在しない属性を取得しようとすると、AttributeError エラーが発生します:

>>> getattr(obj, 'z') # 属性'z'を取得
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
AttributeError: 'MyObject' object has no attribute 'z'

存在しない場合はデフォルト値を返す default パラメータを渡すことができます:

>>> getattr(obj, 'z', 404) # 属性'z'を取得し、存在しない場合はデフォルト値404を返す
404

オブジェクトのメソッドも取得できます:

>>> hasattr(obj, 'power') # 属性'power'はありますか?
True
>>> getattr(obj, 'power') # 属性'power'を取得
<bound method MyObject.power of <__main__.MyObject object at 0x10077a6a0>>
>>> fn = getattr(obj, 'power') # 属性'power'を取得して変数fnに代入
>>> fn # fnはobj.powerを指します
<bound method MyObject.power of <__main__.MyObject object at 0x10077a6a0>>
>>> fn() # fn()を呼び出すことはobj.power()を呼び出すのと同じです
81

小結

1 内蔵の一連の関数を使用することで、任意の Python オブジェクトを解析し、その内部データを取得できます。注意すべきは、オブジェクト情報がわからないときにのみオブジェクト情報を取得するということです。直接書ける場合:

sum = obj.x + obj.y

書かない方が良い:

sum = getattr(obj, 'x') + getattr(obj, 'y')

正しい使用法の例は次のとおりです:

def readImage(fp):
    if hasattr(fp, 'read'):
        return readData(fp)
    return None

ファイルストリーム fp から画像を読み取ることを希望する場合、最初にその fp オブジェクトに read メソッドが存在するかどうかを確認する必要があります。存在する場合、そのオブジェクトはストリームであり、存在しない場合は読み取ることができません。hasattr () が役立ちます。

Python のような動的言語では、アヒル型に基づいて、read () メソッドがあるからといって、その fp オブジェクトがファイルストリームであるとは限りません。それはネットワークストリームであるかもしれませんし、メモリ内のバイトストリームであるかもしれませんが、read () メソッドが有効な画像データを返す限り、画像の読み取り機能には影響しません。

インスタンス属性とクラス属性

Python は動的言語であるため、クラスから作成されたインスタンスに属性を任意にバインドできます。

インスタンスに属性をバインドする方法は、インスタンス変数を通じて、または self 変数を通じて行います:

class Student(object):
    def __init__(self, name):
        self.name = name

s = Student('Bob')
s.score = 90

しかし、Student クラス自体に属性をバインドする必要がある場合は、クラス内で属性を直接定義できます。この属性はクラス属性であり、Student クラスに属します:

class Student(object):
    name = 'Student'

クラス属性を定義すると、この属性はクラスに属しますが、クラスのすべてのインスタンスがアクセスできます。テストしてみましょう:

>>> class Student(object):
...     name = 'Student'
...
>>> s = Student() # インスタンスsを作成
>>> print(s.name) # name属性を印刷します。インスタンスにはname属性がないため、クラスのname属性を探します
Student
>>> print(Student.name) # クラスのname属性を印刷します
Student
>>> s.name = 'Michael' # インスタンスにname属性をバインドします
>>> print(s.name) # インスタンス属性の優先度がクラス属性よりも高いため、インスタンス属性がクラスのname属性を隠します
Michael
>>> print(Student.name) # しかし、クラス属性は消えていません。Student.nameを使用すると、まだアクセスできます
Student
>>> del s.name # インスタンスのname属性を削除します
>>> print(s.name) # 再度s.nameを呼び出すと、インスタンスのname属性が見つからないため、クラスのname属性が表示されます
Student

プログラムを書く際には、インスタンス属性とクラス属性に同じ名前を使用しないでください。なぜなら、同じ名前のインスタンス属性がクラス属性を隠すため、難解なエラーが発生する可能性があるからです。

高度なオブジェクト指向プログラミング#

多重継承、カスタムクラス、メタクラス
__slots__の使用
通常、クラスを定義し、クラスのインスタンスを作成した後、インスタンスに任意の属性やメソッドをバインドできます。これが動的言語の柔軟性です。まずクラスを定義します:

class Student(object):
    pass

次に、インスタンスに属性をバインドしてみます:

>>> s = Student()
>>> s.name = 'Michael' # 動的にインスタンスに属性をバインドします
>>> print(s.name)
Michael

さらに、インスタンスにメソッドをバインドしてみます:

>>> def set_age(self, age): # インスタンスメソッドとして関数を定義します
...     self.age = age
...
>>> from types import MethodType
>>> s.set_age = MethodType(set_age, s) # インスタンスにメソッドをバインドします
>>> s.set_age(25) # インスタンスメソッドを呼び出します
>>> s.age # テスト結果
25

ただし、インスタンスにメソッドをバインドしても、別のインスタンスには影響しません:

>>> s2 = Student() # 新しいインスタンスを作成します
>>> s2.set_age(25) # メソッドを呼び出そうとします
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
AttributeError: 'Student' object has no attribute 'set_age'

すべてのインスタンスにメソッドをバインドするには、クラスにメソッドをバインドできます:

>>> def set_score(self, score):
...     self.score = score
...
>>> Student.set_score = set_score

クラスにメソッドをバインドすると、すべてのインスタンスが呼び出すことができます:

>>> s.set_score(100)
>>> s.score
100
>>> s2.set_score(99)
>>> s2.score
99

通常、上記の set_score メソッドはクラス内で直接定義できますが、動的バインディングにより、プログラムの実行中にクラスに機能を追加することができます。これは静的言語では非常に難しいです。

__slots__の使用
ただし、インスタンスの属性を制限したい場合はどうすればよいでしょうか?例えば、Student インスタンスに name と age 属性のみを追加することを許可したい場合です。

その目的を達成するために、Python はクラスを定義する際に、特別な__slots__変数を定義して、そのクラスインスタンスが追加できる属性を制限することを許可します

class Student(object):
    __slots__ = ('name', 'age') # タプルを使用して許可される属性名を定義します

次に、試してみましょう:

>>> s = Student() # 新しいインスタンスを作成します
>>> s.name = 'Michael' # 属性'name'をバインドします
>>> s.age = 25 # 属性'age'をバインドします
>>> s.score = 99 # 属性'score'をバインドしようとします
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
AttributeError: 'Student' object has no attribute 'score'

'score' が__slots__に含まれていないため、score 属性をバインドできず、score をバインドしようとすると AttributeError エラーが発生します。

__slots__を使用する際には、__slots__で定義された属性は現在のクラスインスタンスにのみ適用され、継承された子クラスには適用されません

>>> class GraduateStudent(Student):
...     pass
...
>>> g = GraduateStudent()
>>> g.score = 9999

子クラスでも__slots__を定義する必要があります。そうすれば、子クラスインスタンスが許可される属性は自身の__slots__と親クラスの__slots__の合計になります

@property の使用

属性をバインドする際、属性をそのまま公開すると、簡単に見えますが、パラメータをチェックできないため、成績を自由に変更できてしまいます

s = Student()
s.score= 9999

これは明らかに論理的ではありません。score の範囲を制限するために、set_score () メソッドを使用して成績を設定し、get_score () メソッドを使用して成績を取得することができます。これにより、set_score () メソッド内でパラメータをチェックできます:

class Student(object):

    def get_score(self):
         return self._score

    def set_score(self, value):
        if not isinstance(value, int):
            raise ValueError('score must be an integer!')
        if value < 0 or value > 100:
            raise ValueError('score must between 0 ~ 100!')
        self._score = value

これで、任意の Student インスタンスを操作する際、自由に score を設定することはできなくなります:

>>> s = Student()
>>> s.set_score(60) # ok!
>>> s.get_score()
60
>>> s.set_score(9999)
Traceback (most recent call last):
  ...
ValueError: score must between 0 ~ 100!

しかし、上記の呼び出し方法はやや複雑で、属性のように直接簡単にアクセスできる方法はないのでしょうか?完璧を追求する Python プログラマーにとって、これは必ず実現したいことです!

デコレーター(decorator)を覚えていますか?関数に動的に機能を追加できるものです。クラスのメソッドにもデコレーターは同様に機能します。Python に組み込まれている @property デコレーターは、メソッドを属性呼び出しに変換するためのものです:

class Student(object):

    @property
    def score(self):
        return self._score

    @score.setter
    def score(self, value):
        if not isinstance(value, int):
            raise ValueError('score must be an integer!')
        if value < 0 or value > 100:
            raise ValueError('score must between 0 ~ 100!')
        self._score = value

@property の実装は比較的複雑ですが、使用方法を考察します。getter メソッドを属性に変換するには、@property を追加するだけで済みます。このとき、@property 自体が別のデコレーター @score.setter を作成し、setter メソッドを属性代入に変換します。これにより、制御された属性操作を持つことができます:

>>> s = Student()
>>> s.score = 60 # OK、実際にはs.set_score(60)に変換されます
>>> s.score # OK、実際にはs.get_score()に変換されます
60
>>> s.score = 9999
Traceback (most recent call last):
  ...
ValueError: score must between 0 ~ 100!

この素晴らしい @property により、インスタンス属性を操作する際、その属性が直接公開されていない可能性があることがわかります。実際には getter と setter メソッドを通じて実現されているのです。

読み取り専用属性を定義することもできます。getter メソッドのみを定義し、setter メソッドを定義しないと、読み取り専用属性になります:

class Student(object):

    @property
    def birth(self):
        return self._birth

    @birth.setter
    def birth(self, value):
        self._birth = value

    @property
    def age(self):
        return 2015 - self._birth

上記の birth は読み書き可能な属性ですが、age は読み取り専用属性です。なぜなら、age は birth と現在の時間から計算できるからです。

特に注意すべきは、属性のメソッド名をインスタンス変数と同じにしないことです。例えば、以下のコードは誤りです:

class Student(object):

    # メソッド名とインスタンス変数が両方ともbirthです:
    @property
    def birth(self):
        return self.birth

これは、s.birth を呼び出すと、最初にメソッド呼び出しに変換され、return self.birth を実行すると、self の属性にアクセスすることになり、再びメソッド呼び出しに変換され、無限再帰を引き起こし、最終的にスタックオーバーフローエラーRecursionError を引き起こします。

小結
@property はクラス定義で広く使用され、呼び出し側に短いコードを書くことを可能にし、同時にパラメータに対する必要なチェックを保証します。これにより、プログラムの実行時にエラーの可能性が減ります。

多重継承
クラス階層は哺乳類と鳥類の設計に従います:

class Animal(object):
    pass

# 大クラス:
class Mammal(Animal):
    pass

class Bird(Animal):
    pass

# 様々な動物:
class Dog(Mammal):
    pass

class Bat(Mammal):
    pass

class Parrot(Bird):
    pass

class Ostrich(Bird):
    pass

動物に Runnable と Flyable の機能を追加する必要がある場合、Runnable と Flyable のクラスを定義するだけで済みます:

class Runnable(object):
    def run(self):
        print('Running...')

class Flyable(object):
    def fly(self):
        print('Flying...')

Runnable 機能が必要な動物には、Dog が多重継承を使用します:

class Dog(Mammal, Runnable):
    pass

Flyable 機能が必要な動物には、Bat が多重継承を使用します:

class Bat(Mammal, Flyable):
    pass

多重継承により、子クラスは複数の親クラスのすべての機能を同時に取得できます

MixIn
クラスの継承関係を設計する際、通常、主なラインは単一の継承から派生します。例えば、Ostrich は Bird から継承します。しかし、追加の機能を「混入」させる必要がある場合、多重継承を使用して実現できます。例えば、Ostrich が Bird から継承するだけでなく、Runnable も同時に継承するようにします。このような設計は通常MixInと呼ばれます。

継承関係をより明確にするために、Runnable と Flyable を RunnableMixIn と FlyableMixIn に変更します。同様に、肉食動物 CarnivorousMixIn や草食動物 HerbivoresMixIn を定義し、特定の動物が複数の MixIn を持つことができます:

class Dog(Mammal, RunnableMixIn, CarnivorousMixIn):
    pass

MixIn の目的は、クラスに複数の機能を追加することです。したがって、クラスを設計する際には、多重継承を使用して複数の MixIn の機能を組み合わせることを優先的に考慮し、複雑な多層の継承関係を設計するのではなく、MixIn を使用します。

Python に付属する多くのライブラリも MixIn を使用しています。例えば、Python には TCPServer と UDPServer という 2 つのネットワークサービスが付属しており、複数のユーザーにサービスを提供するには、多プロセスまたはマルチスレッドモデルを使用する必要があります。この 2 つのモデルは ForkingMixIn と ThreadingMixIn によって提供されます。組み合わせることで、必要なサービスを迅速に構築できます。

例えば、多プロセスモードの TCP サービスを作成する場合、次のように定義します:

class MyTCPServer(TCPServer, ForkingMixIn):
    pass

多スレッドモードの UDP サービスを作成する場合、次のように定義します:

class MyUDPServer(UDPServer, ThreadingMixIn):
    pass

さらに進んだコルーチンモデルを作成する場合、次のように CoroutineMixIn を定義できます:

class MyTCPServer(TCPServer, CoroutineMixIn):
    pass

これにより、複雑で巨大な継承チェーンを必要とせず、異なるクラスの機能を組み合わせるだけで、必要な子クラスを迅速に構築できます。

小結
1 Python は多重継承を許可するため、MixIn は一般的な設計です。

2 単一継承のみを許可する言語(Java など)は、MixIn 設計を使用できません。

カスタムクラス
Python のクラスには、クラスをカスタマイズするための多くの特殊な関数があります。

str
まず、Student クラスを定義し、インスタンスを印刷します

>>> class Student(object):
...     def __init__(self, name):
...         self.name = name
...
>>> print(Student('Michael'))
<__main__.Student object at 0x109afb190>

一連の <main.Student object at 0x109afb190 > が表示され、見栄えが悪いです。

どうすれば見栄えを良くできるでしょうか?str() メソッドを定義し、見栄えの良い文字列を返すだけで済みます:

>>> class Student(object):
...     def __init__(self, name):
...         self.name = name
...     def __str__(self):
...         return 'Student object (name: %s)' % self.name
...
>>> print(Student('Michael'))
Student object (name: Michael)

こうすることで、印刷されたインスタンスは見栄えが良く、内部の重要なデータがわかりやすくなります。

しかし、注意深い友人は、変数を直接入力しても print を使用しないと、インスタンスがまだ見栄えが悪いことに気づくでしょう:

>>> s = Student('Michael')
>>> s
<__main__.Student object at 0x109afb310>

これは、直接変数を表示すると__str__() ではなく__repr__() が呼び出されるためです。両者の違いは、str() はユーザーが見る文字列を返し、repr() はプログラム開発者が見る文字列を返します。つまり、repr() はデバッグのためのものです。

解決策は、repr() も定義することです。しかし、通常、str() と__repr__() のコードは同じであるため、手抜きの書き方があります:

class Student(object):
    def __init__(self, name):
        self.name = name
    def __str__(self):
        return 'Student object (name=%s)' % self.name
    __repr__ = __str__

iter
もしクラスを for ... in ループで使用したい場合、list や tuple のように、iter() メソッドを実装する必要があります。このメソッドはイテレータオブジェクトを返し、Python の for ループはこのイテレータオブジェクトの__next__() メソッドを呼び出して次の値を取得し、StopIterationエラーに遭遇するまでループを続けます。

フィボナッチ数列の例を挙げて、Fib クラスを作成し、for ループで使用できるようにします:

class Fib(object):
    def __init__(self):
        self.a, self.b = 0, 1 # 2つのカウンタa、bを初期化します

    def __iter__(self):
        return self # インスタンス自体がイテレータオブジェクトであるため、自分自身を返します

    def __next__(self):
        self.a, self.b = self.b, self.a + self.b # 次の値を計算します
        if self.a > 100000: # ループを終了する条件
            raise StopIteration()
        return self.a # 次の値を返します

次に、Fib インスタンスを for ループで使用してみましょう:

>>> for n in Fib():
...     print(n)
...
1
1
2
3
5
...
46368
75025

getitem
Fib インスタンスは for ループで使用できますが、list のように使用することはできません。例えば、5 番目の要素を取得しようとすると:

>>> Fib()[5]
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: 'Fib' object does not support indexing

リストのようにインデックスで要素を取得できるようにするには、getitem() メソッドを実装する必要があります:

class Fib(object):
    def __getitem__(self, n):
        a, b = 1, 1
        for x in range(n):
            a, b = b, a + b
        return a

これで、インデックスを使って数列の任意の項にアクセスできるようになります:

>>> f = Fib()
>>> f[0]
1
>>> f[1]
1
>>> f[2]
2
>>> f[3]
3
>>> f[10]
89
>>> f[100]
573147844013817084101

しかし、list には不思議なスライス方法があります:

>>> list(range(100))[5:10]
[5, 6, 7, 8, 9]

Fib に対してはエラーが発生します。理由は、getitem() に渡される引数が int である場合もあれば、スライスオブジェクト slice である場合もあるため、判断する必要があります:

class Fib(object):
    def __getitem__(self, n):
        if isinstance(n, int): # nはインデックスです
            a, b = 1, 1
            for x in range(n):
                a, b = b, a + b
            return a
        if isinstance(n, slice): # nはスライスです
            start = n.start
            stop = n.stop
            if start is None:
                start = 0
            a, b = 1, 1
            L = []
            for x in range(stop):
                if x >= start:
                    L.append(a)
                a, b = b, a + b
            return L

これで Fib のスライスを試してみましょう:

>>> f = Fib()
>>> f[0:5]
[1, 1, 2, 3, 5]
>>> f[:10]
[1, 1, 2, 3, 5, 8, 13, 21, 34, 55]

ただし、step パラメータには対応していません:

>>> f[:10:2]
[1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89]

負数にも対応していません。したがって、getitem() を正しく実装するには多くの作業が必要です。

さらに、オブジェクトを dict として見なす場合、getitem() の引数は str のような key として使用できるオブジェクトである可能性もあります。

それに対応するのが__setitem__() メソッドであり、オブジェクトをリストや辞書として集合に値を設定するために使用されます。最後に、delitem() メソッドは特定の要素を削除するために使用されます。

要するに、上記のメソッドを使用することで、私たちが定義したクラスは Python の組み込みの list、tuple、dict と同じように振る舞うことができます。これはすべて動的言語の「アヒル型」によるもので、特定のインターフェースを強制的に継承する必要はありません。

getattr
通常、クラスのメソッドや属性を呼び出すとき、存在しない場合はエラーが発生します。例えば、Student クラスを定義します:

class Student(object):
    
    def __init__(self):
        self.name = 'Michael'

name 属性を呼び出すと問題ありませんが、存在しない score 属性を呼び出すと問題が発生します:

>>> s = Student()
>>> print(s.name)
Michael
>>> print(s.score)
Traceback (most recent call last):
  ...
AttributeError: 'Student' object has no attribute 'score'

エラーメッセージは、score という属性が見つからないことを明確に示しています。

このエラーを回避するために、score 属性を追加する以外に、Python には別のメカニズムがあります。それは、getattr() メソッドを書いて、動的に属性を返すことです。次のように変更します:

class Student(object):

    def __init__(self):
        self.name = 'Michael'

    def __getattr__(self, attr):
        if attr=='score':
            return 99

存在しない属性を呼び出すと、例えば score の場合、Python インタプリタは__getattr__(self, 'score') を呼び出して属性を取得しようとします。これにより、score の値を返す機会が得られます:

>>> s = Student()
>>> s.name
'Michael'
>>> s.score
99

返す関数も完全に可能です:

class Student(object):

    def __getattr__(self, attr):
        if attr=='age':
            return lambda: 25

ただし、呼び出し方法が変わります:

>>> s.age()
25

注意すべきは、属性が見つからない場合にのみ__getattr__が呼び出され、既存の属性(例えば name)は__getattr__で検索されないことです。

また、s.abc のような任意の呼び出しはNoneを返します。これは、私たちが定義した__getattr__がデフォルトで None を返すためです。クラスが特定のいくつかの属性にのみ応答するようにするには、約束に従って AttributeError エラーを発生させる必要があります:

class Student(object):

    def __getattr__(self, attr):
        if attr=='age':
            return lambda: 25
        raise AttributeError('\'Student\' object has no attribute \'%s\'' % attr)

これにより、クラスのすべての属性とメソッド呼び出しを動的に処理することができます。

この完全に動的な呼び出しの特性にはどのような実際の利点があるのでしょうか?それは、完全に動的な状況に対して呼び出しを行うことができるということです。

call
オブジェクトインスタンスは独自の属性やメソッドを持つことができます。インスタンスメソッドを呼び出すとき、私たちは instance.method () を使用します。インスタンス自体で直接呼び出すことはできるのでしょうか?Python では、その答えは肯定的です。

任意のクラスは、call() メソッドを定義するだけで、インスタンスを直接呼び出すことができます。次の例を見てください:

class Student(object):
    def __init__(self, name):
        self.name = name

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

呼び出し方法は次のようになります:

>>> s = Student('Michael')
>>> s() # self引数は渡さないでください
My name is Michael.

call() には引数を定義することもできます。インスタンスを直接呼び出すことは、関数を呼び出すのと同じように見えます。したがって、オブジェクトを関数と見なすことができ、関数をオブジェクトと見なすことができます。なぜなら、両者の間には根本的な違いはないからです。

オブジェクトを関数と見なす場合、関数自体も実行時に動的に作成できるため、クラスのインスタンスは実行時に作成されます。これにより、オブジェクトと関数の境界が曖昧になります。

では、変数がオブジェクトか関数かをどうやって判断するのでしょうか?実際には、私たちはオブジェクトが呼び出せるかどうかを判断する必要があります。呼び出せるオブジェクトはCallable オブジェクトであり、関数や上記の__call__() を持つクラスインスタンスが含まれます:

>>> callable(Student())
True
>>> callable(max)
True
>>> callable([1, 2, 3])
False
>>> callable(None)
False
>>> callable('str')
False

callable () 関数を使用することで、オブジェクトが「呼び出し可能」なオブジェクトであるかどうかを判断できます

小結
Python のクラスは多くのカスタマイズメソッドを定義でき、特定のクラスを非常に便利に生成できます。

列挙型の使用
定数を定義する必要がある場合、1 つの方法は、大文字の変数を整数で定義することです。例えば、月を定義する場合:

JAN = 1
FEB = 2
MAR = 3
...
NOV = 11
DEC = 12

利点はシンプルであることですが、欠点は型が int であり、依然として変数であることです。

より良い方法は、このような列挙型のためにクラス型を定義し、各定数をクラスのユニークなインスタンスとして定義することです。Python はこの機能を実現するためにEnum クラスを提供しています:

from enum import Enum

Month = Enum('Month', ('Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'))

これにより、Month 型の列挙型を取得でき、Month.Jan を使用して定数を参照したり、すべてのメンバーを列挙したりできます:

for name, member in Month.__members__.items():
    print(name, '=>', member, ',', member.value)

image

value 属性はメンバーに自動的に割り当てられる int 定数で、デフォルトでは 1 からカウントが始まります。

列挙型をより正確に制御する必要がある場合は、Enum からカスタムクラスを派生させることができます:

from enum import Enum, unique

@unique
class Weekday(Enum):
    Sun = 0 # Sunのvalueを0に設定
    Mon = 1
    Tue = 2
    Wed = 3
    Thu = 4
    Fri = 5
    Sat = 6

@unique デコレーターは、重複値がないことを確認するのに役立ちます

これらの列挙型にアクセスする方法はいくつかあります:

>>> day1 = Weekday.Mon
>>> print(day1)
Weekday.Mon
>>> print(Weekday.Tue)
Weekday.Tue
>>> print(Weekday['Tue'])
Weekday.Tue
>>> print(Weekday.Tue.value)
2
>>> print(day1 == Weekday.Mon)
True
>>> print(day1 == Weekday.Tue)
False
>>> print(Weekday(1))
Weekday.Mon
>>> print(day1 == Weekday(1))
True
>>> Weekday(7)
Traceback (most recent call last):
  ...
ValueError: 7 is not a valid Weekday
>>> for name, member in Weekday.__members__.items():
...     print(name, '=>', member)
...
Sun => Weekday.Sun
Mon => Weekday.Mon
Tue => Weekday.Tue
Wed => Weekday.Wed
Thu => Weekday.Thu
Fri => Weekday.Fri
Sat => Weekday.Sat

メンバー名を使用して列挙定数を参照することも、value の値に基づいて列挙定数を取得することもできます。

メタクラスの使用

type()
動的言語と静的言語の最大の違いは、関数とクラスの定義がコンパイル時に定義されるのではなく、実行時に動的に作成されることです。

例えば、Hello というクラスを定義する場合、hello.py モジュールを書きます:

class Hello(object):
    def hello(self, name='world'):
        print('Hello, %s.' % name)

Python インタプリタが hello モジュールを読み込むと、すべての文が順に実行され、結果として Hello というクラスオブジェクトが動的に作成されます。テストは次のようになります:

>>> from hello import Hello
>>> h = Hello()
>>> h.hello()
Hello, world.
>>> print(type(Hello))
<class 'type'>
>>> print(type(h))
<class 'hello.Hello'>

type () 関数は、型や変数の型を確認することができます。Hello はクラスであり、その型は type であり、h はインスタンスであり、その型は class Hello です。

読み込み中...
文章は、創作者によって署名され、ブロックチェーンに安全に保存されています。