属性アクセスとバウンドメソッド

PythonPythonBeginner
今すぐ練習

This tutorial is from open-source community. Access the source code

💡 このチュートリアルは英語版からAIによって翻訳されています。原文を確認するには、 ここをクリックしてください

はじめに

この実験では、Python の属性アクセスについて学びます。getattr()setattr() などの関数を使って、オブジェクトの属性を効果的に操作する方法を探ります。

さらに、バインドメソッド (bound methods) を試してみます。この実験ではこれらの概念を案内し、その過程で tableformat.py という名前のファイルを作成します。


Skills Graph

%%%%{init: {'theme':'neutral'}}%%%% flowchart RL python(("Python")) -.-> python/FunctionsGroup(["Functions"]) python(("Python")) -.-> python/ModulesandPackagesGroup(["Modules and Packages"]) python(("Python")) -.-> python/ObjectOrientedProgrammingGroup(["Object-Oriented Programming"]) python(("Python")) -.-> python/PythonStandardLibraryGroup(["Python Standard Library"]) python/FunctionsGroup -.-> python/function_definition("Function Definition") python/FunctionsGroup -.-> python/build_in_functions("Build-in Functions") python/ModulesandPackagesGroup -.-> python/using_packages("Using Packages") python/ObjectOrientedProgrammingGroup -.-> python/classes_objects("Classes and Objects") python/PythonStandardLibraryGroup -.-> python/data_collections("Data Collections") subgraph Lab Skills python/function_definition -.-> lab-132491{{"属性アクセスとバウンドメソッド"}} python/build_in_functions -.-> lab-132491{{"属性アクセスとバウンドメソッド"}} python/using_packages -.-> lab-132491{{"属性アクセスとバウンドメソッド"}} python/classes_objects -.-> lab-132491{{"属性アクセスとバウンドメソッド"}} python/data_collections -.-> lab-132491{{"属性アクセスとバウンドメソッド"}} end

Pythonにおける属性アクセスの理解

Pythonでは、オブジェクトは基本的な概念です。オブジェクトは属性にデータを格納することができ、属性は値を格納する名前付きのコンテナのようなものです。属性はオブジェクトに属する変数と考えることができます。これらの属性にアクセスする方法はいくつかあります。最も簡単で一般的に使われる方法はドット (.) 表記です。ただし、Pythonには属性を操作する際により柔軟性を与える特定の関数も用意されています。

ドット表記

まず、Stock オブジェクトを作成し、ドット表記を使ってその属性を操作する方法を見てみましょう。ドット表記は、オブジェクトの属性にアクセスして変更するためのシンプルで直感的な方法です。

まず、新しいターミナルを開き、Pythonの対話型シェルを起動します。ここでは、Pythonコードを1行ずつ書いて実行することができます。

## Open a new terminal and run Python interactive shell
python3

## Import the Stock class from the stock module
from stock import Stock

## Create a Stock object
s = Stock('GOOG', 100, 490.1)

## Get an attribute
print(s.name)    ## Output: 'GOOG'

## Set an attribute
s.shares = 50
print(s.shares)  ## Output: 50

## Delete an attribute
del s.shares
## If we try to access s.shares now, we'll get an AttributeError

上記のコードでは、まず stock モジュールから Stock クラスをインポートします。次に、Stock クラスのインスタンス s を作成します。name 属性の値を取得するには、s.name を使用します。shares 属性の値を変更するには、s.shares に新しい値を代入するだけです。属性を削除する場合は、del キーワードの後に属性名を指定します。

属性アクセス関数

Pythonには、属性の操作に非常に便利な4つの組み込み関数が用意されています。これらの関数は、属性を操作する際に、特に動的に属性を扱う必要がある場合に、より多くの制御を与えます。

  1. getattr() - この関数は、属性の値を取得するために使用されます。
  2. setattr() - この関数を使用すると、属性の値を設定することができます。
  3. delattr() - この関数を使用して属性を削除することができます。
  4. hasattr() - この関数は、オブジェクトに属性が存在するかどうかをチェックします。

これらの関数の使い方を見てみましょう。

## Create a new Stock object
s = Stock('GOOG', 100, 490.1)

## Get an attribute
print(getattr(s, 'name'))       ## Output: 'GOOG'

## Set an attribute
setattr(s, 'shares', 50)
print(s.shares)                 ## Output: 50

## Check if an attribute exists
print(hasattr(s, 'name'))       ## Output: True
print(hasattr(s, 'symbol'))     ## Output: False

## Delete an attribute
delattr(s, 'shares')
print(hasattr(s, 'shares'))     ## Output: False

これらの関数は、属性を動的に扱う必要がある場合に特に便利です。ハードコードされた属性名を使用する代わりに、変数名を使用することができます。たとえば、属性名を格納する変数がある場合、その変数をこれらの関数に渡して、対応する属性に対して操作を実行することができます。これにより、特に異なるオブジェクトや属性をより動的に扱う場合に、コードにより多くの柔軟性が与えられます。

getattr() を使用した汎用的なオブジェクト処理

getattr() 関数は、Pythonにおいてオブジェクトの属性に動的にアクセスできる強力なツールです。これは、オブジェクトを汎用的に処理したい場合に特に便利です。特定のオブジェクト型に特化したコードを書く代わりに、getattr() を使用して、必要な属性を持つ任意のオブジェクトを扱うことができます。この柔軟性により、コードの再利用性と適応性が向上します。

複数の属性の処理

まず、getattr() 関数を使用してオブジェクトの複数の属性にアクセスする方法を学びましょう。これは、オブジェクトから特定の情報を抽出する必要がある一般的なシナリオです。

前のPython対話型シェルを閉じた場合は、新しく開きましょう。ターミナルで以下のコマンドを実行することで、対話型シェルを開くことができます。

## Open a Python interactive shell if you closed the previous one
python3

次に、Stock クラスをインポートし、Stock オブジェクトを作成します。Stock クラスは、namesharesprice などの属性を持つ株式を表します。

## Import the Stock class and create a stock object
from stock import Stock
s = Stock('GOOG', 100, 490.1)

ここで、アクセスしたい属性名のリストを定義します。このリストを使って属性を繰り返し処理し、その値を表示します。

## Define a list of attribute names
fields = ['name', 'shares', 'price']

最後に、for ループを使用して属性名のリストを繰り返し処理し、getattr() を使用して各属性にアクセスします。各繰り返しで属性名とその値を表示します。

## Access each attribute using getattr()
for name in fields:
    print(f"{name}: {getattr(s, 'name')}" if name == 'name' else f"{name}: {getattr(s, name)}")

このコードを実行すると、以下の出力が表示されます。

name: GOOG
shares: 100
price: 490.1

この出力は、getattr() 関数を使用して Stock オブジェクトの複数の属性の値にアクセスし、表示できたことを示しています。

getattr() でのデフォルト値

getattr() 関数には、アクセスしようとする属性が存在しない場合にデフォルト値を指定できる便利な機能もあります。これにより、コードが AttributeError を発生させるのを防ぎ、より堅牢にすることができます。

これがどのように機能するか見てみましょう。まず、Stock オブジェクトに存在しない属性にアクセスしてみます。getattr() を使用し、デフォルト値として 'N/A' を指定します。

## Try to access an attribute that doesn't exist
print(getattr(s, 'symbol', 'N/A'))  ## Output: 'N/A'

この場合、Stock オブジェクトに symbol 属性が存在しないため、getattr() はデフォルト値 'N/A' を返します。

次に、存在する属性にアクセスする場合と比較してみましょう。Stock オブジェクトに存在する name 属性にアクセスします。

## Compare with an existing attribute
print(getattr(s, 'name', 'N/A'))    ## Output: 'GOOG'

ここでは、getattr()name 属性の実際の値である 'GOOG' を返します。

オブジェクトのコレクションの処理

getattr() 関数は、オブジェクトのコレクションを処理する必要がある場合にさらに強力になります。株式ポートフォリオを処理するためにこれをどのように使用できるか見てみましょう。

まず、stock モジュールから read_portfolio 関数をインポートします。この関数は、CSVファイルから株式ポートフォリオを読み込み、Stock オブジェクトのリストを返します。

## Import the portfolio reading function
from stock import read_portfolio

次に、read_portfolio 関数を使用して、portfolio.csv という名前のCSVファイルからポートフォリオを読み込みます。

## Read the portfolio from CSV file
portfolio = read_portfolio('portfolio.csv')

最後に、for ループを使用して、ポートフォリオ内の Stock オブジェクトのリストを繰り返し処理します。各株式について、getattr() を使用して nameshares 属性にアクセスし、その値を表示します。

## Print the name and shares of each stock
for stock in portfolio:
    print(f"Stock: {getattr(stock, 'name')}, Shares: {getattr(stock, 'shares')}")

このアプローチは、属性名を文字列として扱うことができるため、コードをより柔軟にします。これらの文字列は引数として渡したり、データ構造に格納したりすることができるため、コードの核心ロジックを変更することなく、アクセスしたい属性を簡単に変更できます。

属性アクセスを使用したテーブルフォーマッタの作成

プログラミングにおいて、属性アクセスはオブジェクトのプロパティとやり取りするための基本的な概念です。今回は、属性アクセスについて学んだことを実践に移します。便利なユーティリティであるテーブルフォーマッタを作成します。このフォーマッタはオブジェクトのコレクションを受け取り、それを表形式で表示します。これにより、データが読みやすく理解しやすくなります。

tableformat.py モジュールの作成

まず、新しいPythonファイルを作成する必要があります。このファイルには、テーブルフォーマッタのコードが格納されます。

ファイルを作成するには、以下の手順に従います。

  1. WebIDEで「File」メニューをクリックします。
  2. ドロップダウンメニューから「New File」を選択します。
  3. 新しく作成したファイルを /home/labex/project/ ディレクトリに tableformat.py として保存します。

ファイルができたので、tableformat.py の中に print_table() 関数のコードを書きましょう。この関数は、オブジェクトを表形式で整形して表示する役割を担います。

def print_table(objects, fields):
    """
    Print a collection of objects as a formatted table.

    Args:
        objects: A sequence of objects
        fields: A list of attribute names
    """
    ## Print the header
    headers = fields
    for header in headers:
        print(f"{header:>10}", end=' ')
    print()

    ## Print the separator line
    for header in headers:
        print("-" * 10, end=' ')
    print()

    ## Print the data
    for obj in objects:
        for field in fields:
            value = getattr(obj, field)
            print(f"{value:>10}", end=' ')
        print()

この関数が行うことを分解してみましょう。

  1. 2つの引数を受け取ります。オブジェクトのシーケンスと属性名のリストです。オブジェクトのシーケンスは表示したいデータで、属性名のリストはオブジェクトのどのプロパティを表示するかを関数に伝えます。
  2. ヘッダー行を表示します。ヘッダー行には、表示したい属性の名前が含まれます。
  3. 区切り線を表示します。この線は、ヘッダーとデータを視覚的に区切るのに役立ちます。
  4. シーケンス内の各オブジェクトについて、指定された各属性の値を表示します。各オブジェクトの属性値にアクセスするために getattr() 関数を使用します。

では、print_table() 関数が期待通りに動作するかテストしてみましょう。

## Open a Python interactive shell
python3

## Import our modules
from stock import read_portfolio
import tableformat

## Read the portfolio data
portfolio = read_portfolio('portfolio.csv')

## Print the portfolio as a table with name, shares, and price columns
tableformat.print_table(portfolio, ['name', 'shares', 'price'])

上記のコードを実行すると、以下の出力が表示されるはずです。

      name     shares      price
---------- ---------- ----------
        AA        100       32.2
       IBM         50       91.1
       CAT        150      83.44
      MSFT        200      51.23
        GE         95      40.37
      MSFT         50       65.1
       IBM        100      70.44

print_table() 関数の素晴らしい点の1つは、その柔軟性です。fields リストを変更するだけで、表示する列を変更することができます。

## Just show shares and name
tableformat.print_table(portfolio, ['shares', 'name'])

このコードを実行すると、以下の出力が得られます。

    shares       name
---------- ----------
       100         AA
        50        IBM
       150        CAT
       200       MSFT
        95         GE
        50       MSFT
       100        IBM

このアプローチの強みは、その汎用性にあります。表示したい属性の名前がわかっていれば、同じ print_table() 関数を使用して、あらゆる種類のオブジェクトの表を表示することができます。これにより、テーブルフォーマッタはプログラミングツールキットの中で非常に便利なツールになります。

✨ 解答を確認して練習

Pythonにおけるバウンドメソッドの理解

Pythonでは、メソッドは呼び出すことができる特殊なタイプの属性です。オブジェクトを通じてメソッドにアクセスすると、「バウンドメソッド」と呼ばれるものを取得します。バウンドメソッドは本質的に、特定のオブジェクトに紐付けられたメソッドです。これは、オブジェクトのデータにアクセスし、それを操作することができることを意味します。

メソッドを属性としてアクセスする

Stock クラスを使って、バウンドメソッドを探索してみましょう。まず、メソッドをオブジェクトの属性としてアクセスする方法を見てみます。

## Open a Python interactive shell
python3

## Import the Stock class and create a stock object
from stock import Stock
s = Stock('GOOG', 100, 490.10)

## Access the cost method without calling it
cost_method = s.cost
print(cost_method)  ## Output: <bound method Stock.cost of <stock.Stock object at 0x...>>

## Call the method
result = cost_method()
print(result)  ## Output: 49010.0

## You can also do this in one step
print(s.cost())  ## Output: 49010.0

上記のコードでは、まず Stock クラスをインポートし、そのインスタンスを作成します。次に、s オブジェクトの cost メソッドを実際に呼び出すことなくアクセスします。これにより、バウンドメソッドが得られます。このバウンドメソッドを呼び出すと、オブジェクトのデータに基づいてコストが計算されます。また、オブジェクトに対してメソッドを直接1ステップで呼び出すこともできます。

getattr() を使ったメソッドのアクセス

メソッドにアクセスする別の方法は、getattr() 関数を使用することです。この関数を使うと、名前でオブジェクトの属性を取得できます。

## Get the cost method using getattr
cost_method = getattr(s, 'cost')
print(cost_method)  ## Output: <bound method Stock.cost of <stock.Stock object at 0x...>>

## Call the method
result = cost_method()
print(result)  ## Output: 49010.0

## Get and call in one step
result = getattr(s, 'cost')()
print(result)  ## Output: 49010.0

ここでは、getattr() を使って s オブジェクトから cost メソッドを取得します。前と同じように、バウンドメソッドを呼び出して結果を得ることができます。また、メソッドの取得と呼び出しを1行で行うこともできます。

バウンドメソッドとそのオブジェクト

バウンドメソッドは、アクセス元のオブジェクトへの参照を常に保持します。これは、メソッドを変数に格納しても、それがどのオブジェクトに属しているかを知り、オブジェクトのデータにアクセスできることを意味します。

## Store the cost method in a variable
c = s.cost

## Call the method
print(c())  ## Output: 49010.0

## Change the object's state
s.shares = 75

## Call the method again - it sees the updated state
print(c())  ## Output: 36757.5

この例では、cost メソッドを変数 c に格納します。c() を呼び出すと、オブジェクトの現在のデータに基づいてコストが計算されます。次に、s オブジェクトの shares 属性を変更します。再度 c() を呼び出すと、更新されたデータを使って新しいコストが計算されます。

バウンドメソッドの内部を探索する

バウンドメソッドには、それに関するより多くの情報を提供する2つの重要な属性があります。

  • __self__: この属性は、メソッドが紐付けられているオブジェクトを参照します。
  • __func__: この属性は、メソッドを表す実際の関数オブジェクトです。
## Get the cost method
c = s.cost

## Examine the bound method attributes
print(c.__self__)  ## Output: <stock.Stock object at 0x...>
print(c.__func__)  ## Output: <function Stock.cost at 0x...>

## You can manually call the function with the object
print(c.__func__(c.__self__))  ## Output: 36757.5 (same as c())

ここでは、バウンドメソッド c__self____func__ 属性にアクセスします。__self__s オブジェクトで、__func__cost 関数であることがわかります。オブジェクトを引数として渡して関数を手動で呼び出すこともでき、これはバウンドメソッドを直接呼び出すのと同じ結果を得ます。

引数を取るメソッドの例

引数を取るメソッド(例えば sell() メソッド)でバウンドメソッドがどのように動作するか見てみましょう。

## Get the sell method
sell_method = s.sell

## Examine the method
print(sell_method)  ## Output: <bound method Stock.sell of <stock.Stock object at 0x...>>

## Call the method with an argument
sell_method(25)
print(s.shares)  ## Output: 50

## Call the method manually using __func__ and __self__
sell_method.__func__(sell_method.__self__, 10)
print(s.shares)  ## Output: 40

この例では、sell メソッドをバウンドメソッドとして取得します。引数を指定して呼び出すと、s オブジェクトの shares 属性が更新されます。__func____self__ 属性を使ってメソッドを手動で呼び出し、引数も渡すこともできます。

バウンドメソッドを理解することは、Pythonのオブジェクトシステムが内部でどのように動作するかを理解するのに役立ちます。これは、デバッグ、メタプログラミング、高度なプログラミングパターンの作成に役立ちます。

まとめ

この実験では、Pythonの属性アクセスシステムとその基礎となるメカニズムについて学びました。ドット表記や getattr()setattr()delattr()hasattr() などの関数を使ってオブジェクトの属性にアクセスする方法を理解しました。さらに、getattr() を使って汎用的かつ柔軟なオブジェクト処理を行う方法や、任意のオブジェクトのコレクションに対するテーブルフォーマッタを作成する方法も学びました。

また、バウンドメソッドの概念と、それがオブジェクトとの関係を維持する仕組みを理解しました。これらの基本的な概念は、イントロスペクション、リフレクション、メタプログラミングなどの高度なPythonプログラミング技術にとって重要です。属性アクセスを理解することで、様々なオブジェクトタイプを扱うことができる、より柔軟で強力なコードを書くことができます。