1. 程式人生 > 其它 >Day 17:Python 物件的相等性比較

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

對於序列型、字典型、集合型物件,只有當一個物件例項指向另一個物件例項時候

,is的比較結果才返回true.

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 裝飾的方法是刪除屬性值的方法。