Python での __init__、__str__、__repr__ メソッドの使用方法

PythonBeginner
オンラインで実践に進む

はじめに

Python のオブジェクト指向プログラミング (OOP) 機能は、表現力豊かでカスタマイズされたオブジェクトを作成するための強力なツールを提供します。その中でも、特殊メソッドである __init__, __str__, および __repr__ は、Python オブジェクトの動作と表現を定義する上で重要な役割を果たします。

このチュートリアルでは、これらの特殊メソッドを探求し、その目的を理解し、Python クラスで効果的に実装する方法を学びます。この実験 (Lab) の終わりには、より直感的で使いやすい Python オブジェクトを作成できるようになります。

__init__ メソッドを使用したクラスの作成

最初に探求する特殊メソッドは __init__ で、オブジェクトが作成されたときに呼び出されます。このメソッドを使用すると、オブジェクトの属性を初期化できます。

__init__ メソッドの理解

__init__ メソッドは、コンストラクタとしても知られており、クラスの新しいインスタンスを作成すると自動的に呼び出されます。その主な目的は、属性に値を割り当てることによって、オブジェクトの初期状態を設定することです。

__init__ メソッドを使用して、シンプルな Python クラスを作成することから始めましょう。

  1. 実験 (LabEx) 環境でターミナルを開きます。

  2. プロジェクトディレクトリに移動します。

    cd ~/project
  3. コードエディタを使用して、person.py という新しい Python ファイルを作成します。

  4. エディタで、person.py に次のコードを追加します。

    class Person:
        def __init__(self, name, age):
            self.name = name
            self.age = age
    
    ## Create a Person object
    person1 = Person("Alice", 30)
    
    ## Access the attributes
    print(f"Name: {person1.name}")
    print(f"Age: {person1.age}")
  5. ファイルを保存します。

  6. Python スクリプトを実行します。

    python3 person.py

次の出力が表示されるはずです。

Name: Alice
Age: 30

__init__ メソッドの説明

上記のコードでは、

  • nameage の 3 つのパラメータを受け取る __init__ メソッドを持つ Person クラスを定義しました。
  • self パラメータは、作成中のインスタンスを参照し、オブジェクトが作成されると自動的に渡されます。
  • __init__ メソッド内で、nameage の値を self.nameself.age を使用してオブジェクトの属性に割り当てます。
  • person1 = Person("Alice", 30) で新しい Person オブジェクトを作成すると、__init__ メソッドが自動的に呼び出されます。
  • その後、ドット表記を使用して属性にアクセスできます:person1.nameperson1.age

より多くの機能の追加

現在の年と個人の年齢に基づいて生年を計算するメソッドを追加して、Person クラスを強化しましょう。

  1. エディタで person.py を開きます。

  2. 生年を計算するメソッドを含めるようにコードを更新します。

    import datetime
    
    class Person:
        def __init__(self, name, age):
            self.name = name
            self.age = age
    
        def birth_year(self):
            current_year = datetime.datetime.now().year
            return current_year - self.age
    
    ## Create a Person object
    person1 = Person("Alice", 30)
    
    ## Access the attributes and call the method
    print(f"Name: {person1.name}")
    print(f"Age: {person1.age}")
    print(f"Birth Year: {person1.birth_year()}")
  3. ファイルを保存します。

  4. Python スクリプトを実行します。

    python3 person.py

出力には、計算された生年も含まれるようになりました。

Name: Alice
Age: 30
Birth Year: 1993

(実際の生年は、スクリプトを実行した現在の年によって異なります)

これで、オブジェクトの属性を初期化するための __init__ メソッドを持つ Python クラスを作成し、それらの属性に基づいて計算を実行するメソッドを追加しました。

__str__ メソッドの実装

__init__ メソッドでクラスを作成する方法を理解したので、__str__ と呼ばれる別の特殊メソッドを探求しましょう。このメソッドを使用すると、オブジェクトを文字列としてどのように表現するかを定義できます。

__str__ メソッドの理解

__str__ メソッドは、オブジェクトに対して str() 関数を使用した場合、または print() 関数を使用してオブジェクトを出力した場合に呼び出されます。オブジェクトの人間が読める文字列表現を返す必要があります。

__str__ メソッドを含めるように Person クラスを更新しましょう。

  1. エディタで person.py を開きます。

  2. __str__ メソッドを含めるようにコードを更新します。

    import datetime
    
    class Person:
        def __init__(self, name, age):
            self.name = name
            self.age = age
    
        def birth_year(self):
            current_year = datetime.datetime.now().year
            return current_year - self.age
    
        def __str__(self):
            return f"{self.name}, {self.age} years old"
    
    ## Create a Person object
    person1 = Person("Alice", 30)
    
    ## Access the attributes and call the method
    print(f"Name: {person1.name}")
    print(f"Age: {person1.age}")
    print(f"Birth Year: {person1.birth_year()}")
    
    ## Use the __str__ method implicitly
    print("\nString representation of the object:")
    print(person1)
    
    ## Use the __str__ method explicitly
    print("\nExplicit string conversion:")
    print(str(person1))
  3. ファイルを保存します。

  4. Python スクリプトを実行します。

    python3 person.py

次のような出力が表示されるはずです。

Name: Alice
Age: 30
Birth Year: 1993

String representation of the object:
Alice, 30 years old

Explicit string conversion:
Alice, 30 years old

__str__ の仕組み

上記のコードでは、

  • 人の名前と年齢を含むフォーマットされた文字列を返す __str__ メソッドを定義しました。
  • print(person1) を呼び出すと、Python は自動的に __str__ メソッドを呼び出して、何を表示するかを決定します。
  • str(person1) を使用してオブジェクトを明示的に文字列に変換することもできます。これも __str__ メソッドを呼び出します。

__str__ がない場合どうなるか

__str__ メソッドの重要性を理解するために、それを定義しない場合に何が起こるかを見てみましょう。

  1. without_str.py という新しいファイルを作成します。

  2. 次のコードを追加します。

    class SimpleClass:
        def __init__(self, value):
            self.value = value
    
    ## Create an object
    obj = SimpleClass(42)
    
    ## Print the object
    print(obj)
  3. ファイルを保存します。

  4. スクリプトを実行します。

    python3 without_str.py

次のような出力が表示されるはずです。

<__main__.SimpleClass object at 0x7f2d8c3e9d90>

出力はあまり情報を提供していません。クラス名とオブジェクトのメモリ アドレスが表示されますが、その内容は表示されません。これが、オブジェクトをより使いやすくするために、適切な __str__ メソッドを実装することが重要な理由です。

実践的な演習

__str__ メソッドを持つ Book という新しいクラスを作成しましょう。

  1. book.py という新しいファイルを作成します。

  2. 次のコードを追加します。

    class Book:
        def __init__(self, title, author, pages):
            self.title = title
            self.author = author
            self.pages = pages
    
        def __str__(self):
            return f'"{self.title}" by {self.author} ({self.pages} pages)'
    
    ## Create book objects
    book1 = Book("The Great Gatsby", "F. Scott Fitzgerald", 180)
    book2 = Book("To Kill a Mockingbird", "Harper Lee", 281)
    
    ## Print the books
    print(book1)
    print(book2)
  3. ファイルを保存します。

  4. スクリプトを実行します。

    python3 book.py

出力は次のようになります。

"The Great Gatsby" by F. Scott Fitzgerald (180 pages)
"To Kill a Mockingbird" by Harper Lee (281 pages)

これで、__str__ メソッドを使用して、オブジェクトの人間が読める文字列表現を作成する方法を理解できました。

__repr__ メソッドの実装

__str__ に加えて、Python は文字列表現のための別の特殊メソッドである __repr__ を提供しています。__str__ が人間が読める表現を提供することを目的としているのに対し、__repr__ は、可能であればオブジェクトを再作成するために使用できる、オブジェクトの明確な表現を提供することを目的としています。

__repr__ メソッドの理解

__repr__ メソッドは、オブジェクトに対して repr() 関数を使用した場合、または対話型セッションでオブジェクトを表示した場合に呼び出されます。eval() に渡すと、同等のオブジェクトを作成する文字列を返す必要があります (可能な場合)。

__repr__ メソッドを含めるように Book クラスを更新しましょう。

  1. エディタで book.py を開きます。

  2. __repr__ メソッドを含めるようにコードを更新します。

    class Book:
        def __init__(self, title, author, pages):
            self.title = title
            self.author = author
            self.pages = pages
    
        def __str__(self):
            return f'"{self.title}" by {self.author} ({self.pages} pages)'
    
        def __repr__(self):
            return f'Book("{self.title}", "{self.author}", {self.pages})'
    
    ## Create book objects
    book1 = Book("The Great Gatsby", "F. Scott Fitzgerald", 180)
    book2 = Book("To Kill a Mockingbird", "Harper Lee", 281)
    
    ## Print the books (uses __str__)
    print("String representation (using __str__):")
    print(book1)
    print(book2)
    
    ## Get the representation (uses __repr__)
    print("\nRepresentation (using __repr__):")
    print(repr(book1))
    print(repr(book2))
  3. ファイルを保存します。

  4. スクリプトを実行します。

    python3 book.py

次のような出力が表示されるはずです。

String representation (using __str__):
"The Great Gatsby" by F. Scott Fitzgerald (180 pages)
"To Kill a Mockingbird" by Harper Lee (281 pages)

Representation (using __repr__):
Book("The Great Gatsby", "F. Scott Fitzgerald", 180)
Book("To Kill a Mockingbird", "Harper Lee", 281)

__str____repr__ の違い

__str____repr__ の主な違いは次のとおりです。

  • __str__ は人間が読める出力を目的としていますが、__repr__ は開発者とデバッグを目的としています。
  • __str__ が定義されておらず、__repr__ が定義されている場合、Python は str() または print() のフォールバックとして __repr__ を使用します。
  • __repr__ は、理想的には、eval() に渡されたときにオブジェクトを再作成できる文字列を返す必要がありますが、これは常に可能または必要とは限りません。

__repr__ を使用した eval() 関数

正しく実装されている場合、__repr__ によって返される文字列は、eval() でオブジェクトを再作成するために使用できます。これを実際に見てみましょう。

  1. repr_eval.py という新しいファイルを作成します。

  2. 次のコードを追加します。

    class Point:
        def __init__(self, x, y):
            self.x = x
            self.y = y
    
        def __str__(self):
            return f"Point at ({self.x}, {self.y})"
    
        def __repr__(self):
            return f"Point({self.x}, {self.y})"
    
    ## Create a point
    p1 = Point(3, 4)
    
    ## Get the repr string
    repr_str = repr(p1)
    print(f"Representation: {repr_str}")
    
    ## Use eval to recreate the object
    p2 = eval(repr_str)
    print(f"Recreated object: {p2}")
    
    ## Verify they have the same values
    print(f"p1.x = {p1.x}, p1.y = {p1.y}")
    print(f"p2.x = {p2.x}, p2.y = {p2.y}")
  3. ファイルを保存します。

  4. スクリプトを実行します。

    python3 repr_eval.py

次のような出力が表示されるはずです。

Representation: Point(3, 4)
Recreated object: Point at (3, 4)
p1.x = 3, p1.y = 4
p2.x = 3, p2.y = 4

これは、__repr__ によって返される文字列と eval() 関数を使用して、元のオブジェクトを再作成できることを示しています。

各メソッドを使用するタイミング

  • オブジェクトの初期状態を設定するには、__init__ を使用します。
  • エンドユーザー向けに人間が読める表現を提供するには、__str__ を使用します。
  • 開発者とデバッグ向けに、正確で明確な表現を提供するには、__repr__ を使用します。

実践的な演習:完全な例

3 つの特殊メソッドすべてを使用して Rectangle クラスを作成することにより、すべてをまとめましょう。

  1. rectangle.py という新しいファイルを作成します。

  2. 次のコードを追加します。

    class Rectangle:
        def __init__(self, width, height):
            self.width = width
            self.height = height
    
        def area(self):
            return self.width * self.height
    
        def perimeter(self):
            return 2 * (self.width + self.height)
    
        def __str__(self):
            return f"Rectangle with width {self.width} and height {self.height}"
    
        def __repr__(self):
            return f"Rectangle({self.width}, {self.height})"
    
    ## Create rectangles
    rect1 = Rectangle(5, 10)
    rect2 = Rectangle(3, 7)
    
    ## Display information about the rectangles
    print(f"Rectangle 1: {rect1}")
    print(f"Area: {rect1.area()}")
    print(f"Perimeter: {rect1.perimeter()}")
    print(f"Representation: {repr(rect1)}")
    
    print("\nRectangle 2: {0}".format(rect2))
    print(f"Area: {rect2.area()}")
    print(f"Perimeter: {rect2.perimeter()}")
    print(f"Representation: {repr(rect2)}")
    
    ## Recreate a rectangle using eval
    rect3 = eval(repr(rect1))
    print(f"\nRecreated rectangle: {rect3}")
    print(f"Is it the same area? {rect3.area() == rect1.area()}")
  3. ファイルを保存します。

  4. スクリプトを実行します。

    python3 rectangle.py

次のような出力が表示されるはずです。

Rectangle 1: Rectangle with width 5 and height 10
Area: 50
Perimeter: 30
Representation: Rectangle(5, 10)

Rectangle 2: Rectangle with width 3 and height 7
Area: 21
Perimeter: 20
Representation: Rectangle(3, 7)

Recreated rectangle: Rectangle with width 5 and height 10
Is it the same area? True

この例は、3 つの特殊メソッド (__init____str__、および repr`) が連携して、適切に設計されたクラスを作成する方法を示しています。

実用的なアプリケーションの作成

__init____str__、 および __repr__ の 3 つの特殊メソッドを理解したので、それらの使用を実際のシナリオで示す、より実用的なアプリケーションを作成しましょう。BankAccount クラスを使用して、シンプルな銀行システムを作成します。

銀行口座クラスの構築

  1. bank_account.py という新しいファイルを作成します。

  2. 次のコードを追加します。

    class BankAccount:
        def __init__(self, account_number, owner_name, balance=0.0):
            self.account_number = account_number
            self.owner_name = owner_name
            self.balance = balance
            self.transactions = []
    
        def deposit(self, amount):
            if amount <= 0:
                print("Deposit amount must be positive")
                return False
    
            self.balance += amount
            self.transactions.append(f"Deposit: +${amount:.2f}")
            return True
    
        def withdraw(self, amount):
            if amount <= 0:
                print("Withdrawal amount must be positive")
                return False
    
            if amount > self.balance:
                print("Insufficient funds")
                return False
    
            self.balance -= amount
            self.transactions.append(f"Withdrawal: -${amount:.2f}")
            return True
    
        def get_transaction_history(self):
            return self.transactions
    
        def __str__(self):
            return f"Account {self.account_number} | Owner: {self.owner_name} | Balance: ${self.balance:.2f}"
    
        def __repr__(self):
            return f'BankAccount("{self.account_number}", "{self.owner_name}", {self.balance})'
  3. ファイルを保存します。

銀行口座クラスのテスト

次に、BankAccount クラスをテストしましょう。

  1. bank_test.py という新しいファイルを作成します。

  2. 次のコードを追加します。

    from bank_account import BankAccount
    
    ## Create bank accounts
    account1 = BankAccount("12345", "John Doe", 1000.0)
    account2 = BankAccount("67890", "Jane Smith", 500.0)
    
    ## Display initial account information
    print("Initial Account Status:")
    print(account1)
    print(account2)
    
    ## Perform transactions
    print("\nPerforming transactions...")
    
    ## Deposit to account1
    print("\nDepositing $250 to account1:")
    account1.deposit(250)
    print(account1)
    
    ## Withdraw from account2
    print("\nWithdrawing $100 from account2:")
    account2.withdraw(100)
    print(account2)
    
    ## Try to withdraw too much from account2
    print("\nTrying to withdraw $1000 from account2:")
    account2.withdraw(1000)
    print(account2)
    
    ## Try to deposit a negative amount to account1
    print("\nTrying to deposit -$50 to account1:")
    account1.deposit(-50)
    print(account1)
    
    ## Display transaction history
    print("\nTransaction history for account1:")
    for transaction in account1.get_transaction_history():
        print(f"- {transaction}")
    
    print("\nTransaction history for account2:")
    for transaction in account2.get_transaction_history():
        print(f"- {transaction}")
    
    ## Recreate an account using repr
    print("\nRecreating account1 using repr:")
    account1_repr = repr(account1)
    print(f"Representation: {account1_repr}")
    
    recreated_account = eval(account1_repr)
    print(f"Recreated account: {recreated_account}")
  3. ファイルを保存します。

  4. スクリプトを実行します。

    python3 bank_test.py

次のような出力が表示されるはずです。

Initial Account Status:
Account 12345 | Owner: John Doe | Balance: $1000.00
Account 67890 | Owner: Jane Smith | Balance: $500.00

Performing transactions...

Depositing $250 to account1:
Account 12345 | Owner: John Doe | Balance: $1250.00

Withdrawing $100 from account2:
Account 67890 | Owner: Jane Smith | Balance: $400.00

Trying to withdraw $1000 from account2:
Insufficient funds
Account 67890 | Owner: Jane Smith | Balance: $400.00

Trying to deposit -$50 to account1:
Deposit amount must be positive
Account 12345 | Owner: John Doe | Balance: $1250.00

Transaction history for account1:
- Deposit: +$250.00

Transaction history for account2:
- Withdrawal: -$100.00

Recreating account1 using repr:
Representation: BankAccount("12345", "John Doe", 1250.0)
Recreated account: Account 12345 | Owner: John Doe | Balance: $1250.00

実装の理解

この銀行アプリケーションでは、

  1. __init__ メソッドは、口座番号、所有者名、およびオプションの初期残高で銀行口座を初期化します。また、トランザクションを追跡するための空のリストも作成します。

  2. deposit および withdraw メソッドは、トランザクションを処理し、残高とトランザクション履歴を更新します。

  3. __str__ メソッドは、口座番号、所有者名、および現在の残高を示す、ユーザーフレンドリーな口座表現を提供します。

  4. __repr__ メソッドは、口座オブジェクトを再作成できる文字列を提供します (ただし、この実装ではトランザクション履歴は保持されないことに注意してください)。

この例は、これらの特殊メソッドを実用的なアプリケーションで使用して、より直感的でユーザーフレンドリーなオブジェクトを作成する方法を示しています。

アプリケーションの拡張

最後の演習として、複数の口座を管理するシンプルな銀行システムを作成しましょう。

  1. banking_system.py という新しいファイルを作成します。

  2. 次のコードを追加します。

    from bank_account import BankAccount
    
    class BankingSystem:
        def __init__(self, bank_name):
            self.bank_name = bank_name
            self.accounts = {}
    
        def create_account(self, account_number, owner_name, initial_balance=0.0):
            if account_number in self.accounts:
                print(f"Account {account_number} already exists")
                return None
    
            account = BankAccount(account_number, owner_name, initial_balance)
            self.accounts[account_number] = account
            return account
    
        def get_account(self, account_number):
            return self.accounts.get(account_number)
    
        def list_accounts(self):
            return list(self.accounts.values())
    
        def __str__(self):
            return f"{self.bank_name} - Managing {len(self.accounts)} accounts"
    
        def __repr__(self):
            return f'BankingSystem("{self.bank_name}")'
    
    ## Create a banking system
    bank = BankingSystem("Python First Bank")
    print(bank)
    
    ## Create some accounts
    bank.create_account("A001", "John Doe", 1000)
    bank.create_account("A002", "Jane Smith", 500)
    bank.create_account("A003", "Bob Johnson", 250)
    
    ## List all accounts
    print("\nAll accounts:")
    for account in bank.list_accounts():
        print(account)
    
    ## Make some transactions
    account = bank.get_account("A001")
    if account:
        print(f"\nBefore deposit: {account}")
        account.deposit(500)
        print(f"After deposit: {account}")
    
    account = bank.get_account("A002")
    if account:
        print(f"\nBefore withdrawal: {account}")
        account.withdraw(200)
        print(f"After withdrawal: {account}")
    
    ## Try to create an existing account
    print("\nTrying to create an existing account:")
    bank.create_account("A001", "Someone Else", 300)
    
    ## Final state of the banking system
    print(f"\n{bank}")
  3. ファイルを保存します。

  4. スクリプトを実行します。

    python3 banking_system.py

次のような出力が表示されるはずです。

Python First Bank - Managing 0 accounts

All accounts:
Account A001 | Owner: John Doe | Balance: $1000.00
Account A002 | Owner: Jane Smith | Balance: $500.00
Account A003 | Owner: Bob Johnson | Balance: $250.00

Before deposit: Account A001 | Owner: John Doe | Balance: $1000.00
After deposit: Account A001 | Owner: John Doe | Balance: $1500.00

Before withdrawal: Account A002 | Owner: Jane Smith | Balance: $500.00
After withdrawal: Account A002 | Owner: Jane Smith | Balance: $300.00

Trying to create an existing account:
Account A001 already exists

Python First Bank - Managing 3 accounts

この例は、以前に作成した BankAccount クラスを、より完全なアプリケーションで使用する方法を示しています。特殊メソッド __init____str__、 および __repr__ が、直感的でユーザーフレンドリーなクラスを作成するための強固な基盤を提供する方法を示しています。

まとめ

この実験では、より表現力豊かでユーザーフレンドリーなオブジェクトを作成するのに役立つ、Python の 3 つの重要な特殊メソッドについて学びました。

  1. __init__: オブジェクトが作成されたときに、オブジェクトの属性を初期化するコンストラクターメソッドです。

  2. __str__: 主にエンドユーザー向けに、オブジェクトの人間が読める文字列表現を提供するメソッドです。

  3. __repr__: 主に開発者とデバッグ向けに、オブジェクトの明確な文字列表現を提供するメソッドです。

これらのメソッドを、PersonBook などのシンプルなクラスから、銀行システムなどのより複雑なアプリケーションまで、いくつかの例で実装しました。また、__str____repr__ の違いと、それぞれをいつ使用するかについても学びました。

これらの特殊メソッドを習得することで、言語の組み込み機能とシームレスに統合される、より直感的でカスタマイズされた Python オブジェクトを作成できます。この知識は、Python でのオブジェクト指向プログラミングの不可欠な部分を形成し、よりクリーンで保守性の高いコードを記述するのに役立ちます。

Python の旅を続ける中で、オブジェクトの動作をさらにカスタマイズできる、他の多くの特殊メソッドがあることを覚えておいてください。これらのメソッドを探索することで、さまざまなコンテキストでオブジェクトがどのように動作するかをさらに制御できるようになります。