2017-05-12

Python - 真值判斷(Truth Value Testing)、布林運算(Boolean Operation)與相等運算(Comparison Operation)

在 Python,除了布林型別的值以外,任何物件都可以用來作為真值判斷(Truth Value Testing),而真值判斷(Truth Value Testing)可以用在於 if、while 與布林運算(Boolean Operation)中,這很重要,因為也只有這三個地方可以進行真值判斷(Truth Value Testing),最常誤用的地方是相等運算(Comparison Operation)。

真值判斷(Truth Value Testing)

以下的值在上述三個地方(if、while 與布林運算(Boolean Operation)),會被認為是 False。
  • None
  • False
  • 整數 0 與浮點數 0.0
  • 空的 sequence,例如空字串、空 list 與空 tuple
  • 空的 dict
  • 自訂 class,且__bool__() 回傳 False
  • 自訂 class,且__len__() 回傳 0
上述以外的值則視為 True。
def t_or_f(v):
    if v:
        print(True)
    else:
        print(False)

t_or_f(None) # False
t_or_f(False) # False
t_or_f(0) # False
t_or_f(5) # True
t_or_f(0.0) # False
t_or_f(0.5) # True
t_or_f('') # False
t_or_f('A') # True
t_or_f([]) # False
t_or_f([ 'Neil' ]) # True
t_or_f(()) # False
t_or_f(( 'Neil' )) # True
t_or_f({}) # False
t_or_f({ 'name': 'Neil' }) # True

class bool_f:
    def __init__(self, v = False):
        self.v = v
    def __bool__(self):
        return self.v
t_or_f(bool_f(False)) # False
t_or_f(bool_f(True)) # True

class len_f:
    def __init__(self, v = 0):
        self.v = v
    def __len__(self):
        return self.v
t_or_f(len_f(0)) # False
t_or_f(len_f(1)) # True

布林運算(Boolean Operation)

布林運算有三個運算子。
  • and
  • or
  • not
除了 if 與 while,布林運算(Boolean Operation)是唯一可以使用真值判斷(Truth Value Testing)的地方。
def bln_and(v, x):
    if v and x:
        print(True)
    else:
        print(False)

def bln_or(v, x):
    if v or x:
        print(True)
    else:
        print(False)

def bln_not(v):
    if not v:
        print(True)
    else:
        print(False)
        
bln_and(None, False) # False
bln_or(None, False) # False
bln_and(0, 0.5) # False
bln_or(0, 0.5) # True
bln_not('') # True
bln_not([ 'Neil' ]) # False

相等運算(Comparison Operation)

相等運算有八個運算子。
  • >
  • >=:小心不要用成 =>
  • <
  • <=:小心不要用成 =<
  • ==
  • !=
  • is:相同的物件參照,適用於 mutable object
  • is not:不同的物件參照,適用於 mutable object
除了 int 與 float 可以進行跨型別的相等運算,其他跨型別的相等運算一律得到 False。

運算優先權都相同,也就是遇到這八個運算子時,如果沒有括弧,就是從左算到右。

八個相等運算子的優先權都高於布林運算子。
a = 1
b = 2
print(a == b) # False
print(not a == b) # True,等於 not (a == b)
print(not (a == b)) # True
# print(a == not b) # Invalid Syntax
print(False == (not b)) # True
用來判斷物件參照的 is 與 is not,要特別小心是不是「mutable」,正常使用方式如下。
a = []
b = a
c = []
print(id(a)) # 31564344
print(id(b)) # 31564344
print(id(c)) # 31564144
print(a == b) # True,值相同
print(a is b) # True,參照相同
print(a == c) # True,值相同
print(a is c) # False,參照不同
b 參照 a 變數,所以 a 與 b 是參照同一個物件,因此 a is b。

而 c 是另外建立的,雖然內容和 a 相同(因此 a == c),但是參照不同的物件,所以 a is not c。

但是對 immutable 物件來說,Python 為了節省資源,通常會參照相同的物件,不論是如何建立的(甚至是計算或組裝的結果),只要值相同,就會使用相同的物件參照。
a = 256
b = 256
c = 250 + 6
print(id(a)) # 1362847456
print(id(b)) # 1362847456
print(id(c)) # 1362847456
print(a == b) # True
print(a is b) # True
print(a is c) # True
a = 'Hello'
b = 'Hello'
c = 'He' + 'llo'
print(id(a)) # 32444640
print(id(b)) # 32444640
print(id(b)) # 32444640
print(a == b) # True
print(a is b) # True
print(a is c) # True
因此 is 與 is not 只適用於 mutable 物件的「參照」比對,若是「值」的比對,不管是 mutable 或者 immutable,都適用其他六個相等運算子(>、>=、<、<=、==、!=)。
x = 6
y = 7
z = 7
print(x < y) # True
print(y <= z) # True
print(x < y and y <= z) # True
print(x < y <= z)  # True, 最不可思議的語法
同一個 class 的不同 instance 預設視為不相等(!=),除非 class 定義了__eq__。
class A:
    def a():
        pass

a1 = A()
a2 = A()
print(str(a1) + ' - ' + str(id(a1))) # <__main__.A object at 0x01A2CD70> - 27446640
print(str(a2) + ' - ' + str(id(a2))) # <__main__.A object at 0x01A47690> - 27555472
print(a1 == a2) # False

class B:
    def __init__(self, name):
        self.name = name;
    def __eq__(self, other):
        return self.name == other.name
b1 = B('Neil')
b2 = B('Neil')
b3 = B('Nail')
print(str(b1) + ' - ' + str(id(b1))) # <__main__.B object at 0x0151F130> - 22147376
print(str(b2) + ' - ' + str(id(b2))) # <__main__.B object at 0x01EF1DB0> - 32447920
print(str(b3) + ' - ' + str(id(b3))) # <__main__.B object at 0x01EF1E10> - 32448016
print(b1 == b2) # True
print(b1 == b3) # False
可以依此類推,要對同一個 class 的不同 instance 進行相等運算(Comparison Operation),必須分別定義 __lt__、__le__、__gt__ 與 __ge__,當然還有前面提到的 __eq__ 以及 __ne__。
class C:
    def __init__(self, age):
        self.age = age;
    def __eq__(self, other):
        return self.age == other.age
    def __ne__(self, other):
        return self.age != other.age
    def __lt__(self, other):
        return self.age < other.age
    def __le__(self, other):
        return self.age <= other.age
    def __gt__(self, other):
        return self.age > other.age
    def __ge__(self, other):
        return self.age >= other.age
c1 = C(2)
c2 = C(2)
c3 = C(6)
print(str(c1) + ' - ' + str(id(c1))) # <__main__.C object at 0x01A3CD70> - 27512176
print(str(c2) + ' - ' + str(id(c2))) # <__main__.C object at 0x01A57690> - 27621008
print(str(c3) + ' - ' + str(id(c3))) # <__main__.C object at 0x01EF13B0> - 32445360
print(c1 == c2) # True
print(c1 == c3) # False
print(c1 >= c2) # True
print(c1 <= c2) # True
print(c1 > c3) # False
print(c2 < c3) # True
print(c1 != c2) # False
print(c2 != c3) # True
最後,不能也不需要為自訂 class 進行 is 與 is not 的自訂。

官方文件
---
---
---

沒有留言:

張貼留言