<jupyter_start><jupyter_text>6-3. クラスPythonにおけるオブジェクト指向プログラミングのうち、クラスを定義する方法について簡単に説明します。参考- https://docs.python.org/ja/3/tutorial/classes.html- https://docs.python.org/ja/3/reference/datamodel.html  クラス定義Pythonではすべてのデータはオブジェクトなのですが、以下では特に、クラス定義によって作成されたクラスを型とするデータを扱います。このようなデータは、**オブジェクト指向プログラミング**における典型的な**オブジェクト**です。そこで以下では、オブジェクトという用語をもっぱら使います。4-1で見たように、ファイルオブジェクトに対して `readline()` というメソッドを呼び出すと、ファイルの行が文字列として次々と返されます。ここでは、ファイルオブジェクトのようなオブジェクトで、`readline()` というメソッドが呼び出されると、常に `'Hello.\n'` という文字列を返すようなものを作ってみましょう。そのためには、新しいクラスを定義します。**クラス**とは、オブジェクトの種類を意味します。新しいクラスを定義すると、そのクラスに属するオブジェクトを作ることができるようになります。それらのオブジェクトの型は、その新しいクラスになります。ここでは、ずっと `'Hello.\n'` を返し続けるので、`HelloForEver` という名前を持つクラスを定義しましょう。そして、`HelloForEver` というクラスを型とするオブジェクトを作ります。<jupyter_code>class HelloForEver:
    def readline(self):
        return 'Hello.\n'<jupyter_output><empty_output><jupyter_text>一般にクラス定義は、以下のような形をしています。---```Pythonclass クラス名:    def メソッド名(self, 引数, ...):        実行文    def メソッド名(self, 引数, ...):        実行文    ...```---メソッド定義は関数定義と同じ形をしていますが、クラス定義の中に入っています。メソッド定義において、その最初の引数には慣例として `self` という名前を付けます。この引数には、メソッドが呼び出されたオブジェクト自身が渡されます。上の例では、`readline` というメソッドが一つ定義されています。以下のようにして、このクラスのオブジェクトを作ることができます。<jupyter_code>f = HelloForEver()<jupyter_output><empty_output><jupyter_text>`HelloForEver` を型とする新しいオブジェクトが作られて変数 `f` の値となります。一般に、オブジェクトの生成は、---```Pythonクラス名(式, ...)```---という式で行います。このようにオブジェクトを生成する式は**コンストラクタ**と呼ばれます。なお、上の例では、括弧の中に式は一つもありません。 このようにして作ったオブジェクトの型を確認してください。<jupyter_code>type(f)<jupyter_output><empty_output><jupyter_text>`__main__.HelloForEver` と表示されたでしょう。`__main__` は、Jupyter Notebook の式が評価されているモジュールを指すので、このオブジェクトの型が、上で定義した `HelloForEver` クラスであることがわかります。クラスのコンストラクタによって生成されたオブジェクトを、そのクラスの**インスタンス**と言います。上のオブジェクトは `HelloForEver` クラスのインスタンスです。オブジェクトそのものは以下のように表示されます。<jupyter_code>f<jupyter_output><empty_output><jupyter_text>このオブジェクトに対して、`readline` というメソッドを呼び出すことができます。<jupyter_code>f.readline()<jupyter_output><empty_output><jupyter_text>この例では、`f` という変数に入っているオブジェクトが `self` という引数に渡されて、`readline` の本体である以下の文が実行されました。---```Python    return 'Hello.\n'```---（この例では `self` は参照されていません。）何回やっても同じです。<jupyter_code>f.readline()
f.readline()<jupyter_output><empty_output><jupyter_text>初期化と属性以下の例では、初期化のメソッドが定義され、オブジェクトに属性が与えられます。**初期化**のメソッドは **`__init__`** という名前を持ち、オブジェクトが作られたときに自動的に呼び出されます。`__init__` の引数は、オブジェクト自身と、クラス名の後に与えられる式の値です。<jupyter_code>class HelloFile:
    def __init__(self, n):
        self.n = n
    def readline(self):
        if self.n == 0:
            return ''
        self.n = self.n - 1
        return 'Hello.\n'<jupyter_output><empty_output><jupyter_text>この例では、以下のようにしてオブジェクトが作られます。<jupyter_code>f = HelloFile(3)<jupyter_output><empty_output><jupyter_text>すると、`HelloFile` を型とする新しいオブジェクトが作られて、そのオブジェクト自身が `self` に、`3` が `n` に渡されて、`self.n = n` という文が実行されます。`self.n` という式は、このオブジェクトの `n` という名前の属性を表します。一般に、`class` の構文によって定義されたクラスを型とするオブジェクトは、属性を持つことができます。**属性**とは、個々のオブジェクトごとに記録される値であり、オブジェクト内の変数と考えられます。オブジェクトの属性は、オブジェクトに対してその**属性名**を指定して、参照したり設定したりできます。オブジェクトの属性は、`self.属性名` という式で参照されます。`self.属性名` を代入文の左辺に書けば、属性を設定することができます。`self.n = n` のうち、`self.` の次の `n` は属性を表し、右辺の `n` は、`__init__` メソッドの引数を表していますので、混同しないようにしてください。この例では、新しく作られたオブジェクトの `n` という属性が、引数 `n` の値である `3` に設定されます。`readline` メソッドは以下のように定義されています。---```Python    def readline(self):        if self.n == 0:            return ''        self.n = self.n - 1        return 'Hello.\n'```---オブジェクトの属性 `n` を参照して、それが `0` ならば空文字列を返します。そうでなければ、属性 `n` を `1` 減らしてから文字列 `'Hello.\n'` を返します。<jupyter_code>f.readline()
f.readline()
f.readline()
f.readline()<jupyter_output><empty_output><jupyter_text>変数 `f` の値であるオブジェクトの属性 `n` は、`f.n` という式によって参照できます。<jupyter_code>f.n<jupyter_output><empty_output><jupyter_text>ここでは詳しく説明しませんが、オブジェクトのメソッドも属性の一種です。  継承**継承**は、既存のクラスをもとにして、変更部分だけを与えることにより、新たなクラスを定義する機能です。以下の例では、`HelloForEver` をもとにして `HelloFile` を定義しています。一般に、新しく定義されるクラスを**子クラス**、そのもとになるクラスを**親クラス**と言います。<jupyter_code>class HelloFile(HelloForEver):
    def __init__(self, n):
        self.n = n
    def readline(self):
        if self.n == 0:
            return ''
        self.n = self.n - 1
        return super().readline()<jupyter_output><empty_output><jupyter_text>ここでは、`__init__` と `readline` を新たに定義しています。`HelloForEver` にも `readline` があります。こちらの `readline` は、`super().readline()` という式で呼び出すことができます。**`super()`** は、子クラスのオブジェクトに対して親クラスのメソッドを呼び出すための構文です。実際に、`HelloFile` の `readline` の中で、`HelloForEver` の `readline` を呼び出しています。<jupyter_code>f = HelloFile(3)
f.readline()<jupyter_output><empty_output><jupyter_text>特殊メソッドPythonでは、**特殊メソッド**と呼ばれるメソッドが多数あります。これらのメソッドの名前は `__` で始まり `__` で終わります。クラス定義の中で特殊メソッドを定義すると、そのクラスのオブジェクトに対して、その特殊メソッドに対応する機能が付与されます。初期化メソッド `__init__` も特殊メソッドですが、以下のクラス `HelloFileIterator` では、**`__iter__`** と **`__next__`** という特殊メソッドが定義されています。このクラスは、`HelloFile` を継承して定義されています。`__iter__` メソッドは、オブジェクトに対して関数 `iter` が適用されたときに呼び出されます。`__iter__` メソッドの値が関数 `iter` の値となります。以下の例では、`__iter__` はオブジェクト自身を返しています。したがって、オブジェクトに `iter` が適用されると、オブジェクト自身が返ります。<jupyter_code>class HelloFileIterator(HelloFile):
    def __iter__(self):
        return self
    def __next__(self):
        line = self.readline()
        if line == '':
            raise StopIteration
        return line
f = HelloFileIterator(3)
print(f is iter(f))<jupyter_output><empty_output><jupyter_text>上の例で、`iter(f)` として関数 `iter` を呼び出すと、`f.__iter__()` としてメソッド `__iter__` が `f` に対して呼び出され、その結果が `iter(f)` の値となります。したがって、`iter(f)` は `f` と同じ値を返します。`__next__` メソッドも、オブジェクトに対して関数 `next` が適用されたときに呼び出されます。`__next__` メソッドの値が `next` の値となります。上の例では、`self.readline()` として、オブジェクト自身に対してメソッド `readline` を呼び出しています。その値が空文字列ならば、---```Python            raise StopIteration```---という文を実行して、**`StopIteration`** というエラーを投げます。実は、このエラーは、for文が捕まえて繰り返しを止める効果を持ちます。なお、**``raise``** は強制的にエラーを発生させる構文です。<jupyter_code>for line in f:
    print(line)<jupyter_output><empty_output><jupyter_text>4-2で説明したように、上のfor文では、まず `f` のオブジェクトに対して関数 `iter` が適用されます。すると `f` のオブジェクト自身が返ります。そして、このオブジェクトに対して関数 `next` が繰り返し適用されて、その結果が変数 `line` の値となります。`StopIteration` のエラーが検知されると、for文が終了します。  練習`'Hello.\n'` ではなくて、初期時に指定された文字列を繰り返し返すように、新たなクラス `StringFileIterator` を定義してください。`StringFileIterator` は `HelloFileIterator` を継承し、初期化メソッドには、文字列と回数を指定します。<jupyter_code>class StringFileIterator(HelloFileIterator):
    def __init__(self, s, n):
        ...
    ...<jupyter_output><empty_output><jupyter_text>上のセルで解答を作成した後、以下のセルを実行し、実行結果が `True` になることを確認してください。<jupyter_code>f = StringFileIterator('abc', 3)
print(list(f) == ['abc','abc','abc'])<jupyter_output><empty_output><jupyter_text>▲with文への対応ここでは詳しく説明しませんが、さらに特殊メソッドである **`__enter__`** と **`__exit__`** を定義すると、**with文**にも対応できます。<jupyter_code>class HelloFileIterator(HelloFile):
    def __enter__(self):
        return self
    def __exit__(self,exception_type,exception_value,traceback):
        pass
    def __next__(self):
        line = self.readline()
        if line == '':
            raise StopIteration
        return line
    def __iter__(self):
        return self
with HelloFileIterator(3) as f:
    for line in f:
        print(line)<jupyter_output><empty_output><jupyter_text>練習の解答<jupyter_code>class StringFileIterator(HelloFileIterator):
    def __init__(self, s, n):
        self.s = s
        self.n = n
    def readline(self):
        if self.n == 0:
            return ''
        self.n = self.n - 1
        return self.s<jupyter_output><empty_output>
{'fraction_non_alphanumeric': 0.14144871970423115, 'fraction_numerical': 0.003423250718882651, 'mean_word_length': 7.026373626373626, 'pattern_counts': {'":': 0, '<': 73, '<?xml version=': 0, '>': 73, 'https://': 2, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'chain_length': '18', 'dir': 'jupyter-structured-clean-dedup', 'id': '277824', 'license': 'MIT', 'n_tokens_mistral': 4750, 'n_tokens_neox': 4408, 'n_words': 328, 'path': '6/6-3.ipynb', 'repo_name': 'salikh/utpython', 'size': '7303'}