Day 17:Python 物件的相等性比較
掌握一些判斷物件相等時用的關鍵字,Python 中,物件相等性比較相關關鍵字包括 is、in,比較運算子有==
is 關鍵字判斷標識號是否相等
is 比較的是兩個物件的標識號是否相等,標識號的獲取用id()函式。
a=[1,2,3,4,5] b=[1,2,3,4,5] print(id(a)) print(id(b))
a is b
output:
7696573018440 7696556316232
False
由於這兩個list例項化與不同的記憶體地址,標識號不相等,那麼兩個空list例項,會不會相等呢?實則不然。
c,d = [], [] c is d output: False
對於序列型、字典型、集合型物件,只有當一個物件例項指向另一個物件例項時候
a,b = [1,2,3],{'a':111,'2':222} print(a is b) a = b print(a,b,a is b) output: False {'a': 111, '2': 222} {'a': 111, '2': 222} True
但是對於數值型別,結果可能會不太一樣,看編譯器。
a = 1
b = 1
print(a is b)
c = 65535
d = 65535
print(c is d)
output:
True
Python 直譯器,對位於區間 [-5,256] 內的小整數,會進行快取,不在該範圍內的不會快取,所以才出現上面的現象。
還需要注意的是,python中有一個None物件,具有唯一,不變的標識號(當前環境下)
print(id(None)) a = None print(a is None) #判斷某個物件是否為None print(id(None)) output: 17106540000 True 17106540000
in 關鍵字用於成員檢測
- 如果元素 i 是 s 的成員,則
i in s
為 True; - 若不是 s 的成員,則返回 False,也就是
i not in s
為 True。 - python內建的序列型別、字典型別和集合型別,都支援 in 操作。
- 字典使用,in 操作判斷 i 是否是字典的鍵。
- 如果是字串s,判斷子串i的操作,也就是 s.find(i)
- 對於自定義型別,判斷是否位於序列型別中,需要重寫序列型別的__contains__。
# 根據 Student 類的 name 屬性(所以要重寫__contains__),判斷某 Student 是否在 Students 序列物件中。 # 自定義 Student 類,無特殊之處 class Student(): def __init__(self,name): self._name = name @property def name(self): return self._name @name.setter def name(self,val): self._name = val # Students 類繼承 list,並重寫 __contains__ 方法 class Students(list): def __contains__(self,_): for s in self: if s.name ==_.name: return True return False s1 = Student('xiaoming') s2 = Student('xiaohong') a = Students() a.extend([s1,s2])#list方法 s3 = Student('xiaoming') print(s3 in a) # True s4 = Student('xiaoli') print(s4 in a) # False output: True False
== 關鍵字判斷值是否相等
- 對於數值型、字串、列表、字典、集合,預設只要元素值相等,
==
比較結果是 True。 - 對於自定義型別,當所有屬性取值完全相同的兩個例項,判斷
==
時,返回 False。 - 一般如果兩個物件的所有屬性相同時,儘管他們的名字不同,那麼他們還是相等的。所以就是需要判斷所有屬性相同與否的方法。
例子:
class Student(): def __init__(self,name,age): self._name = name self._age = age @property def name(self): return self._name @name.setter def name(self,val): self._name = val @property def age(self): return self._age @age.setter def age(self,val): self._age = val # 重寫方法 __eq__,使用 __dict__ 獲取例項的所有屬性。 def __eq__(self,val): print(self.__dict__) return self.__dict__ == val.__dict__ a = [] xiaoming = Student('xiaoming',29) if xiaoming not in a: a.append(xiaoming) xiaohong = Student('xiaohong',30) if xiaohong not in a: a.append(xiaohong) xiaoming2 = Student('xiaoming',29) if xiaoming2 == xiaoming: print('物件完全一致,相等') if xiaoming2 not in a: a.append(xiaoming2) print(len(a)) output: {'_name': 'xiaohong', '_age': 30} {'_name': 'xiaoming', '_age': 29} 物件完全一致,相等 {'_name': 'xiaoming', '_age': 29} 2
Bonus - @property說明
對於類的變數,有私有和全域性,看例子
class Menber(object): def __init__(self, name, sexual): self.__name = name self.__sexual = sexual def get_sexual_fun(self): return self.__sexual def set_sexual_fun(self, value): if value not in ['male','female']: raise ValueError('sexual must be male or female.') self.__sexual = value def print_info(self): print('%s: %s' % (self.__name, self.__sexual))
p = Menber('linda','female')
p.__sexual = 'male'
print(p.__sexual) # male
print(p.get_sexual_fun()) # female
p.print_info()
p.set_sexual_fun('male')
print(p.get_sexual_fun()) # male
p.print_info()
output:
male female linda: female male linda: male
使用一個set來設定變數,使用一個get來獲取變數,就可以達到檢查引數的目的。
還有重要的一點,表面上在外部看似把sexual設定成了另一個值,但其實並沒有改變內部的值,
這個p.__sexual = 'male'和內部的self.__sexual = 'female'壓根就是兩個東西。
內部的_sexual變數已經被Python直譯器自動改成了_Person_sexual,而外部程式碼給p新增了一個_sexual變數。 所以呼叫get_sexual_fun輸出的是初始值 female.
而set_sexual_fun通過class內部改變了_sexual變數值,所以最終輸出linda: male
現在,只是改私有變數為全域性變數,看效果:
class Menber(object): def __init__(self, name, sexual): self._name = name self._sexual = sexual def get_sexual_fun(self): return self._sexual def set_sexual_fun(self, value): if value not in ['male','female']: raise ValueError('sexual must be male or female.') self._sexual = value def print_info(self): print('%s: %s' % (self._name, self._sexual)) p = Menber('linda','female') p._sexual = 'male' print(p._sexual) # male print(p.get_sexual_fun()) # female p.print_info() p.set_sexual_fun('male') print(p.get_sexual_fun()) # male p.print_info() output: male male linda: male male linda: male
因為此時_sexual是全域性變數,外部直接影響到類內部的更新值
那麼怎樣才能避免這種繁瑣的方式呢?
有沒有可以用類似屬性這樣簡單的方式來訪問類的變數呢?
答:裝飾器@property
class Menber(object): def __init__(self, name, sexual): self.__name = name self.__sexual = sexual @property def get_sexual_fun(self): return self.__sexual @get_sexual_fun.setter # get_sexual_fun def set_sexual_fun(self, value): if value not in ['male','female']: raise ValueError('sexual must be male or female.') self.__sexual = value def print_info(self): print('%s: %s' % (self.__name, self.__sexual)) p = Menber('linda','female') p.__sexual = 'male' print(p.__sexual) # male print(p.get_sexual_fun) # female 這裡不能帶() 如果帶()要報錯 p.set_sexual_fun = 'male' print(p.get_sexual_fun) # male p.print_info() output: male female #儘管沒有改變內部的值 male # 但是這樣的設定使得能夠檢查引數 linda: male
這樣,既能檢查引數,又可以用類似屬性這樣簡單的方式來訪問類的變數。
@property
本身又建立了另一個裝飾器@
get_sexual_fun.setter
,負責把一個setter方法變成屬性賦值,於是,我們就擁有一個可控的屬性操作:
class Menber(object): def __init__(self, name, sexual): self.__name = name self.__sexual = sexual @property def get_sexual_fun(self): return self.__sexual @get_sexual_fun.setter # get_sexual_fun def set_sexual_fun(self, value): if value not in ['male','female']: raise ValueError('sexual must be male or female.') self.__sexual = value def print_info(self): print('%s: %s' % (self.__name, self.__sexual)) q = Menber('david', 'male') q.__sexual output: --------------------------------------------------------------------------- AttributeError Traceback (most recent call last) <ipython-input-64-116170232977> in <module>() 18 19 q = Menber('david', 'male') ---> 20 q.__sexual AttributeError: 'Menber' object has no attribute '__sexual'
意到這個神奇的@property
,我們在對例項屬性操作的時候,就知道該屬性很可能不是直接暴露的
總的來說:使用 property 工具,它把方法包裝成屬性,讓方法可以以屬性的形式被訪問和呼叫。
- 被 @property 裝飾的方法是獲取屬性值的方法,被裝飾方法的名字會被用做 屬性名。
- 被 @屬性名.setter 裝飾的方法是設定屬性值的方法。
- 被 @屬性名.deleter 裝飾的方法是刪除屬性值的方法。