33 isinstance, equality, 與 string representation
33.1 type
與 isinstance
- 之前已經用過很多次
type()
,例如下例:
= [1,2]
a print(type(a))
#> <class 'list'>
- 可以看到,顯示出來的type是
list
- 而且,我們終於看懂前面的
class
是什麼意思了:在python中萬物都是object,所以你去問一個object的type,就是在問他的class是誰拉!!
- 那現在多教一個指令,叫
isinstance()
,他的用法如下:
= [1,2]
a print(isinstance(a, list))
#> True
- 所以蠻好懂的,第一個argument就是放你要驗證的object(e.g. 此例的
a
),第二個argument就是去看看他是不是這個class的instance
- 再來個練習,我自己寫的class,用type會長怎樣:
class A:
pass
= A()
a print(type(a))
#> <class '__main__.A'>
- cool,他的type就是A。那用用看
isinstance
print(isinstance(a, A))
#> True
33.1.1 繼承與isinstance
- 重點來了,如果今天有繼承的話,那子類的instance,也會是父類的instance
class A:
pass
class B(A):
pass
= A()
a = B()
b
print(type(b))
#> <class '__main__.B'>
print(isinstance(b, B))
#> True
print(isinstance(b, A))
#> True
print(type(a))
#> <class '__main__.A'>
print(isinstance(a, A))
#> True
print(isinstance(a, B))
#> False
- 可以明顯看到,雖然b的type是B,但在判斷instance時,他仍是A的instance!!
- 因為我們要判斷b是不是A的instance時,就是看A的instance該有的attribute和method他是不是都有。而因為B繼承A,所以A有的,B都有,B做出來的instance全都繼承過去了,所以他仍是A的instance
- 但a就不會是B的instance了,道理很簡單,B的instance該有的attribute和method,a未必都有(因為B就是繼承A後,要加了他獨有的attribute和methods)
33.2 __eq__
(object equality)
- 用我們現在學到的方法來寫class的話,會碰到以下問題:兩個instance的attribute完全相同,但比較結果卻說不同:
class Customer:
def __init__(self, name, id):
self.name = name
self.id = id
= Customer("Hank", 19002329)
cust1 = Customer("Hank", 19002329)
cust2
== cust2
cust1 #> False
- 為什麼會這樣?因為在比相不相同時,是比記憶體位置一不一樣:
print(cust1)
#> <__main__.Customer object at 0x132b0ba30>
print(cust2)
#> <__main__.Customer object at 0x132b103d0>
- 很明顯的看到,兩個記憶體位置不同,所以才說不相等
- 聽起來很合理,但很難用啊,兩個object的attribute相同,我就希望他們比較的結果是一樣啊。
- 其實python內建的class,就有做這種處理了,比如說:
import numpy as np
= np.array([1])
a = np.array([1])
b id(a)
#> 5145446480
id(b)
#> 5145446720
print(a == b)
#> [ True]
- 可以看到,兩個object的記憶體位置不同,但比較結果是True,這怎麼辦到的?
- 這其實就是要多寫一個”equality constructor”。對比於”initial constructor”是
__init__(self, ...)
,這個equality constructor是寫成__eq__(self, other)
,其中”self”, “other”就是固定的參數,不要去換
- 寫法如下:
class Customer:
def __init__(self, name, id):
self.name = name
self.id = id
def __eq__(self, other):
print("__eq__() is called")
return (self.name == other.name) & (self.id == other.id)
= Customer("Hank", 19002329)
cust1 = Customer("Hank", 19002329)
cust2
== cust2
cust1 #> __eq__() is called
#> True
這邊就看到,我們得條件是,兩個比較的instance的name和id若完全相同,我就認定他是equal的。裡面的self和other就各指稱兩個instance,最後的return結果一定要是True/Fale。
-
除了equality operator外,其實還有其他的comparison,比如:
Operator Method == `__eq__`: equal != `__ne__`: not equal >= `__ge__`: greater or equal than <= `__le__`: less or equal than > `__gt__`: greater than < `__lt__`: less than -
接下來講兩個細節:
- 不同class的instance,能不能比啊?會錯亂嗎?
- Parent class的instance和Child class的instance在比時,用誰的equality constructor?
- 不同class的instance,能不能比啊?會錯亂嗎?
先講答案,第一點是,只要equality constructor一樣,就可以比,所以會造錯亂。解決方式是,再多比一個
type(self)==type(other)
就好第二點的答案是,always用child class的equality constructor
先來看第一點的範例吧
class Buyer:
def __init__(self, number):
self.number = number
def __eq__(self, other):
return self.number == other.number
class Phone:
def __init__(self, number):
self.number = number
def __eq__(self, other):
return self.number == other.number
= Buyer(19002329)
buyer1 = Phone(19002329)
phone_number
== phone_number
buyer1 #> True
- 我建了兩個class,第一個是客戶的class,他的attribute是number,指的是他的編號;第二個是電話號碼的class,他的attribute也是number,但指的是電話號碼。結果這兩個不一樣的東西,就剛好都有number,就被比成一樣了。
- 所以實務上在比較時,equality constructor,都會再加上type的條件:
type(buyer1)
#> <class '__main__.Buyer'>
type(phone_number)
#> <class '__main__.Phone'>
class Buyer:
def __init__(self, number):
self.number = number
def __eq__(self, other):
return (self.number == other.number) & (type(self)==type(other))
class Phone:
def __init__(self, number):
self.number = number
def __eq__(self, other):
return (self.number == other.number) & (type(self)==type(other))
= Buyer(19002329)
buyer1 = Phone(19002329)
phone_number
== phone_number
buyer1 #> False
- 接著就講到parent class和child class的比較。always用child class的equality constructor:
class Parent:
def __eq__(self, other):
print("Parent's __eq__() called")
return True
class Child(Parent):
def __eq__(self, other):
print("Child's __eq__() called")
return True
= Parent()
p = Child()
c
== c
p #> Child's __eq__() called
#> True
33.3 __str__
與__repr__
- 接下來的議題,是有關object的printing
- 我們目前對class的寫法,會讓我們每次去print一個instance的時候,他都只給我們記憶體位置,例如:
class salary:
def __init__(self, number):
self.number = number
= salary(22000)
my_salary print(my_salary)
#> <__main__.salary object at 0x132b24520>
- damn…這麼簡單的class,我當然希望他直接print 22K給我看啊
- Python其他內建的class,都有做這種處理,不信你看numpy的instance:
import numpy as np
= np.array([1,2,3])
a print(a)
#> [1 2 3]
-
為什麼勒?因為python有兩種constructor,一個叫
__str__
,一個叫__repr__
a#> array([1, 2, 3])
- 那現在回頭寫我剛剛的salary class:
class salary:
def __init__(self, number):
self.number = number
def __str__(self):
return str(self.number)
def __repr__(self):
return f"Salary({self.number})"
= salary(22000)
my_salary print(my_salary)
#> 22000
my_salary#> Salary(22000)