【Python】深入条件控制

Python 的深入条件控制

第一部分:Python 条件控制的基石

在任何编程语言中,条件控制都是构建复杂逻辑和决策流程的核心。Python 提供了简洁而强大的工具来实现这一点,但其背后蕴含着丰富的机制和微妙之处。本部分将深入探讨构成 Python 条件控制基础的各个方面。

1.1 布尔类型 (bool) 与真值测试 (Truth Value Testing)

1.1.1 TrueFalse:布尔宇宙的原子

Python 有一个内置的布尔类型 bool,它只有两个实例:TrueFalse。这两个值是 Python 关键字,也是 bool 类型的单例对象。

# bool 类型的基本演示
is_active = True
is_admin = False

print(f"is_active: {
     is_active}, type: {
     type(is_active)}")
# 输出: is_active: True, type: 
print(f"is_admin: {
     is_admin}, type: {
     type(is_admin)}")
# 输出: is_admin: False, type: 

# True 和 False 是 bool 的实例
print(f"isinstance(True, bool): {
     isinstance(True, bool)}")   # 输出: True
print(f"isinstance(False, bool): {
     isinstance(False, bool)}") # 输出: True

# bool 是 int 的子类 (这是一个历史实现细节,但很重要)
print(f"issubclass(bool, int): {
     issubclass(bool, int)}") # 输出: True
print(f"True == 1: {
     True == 1}")   # 输出: True
print(f"False == 0: {
     False == 0}") # 输出: True
print(f"True + True: {
     True + True}") # 输出: 2 (因为 True 表现为 1)
print(f"False * 10: {
     False * 10}") # 输出: 0 (因为 False 表现为 0)

# 尽管 bool 是 int 的子类,但直接用 1 或 0 代替 True/False 通常不推荐,以保持代码可读性
# 例如,if user_level == 1: ...  不如 if user_is_privileged: ... 清晰

代码解释:

  • is_active = True, is_admin = False: 直接将布尔值赋给变量。
  • type(is_active): 显示变量的类型是
  • issubclass(bool, int): 揭示了 bool 类型在 CPython 实现中是 int 的子类。True 的整数值是 1,False 的整数值是 0。
  • True == 1, False == 0: 由于子类关系,布尔值可以和整数 1 和 0 进行比较并相等。
  • True + True: 可以进行算术运算,True 表现为 1。
  • 可读性: 尽管 boolint 之间存在这种关系,但在条件判断和逻辑表达中,应始终优先使用 TrueFalse 字面量,以及能明确表达布尔含义的变量或表达式,以增强代码的可读性和意图的清晰性。避免在逻辑判断中使用 if some_int_flag == 1:,而应该使用 if some_bool_flag is True: 或更简洁的 if some_bool_flag:(如果 some_bool_flag 保证是布尔类型)。
1.1.2 Python 中的真值测试 (Truth Value Testing)

Python 的一个强大特性是,不仅仅是 TrueFalse 可以在条件语句(如 if, while)或布尔运算(and, or, not)中被评估其真假性,几乎任何对象都可以。这个过程被称为“真值测试”。

Python 定义了以下对象的真值为 False (或称“假性值”,Falsy values):

  1. None (常量 None)。
  2. False (布尔值 False)。
  3. 任何数值类型的零:
    • 0 (整数)
    • 0.0 (浮点数)
    • 0j (复数)
  4. 任何空的序列:
    • '' (空字符串)
    • () (空元组)
    • [] (空列表)
  5. 任何空的映射(mapping):
    • {} (空字典)
  6. 空集合:
    • set() (通过 set() 创建的空集合)
  7. 用户自定义类的实例,如果该类定义了 __bool__() 方法且该方法返回 False,或者定义了 __len__() 方法且该方法返回 0 (如果同时定义了 __bool____len__,则 __bool__ 优先)。

除以上情况外,所有其他对象在真值测试中都被认为是 True (或称“真性值”,Truthy values)。

def check_truthiness(obj, description):
    """
    辅助函数,检查对象的真值并打印结果。
    """
    if obj: # 这里隐式地进行了真值测试
        print(f"'{
     description}' ({
     type(obj).__name__}: {
     repr(obj)}) is TRUTHY.")
    else:
        print(f"'{
     description}' ({
     type(obj).__name__}: {
     repr(obj)}) is FALSY.")

print("--- Falsy Values ---")
check_truthiness(None, "None")                # NoneType: None is FALSY.
check_truthiness(False, "False literal")      # bool: False is FALSY.
check_truthiness(0, "Integer zero")           # int: 0 is FALSY.
check_truthiness(0.0, "Float zero")           # float: 0.0 is FALSY.
check_truthiness(0j, "Complex zero")          # complex: 0j is FALSY.
check_truthiness("", "Empty string")          # str: '' is FALSY.
check_truthiness((), "Empty tuple")           # tuple: () is FALSY.
check_truthiness([], "Empty list")            # list: [] is FALSY.
check_truthiness({
   }, "Empty dictionary")       # dict: {} is FALSY.
check_truthiness(set(), "Empty set")          # set: set() is FALSY.
check_truthiness(range(0), "Empty range")     # range: range(0, 0) is FALSY.

print("\n--- Truthy Values ---")
check_truthiness(True, "True literal")        # bool: True is TRUTHY.
check_truthiness(1, "Integer one")            # int: 1 is TRUTHY.
check_truthiness(-1, "Negative integer")      # int: -1 is TRUTHY.
check_truthiness(0.0001, "Small float")       # float: 0.0001 is TRUTHY.
check_truthiness("hello", "Non-empty string") # str: 'hello' is TRUTHY.
check_truthiness((1, 2), "Non-empty tuple")   # tuple: (1, 2) is TRUTHY.
check_truthiness([0], "List with zero")       # list: [0] is TRUTHY (因为列表非空)
check_truthiness({
   'key': 'value'}, "Non-empty dict") # dict: {'key': 'value'} is TRUTHY.
check_truthiness({
   None}, "Set with None")     # set: {None} is TRUTHY (因为集合非空)
check_truthiness(object(), "Basic object instance") # object:  is TRUTHY.
check_truthiness(check_truthiness, "A function object") # function:  is TRUTHY.

class AlwaysTruthy:
    """自定义类,默认行为是真性"""
    pass

class AlwaysFalsyViaBool:
    """自定义类,通过 __bool__ 方法定义为假性"""
    def __bool__(self):
        print("AlwaysFalsyViaBool.__bool__() called")
        return False

class AlwaysFalsyViaLen:
    """自定义类,通过 __len__ 方法定义为假性"""
    def __len__(self):
        print("AlwaysFalsyViaLen.__len__() called")
        return 0

class FalsyButBoolOverridesLen:
    """自定义类,__bool__ 优先于 __len__"""
    def __bool__(self):
        print("FalsyButBoolOverridesLen.__bool__() called")
        return False
    def __len__(self):
        print("FalsyButBoolOverridesLen.__len__() called (SHOULD NOT be if __bool__ exists)")
        return 10 # 即使 __len__ 返回非零,__bool__ 也会使其为假

class TruthyViaLen:
    """自定义类,通过 __len__ 定义为真性 (因为没有 __bool__)"""
    def __len__(self):
        print("TruthyViaLen.__len__() called")
        return 1

print("\n--- Custom Object Truthiness ---")
check_truthiness(AlwaysTruthy(), "AlwaysTruthy instance")
check_truthiness(AlwaysFalsyViaBool(), "AlwaysFalsyViaBool instance")
check_truthiness(AlwaysFalsyViaLen(), "AlwaysFalsyViaLen instance")
check_truthiness(FalsyButBoolOverridesLen(), "FalsyButBoolOverridesLen instance")
check_truthiness(TruthyViaLen(), "TruthyViaLen instance")

# 明确使用 bool() 函数进行转换
print("\n--- Explicit bool() conversion ---")
print(f"bool(0): {
     bool(0)}")         # False
print(f"bool(1): {
     bool(1)}")         # True
print(f"bool([]): {
     bool([])}")       # False
print(f"bool([1]): {
     bool([1])}")     # True
print(f"bool(None): {
     bool(None)}")   # False
print(f"bool(''): {
     bool('')}")       # False
print(f"bool('abc'): {
     bool('abc')}") # True
 
  

代码解释:

  • if obj:: 这是进行真值测试的核心。Python 解释器内部会调用一个类似 PyObject_IsTrue() 的 C API 函数,该函数遵循上述规则来确定 obj 的真假性。
  • 数字零: 所有形式的数字零(整型、浮点型、复数)都是假性。
  • 空容器: 所有内置的空容器类型(字符串、元组、列表、字典、集合)以及空的 range 对象都是假性。重要的是,一个包含假性值(如 [0][None][''])的非空容器本身是真性的,因为容器自身不为空。
  • None: None 对象总是假性。
  • 自定义对象:
    • 如果一个类定义了 __bool__(self) 方法,Python 在进行真值测试时会调用它。该方法必须返回 TrueFalse
    • 如果类没有定义 __bool__(self) 但定义了 __len__(self) 方法,Python 会调用 __len__(self)。如果返回 0,则对象为假性;如果返回非零整数,则对象为真性。
    • 如果类既没有定义 __bool__ 也没有定义 __len__,则其实例总是真性(这是 object 基类的默认行为)。
    • FalsyButBoolOverridesLen: 这个例子清晰地展示了 __bool__ 方法的优先级高于 __len__ 方法。
  • bool(obj): 可以使用内置的 bool() 函数显式地获取一个对象的布尔值,它遵循与隐式真值测试完全相同的规则。

真值测试在企业级代码中的意义:
真值测试的灵活性使得 Python 代码可以非常简洁和富有表达力。例如,检查一个列表是否为空可以直接写 if my_list: 而不是 if len(my_list) > 0:。检查一个字符串是否为空可以直接写 if my_string: 而不是 if my_string != '':if len(my_string) > 0:

然而,这种灵活性也要求开发者非常清楚哪些值是真性,哪些是假性,以避免引入潜在的逻辑错误。例如,如果一个函数可能返回 0(表示一个有效但为零的计数)或 None(表示错误或未找到),那么 if result: 这样的检查就无法区分这两种情况。在这种情况下,需要更明确的检查,如 if result is not None:if result == 0:

1.1.3 警惕:is True vs == True vs if x:

在对布尔值或可能为布尔值的表达式进行条件判断时,有几种写法,它们之间有细微但重要的区别:

  1. if x: (推荐用于大多数情况下的真值测试)

    • 这是最 Pythonic 的方式,它利用了 Python 的真值测试规则。
    • 如果 xTrue,或者 x 是任何被认为是真性的对象(如非空列表、非零数字等),条件为真。
    • 如果 xFalse,或者 x 是任何被认为是假性的对象(如 None、空字符串、0 等),条件为假。
  2. if x == True: (通常不推荐)

    • 这个表达式只在 x 的值等于 True 时才为真。
    • 由于 boolint 的子类,True == 1 是成立的。所以 if 1 == True: 也会是真。
    • 然而,if [1, 2] == True: 会是假,尽管 [1, 2] 在真值测试中是真性的。
    • 这种写法限制了 Python 真值测试的灵活性,并且通常更冗余。
  3. if x is True: (仅在你确实需要检查 x 是否是单例对象 True 本身时使用)

    • is 操作符检查的是对象身份 (identity),即两个变量是否指向内存中的同一个对象。
    • TrueFalse 是单例对象,所以 x is True 只有当 x 确实是那个唯一的 True 对象时才为真。
    • 1 is TrueFalse (尽管 1 == TrueTrue),因为整数 1 和布尔值 True 是不同的对象(即使它们的值在比较时相等)。
    • 何时使用: 当一个函数明确约定返回 True, False, 或 None 来区分三种状态时,你可能需要用 if result is True:if result is False:,或 if result is None: 来精确判断。
def truth_comparison_demo(value, description):
    print(f"\n--- Testing '{
     description}' (value: {
     repr(value)}, type: {
     type(value).__name__}) ---")

    # 1. 'if value:' (真值测试)
    is_truthy_if = False
    if value:
        is_truthy_if = True
    print(f"  'if value:' results in: {
     is_truthy_if}")

    # 2. 'if value == True:'
    is_truthy_eq_true = False
    if value == True: # pylint: disable=singleton-comparison (Pylint 会警告这种用法)
        is_truthy_eq_true = True
    print(f"  'if value == True:' results in: {
     is_truthy_eq_true}")

    # 3. 'if value is True:'
    is_truthy_is_true = False
    if value is True: # pylint: disable=singleton-comparison (Pylint 可能会警告,但有时这是故意的)
        is_truthy_is_true = True
    print(f"  'if value is True:' results in: {
     is_truthy_is_true}")


truth_comparison_demo(True, "Boolean True")
#   'if value:' results in: True
#   'if value == True:' results in: True
#   'if value is True:' results in: True

truth_comparison_demo(False, "Boolean False")
#   'if value:' results in: False
#   'if value == True:' results in: False
#   'if value is True:' results in: False

truth_comparison_demo(1, "Integer 1")
#   'if value:' results in: True       (1 is truthy)
#   'if value == True:' results in: True       (1 == True because True is like 1 for equality)
#   'if value is True:' results in: False      (1 is not the same object as True)

truth_comparison_demo(0, "Integer 0")
#   'if value:' results in: False      (0 is falsy)
#   'if value == True:' results in: False
#   'if value is True:' results in: False

truth_comparison_demo("hello", "String 'hello'")
#   'if value:' results in: True       ('hello' is truthy)
#   'if value == True:' results in: False      ('hello' != True)
#   'if value is True:' results in: False      ('hello' is not True)

truth_comparison_demo("", "Empty String ''")
#   'if value:' results in: False      ('' is falsy)
#   'if value == True:' results in: False
#   'if value is True:' results in: False

truth_comparison_demo(None, "None object")
#   'if value:' results in: False      (None is falsy)
#   'if value == True:' results in: False
#   'if value is True:' results in: False


# PEP 8 推荐:
# "Comparisons to singletons like None should always be done with 'is' or 'is not', never the equality operators."
# "Also, be careful about writing 'if x:' when you really mean 'if x is not None:' – e.g. when testing
#  whether a variable or argument that defaults to None was set to some other value. The other value might
#  have a type (such as a container) that could be false in a boolean context!"

# 企业级场景: API 响应处理
def process_api_response(response_data):
    """
    处理一个API响应,该响应可能包含一个'enabled'字段,
    该字段严格为布尔值 True/False,或者完全不存在 (None)。
    """
    # 假设 response_data 是一个字典,或者 None
    if response_data is None:
        print("API Response: No data received.")
        return

    # 错误的方式,如果 'enabled' 是 0,会被认为是 False
    # if response_data.get('enabled'):
    #     print("Feature is truthy (could be 1, 'yes', etc. - not good)")

    # 正确的方式,如果 'enabled' 必须是布尔值 True
    # enabled_status = response_data.get('enabled') # get() 返回 None 如果键不存在

    # 场景1: 'enabled' 键必须存在且为 True
    # enabled_status = response_data.get('enabled', 'KEY_NOT_FOUND') # 给个哨兵值
    # if enabled_status is True:
    #     print("API Response: Feature is explicitly True.")
    # elif enabled_status is False:
    #     print("API Response: Feature is explicitly False.")
    # elif enabled_status == 'KEY_NOT_FOUND':
    #     print("API Response: 'enabled' key not found.")
    # else:
    #     print(f"API Response: 'enabled' key has unexpected value: {enabled_status}")

    # 场景2: 更常见的做法,检查键是否存在,然后检查其真值 (如果类型已知或灵活)
    if 'enabled' in response_data:
        if response_data['enabled'] is True: # 精确检查布尔 True
            print("API Response (strict check): Feature is ON.")
        elif response_data['enabled'] is False: # 精确检查布尔 False
            print("API Response (strict check): Feature is OFF.")
        else: # 值存在但不是 True 或 False
            print(f"API Response (strict check): 'enabled' has non-boolean value: {
     repr(response_data['enabled'])}")
    else:
        # 'enabled' 键不存在,根据业务逻辑决定是默认开启还是关闭
        print("API Response (strict check): 'enabled' field is missing. Assuming default (e.g., OFF).")

print("\n--- API Response Demo ---")
process_api_response({
   'enabled': True, 'user': 'admin'})
process_api_response({
   'enabled': False, 'user': 'guest'})
process_api_response({
   'user': 'test'}) # 'enabled' 键缺失
process_api_response({
   'enabled': 1, 'user': 'legacy'}) # 'enabled' 是整数 1
process_api_response({
   'enabled': "true", 'user': 'string_val'}) # 'enabled' 是字符串 "true"
process_api_response(None)

代码解释与建议:

  • if value:: 当你关心一个值是否“空”、“零”或“无意义”(即假性)时,这是最通用的写法。例如,检查列表是否为空 (if my_list:),字符串是否为空 (if my_string:), 数字是否为零 (if my_number: )。
  • if value == True:: 几乎总是应该避免。它比 if value: 更冗长,并且会错误地将真性非布尔值(如非空列表)视为假。它唯一的“优势”(如果你能称之为优势的话)是 1 == True 会为真,但这通常不是期望的行为,并可能掩盖类型问题。
  • if value is True:: 仅当你需要严格区分字面量 True 和其他所有值(包括其他真性值如 1, [1], "text")时使用。同样,if value is False: 用于严格检查字面量 Falseif value is None: 用于严格检查 None 对象。
  • PEP 8 指导:
    • 对于单例对象如 None, True, False,应使用 isis not 进行比较(例如 if x is None:)。
    • 当测试一个可能为 None 或其他值的变量时,如果该“其他值”本身在布尔上下文中可能为假(例如,一个空列表或数字0),那么简单的 if x: 检查不足以区分“值是 None”和“值是假性值”。此时,应使用 if x is not None:

企业级考量:
在复杂的系统中,函数的返回值或配置项的含义必须非常清晰。

  • 如果一个标志只能是 TrueFalse,那么接收方代码可以使用 if flag is True:if flag: (如果确信它总是布尔类型)。
  • 如果一个函数可能返回一个集合、None 表示未找到、或者一个空集合表示找到了但结果为空,那么 if result: 就会混淆“未找到”(None,假性)和“找到了但为空”(空集合,假性)。此时需要 if result is not None: 来先确认是否找到了,然后再检查结果是否为空(if result:if not result:,取决于逻辑)。

清晰地理解真值测试、== Trueis True 之间的差异,对于编写健壮、无歧义且易于维护的 Python 代码至关重要。

1.2 比较运算符:构建条件的基础

比较运算符用于比较两个操作数,并返回一个布尔值 (TrueFalse)。这些是构成 if 语句条件部分的最常见元素。

Python 支持以下比较运算符:

  • == : 等于 (equal to)
  • != : 不等于 (not equal to)
  • < : 小于 (less than)
  • > : 大于 (greater than)
  • <= : 小于或等于 (less than or equal to)
  • >= : 大于或等于 (greater than or equal to)
  • is : 对象身份 (is the same object)
  • is not : 对象身份否定 (is not the same object)
  • in : 成员资格 (is a member of)
  • not in : 成员资格否定 (is not a member of)
1.2.1 值比较 (==, !=, <, >, <=, >=)

这些运算符比较的是对象的值。

a = 10
b = 20
s1 = "hello"
s2 = "world"
list1 = [1, 2, 3]
list2 = [1, 2, 3]
list3 = [4, 5, 6]

print(f"a == 10: {
     a == 10}") # True, a 的值是 10
print(f"a != b: {
     a != b}")   # True, 10 不等于 20
print(f"a < b: {
     a < b}")     # True, 10 小于 20
print(f"b >= a: {
     b >= a}")   # True, 20 大于等于 10
print(f"s1 == 'hello': {
     s1 == 'hello'}") # True
print(f"s1 != s2: {
     s1 != s2}")           # True
print(f"s1 < s2: {
     s1 < s2}") # True, 字符串按字典序比较 ('h' < 'w')

# 列表的值比较 (逐元素比较)
print(f"list1 == list2: {
     list1 == list2}") # True, 内容相同
print(f"list1 == list3: {
     list1 == list3}") # False, 内容不同
print(f"list1 < list3: {
     list1 < list3}")   # True, 按字典序比较 ([1,2,3] < [4,5,6])

# 不同类型之间的比较 (通常会导致 TypeError,除非类型定义了如何比较)
try:
    print(f"a < s1: {
     a < s1}") # 比较整数和字符串
except TypeError as e:
    print(f"TypeError comparing int and str: {
     e}") # '<' not supported between instances of 'int' and 'str'

# 对于自定义对象,这些运算符的行为取决于是否实现了富比较方法
# (e.g., __eq__, __ne__, __lt__, __gt__, __le__, __ge__)
class Point:
    def __init__(self, x, y):
        self.x = x
        self.y = y

    def __eq__(self, other): # 定义 '=='
        print(f"Point.__eq__ called for {
     self} and {
     other}")
        if not isinstance(other, Point):
            return NotImplemented # 表示无法与该类型比较,Python可能会尝试 other.__eq__(self)
        return self.x == other.x and self.y == other.y

    def __lt__(self, other): # 定义 '<' (例如,按x排序,然后按y排序)
        print(f"Point.__lt__ called for {
     self} and {
     other}")
        if not isinstance(other, Point):
            return NotImplemented
        if self.x < other.x:
            return True
        if self.x == other.x and self.y < other.y:
            return True
        return False

    # 如果定义了 __eq__ 和一个排序方法 (如 __lt__),
    # Python 的 functools.total_ordering 装饰器可以自动为你生成其他比较方法
    # (e.g., __gt__, __le__, __ge__, __ne__)

    def __repr__(self):
        return f"Point({
     self.x}, {
     self.y})"

p1 = Point(1, 2)
p2 = Point(1, 2)
p3 = Point(2, 1)
p4 = Point(1, 3)

print(f"p1 == p2: {
     p1 == p2}") # True (调用 p1.__eq__(p2))
print(f"p1 == p3: {
     p1 == p3}") # False
print(f"p1 != p3: {
     p1 != p3}") # True (如果没有 __ne__, Python会用 not (p1 == p3))

print(f"p1 < p3: {
     p1 < p3}")   # True (1 < 2)
print(f"p1 < p4: {
     p1 < p4}")   # True (1 == 1, 2 < 3)
print(f"p3 < p4: {
     p3 < p4}")   # False (2 > 1)
# print(f"p1 <= p2: {p1 <= p2}") # 如果没有 __le__, 会尝试 p1 < p2 or p1 == p2 (大致逻辑)
                               # 或者如果使用了 @functools.total_ordering

代码解释:

  • 基本类型的比较按预期工作。字符串比较是按字典顺序(lexicographical)。
  • 列表(以及元组)的比较也是按字典顺序,即逐个比较元素,直到找到第一个不同的元素,或者一个序列先耗尽。
  • 不同类型比较: 直接比较不兼容的类型(如 intstr)使用 <> 等通常会引发 TypeError==!= 对于不同类型通常直接返回 FalseTrue,除非类型有特殊的比较逻辑。
  • 自定义对象:
    • 默认情况下,用户自定义类的实例仅通过对象身份进行比较(即 obj1 == obj2 等同于 obj1 is obj2)。
    • 通过实现 富比较方法 (rich comparison methods),可以自定义比较行为:
      • __eq__(self, other): for ==
      • __ne__(self, other): for != (如果未实现,Python 使用 not (self == other))
      • __lt__(self, other): for <
      • __le__(self, other): for <=
      • __gt__(self, other): for >
      • __ge__(self, other): for >=
    • 这些方法应返回 TrueFalse,或者 NotImplementedNotImplemented 是一个特殊的单例对象,表示该操作对于所提供的类型未实现。如果 a.__op__(b) 返回 NotImplemented,Python 会尝试“反射操作” b.__rop__(a)(例如,如果 a < b (即 a.__lt__(b)) 返回 NotImplemented,Python 会尝试 b > a (即 b.__gt__(a)))。如果两者都返回 NotImplemented,则通常会引发 TypeError
    • @functools.total_ordering: 这是一个类装饰器,如果你定义了 __eq__ 和至少一个排序方法(__lt__, __le__, __gt__, or __ge__),它可以自动为你填充其余的排序方法,非常方便。
1.2.2 对象身份比较 (is, is not)

isis not 比较的是两个变量是否指向内存中的同一个对象,而不是它们的值是否相等。

a = [1, 2, 3]
b = [1, 2, 3] # b 是一个新的列表对象,尽管内容与 a 相同
c = a         # c 指向与 a 相同的列表对象

print(f"a == b: {
     a == b}") # True (值相等)
print(f"a is b: {
     a is b}") # False (不同的对象)
print(f"id(a): {
     id(a)}, id(b): {
     id(b)}") # id() 返回对象的内存地址 (CPython实现细节)

print(f"a == c: {
     a == c}") # True (值相等)
print(f"a is c: {
     a is c}") # True (相同的对象)
print(f"id(a): {
     id(a)}, id(c): {
     id(c)}")

# 对于小整数和短字符串,CPython 实现通常会进行 "interning" (对象复用)
# 这是一种优化,但不应该依赖此行为进行逻辑判断
x = 256
y = 256
print(f"x = {
     x}, y = {
     y}")
print(f"x == y: {
     x == y}") # True
print(f"x is y: {
     x is y}") # True (在CPython中,-5 到 256 的整数通常是单例)

z = 257
w = 257
print(f"z = {
     z}, w = {
     w}")
print(f"z == w: {
     z == w}") # True
print(f"z is w: {
     z is w}") # False (通常情况下,超出小整数范围的数不会被intern)

s_a = "short"
s_b = "short"
print(f"s_a == s_b: {
     s_a == s_b}") # True
print(f"s_a is s_b: {
     s_a is s_b}") # True (短字符串也可能被 intern)

long_s1 = "a very long string that is unlikely to be interned automatically" * 10
long_s2 = "a very long string that is unlikely to be interned automatically" * 10
print(f"long_s1 == long_s2: {
     long_s1 == long_s2}") # True
print(f"long_s1 is long_s2: {
     long_s1 is long_s2}") # False (通常)

# 关键用途: 比较单例对象
val = None
if val is None:
    print("val is indeed None (checked with 'is').")

flag = True
if flag is True:
    print("flag is indeed True (checked with 'is').")

# 避免 'is' 用于可变对象或非单例的字面量比较,除非你明确知道其含义
# 错误示例: if my_list is []: 这样永远不会为 True,除非 my_list 就是那个特定的空列表实例
# 正确检查空列表: if not my_list: 或者 if len(my_list) == 0:
empty_list1 = []
empty_list2 = []
print(f"empty_list1 is empty_list2: {
     empty_list1 is empty_list2}") # False

代码解释:

  • a is b 检查 id(a) == id(b)
  • 可变对象 (mutable) 如列表和字典,每次创建字面量(如 []{}) 或调用构造函数时,通常都会在内存中创建一个新的对象。因此,即使它们的值相同,is 也会返回 False
  • 不可变对象 (immutable) 如数字、字符串、元组:
    • 小整数和短字符串的 Interning: CPython 为了性能优化,会对一定范围内的小整数(通常是 -5 到 256)和一些短的、看起来像标识符的字符串进行“驻留 (interning)”。这意味着这些值的多个实例实际上可能指向内存中的同一个对象。因此,对于这些值,is 可能会返回 True然而,这是一个实现细节,不应在代码逻辑中依赖它。 对于值的比较,始终使用 ==
    • 对于超出这个范围的数字或更长的字符串,is 通常会返回 False,即使它们的值相同。
  • None, True, False: 这三个是真正的单例对象。在整个 Python 程序运行期间,只有一个 None 对象、一个 True 对象和一个 False 对象。因此,比较这些值时,强烈推荐使用 isis not (if x is None:, if x is True:)。这不仅更高效(身份比较比值比较快),而且更准确地表达了意图(“x 是不是那个唯一的 None 对象?”)。
1.2.3 成员资格测试 (in, not in)

innot in 用于测试一个值是否存在于一个序列(如列表、元组、字符串)、集合或字典的键中。

my_list = [10, 20, 30, "apple", "banana"]
my_string = "hello python world"
my_dict = {
   "name": "Alice", "age": 30, "city": "New York"}
my_set = {
   100, 200, 300}

# 列表成员测试
print(f"20 in my_list: {
     20 in my_list}")             # True
print(f"'orange' in my_list: {
     'orange' in my_list}") # False
print(f"'apple' not in my_list: {
     'apple' not in my_list}") # False (因为 'apple' 在里面)

# 字符串成员测试 (检查子串)
print(f"'python' in my_string: {
     'python' in my_string}") # True
print(f"'Java' in my_string: {
     'Java' in my_string}")     # False
print(f"'o p' in my_string: {
     'o p' in my_string}")       # True

# 字典成员测试 (默认检查键)
print(f"'age' in my_dict: {
     'age' in my_dict}")             # True
print(f"30 in my_dict: {
     30 in my_dict}")                   # False (30 是值,不是键)
print(f"'country' not in my_dict: {
     'country' not in my_dict}") # True

# 如果要检查字典的值或键值对:
print(f"30 in my_dict.values(): {
     30 in my_dict.values()}") # True
print(f"('city', 'New York') in my_dict.items(): {
     ('city', 'New York') in my_dict.items()}") # True

# 集合成员测试 (非常高效)
print(f"200 in my_set: {
     200 in my_set}")           # True
print(f"400 not in my_set: {
     400 not in my_set}")   # True

# 对于自定义对象,'in' 的行为取决于是否实现了 __contains__ 方法
class NumberSequence:
    def __init__(self, numbers):
        self.numbers = list(numbers)
        print(f"NumberSequence created with: {
     self.numbers}")

    def __contains__(self, item):
        print(f"NumberSequence.__contains__ called for item: {
     item}")
        return item in self.numbers # 委托给内部列表的 __contains__

    # 如果没有 __contains__,但对象是可迭代的 (有 __iter__),
    # 'in' 会尝试迭代整个序列来查找元素 (效率较低)
    # def __iter__(self):
    #     return iter(self.numbers)

    # 如果都没有,会引发 TypeError

ns = NumberSequence(range(0, 100, 10)) # 0, 10, 20, ..., 90
print(f"30 in ns: {
     30 in ns}")   # True, 调用 ns.__contains__(30)
print(f"35 in ns: {
     35 in ns}")   # False, 调用 ns.__contains__(35)

代码解释与性能考量:

  • item in container: Python 会尝试调用 container.__contains__(item)
  • 如果 __contains__ 未定义,但 container 是可迭代的(定义了 __iter__()__getitem__()),Python 会迭代 container 中的所有元素,逐个与 item 比较,直到找到匹配或迭代完成。
  • 性能:
    • 对于 listtuplein 操作的平均时间复杂度是 O(N),因为可能需要扫描整个序列。
    • 对于 str,子串检查(如 substring in string)通常使用优化的算法(如 Boyer-Moore 或类似算法),其性能比朴素的 O(NM) 更好,但在最坏情况下仍可能接近 O(NM)(N是主串长度,M是子串长度)。
    • 对于 setdict (检查键),in 操作的平均时间复杂度是 O(1)(常数时间),最坏情况是 O(N)(由于哈希冲突)。这使得它们非常适合快速查找成员。
    • 因此,如果需要频繁地进行成员资格测试,并且元素的顺序不重要,通常最好将数据存储在 setdict 中。
# 性能对比示例:在一个大列表中查找 vs 在一个大集合中查找
import time

large_list = list(range(10_000_000)) # 一千万个元素
large_set = set(large_list)
element_to_find = 9_999_999 # 接近末尾的元素
element_not_present = 10_000_001

# 测试列表
start_time = time.perf_counter()
found_in_list = element_to_find in large_list
end_time = time.perf_counter()
print(f"Time to find '{
     element_to_find}' in list: {
     end_time - start_time:.6f}s. Found: {
     found_in_list}")

start_time = time.perf_counter()
found_in_list_absent = element_not_present in large_list
end_time = time.perf_counter()
print(f"Time to find '{
     element_not_present}' in list: {
     end_time - start_time:.6f}s. Found: {
     found_in_list_absent}")


# 测试集合
start_time = time.perf_counter()
found_in_set = element_to_find in large_set
end_time = time.perf_counter()
print(f"Time to find '{
     element_to_find}' in set: {
     end_time - start_time:.6f}s. Found: {
     found_in_set}")

start_time = time.perf_counter()
found_in_set_absent = element_not_present in large_set
end_time = time.perf_counter()
print(f"Time to find '{
     element_not_present}' in set: {
     end_time - start_time:.6f}s. Found: {
     found_in_set_absent}")

# 典型输出 (时间会因机器而异):
# Time to find '9999999' in list: 0.123456s. Found: True
# Time to find '10000001' in list: 0.134567s. Found: False
# Time to find '9999999' in set: 0.000001s. Found: True
# Time to find '10000001' in set: 0.000001s. Found: False

这个性能差异在处理大规模数据集时非常显著,选择正确的数据结构对于条件判断的效率至关重要。

1.2.4 比较运算的链式操作 (Chained Comparisons)

Python 允许将比较运算链接起来,这是一种非常优雅和可读的特性。
例如,a < b < c 等价于 a < b and b < c,但 b 只会被评估一次。

x = 10
y = 15
z = 20
w = 15

# 链式比较
if 5 < x <= y < z: # 等价于 (5 < x) and (x <= y) and (y < z)
    print(f"{
     x}, {
     y}, {
     z} satisfy 5 < x <= y < z")
else:
    print(f"{
     x}, {
     y}, {
     z} DO NOT satisfy 5 < x <= y < z")

if x < y == w < z: # 等价于 (x < y) and (y == w) and (w < z)
    print(f"{
     x}, {
     y}, {
     w}, {
     z} satisfy x < y == w < z")
else:
    print(f"{
     x}, {
     y}, {
     w}, {
     z} DO NOT satisfy x < y == w < z")

# x=10, y=15, z=20, w=15
# 5 < 10 (T), 10 <= 15 (T), 15 < 20 (T) -> True
# 10 < 15 (T), 15 == 15 (T), 15 < 20 (T) -> True

# 中间表达式只计算一次的演示
def get_value(name, val):
    print(f"get_value('{
     name}') called, returning {
     val}")
    return val

print("\n--- Chained comparison evaluation order ---")
# a < func() < c
# 'func()' 只会被调用一次
if get_value('A', <

你可能感兴趣的:(python,开发语言)