Pythonのクラスについて学ぶことで、自分自身のデータ型を定義して、その型に基づいて作成されたオブジェクトにアクセスする方法を学ぶことができます。
このチュートリアルでは、Pythonでクラスを定義する方法、クラスオブジェクトとインスタンスオブジェクトの違い、クラス変数とインスタンス変数、メソッド、継承、プライベート変数とメソッド、プロパティ、抽象基底クラスなどのトピックについて説明します。
__TOC__
Pythonのクラスは、オブジェクト指向プログラミングにおける基本的な概念の一つで、データと関数をまとめて扱うことができます。
クラスは、プログラム内でオブジェクトを作成するための設計図のようなものです。クラスには、そのオブジェクトが持つ属性（データ）や振る舞い（メソッド）が定義されています。クラスを定義することで、同じような性質を持ったオブジェクトを簡単に作成することができます。
クラスの中で定義されたデータは、クラス変数とインスタンス変数の2種類があります。クラス変数は、そのクラスが持つデータであり、全てのインスタンスで共有されます。インスタンス変数は、オブジェクトごとに異なる値を持つデータであり、各インスタンスごとに独立しています。
メソッドは、オブジェクトの振る舞いを定義するための関数です。クラスに定義されたメソッドは、インスタンスを通じて呼び出すことができます。
クラスは、継承を使って他のクラスを拡張することができます。継承することで、既存のクラスの機能を再利用し、新たな機能を追加することができます。
また、Pythonにはプライベート変数やプロパティ、抽象基底クラスなど、クラスをより強力にするための機能があります。これらの機能を使うことで、より柔軟で安全なコードを書くことができます。
Pythonでは class キーワードを使用してクラスを定義します。基本的な構文は以下の通りです。
class ClassName:
    # クラスの本体
クラス名は、PascalCase で書かれた単語である必要があります。クラスの本体には、変数や関数を定義することができます。
以下の例では、Person というクラスを定義しています。このクラスには、名前と年齢のインスタンス変数、およびそれらを設定するための __init__ メソッドが含まれています。
class Person:
    def __init__(self, name, age):
        self.name = name
        self.age = age
このクラスを使うと、以下のようにして Person のインスタンスを作成することができます。
person1 = Person("Alice", 25)
person2 = Person("Bob", 30)
クラス定義によって作成されたオブジェクトには、2つの種類があります。1つはクラスオブジェクトであり、もう1つはインスタンスオブジェクトです。
クラスオブジェクトは、クラス定義に基づいて作成され、クラス変数にアクセスするために使用されます。
class MyClass:
    class_variable = 0
print(MyClass.class_variable)
この場合は、MyClass がクラスオブジェクトです。
インスタンスオブジェクトは、クラスから作成された各オブジェクトのことを指します。それぞれのインスタンスは、独自の状態を保持できます。
class MyClass:
    def __init__(self):
        self.instance_variable = 0
my_object1 = MyClass()
my_object2 = MyClass()
print(my_object1.instance_variable)
print(my_object2.instance_variable)
この場合は、my_object1とmy_object2 がインスタンスオブジェクトです。
曖昧さのない場合は、クラスオブジェクトをクラス、インスタンスオブジェクトをインスタンスと呼びます。
クラス変数はクラス内で定義され、すべてのインスタンスで共有される変数です。インスタンス変数は、各インスタンスごとに異なる値を持つ変数です。
以下は、クラス変数とインスタンス変数を定義する例です。
class MyClass:
    class_variable = 0  # クラス変数
    def __init__(self, instance_variable):
        self.instance_variable = instance_variable  # インスタンス変数
上記の例では、class_variableはクラス変数、instance_variableはインスタンス変数です。class_variableはクラス内で定義され、インスタンス作成前に定義されます。instance_variableは、__init__メソッド内で初期化されるため、インスタンスが作成されるたびに異なる値が割り当てられます。
以下は、クラス変数とインスタンス変数をアクセスする例です。
class MyClass:
    class_variable = 0  # クラス変数
    def __init__(self, instance_variable):
        self.instance_variable = instance_variable  # インスタンス変数
my_instance = MyClass(1)
print(my_instance.class_variable)  # クラス変数のアクセス方法
print(my_instance.instance_variable)  # インスタンス変数のアクセス方法
MyClass.class_variable = 2  # クラス変数の変更
my_other_instance = MyClass(3)
print(my_other_instance.class_variable)  # クラス変数の変更後のアクセス方法
上記の例では、my_instanceはMyClassのインスタンスであり、class_variableとinstance_variableにアクセスしています。MyClass.class_variableを変更すると、MyClassのすべてのインスタンスが新しい値を持つようになります。
Pythonにおけるメソッドとは、オブジェクトが持つ関数のことです。メソッドは、クラス内に定義された関数であり、オブジェクトに対して呼び出されることが前提となっています。
メソッドは、インスタンスメソッド、クラスメソッド、スタティックメソッド、特殊メソッドなど、種類によって様々な特徴を持っています。
インスタンスメソッドは、そのオブジェクトに対して呼び出され、クラスメソッドは、そのクラスに対して呼び出されます。
スタティックメソッドは、オブジェクトやクラスに関係なく、独立して呼び出されます。
特殊メソッドは、Pythonが提供する特別な機能を持つメソッドであり、例えば、__init__や__str__などがあります。
インスタンスメソッドは、クラスのインスタンスによって呼び出されます。第1引数は必ず self で、それを使ってインスタンスの属性にアクセスできます。
class MyClass:
    def instance_method(self, arg1, arg2):
        self.arg1 = arg1
        self.arg2 = arg2
        print(f"Called instance_method with {self.arg1} and {self.arg2}")
obj = MyClass()
obj.instance_method("hello", "world")  # "Called instance_method with hello and world"
このコードは、インスタンスメソッドの例です。 instance_methodは、selfを最初の引数として受け取り、その引数はメソッドが呼び出されたインスタンス自体を指します。このインスタンスを介して、メソッドはオブジェクトの状態を変更することができます。引数arg1とarg2は、メソッドに渡され、メソッドの処理に使用されます。
上記のコードは、MyClassのインスタンスobjを作成し、instance_methodを呼び出しています。 instance_methodは、 objがインスタンスであるため、selfとして自分自身を参照し、 "Called instance_method with hello and world"という文字列を出力します。
単にメソッドと言った場合、インスタンスメソッドを示すことが多いですが、文脈に注意しましょう。
クラスメソッドは、クラス自体によって呼び出されます。第1引数は必ず cls で、それを使ってクラスの属性にアクセスできます。
class MyClass:
    class_var = "class variable"
    
    @classmethod
    def class_method(cls, arg):
        cls.arg = arg
        print(f"Called class_method with {cls.arg} and {cls.class_var}")
MyClass.class_method("hello")  # "Called class_method with hello and class variable"
上記のコードは、MyClass というクラスを定義し、その中に class_var というクラス変数を定義し、class_method というクラスメソッドを定義しています。
class_method は、@classmethod デコレータで修飾されており、第一引数が cls というクラスオブジェクトであることが示されています。クラスメソッドは、クラス自身を第一引数として受け取るため、クラス変数にアクセスしたい場合は、cls.class_var のようにしてアクセスすることができます。
このクラスメソッドは、arg という引数を受け取り、cls.arg にその値を代入し、cls.arg と cls.class_var を用いてメッセージを出力するという動作をします。
最後の行では、class_method をクラス名で呼び出しています。class_method がクラスメソッドであるため、第一引数にはクラスオブジェクト MyClass が自動的に渡されます。
スタティックメソッドは、クラス自体やインスタンスから呼び出すことができます。スタティックメソッドは self や cls が必要ないため、第1引数を定義しません。
class MyClass:
    @staticmethod
    def static_method(arg):
        print(f"Called static_method with {arg}")
MyClass.static_method("hello")  # "Called static_method with hello"
この例では、@staticmethod デコレータが使われています。このデコレータを使うことで、メソッドがスタティックメソッドであることを明示的に示すことができます。
スタティックメソッドはクラスに属していますが、インスタンスには属していないため、引数に self や cls を必要としません。そのため、スタティックメソッドは通常、インスタンス変数を使用する必要がない場合に使用されます。
上記の例では、@staticmethod を使って static_method メソッドを定義しています。このメソッドは arg という引数を受け取り、"Called static_method with {arg}" というメッセージを出力します。
最後の行では、MyClass.static_method("hello") というコードを実行することで、static_method メソッドを呼び出しています。この結果、"Called static_method with hello" というメッセージが出力されます。
特殊メソッドは、Pythonによって予約されたメソッドで、オブジェクトの様々な振る舞いを定義することができます。特殊メソッドは、名前が __ で始まり、終わります。
例えば、__init__() は、オブジェクトが作成されるときに自動的に呼び出されるメソッドで、インスタンス変数の初期化などの処理を行います。
class MyClass:
    def __init__(self, arg1, arg2):
        self.arg1 = arg1
        self.arg2 = arg2
        print(f"Called __init__ with {self.arg1} and {self.arg2}")
obj = MyClass("hello", "world")  # "Called __init__ with hello and world"
このコードは、MyClassという名前のクラスを定義し、__init__という特殊メソッドを使ってインスタンスを初期化しています。
__init__メソッドは、クラスがインスタンス化される際に自動的に呼び出されます。このメソッドは、インスタンスの属性を初期化するのに使用されます。
上記の例では、MyClassのインスタンスを作成し、"hello"という値をarg1に、"world"という値をarg2に割り当て、__init__メソッドが呼び出されたことを示す出力が表示されます。
例えば、__add__メソッドを定義すると、+演算子をオーバーロードして、クラスのインスタンス同士を加算できるようになります。
class Point:
    def __init__(self, x, y):
        self.x = x
        self.y = y
    
    def __add__(self, other):
        return Point(self.x + other.x, self.y + other.y)
    
    def __str__(self):
        return f"Point({self.x}, {self.y})"
p1 = Point(1, 2)
p2 = Point(3, 4)
result = p1 + p2
print(result)  # Point(4, 6)
上記の例では、Pointクラスに__add__メソッドを定義して、2つのPointインスタンスを加算できるようにしています。__str__メソッドはオブジェクトを文字列に変換する際に呼ばれる特殊メソッドであり、ここではprint関数を使ってPointオブジェクトを出力する際に使用されています。
他にも、__sub__（減算）、__mul__（乗算）、__eq__（等価）、__lt__（小なり）など、さまざまな特殊メソッドがあります。これらを使うことで、クラスのインスタンスが自然な方法で演算子を扱えるようにすることができます。
{{コラム|Pythonのクラスのインスタンスにforを適用する|2=Pythonのクラスのインスタンスに for 文を適用するには、クラスに __iter__() と __next__() メソッドを実装する必要があります。これらのメソッドを実装することで、クラスのインスタンスをイテレータとして扱うことができます。
__iter__() メソッドは、イテレータ自身を返す必要があります。通常は、クラスのインスタンス自身を返します。
__next__() メソッドは、次の要素を返します。もう要素がない場合は、StopIteration を発生させます。
以下は、クラスのインスタンスに for 文を適用する例です。
class MyIterator:
    def __init__(self, items):
        self.items = items
        self.current = 0
    def __iter__(self):
        return self
    def __next__(self):
        if self.current < len(self.items):
            item = self.items[self.current]
            self.current += 1
            return item
        else:
            raise StopIteration
my_iter = MyIterator([2, 3, 5])
for item in my_iter:
    print(item)
上記の例では、MyIterator というクラスを定義し、__iter__() と __next__() メソッドを実装しています。
MyIterator クラスのインスタンスを for 文でループさせることができます。
継承とは、既存のクラスを基に新しいクラスを作り出すことで、既存のクラスの機能を引き継ぎながら、新たな機能を追加することができます。
スーパークラスは、継承元のクラスのことを指します。スーパークラスの定義は、通常のクラス定義と同じように行います。
以下の例では、Person というクラスをスーパークラスとして定義しています。
class Person:
    def __init__(self, name, age):
        self.name = name
        self.age = age
        
    def say_hello(self):
        print(f"My name is {self.name} and I am {self.age} years old.")
このスーパークラス Person は、name と age の属性を持ち、say_hello() メソッドを定義しています。
サブクラスは、スーパークラスを継承して新たなクラスを作り出すことができます。サブクラスは、新たに追加する属性やメソッドを定義することができます。
以下の例では、Student というクラスを、スーパークラスである Person を継承して定義しています。
class Student(Person):
    def __init__(self, name, age, grade):
        super().__init__(name, age)
        self.grade = grade
        
    def say_hello(self):
        print(f"My name is {self.name}, I am {self.age} years old and my grade is {self.grade}.")
このサブクラス Student は、Person クラスを継承して、grade の属性を追加し、say_hello() メソッドをオーバーライドしています。
サブクラスでスーパークラスのメソッドを再定義することをオーバーライドと呼びます。サブクラスで同名のメソッドを定義することで、スーパークラスのメソッドを上書きすることができます。
上記の例では、Student クラスで say_hello() メソッドを再定義し、出力内容を変更しています。
サブクラスでスーパークラスのメソッドを呼び出すには、super() 関数を使用します。super() 関数を呼び出すことで、スーパークラスのメソッドを呼び出すことができます。
上記の例では、Student クラスの __init__() メソッドで、super() 関数を使用して、スーパークラスの __init__() メソッドを呼び出しています。
これにより、Person クラスで定義した name と age の属性を、Student クラスでも使用することができます。
Python では、複数のクラスを同時に継承することができます。これを多重継承と呼びます。多重継承では、カンマで区切って複数のスーパークラスを指定します。
以下の例では、Person クラスというスーパークラスを継承し、さらに、Swimmer というクラスも同時に継承しています。
class Swimmer:
    def swim(self):
        print("I'm swimming!")
class SwimmingStudent(Person, Swimmer):
    def __init__(self, name, age, grade):
        super().__init__(name, age)
        self.grade = grade
    def say_hello(self):
        print(f"My name is {self.name}, I am {self.age} years old and my grade is {self.grade}.")
    
    def swim(self):
        print("I'm swimming like a pro!")
    
SwimmingStudent("Alice", 10, 5).swim() # => I'm swimming like a pro!
上記の例では、Swimmer クラスを定義して、swim() メソッドを持たせています。
そして、SwimmingStudent クラスで Person クラスと Swimmer クラスの両方を継承しています。
さらに、SwimmingStudent クラスで swim() メソッドをオーバーライドして、Swimmer クラスで定義した swim() メソッドと異なる動作をするようにしています。
最後に、SwimmingStudent クラスのインスタンスを作成して、swim() メソッドを呼び出した結果、Swimmer クラスで定義した swim() メソッドではなく、SwimmingStudent クラスでオーバーライドした swim() メソッドが呼び出されることが確認できます。
{{コラム|継承されることを想定した標準モジュールのクラス|2=
Pythonの標準モジュールの中で、継承を想定して設計されたクラスとしては、以下のようなものがあります。
これらのクラスは、Pythonの標準ライブラリで広く使われており、継承によってカスタマイズされたクラスを作成することができます。また、これらのクラスを継承することで、多態性の実現や、型ヒントによる静的解析などの恩恵を受けることができます。
プライベート変数やメソッドとは、クラスの外から直接アクセスできないようにするために用意されたものです。Pythonでは、アンダースコア（_）で始まる変数名やメソッド名を定義することで、プライベート変数やメソッドを作成することができます。
プライベート変数を定義する場合、変数名の先頭にアンダースコアを付けます。以下の例では、Person クラスにプライベート変数 _password を定義しています。
class Person:
    def __init__(self, name, age, password):
        self.name = name
        self.age = age
        self._password = password
        
    def say_hello(self):
        print(f"My name is {self.name} and I am {self.age} years old.")
        
    def _show_password(self):
        print(f"My password is {self._password}.")
同様に、プライベートメソッドを定義する場合も、メソッド名の先頭にアンダースコアを付けます。上記の例では、Person クラスにプライベートメソッド _show_password() を定義しています。
プライベート変数やメソッドは、クラスの外から直接アクセスすることができません。しかし、アクセスする方法が用意されています。
プライベート変数にアクセスする場合、変数名の前にアンダースコアを付けます。以下の例では、Person クラスのインスタンス p から、プライベート変数 _password にアクセスしています。
p = Person("Alice", 25, "password123")
print(p._password) # "password123" を出力
ただし、この方法は推奨されていません。Pythonでは、アンダースコアで始まる変数やメソッドは、外部から直接アクセスしないことが慣例となっています。プライベート変数やメソッドにアクセスする場合は、公開されているインターフェースを通じて行うようにしましょう。
プライベートメソッドにアクセスする場合、同様にメソッド名の前にアンダースコアを付けます。以下の例では、Person クラスのインスタンス p から、プライベートメソッートメソッド _show_password() にアクセスしています。
p = Person("Alice", 25, "password123")
p._show_password() # "My password is password123." を出力
同様に、クラス内で定義された公開メソッドを通じて、プライベート変数やメソッドにアクセスすることもできます。
以下の例では、Person クラスに公開メソッド get_password() を定義し、それを通じてプライベート変数 _password にアクセスしています。
class Person:
    def __init__(self, name, age, password):
        self.name = name
        self.age = age
        self._password = password
    def say_hello(self):
        print(f"My name is {self.name} and I am {self.age} years old.")
    
    def _show_password(self):
        print(f"My password is {self._password}.")
    
    def get_password(self):
        return self._password
p = Person("Alice", 25, "password123")
print(p.get_password()) # "password123" を出力
このように、Pythonでは、プライベート変数やメソッドを定義することで、クラスの外からの直接アクセスを制限することができます。
ただし、アクセスするための手段は用意されているため、必要な場合には適切に利用するようにしましょう。
プロパティとは、クラスの外からもアクセスできるようにしたい変数を指します。
しかし、変数に直接アクセスすると不適切な値が設定される可能性があるため、アクセス時に特定の処理を実行する必要があります。
このような場合に、プロパティを使います。
Pythonでは、プロパティを定義するには property ビルトイン関数を使用します。プロパティを定義するには、以下のような方法があります。
getter関数のみを定義する場合、以下のように @property デコレータを使用して、getter関数にアクセスすることができます。
class Person:
    def __init__(self, name, age):
        self._name = name
        self._age = age
    @property
    def name(self):
        return self._name
    @property
    def age(self):
        return self._age
このように定義することで、インスタンスから以下のようにして name や age にアクセスすることができます。
person = Person("Alice", 25)
print(person.name) # "Alice" を出力
print(person.age) # 25 を出力
getter関数とsetter関数の両方を定義する場合、以下のように @property デコレータと @.setter デコレータを使用して、getter関数とsetter関数にアクセスすることができます。
class Person:
    def __init__(self, name, age):
        self._name = name
        self._age = age
    @property
    def name(self):
        return self._name
    @name.setter
    def name(self, value):
        self._name = value
    @property
    def age(self):
        return self._age
    @age.setter
    def age(self, value):
        self._age = value
このように定義することで、インスタンスから以下のようにして name や age にアクセスし、変更することができます。
person = Person("Alice", 25)
print(person.name) # "Alice" を出力
print(person.age) # 25 を出力
person.name = "Bob"
person.age = 30
print(person.name) # "Bob" を出力
print(person.age) # 30 を出力
プロパティにアクセスする場合、通常のインスタンス変数と同様に、以下のようにしてアクセスします。
person = Person("Alice", 25)
print(person.name) # "Alice" を出力
print(person.age) # 25 を出力
また、setter関数を定義している場合、以下のようにしてプロパティの値を変更することができます。
person.name = "Bob"
person.age = 30
print(person.name) # "Bob" を出力
print(person.age) # 30 を出力
プロパティを使用することで、クラスの外部からもインスタンス変数にアクセスできるようになり、getter関数やsetter関数を使用することで、変数の値に対して特定の処理を行うことができます。
これにより、クラスのカプセル化が実現され、安全で信頼性の高いコードを作成することができます。
__getattr__ と __setattr__ は Python の特殊メソッドで、オブジェクトの属性に対するアクセスをカスタマイズすることができます。
__getattr__ メソッドは、インスタンスの属性が未定義の場合に呼び出されます。通常の属性アクセスが失敗した場合に呼び出され、属性名を引数に取り、属性の値を返すように実装することができます。このメソッドを使用することで、動的な属性アクセスをサポートすることができます。
例えば、以下のようなクラス DynamicObject を考えます。
class DynamicObject:
    def __getattr__(self, name):
        return f"{name} is not defined."
このクラスは、未定義の属性にアクセスした場合に、属性名と "is not defined." の文字列を連結したものを返します。
obj = DynamicObject()
print(obj.foo)
一方、__setattr__ メソッドは、属性に値を代入する際に呼び出されます。通常の属性代入が行われた後に、属性名と代入された値を引数に取り、任意の処理を実行することができます。このメソッドを使用することで、属性代入時のチェックや変換処理を実装することができます。
例えば、以下のようなクラス PositiveNumber を考えます。
class PositiveNumber:
    def __init__(self, value):
        self.value = value
    def __setattr__(self, name, value):
        if value < 0:
            raise ValueError("Value must be positive.")
        super().__setattr__(name, value)
このクラスは、属性に代入される値が負数である場合に、 ValueError を発生させます。
n = PositiveNumber(1)
n.value = 2
print(n.value)
n.value = -1
注意点
__getattr__ メソッドや __setattr__ メソッドは、インスタンスの属性アクセスや属性代入時にのみ呼び出されます。
クラス自体にアクセスする場合には、 __getattr__ メソッドや __setattr__ メソッドは呼び出されません。
属性が存在する場合は __getattr__ メソッドは呼び出されず、通常の属性アクセスが行われます。
Pythonでは、クラス変数とインスタンス変数をプライベート化することができます。プライベート変数は、クラスの外から直接アクセスすることができなくなり、情報の隠蔽や保護に役立ちます。
クラス変数とインスタンス変数をプライベート化するには、変数名の前にアンダースコア2つ（__）をつけます。例えば、以下のように定義します。
class MyClass:
    __private_class_var = 10  # クラス変数のプライベート化
    def __init__(self):
        self.__private_instance_var = 20  # インスタンス変数のプライベート化
これにより、MyClass の外からは __private_class_var や __private_instance_var に直接アクセスできなくなります。ただし、Pythonでは名前修飾という仕組みがあり、アンダースコア2つを付けることで、クラス外からアクセスできなくするだけで、実際にはアクセス可能な変数として存在します。
プライベート変数にアクセスするためには、アンダースコア1つ（_）を変数名の前に付けることでアクセスできます。例えば、以下のようにします。
class MyClass:
    __private_class_var = 10  # クラス変数のプライベート化
    def __init__(self):
        self.__private_instance_var = 20  # インスタンス変数のプライベート化
    def get_private_class_var(self):
        return MyClass.__private_class_var  # アクセス方法
    def get_private_instance_var(self):
        return self.__private_instance_var  # アクセス方法
上記の例では、get_private_class_var() メソッドと get_private_instance_var() メソッドを定義し、それぞれでプライベート変数にアクセスする方法を示しています。
my_class = MyClass()
print(my_class.get_private_class_var())  # 10 を出力
print(my_class.get_private_instance_var())  # 20 を出力
プライベート変数はクラスの外からアクセスできないようになっていますが、名前修飾を使用することで、直接アクセスすることができます。
ただし、名前修飾を使うことでアクセスできるようになっているため、注意が必要です。
プライベート変数は、クラス内部でしか使用しない変数であることを明確にし、情報を保護するために使用することが望ましいです。
抽象基底クラスは、インスタンス化できない抽象的なクラスで、共通のインターフェースを定義することができます。
具体的な実装は、抽象基底クラスを継承したサブクラスで行います。
Pythonでは、抽象基底クラスを作成するために abc モジュールを使用します。
抽象基底クラスを定義するには、abc.ABC クラスを継承する必要があります。また、抽象メソッドを定義するには @abstractmethod デコレータを使用します。
以下は、抽象基底クラスの定義例です。
import abc
class MyABC(abc.ABC):
    
    @abc.abstractmethod
    def do_something(self):
        pass
上記の例では、MyABC という抽象基底クラスを定義しています。このクラスには、do_something という抽象メソッドが定義されています。
抽象基底クラスを継承するサブクラスを定義する場合、@abstractmethod で定義されたメソッドを実装する必要があります。また、抽象基底クラスを継承することで、共通のインターフェースを持つことができます。
以下は、抽象基底クラスを継承したクラスの定義例です。
class MyClass(MyABC):
    def do_something(self):
        print("MyClassの処理を実行しました")
上記の例では、MyClass というクラスを定義し、MyABC を継承しています。MyABC に定義された do_something メソッドを実装し、処理を行っています。
以上が、Pythonでの抽象基底クラスの定義方法、抽象基底クラスを継承したクラスの定義方法です。
抽象基底クラスを使用することで、共通のインターフェースを持つクラスを実装し、コードの再利用性を高めることができます。
{{コラム|Pythonのクラスと型アノテーション|2=Pythonでは、型アノテーションを使用して関数やメソッドの引数や返り値の型を指定できますが、クラスの属性やメソッドの戻り値などでも型アノテーションを指定することができます。
例えば、以下のようにクラス定義時に属性に対して型アノテーションを指定することができます。
class MyClass:
    def __init__(self, value: int) -> None:
        self.value = value
    def add_value(self, x: int) -> int:
        return self.value + x
この例では、__init__メソッドの引数valueと、add_valueメソッドの引数x、そしてadd_valueメソッドの戻り値に対して、型アノテーションが指定されています。value属性は整数型を持ち、add_valueメソッドは整数型の引数を受け取り、整数型の戻り値を返すことを表しています。
また、クラス全体に対して型アノテーションを指定することもできます。例えば、以下のようにクラス定義の先頭で型アノテーションを指定することができます。
class MyClass:
    value: int
    def __init__(self, value: int) -> None:
        self.value = value
    def add_value(self, x: int) -> int:
        return self.value + x
この例では、クラス定義の先頭でvalue属性に対して整数型を指定しています。このように、クラス定義の先頭で型アノテーションを指定することで、クラス全体に対して型を指定することができます。
なお、Pythonの型アノテーションは実行時には無視されますが、型チェッカーやIDEの補完機能などで利用されます。また、Pythonの型アノテーションはオプションであるため、必ずしも指定する必要はありません。ただし、型アノテーションを指定することで、コードの読みやすさや保守性を向上させることができます。
Ruby#ユーザー定義クラスをPython3に移植しました。
[https://paiza.io/projects/illcdLFNySpNC2b1huVa9w?language=python3 Ruby からの移植]:
import math
class GeoCoord(object):
    def __init__(self, longitude, latitude):
        self.longitude, self.latitude = longitude, latitude
    def __str__(self):
        ew, ns = "東経", "北緯"
        long, lat = self.longitude, self.latitude
        if long < 0.0:
            ew = "西経"
            long = -long
        if lat < 0.0:
            ns = "南緯"
            lat = -lat
        return f"({ew}: {long}, {ns}: {lat})"
    def distance(self, other):
        i = math.pi / 180
        r = 6371.008
        return (
            math.acos(
                math.sin(self.latitude * i) * math.sin(other.latitude * i)
                + math.cos(self.latitude * i)
                * math.cos(other.latitude * i)
                * math.cos(self.longitude * i - other.longitude * i)
            )
            * r
        )
Sites = {
    "東京駅": [139.7673068, 35.6809591],
    "シドニー・オペラハウス": [151.215278, -33.856778],
    "グリニッジ天文台": [-0.0014, 51.4778],
}
for name in Sites:
    Sites[name] = GeoCoord(*Sites[name])
for name in Sites:
    print(f"{name}: {Sites[name]}")
keys, len = tuple(Sites.keys()), len(Sites)
for i in range(len):
    x, y = keys[i], keys[(i + 1) % len]
    print(f"{x} - {y}: {Sites[x].distance(Sites[y])} [km]")
実行結果:
東京駅: (東経: 139.7673068, 北緯: 35.6809591)
シドニー・オペラハウス: (東経: 151.215278, 南緯: 33.856778)
グリニッジ天文台: (西経: 0.0014, 北緯: 51.4778)
東京駅 - シドニー・オペラハウス: 7823.269299386704 [km]
シドニー・オペラハウス - グリニッジ天文台: 16987.2708377249 [km]
グリニッジ天文台 - 東京駅: 9560.546566490015 [km]
内包表記などを使えば、もう少し「Pythonぽい」プログラムに出来たと思いますが、なるべく一対一に移植元と対応するよう実装しました。
end が要らないのは簡便な反面、どこで定義が終わっているか判りづらいという問題もあり、__doc__ を充実させるなど工夫が必要そうです。
型アノテーションは付けていません。
class MyClass:
    class_variable = "hello"
    
    def __init__(self, instance_variable):
        self.instance_variable = instance_variable
    
    def instance_method(self):
        print("This is an instance method.")
        
    @classmethod
    def class_method(cls):
        print("This is a class method.")
        
    @staticmethod
    def static_method():
        print("This is a static method.")
obj = MyClass("world")
print(obj.instance_variable)
print(obj.class_variable)
obj.instance_method()
MyClass.class_method()
MyClass.static_method()
class MySubClass(MyClass):
    def __init__(self, instance_variable, sub_instance_variable):
        super().__init__(instance_variable)
        self.sub_instance_variable = sub_instance_variable
        
    def sub_instance_method(self):
        print("This is a subclass instance method.")
class MyClass:
    def __init__(self, instance_variable):
        self._instance_variable = instance_variable
    
    @property
    def instance_variable(self):
        return self._instance_variable
    
    @instance_variable.setter
    def instance_variable(self, value):
        self._instance_variable = value
class MyClass:
    def __init__(self, instance_variable):
        self.instance_variable = instance_variable
    
    def __str__(self):
        return f"MyClass object: {self.instance_variable}"
    
    def __eq__(self, other):
        return self.instance_variable == other.instance_variable
class MyClass:
    def __init__(self, instance_variable):
        self._instance_variable = instance_variable
    
    def __getattr__(self, name):
        return f"{name} is not defined."
    
    def __setattr__(self, name, value):
        print(f"{name} is being set to {value}.")
        super().__setattr__(name, value)
クラス(class)：オブジェクト指向プログラミングにおける基本的な概念の1つで、同じ属性やメソッドを持つオブジェクトの集合を定義します。
インスタンス(instance)：クラスを元に生成されるオブジェクトのことで、独自の属性やメソッドを持ちます。
属性(attribute)：オブジェクトが持つデータを表します。クラスの属性は全てのインスタンスで共通です。
メソッド(method)：オブジェクトが持つ振る舞いを表します。クラスのメソッドは全てのインスタンスで共通です。
コンストラクタ(constructor)：クラスからインスタンスを生成する際に、インスタンスの初期化を行うメソッドです。通常、__init__メソッドとして定義されます。
継承(inheritance)：既存のクラスを基に新たなクラスを定義することで、属性やメソッドを共有することができます。
親クラス(superclass)：継承元のクラスのことを指します。
子クラス(subclass)：継承したクラスのことを指します。
オーバーライド(override)：子クラスが親クラスの属性やメソッドを再定義することを指します。子クラスで同名の属性やメソッドを定義することで、親クラスのものを上書きすることができます。
多重継承(multiple inheritance)：複数のクラスから同時に継承することを指します。
ダックタイピング(duck typing)：オブジェクトの型よりも、そのオブジェクトが持つ属性やメソッドを重視するプログラミングスタイルのことを指します。
抽象基底クラス(abstract base class)：インスタンス化できない抽象的なクラスで、共通のインターフェースを定義することができます。抽象基底クラスを作成するために abc モジュールを使用します。
デコレータ(decorator)：関数やクラスに機能を追加するための構文で、@記号で始まり、関数やクラスの前に書かれます。クラスの場合、@classmethodや@staticmethodが使用されます。