· 

PythonでOOP(Part 6)

前の編もお読みください:Part 1Part 2Part 3Part 4Part 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

python

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 age allows invalid values (e.g., -5).

Example With Getters and Setters

python

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.

python

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

 

python

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 email method always returned '{self.first}.{self.last}@company.com', even when first and last were None, which resulted in '[email protected]'.
  • The fix checks if self.first or self.last is None and returns None instead of a malformed email address.

 

Now, after deleting fullname, the email property correctly returns None. 🚀

つまり、emailのsetter methodにif文を付けて、first かlastがNoneのときは、Noneをreturnするというものでした。