▌ 為什麼要學習 OOP 物件導向設計原因:
- 使程式碼的維護和擴充更容易
- 使人更容易閱讀理解程式碼邏輯
▌ OOP 物件導向有三大基本特性,分別是:
- 繼承 (inheritance)
- 封裝 (encapsulation)
- 多型 (polymorphism)
▌ Encapsulation 類別封裝
- Tranditionally, OOP design emphasizes information hiding; this is known as “encapsulation”. All attributes of an object should be as private as possible. The purpose of encapsulation is to prevent changing attributes of an object as that might cause an unexpected error.
- When we really have to access attributes of an object, we usually define public methods called getter and setter.
- 在物件導向程式裡面,封裝是個很基本的要素,不讓外部使用者直接碰到物件的內容,而只能透過方法來修改或取用內部的屬性。
為何需要設定成 Private attribute?
- 如果不設定為 private,使用者可以把 age 替換成任何不合理的數值,例如:負值。
- 存取 age 屬性,可以從外部設定 my_robot.age = -30 ,因此必須對 age 屬性作適當保護。
- Class 類別中可以讓外部引用的屬性稱為共用 (Public) 屬性、方法稱為共用方法,年齡 age 可以 greet () 方法取出,不可以從外部以 robot.__age 直接設定。
class Robot:
def __init__(self, name):
self.name = name
self.age = 25 # public property
my_robot = Robot("Wilson")
my_robot.age = -30
print(my_robot.age) # -30
-----------------------------------------------------------------
-30
Private Attributes and Methods
- In the context of class, private means the attributes or methods are only available for the class members, not for the outside of the class.
Private attribute-class 外面無法拿到 self.__age attribute
class Robot:
def __init__(self, name):
self.name = name
# public property
self.age = 25
# private property
self.__age = 25
my_robot = Robot("Wilson")
print(my_robot.age)
# class 外面
print(my_robot.__age)
-----------------------------------------------------------------
25
AttributeError: 'Robot' object has no attribute '__age'
Private attribute - class 裡面可以拿到 self.__age attribute
class Robot:
def __init__(self, name):
self.name = name
self.__age = 25
def greet(self): # class 裡面
print(f"Hi, I am {self.__age} years old.")
my_robot = Robot("Wilson")
my_robot.greet()
-----------------------------------------------------------------
Hi, I am 25 years old.
Private method
class Robot:
def __init__(self, name):
self.name = name
# private property
self.__age = 25
# private method
def __this_is_private(self):
print("Hello from private method.")
def greet(self):
print("Hi, I am a robot")
self.__this_is_private()
my_robot = Robot("Wilson")
my_robot.greet()
-----------------------------------------------------------------
Hi, I am a robot
Hello from private method.
▌ Polymorphism 類別多型
- 不同的類別底下可以有相同的方法,這樣的觀念稱為 多型。Python 會根據呼叫的類別,來決定要執行哪個方法實作。
class Employee:
def work(self):
print('Employee work')
class Andy(Employee):
def work(self):
print('Andy work')
class Joy(Employee):
def work(self):
print('Joy work')
worker = Employee()
worker_1 = Andy()
worker_2 = Joy()
worker.work()
worker_1.work()
worker_2.work()
-----------------------------------------------------------------
Employee work
Andy work
Joy work
▌ What are Decorators?
- Decorators in Python are like special badges you give to functions to add extra powers without changing the function itself. Think of them as add-ons. In Data Science, you might use a decorator to measure how long a machine learning model takes to train.
@property decorator
- In Python, properties allow you to change how you access an object’s attributes. It is a gateway that controls both the input and output of data. In the world of Data Science, you could use properties to validate if the data being input into a machine learning model meets certain conditions.
- A built-in decorator in Python, which is helpful in defining virtual properties effortlessly.
- 使用 @property getter, setter, deleter 仍然可以讀取、設定、刪除 private atrribute 的值
class Bank_account:
def __init__(self):
self.__password = '預設密碼 0000'
account_1 = Bank_account()
print(account_1.password) # 因為屬性為 private,無法直接讀取
-----------------------------------------------------------------
AttributeError: 'Bank_account' object has no attribute 'password'
class Bank_account:
def __init__(self):
self.__password = '預設密碼 0000'
@property # 只能讀取屬性
def password(self):
return self.__password
account_1 = Bank_account()
print(account_1.password) # 加上 @propery 可以讀取了
account_1.password = '1234'
print(account_1.password) # 可是只能讀取,無法變更屬性
-----------------------------------------------------------------
預設密碼 0000
AttributeError: can't set attribute 'password'
class Bank_account:
def __init__(self):
self.__password = '預設密碼 0000'
@property # 只能讀取屬性
def password(self):
return self.__password
@password.setter # 可以設定屬性
def password(self, value):
self.__password = value
@password.deleter # 可以刪除屬性
def password(self):
del self.__password
print('del complite')
account_1 = Bank_account()
print(account_1.password) # 加上 @propery 可以讀取了
account_1.password = '1234'
print(account_1.password) # 加上 @propery setter 可以設定屬性
del account_1.password
print(account_1.password) # 加上 @propery deleter 可以刪除屬性
-----------------------------------------------------------------
預設密碼 0000
1234
del complite
AttributeError: 'Bank_account' object has no attribute '_Bank_account__password'
@property 課外範例
class TemperatureModel:
def __init__(self, initial_temperature):
self._temperature = initial_temperature
@property
def temperature(self):
return self._temperature
@temperature.setter
def temperature(self, value):
if 10 <= value <= 40:
self._temperature = value
else:
print("Temperature out of range. Please set between 10 and 40.")
# Create a model with initial temperature 20
model = TemperatureModel(20)
print(model.temperature)
# Try to set the temperature to 5
model.temperature = 5
-----------------------------------------------------------------
20
Temperature out of range. Please set between 10 and 40.
▌ The Mighty Hash Function
- A hash is a built-in function that converts one value to another.
- This is to make it harder to guess what hash value a particular sting will have, which is an important security feature for were application etc. (hash 每次 run 得到的結果都不一樣,會增加 reverse engineering 的困難)
- Hash values should therefore not be stored permanently. If we need to use hash values in a permanent way, we can take a look at the more “serious” types of hashes, cryptographic (密碼學) hash functions, such as bcrypt, SHA-256, RSA algorithm, Elliptic Curve Cryptography (ECC).
- The idea of hash function is widely used in the real world.
Passwords : they are hashed before get stored in the database. Avoid reverse engineering.
Hashtable uses hash function : In Python, dictionary and set both implements hashtable.
What can be hashed in Python?
- All elements in the tuple need to be hashable so that the tuple can be hashable.
- All immutable objects are hashable, but not all hashable objects are immutable.
hashable type: string, interger, boolen, float, tuple
a = 100
b = 'This is just a string'
c = 1.0
d = 15.8
e = True
f = None
print(hash(a)) # hash 整數沒有意義
print(hash(b))
print(hash(c)) # hash 整數沒有意義
print(hash(d))
print(hash(e)) # hash True 沒有意義
print(hash(f))
-----------------------------------------------------------------
100
558643166140159030
1
1844674407370956815
1
6181196472382
unhashable type: list, dictionary, set
i = [1, 4, 7, 8]
j = {1, 4, 7, 8}
k = {'a':1, 'b':4}
print(hash(i))
print(hash(j))
print(hash(k))
-----------------------------------------------------------------
TypeError: unhashable type: 'list'
TypeError: unhashable type: 'set'
TypeError: unhashable type: 'dict'
▌ Here are some key features of a good hash function:
- Consistent (同一性): each time we give the same input to the hash() function, we need to get the same output.
- Distributed Evenly (分散的): a small change in input should result in a hit difference in output. This will reduce the number of hash collisions.
- Not invertible (不可逆): this function should not be invertible for security purposes. 無法透過 output 去逆推 input
Consistent (同一性)
a = None
b = None
print(hash(a))
print(hash(b))
-----------------------------------------------------------------
6181196472382
6181196472382
Distributed Evenly (分散的)
c = 'Fun'
d = 'Fun-'
print(hash(c))
print(hash(d))
-----------------------------------------------------------------
1176437743772278776
-1613839928295932065
Not invertible (不可逆)
請另開新的 Colab 嘗試,每次都會得到不同的數值
print(hash('this is a test'))
print(hash('this is a test'))
-----------------------------------------------------------------
961577223776434629
6455706850241107818
▌ __hash__() function
- Any object that implements the __hash__() function can be used as a key for dictionary.
class Robot:
def __init__(self, name, age, address):
self.__name = name
self.__age = age
self.__address = address
# define a private method __key()
def __key(self):
return (self.__name, self.__age, self.__address) # return tuple
# implement __hash__() function
def __hash__(self):
return hash(self.__key()) # return hash(tuple)
robot1 = Robot("Wilson", 25, "Wilson")
robot2 = Robot("Wilson", 25, "Wilson")
print(id(robot1)) # robot1 object 記憶體位置不同
print(id(robot2)) # robot2 object 記憶體位置不同
print(robot1.__hash__()) # robot1 hash值相同
print(robot2.__hash__()) # robot2 hash值相同
dictionary = {robot1: 1}
print(dictionary[robot1])
print(robot1 == robot2) # False
# 因為這兩個物件在記憶體的位置不一樣,其實是不同的 object
print(robot1.__hash__() == robot2.__hash__()) # hash值相同
-----------------------------------------------------------------
137274881818336
137274893614720
7010338228428340438
7010338228428340438
1
False
True
▌ __eq__() function
- The __eq__() is a dunder method in Python classes which stands for equality, meaning it defines the functionality of the equality operator (==).
- __eq__ 判定方式:
Check if the type matches
Check if the key() matches
class Robot:
def __init__(self, name, age, address):
self.name = name
self.age = age
self.address = address
# define a private method __key()
def __key(self):
return (self.name, self.age, self.address)
# implement __hash__() function
def __hash__(self):
return hash(self.__key()) # hash(tuple)
def __eq__(self, other):
# isinstance: Return whether an object is an instance of a class or
# of a subclass thereof.
if isinstance(other, Robot):
return self.__key() == other.__key()
return NotImplemented
robot1 = Robot("Wilson", 25, "Taiwan")
robot2 = Robot("Wilson", 25, "Taiwan")
print(robot1 == robot2)
-----------------------------------------------------------------
True
class Person:
def __init__(self, name, age):
self.name = name
self.age = age
x = Person('Mike', 25)
y = Person('Sarah', 27)
z = Person('Mike', 25)
print(x == y)
print(y == z)
print(z == x)
-----------------------------------------------------------------
False
False
False
class Person:
def __init__(self, name, age):
self.name = name
self.age = age
def __eq__(self, other):
return self.name == other.name and self.age == other.age
x = Person('Mike', 25)
y = Person('Sarah', 27)
z = Person('Mike', 25)
print(x == y)
print(y == z)
print(z == x)
-----------------------------------------------------------------
False
False
True
▌ Under or Magic Methods
- Under or magic methods in Python are the methods having two prefixes and suffix underscores in the method name. Dunder here means “Double Under (Underscores)”. These are commonly used for operator overloading.
- Besides the __init_(), __eq_(), __hash_() we learned, the following list is some under methods we can implement in Python classes:
__len_()
__str_(): (user-readable string) is used for creating output for end user.
__repr_(): (stands for representation)is mainly used for debugging and development.
__add_()
__gt_(): (greater than)
__ge_(): (greater than or equal to)
__lt_(): (less than)
__le__(): (less than or equal to)
class Robot:
def __init__(self, name, age, address):
self.name = name
self.age = age
self.address = address
# define a private method __key()
def __key(self):
return (self.name, self.age, self.address)
# implement __hash__() function
def __hash__(self):
return hash(self.__key()) # hash(tuple)
def __eq__(self, other):
# isinstance: Return whether an object is an instance of a class or
# of a subclass thereof.
if isinstance(other, Robot):
return self.__key() == other.__key()
return NotImplemented
def __len__(self):
return self.age
def __str__(self):
return f"{self.name} is now {self.age} years old, living in {self.address}"
def __repr__(self):
return f"name: {self.name}, age:{self.age}, address:{self.address}"
def __add__(self, other):
if isinstance(other, Robot):
return self.age + other.age
return NotImplemented
def __gt__(self, other):
if isinstance(other, Robot):
return self.age > other.age
return NotImplemented
robot1 = Robot("Wilson", 25, "Taiwan")
robot2 = Robot("Wilson", 25, "Taiwan")
print(len(robot1)) # 25
print(str(robot1)) # Wilson is now 25 years old, living in Taiwan
print(repr(robot1)) # name: Wilson, age:25, address:Taiwan
print(robot1 + robot2) # 50
print(robot1 > robot2) # False
-----------------------------------------------------------------
25
Wilson is now 25 years old, living in Taiwan
name: Wilson, age:25, address:Taiwan
50
False
▌參考資料
[Python] setter 和 getter
https://medium.com/bryanyang0528/python-setter-和-getter-6c08a9d37d46
[Python教學] @property是什麼? 使用場景和用法介紹
https://www.maxlist.xyz/2019/12/25/python-property/
[Python教學] 物件導向-Class類的封裝/繼承/多型
https://www.maxlist.xyz/2019/12/12/python-oop/
[stratascratch] How to Use Python Property Decorator (With Examples)
https://www.stratascratch.com/blog/how-to-use-python-property-decorator-with-examples/
[Medium] What is the____eq____method in Python classes?
https://medium.com/@DavidHofff/what-is-the-eq-method-in-python-classes-614779526843