Python 基础

Dec 26, 2024

Python 是一种功能强大且易于学习的编程语言,它在人工智能、Web 开发、数据分析等领域都有着广泛的应用。本教程旨在帮助零基础的读者快速入门 Python 编程。

为什么选择 Python?

  • 简洁易读: Python 语法清晰简洁,接近自然语言,易于理解和学习。
  • 应用广泛: 从人工智能、机器学习到 Web 开发、数据科学,Python 无处不在。
  • 强大的库支持: Python 拥有海量的标准库和第三方库,可以轻松完成各种任务。
  • 社区活跃: 庞大的 Python 社区提供了丰富的学习资源和技术支持。

Python 在人工智能领域的应用 (包括但不限于):

  • 文本到图像生成 (Text to Images)
  • 大型语言模型 (LLMs)
  • 深度学习 (Deep Learning)

知名公司都在使用 Python:

  • Tesla (特斯拉): 使用 OpenCV 进行计算机视觉应用开发。
  • Instagram (照片墙): 采用 Django 框架构建 Web 应用。

Python 的应用场景

  • 人工智能 (AI) & 机器学习 (ML)
  • 计算机视觉 (Computer Vision)
  • Web 应用程序开发 (Web Applications): 例如 Flask, Django 等框架。
  • 数据分析与可视化 (Data Analytics and Visualization)
  • 自动化脚本 (Automation Scripts)
  • 科学计算 (Scientific Computing)

基础语法

变量 (Variables)

变量是用于存储数据值的容器。在 Python 中,变量无需声明类型,直接赋值即可。

a = 1       # 整型变量
b = False   # 布尔型变量
c = "你好"   # 字符串变量 (中文 Unicode 字符)
d = None    # NoneType 空值

数据类型 (Data Types)

Python 提供了多种内置数据类型:

  1. 数字 (Number): 用于表示数值。

    • 整型 (int): 整数,例如 3, -8, 0
    • 浮点型 (float): 小数,例如 3.8, 0.001, -9.0
    • 复数 (complex): 复数,例如 2 + 3j,可以使用 complex(实部, 虚部) 创建,例如 a = complex(123, 2) # 123 + 2j
  2. 文本 (Text): 用于表示文本信息。

    • 字符串 (str): 文本序列,使用单引号 ' 或双引号 " 括起来,例如 "Hello World", 'Python 教程'
  3. 布尔值 (Boolean): 用于表示真假。

    • True (真) 或 False (假)
  4. 序列 (Sequence): 有序的数据集合。

    • 列表 (list): 可变的有序集合,元素用逗号分隔,方括号 [] 括起来。

      list1 = [1, 2.3, [-4, 5], ['apple']] # 列表可以包含不同类型的元素
      
    • 元组 (tuple): 不可变的有序集合,元素用逗号分隔,圆括号 () 括起来。

      tuple1 = (("张三", "李四"), ("王五", "赵六")) # 元组一旦创建就不能修改
      
    • 可变 (Mutable) vs 不可变 (Immutable):

      • 可变数据类型 (如列表) 创建后可以修改其内容。
      • 不可变数据类型 (如元组、字符串、数字) 创建后内容不能直接修改,任何修改操作都会创建新的对象。
  5. 映射 (Mapping): 键值对集合。

    • 字典 (dictionary 或 dict): 无序的键值对集合,键值对之间用逗号分隔,花括号 {} 括起来。

      dict1 = { "姓名": "张三", "年龄": 20, "性别": "男", "会编程": True } # 字典使用键值对存储数据
      

    注意: 在 Python 中,一切皆对象 (OBJECT)! 包括字典、数字、布尔值等。

运算符 (Operators)

运算符 (Operator)	运算符名称 (Operator Name)	示例 (Example)
+	加法 (Addition)	5 + 7
-	减法 (Subtraction)	5 - 7
*	乘法 (Multiplication)	5 * 7
/	除法 (Division)	5 / 7
%	取模 (Modulus)	5 % 7 (求余数)
//	地板除法 (Floor Division)	5 // 7 (向下取整)
**	幂运算 (Exponentiation)	5 ** 2 (5 的 2 次方)

类型转换 (Type Casting)

类型转换是将一个数据类型的值转换为另一个数据类型的过程。Python 中称为类型转换 (Type Conversion)。

常用类型转换函数:

  • int(): 转换为整型
  • float(): 转换为浮点型
  • str(): 转换为字符串
  • ord(): 返回字符的 Unicode 代码点
  • hex(): 转换为十六进制字符串
  • oct(): 转换为八进制字符串
  • tuple(): 转换为元组
  • list(): 转换为列表
  • set(): 转换为集合
  • dict(): 转换为字典

类型转换方式:

  • 显式转换 (Explicit Conversion): 开发者手动进行类型转换。
  • 隐式转换 (Implicit Conversion): Python 自动进行的类型转换。

显式转换示例:

a = "1"  # 字符串类型
b = "2"
print(int(a) + int(b))  # 将字符串转换为整型后再相加,输出 3

隐式转换示例:

c = 1.9  # 浮点型
d = 8     # 整型
print(c + d) # d 会被隐式转换为浮点型,输出 9.9

用户输入 (Taking User Input)

使用 input() 函数获取用户输入,该函数返回的始终是 字符串 类型。

x = input("请输入第一个数字: ") # 提示用户输入
y = input("请输入第二个数字: ")
print(x + y) # 字符串拼接,例如输入 12 和 100,输出 12100

要进行数值运算,需要将输入的字符串转换为数值类型:

print(int(x) + int(y))  # 类型转换后相加,例如输入 12 和 100,输出 112

字符串操作 (String Handling)

字符串 (Strings) 是文本数据的序列,使用单引号或双引号括起来。

访问字符 (Accessing Characters)

字符串可以像数组一样通过索引访问字符,索引从 0 开始。

name = "张三"
print(name[0])  # 输出: '张'
print(name[1])  # 输出: '三'

字符串切片 (String Slicing)

切片用于提取字符串的一部分。

fruit = "苹果"
length = len(fruit) # len() 函数获取字符串长度
print("“", fruit, "” 是一个", length, "个字的词") # 输出: “ 苹果 ” 是一个 2 个字的词
pie = "苹果派"
print(pie[:2])  # 输出: '苹果' (从开始到索引 2 之前,不包含索引 2)
print(pie[2])   # 输出: '派' (索引为 2 的字符)

字符串方法 (String Methods)

Python 字符串对象提供了丰富的方法,用于字符串操作。

  • upper(): 转换为大写。

    str_example = "HelloWorld"
    print(str_example.upper())  # 输出: 'HELLOWORLD'
    
  • lower(): 转换为小写。

  • strip(): 去除字符串首尾的空白字符 (空格、制表符、换行符等)。

    str_example = "  空格很多  "
    print(str_example.strip())  # 输出: '空格很多'
    
  • rstrip(): 去除字符串尾部的指定字符。

    str_example = "hello!!!"
    print(str_example.rstrip("!"))  # 输出: 'hello'
    
  • replace(old, new): 将字符串中所有出现的 old 子串替换为 new 子串。

    str_example = "香蕉苹果"
    print(str_example.replace("苹果", "梨子"))  # 输出: '香蕉梨子'
    
  • split(sep): 根据分隔符 sep 将字符串分割成列表。

    str_example = "苹果 香蕉 橘子"
    print(str_example.split(" "))  # 输出: ['苹果', '香蕉', '橘子']
    
  • capitalize(): 将字符串的第一个字符转换为大写。

  • center(width, fillchar): 将字符串居中,并使用 fillchar 填充两侧至指定宽度 width

  • count(sub): 统计子串 sub 在字符串中出现的次数。

    str_example = "abracadabra"
    count_a = str_example.count("a")
    print(count_a)  # 输出: 5
    
  • endswith(suffix): 检查字符串是否以 suffix 结尾,返回 TrueFalse

  • find(sub): 查找子串 sub 在字符串中首次出现的索引,如果未找到则返回 -1

    str_example = "我的名字是张三,他也是。"
    print(str_example.find("是"))  # 输出: 4
    print(str_example.find("李四"))  # 输出: -1
    

    注意: index() 方法与 find() 类似,但如果子串不存在,index() 会抛出异常 (ValueError),而 find() 返回 -1

  • isalnum(): 检查字符串是否只包含字母和数字字符,返回 TrueFalse

  • islower(): 检查字符串是否所有字符都是小写,返回 TrueFalse

  • isprintable(): 检查字符串是否所有字符都是可打印字符,返回 TrueFalse

  • isspace(): 检查字符串是否只包含空白字符,返回 TrueFalse

  • istitle(): 检查字符串是否是标题格式 (每个单词首字母大写),返回 TrueFalse

  • isupper(): 检查字符串是否所有字符都是大写,返回 TrueFalse

条件语句 (Conditional Statements)

  • if: 基本条件判断。
  • if-else: 条件成立和不成立时分别执行不同的代码块。
  • if-elif-else: 多条件判断。
  • 嵌套 if-elif-else: 条件语句内部再嵌套条件语句。

if 语句示例

iphone_price = 60000
budget = 20000
if budget < iphone_price:
    print("预算不足,再攒攒钱吧!") # 如果预算小于 iPhone 价格,则输出
else:
    print("可以买个 iPhone 啦!") # 否则输出

elif 语句示例

number = 0
if number < 0:
    print("数字是负数")
elif number == 0:
    print("数字是零")
else:
    print("数字是正数")

嵌套 if 语句示例

number = 18
if number < 0:
    print("数字是负数")
elif number > 0:
    if number <= 10:
        print("数字在 1 到 10 之间")
    elif number > 10 and number <= 20:
        print("数字在 11 到 20 之间")
    else:
        print("数字大于 20")
else:
    print("数字是零")
# 输出: 数字在 11 到 20 之间

条件运算符 (Conditional Operators)

条件运算符用于比较值。

  • 运算符: > (大于), < (小于), >= (大于等于), <= (小于等于), == (等于), != (不等于)
a = 18
print(a > 18)   # 输出: False
print(a <= 18)  # 输出: True
print(a == 18)  # 输出: True
print(a != 18)  # 输出: False

循环语句 (Loops)

for 循环 (For Loops)

for 循环用于遍历可迭代对象 (Iterable objects),例如字符串、列表、元组、集合、字典等。

  • 遍历字符串示例:

    name = "Martin"
    for char in name:
        print(char, end=", ") # end=", "  表示每次打印后以逗号和空格结尾,而不是默认的换行符
    # 输出: M, a, r, t, i, n,
    
  • 遍历列表示例:

    colors = ["红色", "绿色", "蓝色"]
    for color in colors:
        print(color)
        for letter in color:
            print(letter)
    
  • 使用 range() 函数生成数字序列:

    for k in range(1, 200): # range(start, stop) 生成从 start 到 stop-1 的整数序列
        print(k)  # 输出: 从 1 到 199 的数字
    
    for k in range(2, 12, 2): # range(start, stop, step)  step 为步长
        print(k)  # 输出: 2, 4, 6, 8, 10
    

while 循环 (While Loop)

while 循环在条件为 True 时重复执行代码块。

count = 5
while count > 0:
    print(count)
    count -= 1 # 每次循环 count 减 1
else:
    print("循环结束,进入 else 块") # 当 while 循环条件变为 False 时,会执行 else 块 (可选)

breakcontinue 语句 (Break & Continue)

  • break: 立即跳出循环,终止循环的执行。

  • continue: 跳过当前循环迭代的剩余代码,直接进入下一次迭代。

  • break 语句示例:

    for i in range(1, 101):
        print(i, end=" ")
        if i == 50:
            break # 当 i 等于 50 时,跳出循环
        else:
            print("继续循环")
    # 输出: 1 继续循环 2 继续循环 ... 49 继续循环 50
    
  • continue 语句示例:

    for i in range(12):
        if i == 10:
            print("跳过本次迭代")
            continue # 当 i 等于 10 时,跳过本次循环的剩余代码
        print("5 x", i, "=", 5 * i)
    # 输出: 5 x 0 = 0 ... 5 x 9 = 45 跳过本次迭代 5 x 11 = 55
    

函数 (Python Functions)

函数是一段组织好的、可重复使用的代码块,用于执行特定任务。函数可以提高代码的模块化和可重用性。

  • 内置函数 (Built-in Functions): Python 提供的预定义函数,例如 min(), max(), len(), sum(), type(), tuple(), list(), set(), print(), range(), dict() 等。

  • 用户自定义函数 (User-Defined Functions): 开发者根据需求定义的函数。

    用户自定义函数示例:

    def greet(first_name, last_name): # 定义函数 greet,接收两个参数
        print("你好,", first_name, last_name)
    
    greet("三", "张") # 调用函数,传入参数
    

函数参数 (Function Arguments)

  • 默认参数 (Default Arguments): 在定义函数时为参数设置默认值。

    def greet(first_name, middle_name="某某", last_name="先生"): # middle_name 和 last_name 设置了默认值
        print("你好,", first_name, middle_name, last_name)
    
    greet("李")  # 输出: 你好, 李 某某 先生 (middle_name 和 last_name 使用默认值)
    
  • 关键字参数 (Keyword Arguments): 在调用函数时使用 参数名=值 的形式传递参数,可以不按参数顺序传递。

    def greet(first_name, middle_name, last_name):
        print("你好,", first_name, middle_name, last_name)
    
    greet(middle_name="彼得", last_name="韦斯克", first_name="杰登") # 使用关键字参数,顺序可以改变
    # 输出: 你好, 杰登 彼得 韦斯克
    
  • 必需参数 (Required Arguments): 在函数定义中没有默认值的参数,调用时必须提供值。

    # 调用 greet("张三", "先生") 会报错,因为缺少 last_name 参数
    # greet("张三", "先生")
    # TypeError: greet() missing 1 required positional argument: 'last_name'
    
  • 可变长度参数 (Variable-Length Arguments): 允许函数接收不定数量的参数。

    • *任意位置参数 (Arbitrary Arguments - args): 使用 * 前缀,将传入的位置参数打包成元组。

      def greet(*names): # *names 将接收到的所有位置参数打包成元组 names
          print("你好,", names[0], names[1], names[2])
      
      greet("张三", "李四", "王五")
      
    • **任意关键字参数 (Arbitrary Keyword Arguments - kwargs): 使用 ** 前缀,将传入的关键字参数打包成字典。

      def greet(**names): # **names 将接收到的所有关键字参数打包成字典 names
          print(names["first_name"], names["middle_name"], names["last_name"])
      
      greet(first_name="张", middle_name="小", last_name="三")
      
  • return 语句 (Return Statement): 函数可以使用 return 语句返回值给调用者。

    def add(a, b):
        return a + b # 返回 a + b 的结果
    
    result = add(5, 3)
    print(result)  # 输出: 8
    

列表 (List)

列表 (List) 是 Python 中常用的数据结构,它是一个 有序可变 的元素集合。列表中的元素可以是不同的数据类型。

  • 列表示例:

    list1 = [1, 2, 3, 4, 5, 6]
    list2 = ["红色", "绿色", "蓝色"]
    details = ["张三", 20, "计算机科学"]
    

检查元素是否在列表中:使用 in 关键字。

colors = ["红色", "绿色", "蓝色", "黄色"]

if "黄色" in colors:
    print("黄色在列表中")

列表索引范围 (Range of Index)

可以使用索引访问列表的不同部分:

  • 打印从指定索引到末尾的所有元素:

    animals = ["猫", "狗", "蝙蝠", "狮子", "老虎", "山羊", "牛"]
    print(animals[4:]) # 从索引 4 开始到末尾
    
  • 打印从开始到指定索引之前的所有元素:

    print(animals[:6]) # 从开始到索引 6 之前
    
  • 打印间隔取值的元素 (步长为 2):

    print(animals[::2]) # 从开始到末尾,步长为 2
    
  • 在指定范围内,每隔 3 个元素取一个:

    print(animals[1:8:3]) # 从索引 1 到 8 之前,步长为 3
    

列表推导式 (List Comprehension)

列表推导式提供了一种简洁的方式来创建列表,可以从其他可迭代对象 (如列表、元组、字典、集合,甚至字符串) 创建新列表。

  • 示例 1: 筛选包含字母 "o" 的元素:

    names = ["Milo", "Sarah", "Bruno", "Anastasia", "Rose"]
    names_with_o = [item for item in names if "o" in item] # 列表推导式,筛选包含 "o" 的名字
    print(names_with_o)
    # 输出: ['Milo', 'Bruno', 'Rose']
    
  • 示例 2: 筛选长度大于 4 的元素:

    names_with_4_letters = [item for item in names if len(item) > 4] # 列表推导式,筛选长度大于 4 的名字
    print(names_with_4_letters)
    # 输出: ['Sarah', 'Bruno', 'Anastasia']
    

列表方法 (List Methods)

以下是一些常用的列表方法:

  1. list.sort(): 对列表进行升序排序 (默认)。

    colors = ["紫色", "靛蓝", "蓝色", "绿色"]
    colors.sort() # 升序排序
    print(colors) # 输出: ['蓝色', '绿色', '靛蓝', '紫色']
    

    降序排序:

    numbers = [4, 5, 1, 2, 3, 6, 7, 9, 8]
    numbers.sort(reverse=True) # 降序排序
    print(numbers)
    # 输出: [9, 8, 7, 6, 5, 4, 3, 2, 1]
    
  2. list.reverse(): 反转列表中的元素顺序。

    colors.reverse() # 反转列表
    print(colors)
    # 输出 (基于之前的排序结果): ['紫色', '靛蓝', '绿色', '蓝色']
    
  3. list.index(item): 返回列表中第一个匹配项 item 的索引。

    index_green = colors.index("绿色")
    print(index_green) # 输出: 2 (假设列表为 ['紫色', '靛蓝', '绿色', '蓝色'])
    
  4. list.count(item): 返回列表中 item 出现的次数。

    count_green = colors.count("绿色")
    print(count_green) # 输出: 1 (假设列表为 ['紫色', '靛蓝', '绿色', '蓝色'])
    
  5. list.copy(): 返回列表的浅拷贝。

    • 用于在不修改原始列表的情况下,对列表副本进行操作。
    copy_colors = colors.copy() # 创建 colors 列表的副本
    
  6. list.append(item): 将 item 添加到列表末尾。

    colors = ["紫色", "靛蓝"]
    colors.append("蓝色") # 添加 "蓝色" 到列表末尾
    print(colors)
    # 输出: ['紫色', '靛蓝', '蓝色']
    
  7. list.insert(index, item): 在指定索引 index 处插入 item

    colors = ["紫色", "靛蓝"]
    colors.insert(1, "绿色") # 在索引 1 处插入 "绿色"
    print(colors)
    # 输出: ['紫色', '绿色', '靛蓝']
    
  8. list.extend(iterable): 将另一个可迭代对象 iterable (如列表、元组) 中的元素添加到列表末尾。

    colors = ["紫", "靛", "蓝"]
    rainbow = ["绿", "黄", "橙", "红"]
    colors.extend(rainbow) # 将 rainbow 列表中的元素添加到 colors 列表末尾
    print(colors)
    # 输出: ['紫', '靛', '蓝', '绿', '黄', '橙', '红']
    
  9. 列表连接 (Concatenating Two Lists):

    color1 = ["红", "橙"]
    color2 = ["黄", "绿"]
    combined_list = color1 + color2 # 使用 + 运算符连接两个列表
    print(combined_list)
    # 输出: ['红', '橙', '黄', '绿']
    

元组 (Tuples)

  • 元组 (Tuple) 也是 有序 的数据集合,类似于列表,但元组是 不可变 的 (immutable),即创建后不能修改。

    tuple1 = (1, 2, 2, 3, 4, 5, 6)
    tuple2 = ("红色", "绿色", "蓝色")
    details = ("张三", 19, "计算机科学", 9.7)
    
    • 元组索引 (Tuple Indexes):

      • 元组中的每个元素都有自己的索引,索引从 0 开始。
      • country = ("中国", "意大利", "西班牙")
      • [0] [1] [2]
      • country[0] # -> "中国"
    • 检查元素是否在元组中使用 in 关键字:

country = ("中国", "意大利", "西班牙")
if "中国" in country:
   print("中国在元组中")

操作元组 (Manipulating Tuples)

  • 由于元组是不可变的,如果需要添加、删除或修改元组中的元素,需要先将元组转换为 列表 (list)

  • 元组转换为列表 (Tuple to List Conversion):

    countries = ("意大利", "中国", "英国")
    temp_list = list(countries) # 将元组转换为列表
    temp_list.append("德国") # 列表可以添加元素
    temp_list.pop(1) # 列表可以删除元素 (删除索引 1 的元素)
    temp_list[2] = "芬兰" # 列表可以修改元素
    countries = tuple(temp_list) # 将列表转换回元组
    print(countries)
    

注意: 可以直接连接两个元组,无需转换为列表。

countries1 = ("中国",) # 注意,只有一个元素的元组,元素后要加逗号
countries2 = ("英国",)
print(countries1 + countries2) # 元组连接
# 输出: ('中国', '英国')

元组方法 (Tuple Methods)

  1. count(item): 返回元组中 item 出现的次数。

    tup1 = (1, 2, 3, 3, 3)
    print(tup1.count(3))  # 输出: 3
    
  2. index(item): 返回元组中第一个匹配项 item 的索引。

    tup1 = (1, 2, 3, 4)
    print(tup1.index(3))  # 输出: 2
    

    注意:

    • tup1 = (1) 创建的是整型变量,而不是元组。要创建只有一个元素的元组,需要使用 tup2 = (1,)

      tup1 = (1)
      print(type(tup1))  # 输出: <class 'int'>
      
      tup2 = (1,)
      print(type(tup2))  # 输出: <class 'tuple'>
      

注意: 元组通常用于存储不应被修改的常量数据。

F-字符串 (F-Strings)

F-字符串 (Formatted String Literals) 提供了一种简洁的方式,在字符串字面值中嵌入表达式,使用花括号 {} 括起来。

name = "张三"
print(f"你好, {name}!") # 使用 f-字符串,直接在字符串中嵌入变量 name

Python 文档字符串 (Docstrings)

  • 文档字符串 (Docstrings) 是字符串字面值,出现在函数、方法、类或模块定义的正文之后。
  • 它们用于为代码提供文档说明。
def square(n):
    '''计算数字 n 的平方并返回。''' # 文档字符串,描述函数功能
    return n**2

print(square(5)) # 输出:25
print(square.__doc__) # 访问函数的文档字符串,输出:计算数字 n 的平方并返回。

Python 注释 (Comments) vs. 文档字符串 (Docstrings)

  • 注释 (Comments): 用于解释代码,给人类阅读。Python 解释器会忽略注释。类似于你暗恋的人忽略你!

  • 文档字符串 (Docstrings): 用于为函数、方法、类等编写文档。可以使用 __doc__ 属性访问文档字符串。

    print(square.__doc__) # 访问 square 函数的文档字符串
    

PEP 8 代码规范 (PEP 8)

  • PEP 8 是一份 Python 代码风格指南,提供了编写 Python 代码的最佳实践建议。
  • 旨在提高 Python 代码的可读性和一致性。
  • PEP: Python 增强提案 (Python Enhancement Proposal)

递归 (Recursion)

  • 递归 (Recursion) 是一种定义事物的方式,其中该事物在其自身定义中被引用。
  • 函数可以调用其他函数,也可以调用自身。
def factorial(num):
   if (num == 1 or num == 0):
      return 1 # 递归基例:当 num 为 1 或 0 时,阶乘为 1
   else:
      return (num * factorial(num-1)) # 递归调用:factorial(num) 调用 factorial(num-1)

阶乘计算过程示例 (以 5! 为例):

factorial(5)
= 5 * factorial(4)
= 5 * 4 * factorial(3)
= 5 * 4 * 3 * factorial(2)
= 5 * 4 * 3 * 2 * factorial(1)
= 5 * 4 * 3 * 2 * 1  # factorial(1) 返回 1,递归结束
= 120

集合 (Sets)

  • 集合 (Set) 是 无序唯一 的元素集合。
  • 集合中的元素用逗号分隔,花括号 {} 括起来。
  • 集合是 可变 的,但集合中的元素必须是 不可变 的数据类型 (例如,数字、字符串、元组)。
  • 集合 不包含重复元素
info = {"张三", 20, False, 5.9} # 集合示例
print(info) # 输出的顺序可能与定义的顺序不同,因为集合是无序的
  • 集合是无序的,因此不能使用索引访问元素。
  • 集合的应用场景:例如,在办公室检查哪些员工没有收到礼物,需要收集员工姓名,并去重。

集合操作: add, union, difference, intersection, update, intersection_update, symmetric_difference, discard (不报错), remove (报错)

集合的连接操作 (Joining operations in sets):

  1. union() update(): 并集

    • 返回两个集合中所有元素的并集。
    • update() 方法将另一个集合的元素添加到现有集合中。
    cities1 = {"东京", "马德里", "柏林", "德里"}
    cities2 = {"东京", "首尔", "喀布尔", "马德里"}
    cities3 = cities1.union(cities2) # 返回 cities1 和 cities2 的并集,创建新集合 cities3
    print(cities3) # 输出: {'东京', '马德里', '柏林', '德里', '首尔', '喀布尔'}
    cities1.update(cities2) # 将 cities2 的元素添加到 cities1 中,修改 cities1 本身
    print(cities1) # 输出: {'东京', '马德里', '柏林', '德里', '首尔', '喀布尔'}
    
  2. intersection() intersection_update(): 交集

    • 返回两个集合中共同的元素 (交集)。
    cities3 = cities1.intersection(cities2) # 返回 cities1 和 cities2 的交集,创建新集合 cities3
    print(cities3) # 输出: {"马德里", "东京"}
    cities1.intersection_update(cities2) # 将 cities1 更新为 cities1 和 cities2 的交集,修改 cities1 本身
    print(cities1) # 输出: {"马德里", "东京"}
    
  3. symmetric_difference() symmetric_difference_update(): 对称差集

    • 返回两个集合中不相同的元素 (对称差集)。
    cities1 = {"东京", "马德里", "柏林", "德里"}
    cities2 = {"东京", "首尔", "喀布尔", "马德里"}
    print(cities1.symmetric_difference(cities2)) # 返回 cities1 和 cities2 的对称差集,创建新集合
    # 输出: {"首尔", "喀布尔", "柏林", "德里"}
    
  4. difference() difference_update(): 差集

    • 返回集合间的差集。例如 cities1.difference(cities2) 返回 cities1 中存在,但 cities2 中不存在的元素。

集合方法 (Set methods):

  1. isdisjoint(other_set):

    • 检查当前集合与 other_set 是否不相交 (没有共同元素)。
    • 如果不相交,返回 True,否则返回 False
    cities1 = {"东京", "马德里"}
    cities2 = {"首尔", "喀布尔"}
    is_disjoint = cities1.isdisjoint(cities2)
    print(is_disjoint) # 输出: True (因为 cities1 和 cities2 没有共同元素)
    
  2. issuperset(other_set):

    • 检查当前集合是否是 other_set 的超集 (包含 other_set 的所有元素)。
    • 如果是超集,返回 True,否则返回 False
    cities1 = {"东京", "马德里", "柏林"}
    cities2 = {"东京", "马德里"}
    is_superset = cities1.issuperset(cities2)
    print(is_superset) # 输出: True (因为 cities1 包含 cities2 的所有元素)
    
  3. issubset(other_set): 检查当前集合是否是 other_set 的子集。

  4. add(item): 向集合中添加元素 item

    cities1 = {"东京", "马德里"}
    cities1.add("赫尔辛基") # 向 cities1 集合添加 "赫尔辛基" 元素
    print(cities1)
    # 输出 (顺序可能不同): {'东京', '马德里', '赫尔辛基'}
    
  5. update(other_set): 将另一个集合 other_set 中的元素添加到当前集合。

    cities1 = {"东京", "马德里"}
    cities2 = {"柏林", "首尔"}
    cities1.update(cities2) # 将 cities2 的元素添加到 cities1
    print(cities1)
    # 输出 (顺序可能不同): {'东京', '马德里', '柏林', '首尔'}
    
  6. remove(item) / discard(item): 从集合中删除元素 item

    • remove(item): 如果元素 item 不存在,会抛出 KeyError 错误。

      cities1 = {"东京", "马德里"}
      cities1.remove("东京") # 删除 "东京" 元素
      print(cities1) # 输出: {'马德里'}
      
      # cities1.remove("北京") # 如果 "北京" 不在 cities1 中,会抛出 KeyError 错误
      
    • discard(item): 如果元素 item 不存在,不会报错,安全删除。

      cities2 = {"东京", "首尔"}
      cities2.discard("东京") # 删除 "东京" 元素
      print(cities2) # 输出: {'首尔'}
      
      cities2.discard("北京") # 如果 "北京" 不在 cities2 中,不会报错
      
  7. pop(): 随机删除并返回集合中的一个元素。由于集合是无序的,所以删除的是“随机”的元素。

  8. del set_name: 删除整个集合。

  9. clear(): 清空集合中的所有元素,集合变为空集。

字典 (Python Dictionaries)

  • 字典 (Dictionary) 是 Python 中的 有序 (Python 3.7+ 版本开始有序) 键值对集合。
  • 字典用于存储 键 (Key) - 值 (Value) 对,键必须是 唯一不可变 的数据类型 (例如,字符串、数字、元组),值可以是任意数据类型。
  • 字典中的键值对用逗号分隔,花括号 {} 括起来。
info = {"姓名": "张三", "年龄": 19, "语言": "Python"} # 字典示例
  1. 访问单个值 (Accessing single values):

    name = info['姓名'] # 使用键 '姓名' 访问对应的值
    print(name) # 输出: '张三'
    
    language = info.get('语言') # 使用 get() 方法,更安全,如果键不存在返回 None 或默认值
    print(language) # 输出: 'Python'
    
  2. 访问多个值和键 (Access multiple values and keys):

    values = info.values() # 获取字典中所有值的视图对象
    print(values) # 输出: dict_values(['张三', 19, 'Python'])
    
    keys = info.keys() # 获取字典中所有键的视图对象
    print(keys) # 输出: dict_keys(['姓名', '年龄', '语言'])
    
  3. 访问键值对 (Access key-value pairs): 使用 items() 方法遍历键值对。

    for key, value in info.items(): # items() 方法返回键值对的视图对象
        print(f"{key}: {value}") # 使用 f-字符串格式化输出
    # 输出:
    # 姓名: 张三
    # 年龄: 19
    # 语言: Python
    

字典方法 (Dictionary methods):

ep1 = {122: 45, 123: 89, 567: 69, 670: 69}
ep2 = {222: 67, 566: 90}
ep1.update(ep2) # 将 ep2 中的键值对添加到 ep1 中,如果键已存在则更新值
print(ep1) # 输出: {122: 45, 123: 89, 567: 69, 670: 69, 222: 67, 566: 90}
ep1.clear() # 清空字典 ep1
print(ep1) # 输出: {}
ep1 = {122: 45, 123: 89}
removed_value = ep1.pop(122) # 删除键为 122 的键值对,并返回对应的值 45
print(ep1) # 输出: {123: 89}
print(removed_value) # 输出: 45
ep1 = {122: 45, 123: 89}
removed_item = ep1.popitem() # 删除并返回字典的最后一个键值对 (Python 3.7+ 版本中字典是有序的,删除的是最后添加的)
print(ep1) # 输出: {122: 45} (假设 123: 89 是最后添加的)
print(removed_item) # 输出: (123, 89)
ep1 = {122: 45, 123: 89}
del ep1[122] # 删除键为 122 的键值对
print(ep1) # 输出: {123: 89}
  1. update(other_dict): 更新字典,将 other_dict 中的键值对添加到当前字典。如果键已存在,则更新值;如果键不存在,则添加新的键值对。

    info = {'年龄': 19}
    info.update({'年龄': 20}) # 更新键 '年龄' 的值,19 -> 20
    print(info) # 输出: {'年龄': 20}
    info.update({'出生年份': 2003}) # 添加新的键值对 '出生年份': 2003
    print(info) # 输出: {'年龄': 20, '出生年份': 2003}
    
  2. clear(): 清空字典中的所有键值对,使字典变为空字典。

  3. pop(key): 删除指定键 key 的键值对,并返回对应的值。如果键不存在,会抛出 KeyError 错误。

  4. popitem(): 删除并返回字典的最后一个键值对 (Python 3.7+ 版本中字典是有序的,删除的是最后添加的)。

  5. del dict[key]: 删除指定键 key 的键值对。也可以使用 del dict_name 删除整个字典。

    注意: del dict[key]pop(key) 的区别: pop(key) 会返回被删除的值!

for...else 循环 (For loop with else)

for x in range(5):
   print(f"{x} 数字")
else:
   print("for 循环结束,进入 else 块") # for 循环正常结束后,执行 else 块

x = 0
while x < 7:
   print(x)
   x += 1
else:
   print("while 循环结束,进入 else 块") # while 循环条件变为 False 后,执行 else 块
  • else 块出现在循环体之后。
  • else 块在循环正常结束 (即,不是通过 break 语句跳出循环) 后执行。
  • 程序只有在循环的所有迭代都完成后才会退出循环并执行 else 块。

异常处理 (Exception Handling)

try:
   num = int(input("请输入一个整数: "))
   a = [6, 3]
   print(a[num]) # 尝试访问列表 a 的索引 num 的元素
except IndexError:
   print("索引错误: 列表索引超出范围") # 捕获 IndexError 异常
except ValueError:
    print("值错误:输入的不是整数") # 捕获 ValueError 异常
a_str = input("请输入一个数字: ")
print(f"{a_str} 的乘法表是:")
try:
   a = int(a_str)
   for i in range(1, 11):
       print(f"{a} x {i} = {a * i}")
except ValueError:
   print("无效输入:请输入有效的数字") # 捕获 ValueError 异常,处理非数字输入的情况
  • 异常处理是一种处理程序运行时错误 (异常) 的机制。
  • 避免程序因错误而崩溃。

finally 子句 (Finally clause)

finally 子句用于定义无论是否发生异常都 始终 执行的代码块 (例如,关闭文件、关闭数据库连接、程序结束时输出提示信息)。

使用 finally 示例:

try:
    result = 10 / 0 # 可能会抛出 ZeroDivisionError 异常
except ZeroDivisionError:
    print("不能除以零。") # 捕获 ZeroDivisionError 异常并处理
finally:
    print("finally 块总是会被执行。") # finally 块中的代码始终会被执行

在这个例子中:

  • 如果发生异常 (例如,除以零),except 块会处理异常。
  • 无论是否发生异常,finally 块都会被执行。这确保了清理代码或重要的最后步骤始终能够执行。

不使用 finally 的情况:

try:
    result = 10 / 0
except ZeroDivisionError:
    print("不能除以零。")
# 没有 `finally` 块,如果发生异常,这行代码可能不会执行
print("如果发生异常,这行代码可能不会执行。")

在这种情况下,try-except 块之后的代码 (print("如果发生异常,这行代码可能不会执行。")) 只有在 没有 异常抛出时才会执行。如果捕获到异常,except 块之后的代码不会被执行。

自定义异常 (Custom Errors)

a = int(input("请输入 5 到 9 之间的值: "))
if a < 5 or a > 9:
   raise ValueError("值应该在 5 到 9 之间 :)") # 使用 raise 关键字抛出自定义 ValueError 异常
  • 使用 raise 关键字创建和抛出自定义异常。
  • 当需要在特定异常发生时执行特定操作时很有用 (例如,发送错误报告给管理员、调用 API 等)。

简写 if-else 语句 (Short-hand if-else):

a = 330000
b = 3303

print("A") if a > b else print("=") if a == b else print("B") # 简写 if-elif-else 结构

c = 9 if a > b else 0 # 基于条件赋值
result = value_if_true if condition else value_if_false # 通用简写 if-else 结构
  • 用于简单的 if-else 语句,特别是在需要根据条件为变量赋值时。

enumerate() 函数 (Enumerate)

marks = [12, 56, 32, 98, 12, 45, 1, 4]
index = 0
for mark in marks:
   print(mark)
   if index == 3:
      print("真棒!")
   index += 1
for index, mark in enumerate(marks, start=1): # enumerate() 函数同时返回索引和值,start=1 指定索引起始值
   print(index, mark)
   if index == 3:
      print("真棒!")
  • enumerate() 函数允许在遍历序列 (列表、元组、字符串) 时,同时获取元素的索引和值。
  • 当需要同时操作索引和值时非常有用。
fruits = ['苹果', '香蕉', '芒果']
for index, fruit in enumerate(fruits, start=1): # 索引从 1 开始
   print(index, fruit)
# 输出:
1 苹果
2 香蕉
3 芒果

虚拟环境 (Virtual Environment)

  • 虚拟环境 (Virtual Environment) 用于隔离 Python 项目的依赖环境,以便在同一台机器上开发多个项目,避免不同项目之间包版本冲突。

  • 例如:项目 1 使用 OpenCV 版本 4.9.0,而另一个项目使用 OpenCV 版本 4.5。

  • 常用命令:

    python -m venv env # 创建名为 env 的虚拟环境
    env/scripts/activate # (Windows) 激活虚拟环境
    source env/bin/activate # (macOS/Linux) 激活虚拟环境
    pip freeze > requirements.txt # 将当前环境的包列表导出到 requirements.txt 文件
    pip install -r requirements.txt # 从 requirements.txt 文件安装包
    deactivate # 退出虚拟环境
    
  • 方便在云端或新机器上快速部署项目环境。

  • 检查包版本示例:

    import pandas as pd
    print(pd.__version__)
    

import 语句的工作原理 (How Import Works)

  • math 模块导入 sqrt 函数并重命名为 s:

    from math import sqrt as s # 从 math 模块导入 sqrt 函数并重命名为 s
    import math as math_builtin # 导入 math 模块并重命名为 math_builtin
    result = math_builtin.pi * s(9) # 使用导入的模块和函数
    print(result)
    
  • my_module.py (自定义模块文件):

    def welcome():
       print("欢迎!")
    
    module_variable = "模块变量"
    
  • main.py (主程序文件):

    from my_module import welcome # 从 my_module 模块导入 welcome 函数
    
    welcome() # 调用 my_module 模块中的 welcome 函数
    
  • import 语句用于将模块 (module) 中的代码加载到当前脚本中,允许使用模块中定义的函数、类和变量。

  • 导入 math 模块的所有内容:

    from math import * # 导入 math 模块的所有内容 (不推荐,可能导致命名空间污染)
    
  • 重命名导入的模块:

    import math as m # 将 math 模块重命名为 m
    
  • 列出模块的方法和属性:

    import math
    print(dir(math)) # 使用 dir() 函数查看模块的属性和方法列表
    

if __name__ == "__main__": 语句 (if name == "main")

  • if __name__ == "__main__": 是 Python 脚本中常见的模式,用于判断脚本是被直接运行还是作为模块被导入到其他脚本中。
def main():
   print("脚本正在直接运行")

if __name__ == "__main__": # 当脚本被直接运行时,__name__ 的值为 "__main__"
   main() # 直接运行时执行 main() 函数
  • 这种模式允许代码既可以作为脚本直接运行,也可以作为模块被导入到其他脚本中使用,而不会在导入时执行脚本中的代码。
  • 有助于组织代码,区分可以直接运行的代码和作为模块导入的代码。

好的,我们继续深入 Python 编程的学习之旅。


OS 模块 (OS Module)

import os

if not os.path.exists("data"): # 检查 "data" 目录是否存在
    os.mkdir("data") # 如果不存在,则创建 "data" 目录

for i in range(0, 100):
    os.makedirs(f"data/Day{i+1}", exist_ok=True) # 创建 "data/Day1", "data/Day2", ... "data/Day100" 等目录,exist_ok=True 表示目录已存在时不会报错
  • os 模块是 Python 内置的操作系统接口模块,提供了许多与操作系统交互的函数。
  • 可以进行文件和目录操作,执行系统命令等。
try:
    f = os.open("file.txt", os.O_RDONLY) # 以只读模式打开文件 "file.txt"
    g = os.open("gfile.txt", os.O_WRONLY | os.O_CREAT) # 以只写模式打开文件 "gfile.txt",如果文件不存在则创建
    contents = os.read(f, 1024) # 从文件描述符 f 读取最多 1024 字节的内容

    os.write(g, b"你好,世界!") # 向文件描述符 g 写入字节字符串
    os.close(f) # 关闭文件描述符 f
    os.close(g) # 关闭文件描述符 g
except FileNotFoundError:
    print("文件未找到")
except Exception as e:
    print(f"发生错误: {e}")
  • os.listdir(path) -> 获取指定目录 path 下的文件和目录列表。

    files = os.listdir(".") # 获取当前目录下的文件和目录列表
    print(files)
    
  • os.mkdir(path) -> 创建一个目录 path

  • os.makedirs(path, exist_ok=False) -> 递归创建目录 path,可以创建多级目录。exist_ok=True 表示如果目录已存在,不会抛出异常。

  • 运行系统命令:

    os.system("date") # 在终端中执行 "date" 命令 (显示当前日期和时间)
    

局部变量与全局变量 (Local & Global Variable)

x = 10 # 全局变量 x

def my_function():
    global x # 声明在函数内部使用的是全局变量 x
    x = 5  # 修改全局变量 x 的值
    y = 5  # 局部变量 y,只在函数内部有效

my_function()
print(x)  # 输出: 5 (全局变量 x 的值被函数修改)
# print(y)  # 报错: NameError: name 'y' is not defined (局部变量 y 在函数外部不可访问)

注意: 尽量避免在函数内部直接修改全局变量,容易导致代码维护性降低。

文件输入/输出 (File I/O)

try:
    f = open('myfile.txt', 'r', encoding='utf-8')  # 'r' -> 读取模式,encoding='utf-8' 指定编码
    print(f) # 打印文件对象信息
    text = f.read() # 读取文件所有内容
    print(text) # 打印文件内容
except FileNotFoundError:
    print("文件未找到")
finally:
    if 'f' in locals() and f and not f.closed: # 检查文件对象 f 是否已成功打开并存在
        f.close() # 确保文件被关闭

文件写入 (Writing to a File)

try:
    f = open('myfile.txt', 'a', encoding='utf-8') # 'a' -> 追加模式,encoding='utf-8' 指定编码
    f.write('你好,世界!\n') # 写入字符串到文件末尾,\n 表示换行
except Exception as e:
    print(f"写入文件时发生错误: {e}")
finally:
    if 'f' in locals() and f and not f.closed:
        f.close()

# 使用 with 语句,自动管理文件关闭
with open('myfile.txt', 'a', encoding='utf-8') as f:
    f.write("这是使用 with 语句写入的内容。\n") # 文件操作结束后,文件会自动关闭

文件模式 (Modes):

  1. 读取 (r): 打开文件用于读取,如果文件不存在则抛出 FileNotFoundError 异常。
  2. 写入 (w): 打开文件用于写入,如果文件不存在则创建新文件;如果文件已存在,则清空文件内容。
  3. 追加 (a): 打开文件用于追加写入,如果文件不存在则创建新文件;如果文件已存在,则在文件末尾追加内容。
  4. 创建 (x): 创建新文件,如果文件已存在则抛出 FileExistsError 异常。
  5. 文本模式 (t): 默认模式,处理文本文件,例如读取和写入字符串。
  6. 二进制模式 (b): 用于处理二进制文件,例如图片、PDF 等。

注意: 使用 with open(...) as f: 语句可以确保文件在使用完毕后自动关闭,即使发生异常也能保证文件资源被释放。

Lambda 函数 (Lambda Functions)

def apply_func(func, value):  # func 参数接收一个函数
    return 6 + func(value)

double = lambda x: x * 2 # 定义 lambda 函数 double,返回输入 x 的两倍
cube = lambda x: x ** 3 # 定义 lambda 函数 cube,返回输入 x 的立方
average = lambda x, y, z: (x + y + z) / 3 # 定义 lambda 函数 average,计算三个数的平均值

print(double(5))  # 输出: 10
print(apply_func(lambda x: x ** 2, 2))  # 输出: 10 (将 lambda 函数 x**2 作为参数传递给 apply_func)
  • Lambda 函数 (匿名函数) 是一种简洁的函数定义方式,适用于简单的、功能单一的函数。
  • 语法: lambda arguments: expression (lambda 关键字,参数列表,冒号,返回值表达式)

示例:

def multiply(x, y):
    return x * y

# Lambda 函数等价形式
lambda x, y: x * y

lambda x, y: print(f"{x} * {y} = {x * y}") # Lambda 函数也可以包含 print 等语句

Map, Filter, Reduce

  • Map, Filter, Reduce 是 Python 中常用的高阶函数 (Higher-order functions),它们接收函数作为参数,并对序列 (或其他可迭代对象) 进行操作。

Map

  • map(function, iterable): 将函数 function 应用于可迭代对象 iterable 的每个元素,返回一个新的迭代器,包含每次函数调用的结果。
numbers = [1, 2, 3, 4, 5]
doubled_numbers = map(lambda x: x * 2, numbers) # 使用 map 函数和 lambda 函数将 numbers 列表中的每个元素乘以 2
print(list(doubled_numbers))  # 输出: [2, 4, 6, 8, 10] (将迭代器转换为列表输出)

Filter

  • filter(function, iterable): 使用函数 function (返回布尔值) 过滤可迭代对象 iterable 的元素,返回一个新的迭代器,包含 function 返回 True 的元素。
numbers = [1, 2, 3, 4, 5]
even_numbers = filter(lambda x: x % 2 == 0, numbers) # 使用 filter 函数和 lambda 函数筛选 numbers 列表中的偶数
print(list(even_numbers))  # 输出: [2, 4] (将迭代器转换为列表输出)

Reduce

  • reduce(function, iterable[, initializer]): 将函数 function (接收两个参数) 累积地应用于可迭代对象 iterable 的元素,将序列缩减为单个返回值。需要从 functools 模块导入。
from functools import reduce

numbers = [1, 2, 3, 4, 5]
sum_numbers = reduce(lambda x, y: x + y, numbers) # 使用 reduce 函数和 lambda 函数计算 numbers 列表中所有元素的和
print(sum_numbers)  # 输出: 15

is vs ==

a = None
b = None
print(a is b)  # True:  a 和 b 都指向 None 对象,是同一个对象 (身份相同)

print(a is None)  # True: a 是否是 None 对象 (身份比较)
print(a == b)  # True: a 和 b 的值相等 (值比较)
  • is== 都是 Python 中的比较运算符,但它们比较的内容不同。
  • is: 比较的是 身份 (identity),即判断两个变量是否引用同一个对象 (内存地址是否相同)。
  • ==: 比较的是 值 (value),即判断两个变量的值是否相等。

示例:

a = [1, 2, 3]
b = [1, 2, 3]
print(a == b)  # True: a 和 b 的值相等 (元素相同,顺序相同)
print(a is b)  # False: a 和 b 是不同的列表对象,它们在内存中位于不同的位置 (身份不同)

注意: 对于不可变对象 (如字符串、小整数),Python 可能会进行对象缓存或 intern 机制,导致值相同的不同变量可能指向同一个对象,此时 is== 的结果可能相同,但一般情况下,应该使用 == 进行值比较,使用 is 比较对象身份,例如判断变量是否为 None

面向对象编程 (OOPS)

OOP 的四大支柱 (4 Pillars of OOP)

  • 继承 (Inheritance): 属性共享,子类继承父类的属性和方法。
  • 封装 (Encapsulation): 数据隐藏,将数据和操作数据的方法封装在对象内部,对外隐藏实现细节。
  • 抽象 (Abstraction): 简化接口,只对外暴露必要的接口,隐藏复杂的内部实现。
  • 多态 (Polymorphism): 多种形态,允许不同类的对象对同一消息做出不同的响应。

OOP 四大支柱### 面向对象编程 (Object-Oriented Programming)

  • 类 (Class): 定义对象的蓝图或模板,描述对象的属性和行为。
    • 属性 (Properties): 对象的数据或状态 (成员变量)。
    • 方法 (Methods): 对象可以执行的操作 (成员函数)。
  • 对象 (Object): 类的实例,包含类定义的属性和方法。每个对象都有自己的数据和方法。

类与对象## 封装 (Encapsulation)

  • 封装 (Encapsulation) 的目的是隐藏对象的内部状态,并通过对象的方法来访问和修改状态。
  • 保护对象的数据,防止外部代码意外修改对象的状态。

访问修饰符 (Access Specifiers)

Python 中通过命名约定来实现访问控制,没有像 Java 或 C++ 那样的强制访问修饰符。

  • 公共成员 (Public Members): 默认情况下,类的所有成员都是公共的,可以在类外部访问。
  • 受保护成员 (Protected Members): 以下划线 _ 开头的成员,表示受保护的,建议在类内部和子类中使用,但不强制限制外部访问。
  • 私有成员 (Private Members): 以双下划线 __ 开头的成员,Python 会进行名称修饰 (name mangling),使其更难从外部直接访问,但并非完全无法访问。

抽象 (Abstraction)

抽象 (Abstraction) 的目的是隐藏功能的内部实现细节,只对外暴露必要的接口。用户只需要知道如何使用接口,而不需要了解接口内部的复杂实现。

多态 (Polymorphism)

  • 多态 (Polymorphism) 允许不同类的对象对同一消息 (方法调用) 做出不同的响应。
  • 提高代码的灵活性和可扩展性。
class Animal:
    def speak(self):
        pass # 父类 Animal 的 speak 方法默认不做任何事情

class Dog(Animal):
    def speak(self):
        return "汪汪!" # 子类 Dog 重写 speak 方法,返回 "汪汪!"

class Cat(Animal):
    def speak(self):
        return "喵喵!" # 子类 Cat 重写 speak 方法,返回 "喵喵!"

class Bird(Animal):
    def speak(self):
        return "啾啾!" # 子类 Bird 重写 speak 方法,返回 "啾啾!"

# 演示多态性的函数
def make_animal_speak(animal):
    print(animal.speak()) # 调用传入对象的 speak 方法,根据对象类型的不同,执行不同的 speak 方法

# 创建不同动物类的实例
dog = Dog()
cat = Cat()
bird = Bird()

# 演示多态性
make_animal_speak(dog)  # 输出: 汪汪! (调用 Dog 类的 speak 方法)
make_animal_speak(cat)  # 输出: 喵喵! (调用 Cat 类的 speak 方法)
make_animal_speak(bird) # 输出: 啾啾! (调用 Bird 类的 speak 方法)

类 (Classes)

类 (Class) 是创建对象的蓝图或模板,定义了对象的属性 (数据) 和方法 (行为)。

class Details:
    name = "张三" # 类属性 name
    age = 20 # 类属性 age

对象:类的实例 (Object: Instance of Class)

obj1 = Details() # 创建 Details 类的对象 obj1
print(obj1.name)  # 输出: 张三 (访问对象 obj1 的 name 属性)
print(obj1.age)  # 输出: 20 (访问对象 obj1 的 age 属性)

self 参数 (Self Parameter)

  • self 参数在类的方法定义中是必需的,它指向类的当前实例 (对象) 本身。
  • 通过 self 可以访问对象的属性和调用对象的方法。
class Details:
    name = "张三"
    age = 20

    def describe_person(self): # 类方法 describe_person,self 参数指向类的实例
        print("我的名字是", self.name, ",今年", self.age, "岁。") # 通过 self 访问对象的 name 和 age 属性

obj1 = Details() # 创建 Details 类的对象 obj1
obj1.describe_person() # 调用对象 obj1 的 describe_person 方法

示例 (Example)

class Person:
    name = "默认姓名" # 类属性 name
    occupation = "程序员" # 类属性 occupation
    net_worth = 10 # 类属性 net_worth

    def info(self): # 类方法 info
        print(f"{self.name} 是一名 {self.occupation}") # 使用 f-字符串格式化输出

a = Person() # 创建 Person 类的对象 a
b = Person() # 创建 Person 类的对象 b
c = Person() # 创建 Person 类的对象 c
a.name = "李四" # 修改对象 a 的 name 属性
a.occupation = "会计" # 修改对象 a 的 occupation 属性
b.name = "王五" # 修改对象 b 的 name 属性
b.occupation = "人力资源" # 修改对象 b 的 occupation 属性
a.info()  # 输出: 李四 是一名 会计 (调用对象 a 的 info 方法)
b.info()  # 输出: 王五 是一名 人力资源 (调用对象 b 的 info 方法)
c.info()  # 输出: 默认姓名 是一名 程序员 (调用对象 c 的 info 方法,使用默认属性值)

构造函数 (__init__)

  • 构造函数 (__init__ 方法) 是类中的特殊方法,用于创建和初始化类的对象。
  • 当创建类的对象时,构造函数会自动被调用。
  • 主要作用是为对象的属性赋初始值。
def __init__(self):
    # __init__ -> Python 中的保留函数名,用于定义构造函数
    pass # pass 语句表示空代码块,什么也不做
  1. 参数化构造函数 (Parameterized Constructors):

    • 构造函数接收除了 self 之外的其他参数。
    • 这些参数用于在创建对象时为对象的属性赋值。
    class Details:
        def __init__(self, animal_name, animal_group): # 构造函数接收 animal_name 和 animal_group 参数
            self.animal = animal_name # 将 animal_name 赋值给对象的 animal 属性
            self.group = animal_group # 将 animal_group 赋值给对象的 group 属性
    
    obj1 = Details("狮子", "食肉动物") # 创建 Details 类的对象 obj1,并传入参数 "狮子" 和 "食肉动物"
    print(obj1.animal, "属于", obj1.group) # 输出: 狮子 属于 食肉动物 (访问对象 obj1 的 animal 和 group 属性)
    
  2. 默认构造函数 (Default Constructors):

    • 构造函数只接收 self 参数,不接收其他参数。
    • 如果类中没有显式定义构造函数,Python 会自动提供一个默认的构造函数 (不执行任何操作)。
    class Details:
        def __init__(self): # 默认构造函数,只接收 self 参数
            print("动物:狮子,食肉动物")
    
    obj1 = Details() # 创建 Details 类的对象 obj1,调用默认构造函数
    # 输出: 动物:狮子,食肉动物 (构造函数中的 print 语句被执行)
    

装饰器 (Decorators)

  • Python 装饰器 (Decorators) 是一种强大的工具,用于修改函数或方法的行为,而无需修改其源代码。

  • 装饰器本质上是一个函数,它接收一个函数作为参数,并返回一个新的函数,新函数通常会在原函数的基础上增加一些功能。

  • 新返回的函数被称为“被装饰”的函数。

    @decorator_function # 使用 @ 符号应用装饰器 decorator_function 到 my_function 函数
    def my_function():
        pass
    
    def smart_divide(func): # 装饰器函数 smart_divide,接收一个函数 func 作为参数
        def inner(a, b): # 内部函数 inner,接收参数 a 和 b
            print("我要计算", a, "除以", b) # 打印提示信息
            if b == 0:
                print("不能除以零") # 如果除数为 0,打印错误信息
                return # 提前返回,不执行原函数
            return func(a, b) # 调用原函数 func 并返回结果
        return inner # 返回内部函数 inner
    
    @smart_divide # 使用 @smart_divide 装饰 divide 函数
    def divide(a, b): # 被装饰的函数 divide
        print(a / b) # 执行除法运算并打印结果
    
    divide(2, 5) # 调用被装饰的 divide 函数
    # 输出:
    # 我要计算 2 除以 5
    # 0.4
    divide(2, 0) # 调用被装饰的 divide 函数,除数为 0
    # 输出:
    # 我要计算 2 除以 0
    # 不能除以零
    

Getter 方法 (Getters)

  • Getter 方法 (取值器) 用于访问对象属性的值。
  • 通常使用 @property 装饰器定义,用于返回特定属性的值。
   class MyClass:
       def __init__(self, value):
           self._value = value # 使用 _value 存储实际的值,以区分 getter 方法名

       @property # 使用 @property 装饰器将 value 方法定义为属性 getter
       def value(self): # 定义 getter 方法 value,方法名即属性名
           return self._value # 返回 _value 的值

   obj = MyClass(10) # 创建 MyClass 类的对象 obj,初始化 _value 为 10
   print(obj.value)  # 输出: 10 (访问 value 属性,实际调用 getter 方法)

注意: Getter 方法不接收参数,并且不能通过 getter 方法直接设置属性的值。

Setter 方法 (Setters)

    class MyClass:
        def __init__(self, value):
            self._value = value

        @property
        def value(self): # getter 方法
            return self._value

        @value.setter # 使用 @value.setter 装饰器定义 value 属性的 setter 方法
        def value(self, new_val): # setter 方法名必须与 getter 方法名相同
            if new_val < 0:
                raise ValueError("值不能为负数") # 添加数据验证逻辑
            self._value = new_val # 设置 _value 的新值

    obj = MyClass(10) # 创建 MyClass 类的对象 obj,初始化 _value 为 10
    obj.value = 20 # 设置 value 属性的值,实际调用 setter 方法
    print(obj.value)  # 输出: 20 (访问 value 属性,实际调用 getter 方法)

    # obj.value = -5 # 会抛出 ValueError 异常,因为 setter 方法中添加了数据验证

注意: Setter 方法与 getter 方法配合使用,可以实现对属性的受控访问,用于数据封装和数据验证。Setter 方法通常用于在设置属性值之前进行一些验证或处理。

继承 (Inheritance)

  • 继承 (Inheritance) 是面向对象编程的重要特性,允许一个类 (子类或派生类) 继承另一个类 (父类或基类) 的属性和方法。
  • 子类可以复用父类的代码,并在此基础上进行扩展或修改。
  • 提高了代码的重用性和可维护性。

继承的类型 (Types of Inheritance):

  1. 单继承 (Single Inheritance)

  2. 多继承 (Multiple Inheritance)

  3. 多层继承 (Multilevel Inheritance)

  4. 层次继承 (Hierarchical Inheritance)

  5. 混合继承 (Hybrid Inheritance)

    继承类型

单继承 (Single Inheritance)

  • 子类只继承一个父类的属性和方法。
class Parent: # 父类 Parent
    def func1(self):
        print("父类方法")

class Child(Parent): # 子类 Child 继承自 Parent 类
    def func2(self):
        print("子类方法")

obj = Child() # 创建 Child 类的对象 obj
obj.func1()  # 输出: 父类方法 (子类对象可以调用父类的方法)
obj.func2()  # 输出: 子类方法 (子类对象可以调用子类自身的方法)

多继承 (Multiple Inheritance)

  • 子类继承多个父类的属性和方法。
class Mother: # 父类 Mother
    mother_name = ""

    def mother(self):
        print("母亲姓名:", self.mother_name)

class Father: # 父类 Father
    father_name = ""

    def father(self):
        print("父亲姓名:", self.father_name)

class Son(Mother, Father): # 子类 Son 多继承自 Mother 和 Father 类
    def parents(self):
        print("父亲姓名:", self.father_name)
        print("母亲姓名:", self.mother_name)

s1 = Son() # 创建 Son 类的对象 s1
s1.father_name = "老爸" # 设置父类 Father 的属性
s1.mother_name = "老妈" # 设置父类 Mother 的属性
s1.parents() # 调用子类 Son 的 parents 方法

多层继承 (Multilevel Inheritance)

  • 形成继承链,子类继承父类,父类又继承自更高级别的父类。
class Grandfather: # 祖父类
    def __init__(self, grandfather_name):
        self.grandfather_name = grandfather_name

class Father(Grandfather): # 父类 Father 继承自 Grandfather
    def __init__(self, father_name, grandfather_name):
        Grandfather.__init__(self, grandfather_name) # 调用父类 Grandfather 的构造函数
        self.father_name = father_name

class Son(Father): # 子类 Son 继承自 Father
    def __init__(self, father_name, grandfather_name, son_name):
        Father.__init__(self, father_name, grandfather_name) # 调用父类 Father 的构造函数
        self.son_name = son_name

    def print_name(self):
        print("祖父姓名:", self.grandfather_name)
        print("父亲姓名:", self.father_name)
        print("儿子姓名:", self.son_name)

s1 = Son("王子", "老王", "小王") # 创建 Son 类的对象 s1,并初始化各个父类的属性
s1.print_name() # 调用子类 Son 的 print_name 方法

层次继承 (Hierarchical Inheritance)

  • 多个子类继承自同一个父类。
class Parent: # 父类 Parent
    def func1(self):
        print("父类方法")

class Child1(Parent): # 子类 Child1 继承自 Parent
    def func2(self):
        print("子类 Child1 方法")

class Child2(Parent): # 子类 Child2 继承自 Parent
    def func3(self):
        print("子类 Child2 方法")

obj1 = Child1() # 创建 Child1 类的对象 obj1
obj2 = Child2() # 创建 Child2 类的对象 obj2
obj1.func1() # 输出: 父类方法 (Child1 对象可以调用父类方法)
obj2.func1() # 输出: 父类方法 (Child2 对象可以调用父类方法)
obj1.func2() # 输出: 子类 Child1 方法 (Child1 对象可以调用自身方法)
obj2.func3() # 输出: 子类 Child2 方法 (Child2 对象可以调用自身方法)

混合继承 (Hybrid Inheritance)

  • 混合使用多种继承类型,例如多层继承和多继承的组合。
class School: # 基类 School
    def func1(self):
        print("学校")

class Student1(School): # 子类 Student1 继承自 School
    def func2(self):
        print("学生 1")

class Student2(School): # 子类 Student2 继承自 School
    def func3(self):
        print("学生 2")

class Student3(Student1, School): # 子类 Student3 多继承自 Student1 和 School (注意继承顺序)
    def func4(self):
        print("学生 3 的方法")

obj1 = Student3() # 创建 Student3 类的对象 obj1
obj1.func1() # 输出: 学校 (Student3 对象可以调用 School 类的方法)
obj1.func2() # 输出: 学生 1 (Student3 对象可以调用 Student1 类的方法)
# obj1.func3() # Student3 类没有直接继承 Student2,无法调用 func3
obj1.func4() # 输出: 学生 3 的方法 (Student3 对象可以调用自身方法)

访问修饰符或访问控制 (Access Modifiers or Specifiers)

  • 访问修饰符 (Access Modifiers) 用于控制类成员 (属性和方法) 的访问权限,限制从类外部访问类成员。
  • Python 中主要通过命名约定实现访问控制。

类型 (Types):

  1. 公共访问修饰符 (Public Access Modifiers)
  2. 私有访问修饰符 (Private Access Modifiers)
  3. 受保护访问修饰符 (Protected Access Modifiers)
class Student:
    def __init__(self):
        self._name = "小明"  # 受保护访问修饰符,以下划线开头

    def _fun_name(self):  # 受保护方法,以下划线开头
        return "张老师"

class Subject(Student): # 子类 Subject 继承自 Student
    pass

obj = Student() # 创建 Student 类的对象 obj
obj1 = Subject() # 创建 Subject 类的对象 obj1
print(dir(obj))  # 使用 dir() 函数查看对象 obj 的属性和方法列表,可以看到 _funName, __init__, _name 等

# 通过 Student 类的对象访问受保护成员
print(obj._name) # 访问受保护属性 _name
print(obj._fun_name()) # 调用受保护方法 _fun_name()

# 通过 Subject 类的对象访问受保护成员 (子类可以访问父类的受保护成员)
print(obj1._name) # 访问继承自父类的受保护属性 _name
print(obj1._fun_name()) # 调用继承自父类的受保护方法 _fun_name()
  • 公共访问修饰符 (Public Access Modifier): 默认情况下,类的所有成员都是公共的,可以在类外部自由访问。

    • self.age = age -> age 属性是公共的。
  • 私有访问修饰符 (Private Access Modifier): 以双下划线 __ 开头的成员被视为私有成员,只能在类内部访问。

    • Python 中并没有严格的私有访问控制,以 __ 开头的成员会被名称修饰 (name mangling),使其更难从外部直接访问,但仍然可以通过特殊方式访问。
class Student:
    def __init__(self, age, name):
        self.__age = age  # 私有变量,以双下划线开头

    def __fun_name(self):  # 私有方法,以双下划线开头
        self.y = 34
        print(self.y)

class Subject(Student): # 子类 Subject 继承自 Student
    pass

obj = Student(21, "小明") # 创建 Student 类的对象 obj
obj1 = Subject() # 创建 Subject 类的对象 obj1

# 以下代码会抛出 AttributeError 错误,因为无法直接从外部访问私有成员
# print(obj.__age) # AttributeError: 'Student' object has no attribute '__age'
# obj.__fun_name() # AttributeError: 'Student' object has no attribute '__fun_name'
# print(obj1.__age) # AttributeError: 'Subject' object has no attribute '__age'
# obj1.__fun_name() # AttributeError: 'Subject' object has no attribute '__fun_name'

# 通过名称修饰后的名称访问私有变量 (不推荐,破坏了封装性)
print(obj._Student__age) # 可以通过 _ClassName__member_name 的方式访问私有成员

如何访问私有变量?: *obj._Student__name* (例如,obj._Student__age),但不推荐这样做,应该尽量避免直接访问私有成员,而是通过公共方法 (如 getter 和 setter) 来间接访问。

方法重写 (Method Overriding)

class Shape: # 父类 Shape
    def __init__(self, x, y):
        self.x = x
        self.y = y

    def area(self): # 父类方法 area,计算矩形面积
        return self.x * self.y

class Circle(Shape): # 子类 Circle 继承自 Shape
    def __init__(self, r):
        self.r = r
        super().__init__(r, r) # 调用父类 Shape 的构造函数,初始化 x 和 y 为 r

    def area(self): # 子类 Circle 重写父类 area 方法,计算圆形面积
        return 3.14 * super().area() # 调用父类 Shape 的 area 方法 (计算 r*r),再乘以 3.14

rect = Shape(3, 5) # 创建 Shape 类的对象 rect
print(rect.area()) # 输出矩形面积: 15 (调用父类 Shape 的 area 方法)

# 输出: 15

c = Circle(5) # 创建 Circle 类的对象 c
print(c.area())  # 输出圆形面积: 78.5 (调用子类 Circle 重写的 area 方法)
# 输出: 78.5
  • 方法重写 (Method Overriding) 发生在继承关系中,子类可以重写 (override) 父类中已有的方法,提供自己的实现。
  • 当创建子类的对象并调用被重写的方法时,实际执行的是子类中重写后的版本,而不是父类中的版本。

静态方法 (Static Methods)

静态方法 (Static Methods) 是属于类本身的方法,而不是类的实例 (对象)。

  • 使用 @staticmethod 装饰器定义。
  • 静态方法 不能访问 类的实例 (对象) 属性 (即不能使用 self 参数)。
  • 静态方法通常用于创建与类相关,但不依赖于特定实例的实用工具函数。
class Math:
   @staticmethod # 使用 @staticmethod 装饰器定义静态方法
   def add(a, b): # 静态方法 add,不接收 self 参数
      return a + b

result = Math.add(1, 2) # 直接通过类名 Math 调用静态方法 add
print(result)  # 输出: 3

类变量 vs 实例变量 (Class vs Instance Variables)

  • 类变量 (Class variables): 属于类本身的变量,被类的所有实例 (对象) 共享。

    • 示例: 用于存储所有实例共享的通用信息。
  • 实例变量 (Instance variables): 属于类的每个实例 (对象) 的变量,每个实例拥有独立的实例变量副本。

    • 示例: 用于存储每个实例的特定信息。

访问变量 (Accessing Variables):

  • 类变量: ClassName.variableself.__class__.variable (在实例方法中)
  • 实例变量: self.variable_name (在实例方法中)
class Animal:
   species = "哺乳动物"  # 类变量 species,所有 Animal 类的实例共享

   def __init__(self, name):
      self.name = name  # 实例变量 name,每个 Animal 类的实例拥有独立的 name 属性

a1 = Animal("狗") # 创建 Animal 类的对象 a1
a2 = Animal("猫") # 创建 Animal 类的对象 a2

print(a1.species)  # 输出: 哺乳动物 (访问对象 a1 的类变量 species)
print(a2.name)     # 输出: 猫 (访问对象 a2 的实例变量 name)

Animal.species = "动物" # 修改类变量 Animal.species
print(a1.species) # 输出: 动物 (类变量被修改后,所有实例访问到的类变量都改变了)
print(a2.species) # 输出: 动物

练习:清理文件夹内的杂乱文件 (重命名 PNG 文件) (Exercise: Clear Clutter Inside Folder)

重命名指定文件夹下所有 PNG 图片文件,按照序号顺序命名 (例如,1.png, 2.png 等)。

import os

folder_path = "clutter_folder" # 指定要清理的文件夹路径
files = os.listdir(folder_path) # 获取文件夹下所有文件和目录列表
i = 1 # 初始化序号计数器

for file in files:
    if file.endswith(".png"): # 检查文件名是否以 ".png" 结尾
        old_filepath = os.path.join(folder_path, file) # 构建旧文件完整路径
        new_filepath = os.path.join(folder_path, f"{i}.png") # 构建新文件完整路径
        os.rename(old_filepath, new_filepath) # 重命名文件
        i += 1 # 序号递增
print("PNG 文件重命名完成!")

练习:图书馆类 (Library Class Exercise)

创建一个 Library 类,包含两个实例变量:no_of_books (图书数量) 和 books (图书列表)。实现添加图书和打印图书信息的功能。

class Library:
   def __init__(self):
      self.no_of_books = 0 # 初始化图书数量为 0
      self.books = [] # 初始化图书列表为空列表

   def add_book(self, book_title): # 添加图书方法
      self.books.append(book_title) # 将图书标题添加到图书列表
      self.no_of_books = len(self.books) # 更新图书数量

   def show_info(self): # 显示图书馆信息方法
      print(f"图书馆藏书数量: {self.no_of_books}") # 打印图书数量
      if self.books: # 如果图书列表不为空
          print("馆藏图书列表:")
          for book in self.books: # 遍历图书列表
              print(f"- {book}") # 打印每本图书标题
      else:
          print("图书馆暂无藏书。")

l1 = Library() # 创建 Library 类的对象 l1
l1.add_book("哈利·波特") # 添加图书
l1.add_book("霍比特人") # 添加图书
l1.show_info() # 显示图书馆信息

# 输出:
# 图书馆藏书数量: 2
# 馆藏图书列表:
# - 哈利·波特
# - 霍比特人

Python 类方法 (Python Class Methods)

  • 类方法 (Class methods) 是绑定到类本身而不是类的实例 (对象) 的方法。
  • 类方法操作的是类本身,而不是特定的实例。
  • 使用 @classmethod 装饰器定义。
  • 常用于创建工厂方法或作为备用构造函数。
class Employee:
   company = "苹果公司" # 类变量 company

   def show(self): # 实例方法 show
      print(f"姓名: {self.name}, 公司: {self.company}")

   @classmethod # 使用 @classmethod 装饰器定义类方法
   def change_company(cls, new_company): # 类方法 change_company,cls 参数指向类本身
      cls.company = new_company # 修改类变量 company

e1 = Employee() # 创建 Employee 类的对象 e1
e1.name = "小明" # 设置实例属性 name
e1.show()  # 输出: 姓名: 小明, 公司: 苹果公司 (调用实例方法 show)
Employee.change_company("特斯拉") # 通过类名 Employee 调用类方法 change_company,修改类变量 company
e1.show()  # 输出: 姓名: 小明, 公司: 特斯拉 (再次调用实例方法 show,类变量 company 已被修改)

类方法作为备用构造函数 (Class Methods as Alternative Constructors)

class Employee:
   def __init__(self, name, salary): # 构造函数
      self.name = name
      self.salary = salary

   @classmethod # 使用 @classmethod 装饰器定义类方法
   def from_string(cls, employee_string): # 类方法 from_string,cls 参数指向类本身
      name, salary = employee_string.split("-") # 从字符串中解析姓名和工资
      return cls(name, int(salary)) # 使用 cls 调用类自身的构造函数,创建并返回类的新对象

e1 = Employee("小明", 1200) # 使用构造函数创建 Employee 对象
print(e1.name)     # 输出: 小明
print(e1.salary)   # 输出: 1200

e2 = Employee.from_string("李四-1500") # 使用类方法 from_string 创建 Employee 对象
print(e2.name)     # 输出: 李四
print(e2.salary)   # 输出: 1500

常用方法:dir(), __dict__, 和 help() (Useful Methods: dir(), __dict__, and help())

  • dir(object): 返回对象 object 的属性和方法列表 (包括特殊方法)。
  • object.__dict__: 返回对象 object 的属性字典 (仅包含实例属性)。
  • help(object): 显示对象 object 的帮助文档。
class Person:
   def __init__(self, name, age):
      self.name = name
      self.age = age

p = Person("小明", 30) # 创建 Person 类的对象 p
print(p.__dict__)   # 输出: {'name': '小明', 'age': 30} (查看对象 p 的实例属性字典)
print(dir(p))       # 输出: ['__class__', '__delattr__', ..., 'age', 'name'] (查看对象 p 的所有属性和方法)
help(Person)       # 查看 Person 类的帮助文档 (docstring)

super() 关键字 (Super Keyword)

  • super() 函数用于调用父类 (超类) 的方法。
  • 在类继承中,子类经常需要扩展父类的行为,super() 可以方便地调用父类的方法,并在其基础上添加新的逻辑。
class Employee: # 父类 Employee
   def __init__(self, name, employee_id): # 父类构造函数
      self.name = name
      self.id = employee_id

class Programmer(Employee): # 子类 Programmer 继承自 Employee
   def __init__(self, name, employee_id, language): # 子类构造函数
      super().__init__(name, employee_id) # 调用父类 Employee 的构造函数,初始化 name 和 id
      self.language = language # 初始化子类 Programmer 的属性 language

harry = Programmer("哈利", 23, "Python") # 创建 Programmer 类的对象 harry
print(harry.name)  # 输出: 哈利 (访问继承自父类的属性 name)
print(harry.language)  # 输出: Python (访问子类自身的属性 language)

Dunder (魔法) 方法 (Dunder (Magic) Methods)

  • Dunder 方法 (Double Under Methods,也称为魔法方法或特殊方法) 是 Python 中以双下划线 __ 开头和结尾的特殊方法。
  • Dunder 方法用于自定义类的行为,例如运算符重载、对象字符串表示、长度计算等。
  1. __init__(self, ...): 构造函数,在创建类的新实例时被调用 (初始化对象)。
  2. __str__(self) & __repr__(self): 将对象转换为字符串表示,__str__ 用于 str() 函数和 print() 函数,__repr__ 用于 repr() 函数和交互式环境输出。
  3. __len__(self): 返回对象的长度,用于 len() 函数。
  4. __call__(self, ...): 使对象可以像函数一样被调用。
class Employee:
   def __init__(self, name): # 构造函数
      self.name = name

   def __len__(self): # 定义 __len__ 方法,返回姓名长度
      return len(self.name)

   def __str__(self): # 定义 __str__ 方法,返回对象的友好的字符串表示
      return f"员工: {self.name}"

   def __repr__(self): # 定义 __repr__ 方法,返回对象的明确的字符串表示 (通常用于调试和开发)
      return f"Employee('{self.name}')"

   def __call__(self): # 定义 __call__ 方法,使对象可以像函数一样被调用
      print(f"你好,我是 {self.name}")

e = Employee("小明") # 创建 Employee 类的对象 e
print(len(e))      # 输出: 2 (调用 __len__ 方法,返回姓名长度)
print(str(e))      # 输出: 员工: 小明 (调用 __str__ 方法,返回友好的字符串表示)
print(repr(e))     # 输出: Employee('小明') (调用 __repr__ 方法,返回明确的字符串表示)
e()                # 输出: 你好,我是 小明 (将对象 e 作为函数调用,执行 __call__ 方法)

方法重写 (Method Overriding) (再次强调)

  • 方法重写 (Method Overriding) 允许子类重新定义父类的方法,以实现不同的行为。
  • 当调用子类对象的重写方法时,会执行子类中定义的版本。
class Shape: # 父类 Shape
   def __init__(self, x, y):
      self.x = x
      self.y = y

   def area(self): # 父类方法 area,计算矩形面积
      return self.x * self.y

class Circle(Shape): # 子类 Circle 继承自 Shape
   def __init__(self, r):
      super().__init__(r, r) # 调用父类 Shape 的构造函数

   def area(self): # 子类 Circle 重写父类 area 方法,计算圆形面积
      return 3.14 * super().area() # 调用父类 Shape 的 area 方法 (计算 r*r),再乘以 3.14

rect = Shape(3, 5) # 创建 Shape 类的对象 rect
print(rect.area())  # 输出: 15 (调用父类 Shape 的 area 方法)

circle = Circle(5) # 创建 Circle 类的对象 circle
print(circle.area())  # 输出: 78.5 (调用子类 Circle 重写的 area 方法)

运算符重载 (Operator Overriding)

  • 运算符重载 (Operator Overriding) 允许你自定义运算符 (如 +, -, *, == 等) 对于自定义类对象的操作行为。
  • 通过实现 dunder 方法 (如 __add__, __sub__, __mul__, __eq__ 等) 来重载运算符。
class Vector:
   def __init__(self, i, j, k):
      self.i = i
      self.j = j
      self.k = k

   def __add__(self, other): # 重载 + 运算符,定义向量加法
      return Vector(self.i + other.i, self.j + other.j, self.k + other.k) # 返回新的 Vector 对象,表示向量和

   def __str__(self): # 重载 str() 函数,定义向量的字符串表示
      return f"{self.i}i + {self.j}j + {self.k}k"

v1 = Vector(3, 5, 6) # 创建 Vector 对象 v1
v2 = Vector(1, 2, 9) # 创建 Vector 对象 v2
v3 = v1 + v2 # 使用 + 运算符进行向量加法,实际调用 __add__ 方法
print(v3)  # 输出: 4i + 7j + 15k (打印向量 v3 的字符串表示,实际调用 __str__ 方法)

注意: super() 关键字在继承中非常有用,可以扩展父类方法的行为,而不是完全替换它。在子类方法中,先使用 super() 调用父类的方法,然后再添加子类特有的逻辑,是一种良好的编程实践。


练习:合并 PDF 文件为一个 PDF 文件 (Exercise: Merge PDFs into a Single PDF)

from PyPDF2 import PdfMerger # 导入 PdfMerger 类
import os

merger = PdfMerger() # 创建 PdfMerger 对象
pdf_files = [file for file in os.listdir() if file.endswith(".pdf")] # 获取当前目录下所有 PDF 文件列表

for pdf in pdf_files: # 遍历 PDF 文件列表
    merger.append(pdf) # 将每个 PDF 文件添加到 PdfMerger 对象中

merger.write("Merged-pdf.pdf") # 将合并后的 PDF 内容写入到 "Merged-pdf.pdf" 文件
merger.close() # 关闭 PdfMerger 对象
print("PDF 文件合并完成!")

输出:

  • 将当前目录下所有 PDF 文件合并为一个名为 Merged-pdf.pdf 的 PDF 文件。

Time 模块 (Time Module):

  • time 模块提供了一组函数,用于处理时间相关的操作,如时间获取、时间格式化、时间转换等。

  • 常用函数 (Common Functions):

    • time.time(): 返回当前时间的时间戳 (timestamp),即从 Epoch (1970 年 1 月 1 日 00:00:00 UTC) 到现在的秒数。

      示例 (Example):

      import time
      
      start_time = time.time() # 获取当前时间戳
      print("当前时间戳:", start_time)
      
    • time.sleep(seconds): 使程序暂停执行指定的秒数 seconds

      示例 (Example):

      import time
      
      print("开始")
      time.sleep(2) # 程序暂停 2 秒
      print("2 秒后结束")
      
    • time.strftime(format[, t]): 根据指定的格式 format 将时间元组 t (可选,默认为当前时间) 格式化为字符串。

      示例 (Example):

      import time
      
      t = time.localtime() # 获取当前时间的本地时间元组
      formatted_time = time.strftime("%Y-%m-%d, %H:%M:%S", t) # 格式化时间为 "年-月-日, 时:分:秒" 字符串
      print(formatted_time)
      

命令行实用工具 (Command Line Utility):

  • 命令行程序 (Command-line programs) 对于自动化任务和提高开发工作效率至关重要。

基本示例 (Basic Example):

import argparse # 导入 argparse 模块

parser = argparse.ArgumentParser(description="这是一个示例命令行程序") # 创建 ArgumentParser 对象,并设置程序描述信息
parser.add_argument("arg1", help="第一个参数的描述") # 添加位置参数 arg1,并设置帮助信息
parser.add_argument("arg2", help="第二个参数的描述") # 添加位置参数 arg2,并设置帮助信息
args = parser.parse_args() # 解析命令行参数

print("参数 1:", args.arg1) # 访问解析后的参数 arg1
print("参数 2:", args.arg2) # 访问解析后的参数 arg2

文件下载器示例 (File Downloader Example):

import argparse
import requests
import shutil

def download_file(url, local_filename=None):
    if local_filename is None:
        local_filename = url.split('/')[-1] # 如果未指定本地文件名,则从 URL 中提取文件名
    try:
        response = requests.get(url, stream=True) # 发送 GET 请求,stream=True 表示使用流式下载
        response.raise_for_status() # 检查请求状态码,如果不是 200 OK 则抛出异常
        with open(local_filename, 'wb') as out_file: # 以二进制写入模式打开本地文件
            shutil.copyfileobj(response.raw, out_file) # 使用 shutil.copyfileobj 高效复制流数据到文件
        print(f"文件下载成功: {local_filename}")
    except requests.exceptions.RequestException as e: # 捕获 requests 模块的异常
        print(f"文件下载失败: {e}")
    finally:
        if 'response' in locals() and response:
            response.close() # 确保 response 连接被关闭

if __name__ == "__main__":
    parser = argparse.ArgumentParser(description="从 URL 下载文件") # 创建 ArgumentParser 对象
    parser.add_argument("url", help="文件 URL") # 添加位置参数 url,帮助信息为 "文件 URL"
    parser.add_argument("output", help="输出文件名") # 添加位置参数 output,帮助信息为 "输出文件名"
    args = parser.parse_args() # 解析命令行参数
    download_file(args.url, args.output) # 调用 download_file 函数下载文件
    # print("文件已下载:", args.output) # 打印下载完成信息,download_file 函数内部已打印成功信息

输出:

  • 从提供的 URL 下载文件,并保存为指定的输出文件名。

海象运算符 (:=):

  • 海象运算符 (Walrus Operator) := 是在 Python 3.8 中引入的新特性,允许在表达式内部进行赋值。

示例 (Example):

happy = False
print("之前:", happy)
print("之后:", happy := True) # 使用海象运算符 := 在 print 函数内部将 happy 赋值为 True 并打印赋值结果

foods = list()
while (food := input("你喜欢什么食物?(输入 'quit' 退出): ")) != "quit": # 使用海象运算符 := 将 input() 的返回值赋值给 food 变量,并在 while 条件中判断
    foods.append(food)

print("你喜欢的食物列表:", foods)

shutil 模块 (shutil Module):

  • shutil 模块是 Python 中用于高级文件操作的实用工具模块。

常用函数 (Functions):

  • shutil.copy(src, dst): 复制文件 srcdst (目标可以是文件或目录)。
  • shutil.copy2(src, dst): 复制文件 srcdst,并保留元数据 (如时间戳)。
  • shutil.copytree(src, dst): 递归复制整个目录树 srcdst
  • shutil.move(src, dst): 移动文件或目录 srcdst
  • shutil.rmtree(path): 递归删除目录树 path (慎用!)。

Beautiful Soup (bs4): Web Scraping (网页抓取)

  • Beautiful Soup 是一个 Python 库,用于从 HTML 和 XML 文件中提取数据,常用于网页抓取 (Web Scraping)。
import requests
from bs4 import BeautifulSoup

url = "https://www.example.com" # 目标网页 URL
try:
    r = requests.get(url) # 发送 GET 请求获取网页内容
    r.raise_for_status() # 检查请求状态码
    soup = BeautifulSoup(r.text, "html.parser") # 使用 BeautifulSoup 解析 HTML 内容,html.parser 是 HTML 解析器

    print("格式化后的 HTML:\n", soup.prettify()) # 打印格式化后的 HTML 代码 (美化输出)

    headings = soup.find_all("h1") # 查找所有 <h1> 标签
    if headings:
        print("\n网页标题 (h1 标签):")
        for heading in headings: # 遍历找到的 <h1> 标签
            print(heading.text.strip()) # 打印 <h1> 标签的文本内容,并去除首尾空白
    else:
        print("\n网页中没有 h1 标题。")

except requests.exceptions.RequestException as e:
    print(f"请求网页失败: {e}")
except Exception as e:
    print(f"解析网页内容失败: {e}")

输出:

  • 打印整个网页内容的结构化格式 (美化后的 HTML 代码) 和所有 <h1> 标题的文本内容。

生成器 (Generators):

  • 生成器 (Generators) 是一种特殊的函数,允许你逐个迭代生成值,而无需一次性将所有值都存储在内存中。
  • 生成器使用 yield 关键字产生值,而不是 return

示例 (Example):

def my_generator(n): # 定义生成器函数 my_generator
    for i in range(n):
        yield i # 使用 yield 关键字产生值 i

gen = my_generator(5) # 创建生成器对象 gen
print(next(gen))  # 输出: 0 (使用 next() 函数获取生成器的下一个值)
print(next(gen))  # 输出: 1
print(next(gen))  # 输出: 2

for j in gen: # 使用 for 循环迭代生成器,从上次 yield 的位置继续执行
    print(j)  # 输出: 3, 4 (for 循环会自动处理 StopIteration 异常,当生成器没有更多值时循环结束)

函数缓存 (@lru_cache):

  • 函数缓存 (Function Caching) 使用缓存 (Cache) 存储函数的计算结果,避免对相同输入重复计算,适用于计算开销较大的函数。
  • Python 中可以使用 functools.lru_cache 装饰器实现函数缓存。

示例 (Example):

from functools import lru_cache # 导入 lru_cache 装饰器
import time

@lru_cache(maxsize=None) # 使用 @lru_cache 装饰器,maxsize=None 表示缓存大小无限制
def fx(n): # 定义函数 fx,模拟耗时计算
    time.sleep(2)  # 模拟耗时操作,休眠 2 秒
    return n * 5

start_time = time.time()
print(fx(20))  # 第一次调用 fx(20),需要计算 2 秒
print(f"第一次调用 fx(20) 耗时: {time.time() - start_time:.2f} 秒")

start_time = time.time()
print(fx(2))   # 第一次调用 fx(2),需要计算 2 秒
print(f"第一次调用 fx(2) 耗时: {time.time() - start_time:.2f} 秒")

start_time = time.time()
print(fx(6))   # 第一次调用 fx(6),需要计算 2 秒
print(f"第一次调用 fx(6) 耗时: {time.time() - start_time:.2f} 秒")

# 下面的调用将直接从缓存中读取结果,无需重新计算,几乎是瞬间完成
start_time = time.time()
print(fx(20)) # 第二次调用 fx(20),从缓存中读取结果,无需计算
print(f"第二次调用 fx(20) 耗时: {time.time() - start_time:.2f} 秒")

start_time = time.time()
print(fx(2))  # 第二次调用 fx(2),从缓存中读取结果,无需计算
print(f"第二次调用 fx(2) 耗时: {time.time() - start_time:.2f} 秒")

start_time = time.time()
print(fx(6))  # 第二次调用 fx(6),从缓存中读取结果,无需计算
print(f"第二次调用 fx(6) 耗时: {time.time() - start_time:.2f} 秒")

正则表达式 (Regex):

  • 正则表达式 (Regular Expressions 或 Regex) 是一种强大的文本模式匹配工具,用于在字符串中查找、替换、验证特定模式的文本。
  • Python 中使用 re 模块处理正则表达式。

基本正则表达式 (Basic Regex):

import re

pattern = r"表达式"  # raw 字符串,定义正则表达式模式 (例如 r"hello")
text = "你好,世界!" # 要搜索的文本

match = re.search(pattern, text) # 使用 re.search() 函数在 text 中搜索 pattern 模式,返回 Match 对象或 None

if match:
    print("找到匹配项!") # 如果找到匹配项
else:
    print("未找到匹配项。") # 如果未找到匹配项

示例 (Example):

import re

text = "The cat is in the hat and a bat." # 文本字符串
pattern = r"[a-z]+at" # 正则表达式模式:匹配一个或多个小写字母 (a-z),后跟 "at"

matches = re.findall(pattern, text) # 使用 re.findall() 函数查找文本中所有匹配模式的子字符串,返回列表
print("匹配项:", matches)  # 输出: ['cat', 'hat', 'bat']

new_text = re.sub(pattern, "dog", text) # 使用 re.sub() 函数将文本中所有匹配 pattern 的子字符串替换为 "dog",返回替换后的新字符串
print("替换后的文本:", new_text)  # 输出: "The dog is in the dog and a dog."

提取邮箱地址 (Extracting Emails):

import re

text = "我的邮箱是 example@example.com,另一个邮箱是 test.user@domain.net。" # 包含邮箱地址的文本
pattern = r"\b[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}\b" # 匹配邮箱地址的正则表达式模式 (更精确的邮箱格式匹配)

matches = re.findall(pattern, text) # 查找所有匹配邮箱地址的子字符串
if matches:
    print("提取到的邮箱地址:")
    for email in matches: # 遍历找到的邮箱地址
        print(f"- {email}") # 打印每个邮箱地址
else:
    print("未找到邮箱地址。")

AsyncIO (异步 I/O)

  • asyncio 模块提供了异步 I/O 编程的支持,允许程序在等待 I/O 操作 (如网络请求、文件读写) 完成时,继续执行其他任务,提高程序的并发性和效率。
import asyncio
import requests

async def download_image(url, filename): # 定义异步函数 download_image,使用 async 关键字
    print(f"开始下载: {filename}")
    response = requests.get(url) # 发送同步 HTTP 请求 (requests 库是同步的)
    if response.status_code == 200:
        with open(filename, "wb") as f:
            f.write(response.content)
        print(f"下载完成: {filename}")
    else:
        print(f"下载失败: {filename}, 状态码: {response.status_code}")
    return filename # 返回文件名

async def main(): # 定义主异步函数 main
    image_urls = [
        ("https://www.easygifanimator.net/images/samples/video-to-gif-sample.gif", "image1.gif"),
        ("https://upload.wikimedia.org/wikipedia/commons/4/4d/Cat_November_2010-1a.jpg", "image2.jpg"),
        ("https://www.thoughtco.com/thmb/zQDr3lG080p4Z_MA8dtmSjhqQ94=/768x0/filters:no_upscale():max_bytes(150000):strip_icc()/close-up-of-cat-eyes-873370984-5c7ff4b4c929250001f6b383.jpg", "image3.jpg"),
    ]
    tasks = [] # 创建任务列表
    for url, filename in image_urls:
        task = asyncio.create_task(download_image(url, filename)) # 使用 asyncio.create_task 创建异步任务
        tasks.append(task)

    downloaded_files = await asyncio.gather(*tasks) # 使用 asyncio.gather 并发执行所有任务,并等待所有任务完成,获取返回值列表
    print("下载完成的文件:", downloaded_files)

if __name__ == "__main__":
    asyncio.run(main()) # 使用 asyncio.run 运行主异步函数 main

输出:

  • image1.gif, image2.jpg, 和 image3.jpg 将被下载到本地目录。
  • 打印下载完成的文件名列表。

多线程 (MultiThreading)

  • 多线程 (MultiThreading) 允许在一个进程中创建多个线程,并发执行代码,提高程序的并发性。
  • Python 中使用 threading 模块实现多线程。
  • 创建线程 -> 调用线程对象的 start() 方法启动线程。
  • join() 方法用于等待线程执行结束。
import threading

def my_func(): # 线程执行的函数
    print(f"你好,来自线程 {threading.current_thread().name}") # 打印当前线程的名字

thread = threading.Thread(target=my_func) # 创建新的线程,target 参数指定线程执行的函数
thread.start() # 启动线程
thread.join() # 等待线程执行结束
print("主线程继续执行")

输出:

  • 你好,来自线程 Thread-1 (线程名字可能略有不同,取决于系统分配)
  • 主线程继续执行

常用函数 (Functions):

  1. threading.Thread(target, args):

    • 创建一个新的线程,target 参数指定线程要执行的函数,args 参数以元组形式传递给 target 函数的参数。
  2. threading.Lock():

    • 创建一个锁 (Lock) 对象,用于同步多线程对共享资源的访问,防止数据竞争。
    • 锁确保在同一时刻只有一个线程可以访问临界区 (critical section) 代码。

使用锁的示例 (Example with Lock):

import threading

def increment(counter, lock): # 线程执行的函数,counter 是共享计数器,lock 是锁对象
    for _ in range(100000): # 循环增加计数器
        lock.acquire() # 获取锁,进入临界区
        counter[0] += 1 # 增加共享计数器
        lock.release() # 释放锁,退出临界区

if __name__ == "__main__":
    counter = [0]  # 使用列表存储计数器,因为列表是可变对象,可以在线程间共享和修改
    lock = threading.Lock() # 创建锁对象
    threads = [] # 存储线程对象的列表

    for _ in range(2): # 创建两个线程
        thread = threading.Thread(target=increment, args=(counter, lock)) # 创建线程,指定执行函数和参数
        threads.append(thread) # 添加线程到列表
        thread.start() # 启动线程

    for thread in threads: # 等待所有线程执行结束
        thread.join()

    print(f"最终计数器值: {counter[0]}") # 打印最终计数器值,期望值为 200000 (两个线程各增加 100000)

输出:

  • 最终计数器值将会是 200000 (理论上,实际运行结果可能因系统调度等因素略有偏差,但使用锁可以很大程度上避免数据竞争)。

使用 ThreadPoolExecutor (Using ThreadPoolExecutor)

import time
from concurrent.futures import ThreadPoolExecutor # 导入 ThreadPoolExecutor 类

def func(seconds): # 模拟耗时操作的函数
    print(f"线程 {threading.current_thread().name} 休眠 {seconds} 秒")
    time.sleep(seconds)
    return seconds

def main():
    time1 = time.perf_counter() # 记录开始时间

    # 使用线程池
    with ThreadPoolExecutor(max_workers=3) as executor: # 创建线程池,最大工作线程数为 3
        futures = []
        for i in range(3):
            seconds_to_sleep = 4 - i # 休眠时间递减
            future = executor.submit(func, seconds_to_sleep) # 提交任务到线程池,返回 Future 对象
            futures.append(future)

        results = [future.result() for future in futures] # 获取所有 Future 对象的结果,会阻塞等待任务完成
        print("任务结果:", results)

    time2 = time.perf_counter() # 记录结束时间
    print(f"程序总耗时: {time2 - time1:.2f} 秒") # 打印程序总耗时

if __name__ == "__main__":
    main()

输出:

  • 线程会并发执行 func 函数,总耗时会远小于串行执行的时间,大约在 4 秒左右 (取决于最长的休眠时间)。
  • 输出线程休眠信息和程序总耗时。

多进程 (Multiprocessing)

  • multiprocessing 模块提供了创建和管理进程的功能,允许程序利用多核 CPU 并行执行任务,充分利用系统资源,提高计算密集型任务的性能。
import concurrent.futures
import requests
import os
import time

def download_image(url, name): # 下载图片的函数
    start_time = time.time()
    try:
        print(f"进程 {os.getpid()} 开始下载 {name}") # 打印进程 ID 和下载文件名
        response = requests.get(url, stream=True) # 发送 HTTP 请求,stream=True 使用流式下载
        response.raise_for_status() # 检查请求状态码
        file_path = f"{name}.jpg"
        with open(file_path, "wb") as file:
            for chunk in response.iter_content(chunk_size=8192): # 分块写入文件
                file.write(chunk)
        end_time = time.time()
        print(f"进程 {os.getpid()} 完成下载 {name},耗时: {end_time - start_time:.2f} 秒")
        return f"{name}.jpg" # 返回保存的文件名
    except requests.exceptions.RequestException as e:
        print(f"进程 {os.getpid()} 下载 {name} 失败: {e}")
        return None

if __name__ == "__main__":
    image_url = "https://picsum.photos/200/300" # 图片 URL
    image_names = [f"image_{i}" for i in range(50)] # 生成 50 个图片文件名
    start_time = time.time()

    with concurrent.futures.ProcessPoolExecutor(max_workers=4) as executor: # 创建进程池,最大工作进程数为 4
        processes = []
        for name in image_names:
            process = executor.submit(download_image, image_url, name) # 提交下载任务到进程池
            processes.append(process)

        downloaded_files = [process.result() for process in concurrent.futures.as_completed(processes)] # 获取已完成进程的结果
        downloaded_files = [file for file in downloaded_files if file] # 过滤掉下载失败的文件

    end_time = time.time()
    print(f"所有图片下载完成,总耗时: {end_time - start_time:.2f} 秒")
    print(f"成功下载的文件数量: {len(downloaded_files)}")

输出:

  • 脚本会并发下载 50 张图片,使用多进程可以显著缩短下载时间,尤其在 CPU 密集型任务或 I/O 密集型任务中,多进程可以充分利用多核 CPU 的性能。
  • 输出每个进程的下载开始、完成信息以及总耗时和成功下载的文件数量。

多进程 vs 多线程 (Multiprocessing vs Multithreading)

序号	多进程	                     多线程
1.	    添加 CPU 以提高计算能力。	     创建单个进程的多个线程以实现并发。
2.	    同时执行多个进程。	         同时执行进程的多个线程。
3.	    分为对称和非对称多进程。	     不分为类别。
4.	    进程创建耗时较长。	         线程创建更经济。
5.	    每个进程都拥有单独的地址空间。	 所有线程共享一个公共地址空间。

本文在@ARYANK-08的python fundamentals的基础上修改和完善。