Pythonの@propertyデコレータ:そのユースケース、利点、および構文

🔹プロパティに会う

ようこそ!この記事では@property、Pythonでデコレータを操作する方法を学習します。

あなたは学びます:

  • Pythonでプロパティを操作する利点。
  • デコレータ関数の基本:それらが何であるか、そしてそれらが@propertyとどのように関連しているか。
  • @propertyを使用して、ゲッター、セッター、およびデリッターを定義する方法。

1️⃣Pythonのプロパティの利点

少しコンテキストから始めましょう。なぜPythonでプロパティを使用するのですか?

プロパティは、次の理由から、属性を操作する「Pythonic」の方法と見なすことができます。

  • プロパティの定義に使用される構文は非常に簡潔で読みやすいものです。
  • 仲介者(ゲッターとセッター)の「魔法」を使用して新しい値を検証し、データに直接アクセスしたり変更したりしないようにしながら、インスタンス属性にパブリック属性であるかのように正確にアクセスできます。
  • @propertyを使用すると、プロパティの名前を「再利用」して、ゲッター、セッター、およびデリッターの新しい名前が作成されないようにすることができます。

これらの利点により、プロパティは、より簡潔で読みやすいコードを作成するのに役立つ非常に優れたツールになります。

2️⃣デコレータの概要

デコレータ機能は基本的に引数として渡される関数に新機能を追加する機能です。デコレータ機能を使用することは、アイスクリームにチョコレートスプリンクルを追加するようなものですか?これにより、既存の関数を変更せずに新しい機能を追加できます。

以下の例では、Pythonでの典型的なデコレータ関数がどのように見えるかを確認できます。

def decorator(f): def new_function(): print("Extra Functionality") f() return new_function @decorator def initial_function(): print("Initial Functionality") initial_function()

これらの要素を詳細に分析してみましょう。

  • まずdef decorator(f)、関数fを引数とするデコレータ関数(スプリンクル✨)を見つけます。
def decorator(f): def new_function(): print("Extra Functionality") f() return new_function
  • このデコレータ関数には、ネストされた関数がありnew_functionます。関数呼び出しの前に新しい機能を追加しながら、同じ機能を実現するためにf内部でどのように呼び出されるかに注意してくださいnew_function(関数呼び出しの後に新しい機能を追加することもできます)。
  • デコレータ関数自体は、ネストされた関数を返しますnew_function
  • 次に(下)、装飾される関数(アイスクリーム?)を見つけますinitial_function@decorator関数ヘッダーの上にある非常に特殊な構文()に注意してください。
@decorator def initial_function(): print("Initial Functionality") initial_function()

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

Extra Functionality Initial Functionality

を呼び出しているだけでも、デコレータ関数がどのように実行されるかに注意してくださいinitial_function()。これは@decorator?を追加する魔法です。

💡注:通常、@@記号の後にデコレータ関数の名前を置き換えて、と記述します。

私はあなたが尋ねているかもしれないことを知っています:これは@propertyとどのように関連していますか?@propertyは、Pythonのproperty()関数の組み込みデコレータです。これは、特定のメソッドに「特別な」機能を与えて、クラスでプロパティを定義するときに、それらをゲッター、セッター、またはデリッターとして機能させるために使用されます。

デコレータに慣れてきたので、@ propertyの使用の実際のシナリオを見てみましょう。

🔸実際のシナリオ:@property

このクラスがあなたのプログラムの一部であるとしましょう。Houseクラスを使用して家をモデル化しています(現時点では、クラスには価格インスタンス属性のみが定義されています)。

class House: def __init__(self, price): self.price = price

このインスタンス属性は、名前に先頭にアンダースコアがないため、パブリックです。属性は現在公開されているため、あなたとチームの他の開発者は、次のようにドット表記を使用して、プログラムの他の部分で属性に直接アクセスして変更した可能性があります。

# Access value obj.price # Modify value obj.price = 40000

💡ヒント:objは、のインスタンスを参照する変数を表しますHouse

これまでのところ、すべてがうまく機能していますよね?ただし、この属性を保護(非公開)にして、新しい値を検証してから割り当てるように求められたとします。具体的には、値が正の浮動小数点数であるかどうかを確認する必要があります。どうしますか?どれどれ。

コードの変更

この時点で、ゲッターとセッターを追加することにした場合、あなたとあなたのチームはおそらくパニックになりますか?これは、属性の値にアクセスまたは変更するコードの各行を、それぞれゲッターまたはセッターを呼び出すように変更する必要があるためです。そうしないと、コードが壊れます⚠️。

# Changed from obj.price obj.get_price() # Changed from obj.price = 40000 obj.set_price(40000)

But... Properties come to the rescue! With @property, you and your team will not need to modify any of those lines because you will able to add getters and setters "behind the scenes" without affecting the syntax that you used to access or modify the attribute when it was public.

Awesome, right?  

🔹 @property: Syntax and Logic

If you decide to use @property, your class will look like the example below:

class House: def __init__(self, price): self._price = price @property def price(self): return self._price @price.setter def price(self, new_price): if new_price > 0 and isinstance(new_price, float): self._price = new_price else: print("Please enter a valid price") @price.deleter def price(self): del self._price

Specifically, you can define three methods for a property:

  • A getter - to access the value of the attribute.
  • A setter - to set the value of the attribute.
  • A deleter - to delete the instance attribute.

Price is now "Protected"

Please note that the price attribute is now considered "protected" because we added a leading underscore to its name in self._price:

self._price = price

In Python, by convention, when you add a leading underscore to a name, you are telling other developers that it should not be accessed or modified directly outside of the class. It should only be accessed through intermediaries (getters and setters) if they are available.

🔸 Getter

Here we have the getter method:

@property def price(self): return self._price

Notice the syntax:

  • @property - Used to indicate that we are going to define a property. Notice how this immediately improves readability because we can clearly see the purpose of this method.
  • def price(self) - The header. Notice how the getter is named exactly like the property that we are defining: price. This is the name that we will use to access and modify the attribute outside of the class. The method only takes one formal parameter, self, which is a reference to the instance.
  • return self._price - This line is exactly what you would expect in a regular getter. The value of the protected attribute is returned.

Here is an example of the use of the getter method:

>>> house = House(50000.0) # Create instance >>> house.price # Access value 50000.0

Notice how we access the price attribute as if it were a public attribute. We are not changing the syntax at all, but we are actually using the getter as an intermediary to avoid accessing the data directly.

🔹 Setter

Now we have the setter method:

@price.setter def price(self, new_price): if new_price > 0 and isinstance(new_price, float): self._price = new_price else: print("Please enter a valid price")

Notice the syntax:

  • @price.setter - Used to indicate that this is the setter method for the price property. Notice that we are not using @property.setter, we are using @price.setter. The name of the property is included before .setter.
  • def price(self, new_price): - The header and the list of parameters. Notice how the name of the property is used as the name of the setter. We also have a second formal parameter (new_price), which is the new value that will be assigned to the price attribute (if it is valid).
  • Finally, we have the body of the setter where we validate the argument to check if it is a positive float and then, if the argument is valid, we update the value of the attribute. If the value is not valid, a descriptive message is printed. You can choose how to handle invalid values according the needs of your program.

This is an example of the use of the setter method with @property:

>>> house = House(50000.0) # Create instance >>> house.price = 45000.0 # Update value >>> house.price # Access value 45000.0

Notice how we are not changing the syntax, but now we are using an intermediary (the setter) to validate the argument before assigning it. The new value (45000.0) is passed as an argument to the setter :

house.price = 45000.0

If we try to assign an invalid value, we see the descriptive message. We can also check that the value was not updated:

>>> house = House(50000.0) >>> house.price = -50 Please enter a valid price >>> house.price 50000.0

💡 Tip: This proves that the setter method is working as an intermediary. It is being called "behind the scenes" when we try to update the value, so the descriptive message is displayed when the value is not valid.

🔸 Deleter

Finally, we have the deleter method:

@price.deleter def price(self): del self._price

Notice the syntax:

  • @price.deleter - Used to indicate that this is the deleter method for the price property. Notice that this line is very similar to @price.setter, but now we are defining the deleter method, so we write @price.deleter.
  • def price(self): - The header. This method only has one formal parameter defined, self.
  • del self._price - The body, where we delete the instance attribute.

💡 Tip: Notice that the name of the property is "reused" for all three methods.

This is an example of the use of the deleter method with @property:

# Create instance >>> house = House(50000.0) # The instance attribute exists >>> house.price 50000.0 # Delete the instance attribute >>> del house.price # The instance attribute doesn't exist >>> house.price Traceback (most recent call last): File "", line 1, in  house.price File "", line 8, in price return self._price AttributeError: 'House' object has no attribute '_price'

The instance attribute was deleted successfully ?. When we try to access it again, an error is thrown because the attribute doesn't exist anymore.

🔹 Some final Tips

You don't necessarily have to define all three methods for every property. You can define read-only properties by only including a getter method. You could also choose to define a getter and setter without a deleter.

If you think that an attribute should only be set when the instance is created or that it should only be modified internally within the class, you can omit the setter.

You can choose which methods to include depending on the context that you are working with.

🔸 In Summary

  • You can define properties with the @property syntax, which is more compact and readable.
  • @property can be considered the "pythonic" way of defining getters, setters, and deleters.
  • By defining properties, you can change the internal implementation of a class without affecting the program, so you can add getters, setters, and deleters that act as intermediaries "behind the scenes" to avoid accessing or modifying the data directly.

I really hope you liked my article and found it helpful. To learn more about Properties and Object Oriented Programming in Python, check out my online course, which includes 6+ hours of video lectures, coding exercises, and mini projects.