前の編もお読みください:Part 1、Part 2、Part 3、Part 4、Part 5
1.Getting Started
Pythonを使ってOOPすなわちObject-Oriented Programmingの解説をします。
今回は、Property Decorators - Getters, Setters, and Deletersです。
2.References
Youtubeとして、
Python OOP Tutorial 6: Property Decorators - Getters, Setters, and Deleters
また、Documentationとして、
https://docs.python.org/3/tutorial/classes.html
を挙げておきます。
3.前回までのCode
前回までのcodeからかなり減らしていますが、今回使うcodeは次のものです。
class Employee:
def __init__(self, first, last):
self.first = first
self.last = last
self.email = first + '.' + last + '@company.com'
def fullname(self):
return '{} {}'.format(self.first, self.last)
emp_1 = Employee('Mark', 'Twain')
print(emp_1.first)
print(emp_1.last)
print(emp_1.email)
print(emp_1.fullname())
これをrunすると、first, last, email, full nameの順に出力されます。
4.改善すべき点
(1) class Employeeで、何か改善すべき点があるかを確認します。
まず、上のcodeをrunすると、emp_1というinstanceが作られ、そのinstanceは、attributeとして、
emp_1.first = 'Mark'
emp_1.last = 'Twain'
emp_1.email = '[email protected]'
を有しています。
この場合、emp_1.fullnameは、attributeではなくて、()を付けて、methodとして実行されると、
'Mark Twain'
がreturnされます。
(2) 上記のcodeの
emp_1 = Employee('Mark', 'Twain')
の下に、
emp_1.first = 'Jim'
を付け加えて、runしてみます。
出力結果の1行目は、ちゃんとJimに変更されています。しかし、3行目は古いemailのままです。これは、emailは、attributeとして設定されているために、
emp_1.email = 'Jim [email protected]'
により、明示的にしか、変更できないからです。その一方で、4行目は、methodの実行結果が表示されるので、firstがJimに変わったので、それに伴って、full nameも変更されたのでした。
しかし、firstをJimに変更したら、emailも自動的に変更される仕組みは作れないでしょうか。
5.getter と setter
(1) ここで登場するのが、getterとsetterと呼ばれるmethodsです。getterとは、attributeの値をgetするためのものであり、setterとは、attributeに値をsetするためのものです。これらについては、ChatGPTに、その概略を聞いてみました。
what are getter and setter methods?
Getter and Setter Methods in Python
In Python, getter and setter methods are used to access and modify private attributes of a class. These methods help enforce encapsulation by controlling how data is retrieved and updated.
1. What is a Getter?
A getter method allows you to retrieve (get) the value of a private attribute.
2. What is a Setter?
A setter method allows you to modify (set) the value of a private attribute, often with validation.
Example Without Getters and Setters
class Person:
def __init__(self, name, age):
self.name = name # Public attribute
self.age = age # Public attribute
p = Person("Alice", 25)
print(p.age) # Direct access (not recommended for private attributes)
p.age = -5 # No validation, possible mistake
Problem:
-
Direct access to
ageallows invalid values (e.g.,-5).
Example With Getters and Setters
class Person:
def __init__(self, name, age):
self.name = name
self._age = age # Private attribute (by convention)
# Getter method
def get_age(self):
return self._age
# Setter method with validation
def set_age(self, new_age):
if new_age > 0:
self._age = new_age
else:
print("Age must be positive!")
p = Person("Alice", 25)
print(p.get_age()) # ✅ Correct way to access
p.set_age(30) # ✅ Correct way to modify
p.set_age(-5) # ❌ Invalid input, handled by setter
Advantages:
- Prevents invalid values from being assigned.
- Adds a layer of control and flexibility.
Using @property (Pythonic
Way)
Python provides the @property decorator to create getters and setters more cleanly.
class Person:
def __init__(self, name, age):
self.name = name
self._age = age
@property # Getter
def age(self):
return self._age
@age.setter # Setter
def age(self, new_age):
if new_age > 0:
self._age = new_age
else:
raise ValueError("Age must be positive!")
p = Person("Alice", 25)
print(p.age) # Calls getter
p.age = 30 # Calls setter
p.age = -5 # Raises an error
Benefits of @property:
✅ Cleaner syntax
✅ Looks like an attribute but behaves like a method
✅ Encapsulation remains intact
Let me know if you need more explanation! 🚀
このように、getter, setterといっても、いろいろな作り方があるようです。Pythonでは、3つめの例にあるように、@propertyというdecoratorを使うことが多いようです。
6.firstの変更によるemailの自動変更
では、先ほどの問題に戻ります。どうしたら、firstを変更したら自動的にemailも変更されるようにできるでしょうか。1つの方法は、emailをmethodにする方法です。init methodの中の、
self.email = first + '.' + last + '@company.com'
をやめて、その代わりに、
def email(self):
return '{}.{}@company.com'.format(self.first, self.last)
を作ります。
instanceの箇所を、
emp_1 = Employee('Mark', 'Twain')
emp_1.first = 'Jim'
print(emp_1.first)
print(emp_1.last)
print(emp_1.email()) # This works, but we want to avoid these ()
print(emp_1.fullname())
にして、runしてみます。
今回は、3行目のemailもJimに変わっています。これは、emailがattributeではなくて、methodに変わったため、firstの変更に伴って、methodの出力内容も変わるからです。
しかし、commentに書いたように、methodなので、()を付けないといけません。
7.@property
(1) ここで、methodではあるけれども、()を付けずに、あたかもattributeのように扱う方法が、@propertyというdecoratorを付ける方法です。
@property
def email(self):
return '{}.{}@company.com'.format(self.first, self.last)
instanceの箇所は、emailの()をとって、
emp_1 = Employee('Mark', 'Twain')
emp_1.first = 'Jim'
print(emp_1.first)
print(emp_1.last)
print(emp_1.email) # () is removed
print(emp_1.fullname())
とした状態で、runします。
うまく行きました。このように、@propertyというdecoratorを付けると、methodをattibuteのように扱うことができるようになります。
(2) さらに、fullnameにも@propertyを付けてみます。
@property
def fullname(self):
return '{} {}'.format(self.first, self.last)
instanceの箇所は、fullnameの()をとって、
emp_1 = Employee('Mark', 'Twain')
emp_1.first = 'Jim'
print(emp_1.first)
print(emp_1.last)
print(emp_1.email) # () is removed
print(emp_1.fullname) # () is removed
とした状態で、runします。
これも予想通りの結果です。fullnameもattributeのように扱うことができました。
(3) このように、@propertyは、methodをattritute化します。その場合のmethodは、getterとして働くことになります。
8.setter
(1) @propertyを付けると、そのmethodはattributeのように扱うことができますが、
emp_1.email = '[email protected]'
のように、emailの中身を設定すること、すなわち、setすることはできません。
instanceの箇所を、次のようにします。
emp_1 = Employee('Mark', 'Twain')
emp_1.first = 'Jim'
emp_1.email = '[email protected]' # causes an error
print(emp_1.first)
print(emp_1.last)
print(emp_1.email) # () is removed
print(emp_1.fullname) # () is removed
これをrunしてみます。
AttributeErrorとなりました。
(2) そこで、setterを組み立ててみます。
@property
def email(self):
return '{}.{}@company.com'.format(self.first, self.last)
@email.setter
def email(self, new_address):
name = new_address.split('@')[0] # get rid of @ and everything to the right
print("name=", name)
first, last = name.split('.')
self.first = first
self.last = last
まず、@propertyというdecoratorがついているmethodがgetterなのですが、setterでは、それと同じ名前の前に@を付けて、うしろに.setterをつけます。これが、setterのdecoratorとなります。また、method名も、getterのmethodと同じにします。このmethodでは、full nameを読み込んで、firstとlastにsplitし、それぞれをself.first, self.lastに割り当てています。
instanceの箇所は、先ほどと同じにします。
emp_1 = Employee('Mark', 'Twain')
emp_1.first = 'Jim'
emp_1.email = '[email protected]' # caused an error
print(emp_1.first)
print(emp_1.last)
print(emp_1.email) # () is removed
print(emp_1.fullname) # () is removed
これをrunしてみます。
今度は、errorは出ませんでした。setterのおかげで、emp_1.emailをattributeのように扱って、'[email protected]'をsetすることができ、さらに、setter内のcodeにより、firstとlastも書き換えることができました。
(3) fullnameにもsetterを設定してみます。先ほどと同様に、getterであるfullname methodの下に、@fullname.setterというdecoratorをつけたmethodを置きます。
@property
def fullname(self):
return '{} {}'.format(self.first, self.last)
@fullname.setter
def fullname(self, name):
first, last = name.split(' ')
self.first = first
self.last = last
instanceの箇所は、先ほどのものに1行追加してあります。
emp_1 = Employee('Mark', 'Twain')
emp_1.first = 'Jim'
emp_1.email = '[email protected]' # caused an error
emp_1.fullname ='Jane Doe'
print(emp_1.first)
print(emp_1.last)
print(emp_1.email) # () is removed
print(emp_1.fullname) # () is removed
これをrunしてみます。
emp_1.email = 'No.body@...'により、いったん、first='No', last='body'となったものの、次の、emp_1.fullname = 'Jane Doe'により、first='Jane', last='Doe'に書き換えられたので、出力結果は、表示のようになりました。
9.deleter
(1) 次に、deleterを組み込みます。deleterは、何かをdeleteしたときに、同時に何かをするmethodです。fullnameのsetterの下に、さらにdeleterを置きます。
@property
def fullname(self):
return '{} {}'.format(self.first, self.last)
@fullname.setter
def fullname(self, name):
first, last = name.split(' ')
self.first = first
self.last = last
@fullname.deleter
def fullname(self):
self.first = None
self.last = None
instanceの箇所は、次のようにします。del emp_1.fullnameは、deleterをcallし、firstとlastをNoneにします。
emp_1 = Employee('Mark', 'Twain')
emp_1.first = 'Jim'
emp_1.email = '[email protected]' # caused an error
emp_1.fullname ='Jane Doe'
print(emp_1.first)
print(emp_1.last)
print(emp_1.email) # () is removed
print(emp_1.fullname) # () is removed
del emp_1.fullname
print(emp_1.first)
print(emp_1.last)
print(emp_1.email)
これをrunしてみます。
出力結果の5行目がfirst、6行目がlastです。Noneになっています。
(2) しかし、emailは、[email protected]となってしまいました。これは、emailがgetterであり、
@property
def email(self):
return '{}.{}@company.com'.format(self.first, self.last)
により、first='None', last='None'なので、[email protected]となってしまうからでした。
(3) この解決策については、ChatGPTに聞いてみました。
With the following code, the resulting output of the last print command is [email protected]. But It should be None. How can I fix it?
(The code is omitted.)
The issue occurs because when you delete fullname using del emp_1.fullname, you
set self.first = None and self.last = None. However, the email property is dynamically generated from self.first and self.last. Since they are now
both None, the email method returns '[email protected]'.
How to Fix It?
Modify the email property to handle the case when first or
last is None:
Fixed Code
class Employee:
def __init__(self, first, last):
self.first = first
self.last = last
@property
def email(self):
if self.first is None or self.last is None:
return None # Return None when either first or last is None
return '{}.{}@company.com'.format(self.first, self.last)
@email.setter
def email(self, new_address):
name = new_address.split('@')[0] # get rid of @ and everything to the right
first, last = name.split('.')
self.first = first
self.last = last
@property
def fullname(self):
return '{} {}'.format(self.first, self.last)
@fullname.setter
def fullname(self, name):
first, last = name.split(' ')
self.first = first
self.last = last
@fullname.deleter
def fullname(self):
self.first = None
self.last = None
emp_1 = Employee('Mark', 'Twain')
emp_1.first = 'Jim'
emp_1.email = '[email protected]'
emp_1.fullname = 'Jane Doe'
print(emp_1.first) # Jane
print(emp_1.last) # Doe
print(emp_1.email) # [email protected]
print(emp_1.fullname) # Jane Doe
del emp_1.fullname
print(emp_1.first) # None
print(emp_1.last) # None
print(emp_1.email) # None (Fixed!)
Explanation of the Fix
-
Before, the
emailmethod always returned'{self.first}.{self.last}@company.com', even whenfirstandlastwereNone, which resulted in'[email protected]'. -
The fix checks if
self.firstorself.lastisNoneand returnsNoneinstead of a malformed email address.
Now, after deleting fullname, the email property correctly returns
None. 🚀
つまり、emailのsetter methodにif文を付けて、first かlastがNoneのときは、Noneをreturnするというものでした。
