From f63df77321fa2d3f5177f0c02e9ab21207592d4c Mon Sep 17 00:00:00 2001 From: "zhouzhongping@7wate.com" Date: Thu, 3 Aug 2023 20:12:42 +0800 Subject: [PATCH] =?UTF-8?q?Python=EF=BC=9A=E5=85=A5=E9=97=A8=E5=9F=BA?= =?UTF-8?q?=E7=A1=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Python/入门/函数方法.md | 159 +-- .../Python/入门/基础语法.md | 29 +- .../Python/入门/控制语句.md | 296 +++--- .../Python/入门/数据类型.md | 245 ++++- .../Python/入门/数据结构.md | 875 ++++++++-------- .../Python/入门/模块和包.md | 207 ++-- .../Python/入门/面对对象.md | 950 +++++++++++++----- 7 files changed, 1728 insertions(+), 1033 deletions(-) diff --git a/wiki/programming-language/Python/入门/函数方法.md b/wiki/programming-language/Python/入门/函数方法.md index 5720e946..18073592 100644 --- a/wiki/programming-language/Python/入门/函数方法.md +++ b/wiki/programming-language/Python/入门/函数方法.md @@ -8,20 +8,20 @@ tags: - Python sidebar_position: 4 author: 7Wate -date: 2022-12-03 +date: 2023-08-03 --- ## 函数 -通过白盒/黑盒封装多行代码的实现,一般情况下拥有输入和输出,用来**简化代码**、**重复调用**和**模块化编程**。 - -在 Python 中可以使用`def`关键字来定义函数,和变量一样每个函数也有一个响亮的名字,而且命名规则跟变量的命名规则是一致的。函数内的第一条语句是字符串时,该字符串就是**文档字符串**,也称为 docstring。 - ![img](https://static.7wate.com/img/2022/11/20/10cf11ddd3b18.png) +函数是通过白盒/黑盒封装多行代码的实现方式,通常具有输入和输出,目的是为了**简化代码**、**重复调用**和**模块化编程**。 + +在 Python 中,`def`关键字用于定义函数,每个函数都具有一个唯一的名称,其命名规则与变量命名规则相同。函数体的第一条语句可以是一个字符串,该字符串被称为**文档字符串**或 docstring,用于提供关于函数的简要描述。 + ```python # 语法 -def 函数名(参数列表): +def 函数名(参数列表): 函数体 # 实例 @@ -36,7 +36,7 @@ def fib(n): ### 参数传递 -python 中类型属于对象,对象有不同类型的区分,变量是没有类型的。**python 中一切都是对象,严格意义我们不能说值传递还是引用传递,我们应该说传不可变对象和传可变对象**。 +python 中类型属于对象,变量是没有类型的。**python 中一切都是对象,严格意义我们不能说值传递还是引用传递,我们应该说传不可变对象和传可变对象。**而不可变对象和可变对象的区别在于:不可变对象的值不可以改变,而可变对象的值可以改变。 #### 可更改与不可更改对象 @@ -58,8 +58,8 @@ python 中类型属于对象,对象有不同类型的区分,变量是没有 def add(a=0, b=0, c=0): """三个数相加""" return a + b + c -add(1,2) -# 3 +add(1,2,3) +# 6 ``` ### 键值参数 @@ -129,7 +129,7 @@ def kwd_only_arg(*, arg): print(arg) ``` -特殊参数组合 +#### 特殊参数组合 ```python def combined_example(pos_only, /, standard, *, kwd_only): @@ -171,7 +171,76 @@ total = sum( 10, 20 ) print ("函数外 : ", total) ``` +## 全局、局部变量 +在 Python 中,变量的作用范围可分为全局变量和局部变量。 + +- **全局变量:**在函数体外部声明的变量被称为全局变量。全局变量在程序的整个生命周期内都是可访问的。 +- **局部变量:**在函数体内部声明的变量被称为局部变量。它们只能在声明它们的函数内部被访问。 + +```python +x = 10 # 这是一个全局变量 + +def func(): + y = 5 # 这是一个局部变量 + print(y) # 输出:5 + +func() +print(x) # 输出:10 +print(y) # 将会引发一个错误,因为 y 在这个作用域内不存在 +``` + +## global、nonlocal + +当需要在函数或其他作用域内部改变全局变量或嵌套作用域中的变量时,我们需要使用 `global` 或 `nonlocal` 关键字。 + +- **global 关键字:** 允许你在函数或其他局部作用域内部修改全局变量。 +- **nonlocal 关键字:** 允许你在嵌套的函数(即闭包)中修改上一层非全局作用域的变量。 + +```python +# 使用 global 关键字: +x = 10 + +def func(): + global x + x = 20 # 修改全局变量 x + +func() +print(x) # 输出:20 + +# 使用 nonlocal 关键字: +def outer(): + x = 10 + def inner(): + nonlocal x + x = 20 # 修改上一层函数中的 x + + inner() + print(x) # 输出:20 + +outer() +``` + +## yield + +`yield` 是 Python 中用于创建生成器(generator)的关键字。一个包含 `yield` 表达式的函数被称为一个生成器函数。这种函数在被调用时不会立即执行,而是返回一个迭代器,这个迭代器可以在其元素需要被处理时生成它们。这种延迟生成元素的方式使得生成器在处理大数据集或无限序列时非常有用,因为它们不需要一次性生成所有元素,从而节省内存。 + +生成器函数在执行到 `yield` 表达式时会暂停并保存当前所有的状态信息(包括局部变量等),在下次从该函数获取下一个元素(即进行下一次迭代)时,它会从保存的状态和位置继续执行。 + +```python +# yield a 语句会暂停函数的执行并返回当前的 a 值作为序列的下一个元素。 +# 在下次迭代时,函数会从 yield a 语句后的语句继续执行,计算出序列的下一个值。 +def fibonacci(): + a, b = 0, 1 + while True: + yield a + a, b = b, a + b + +# zip(range(10), fibonacci()) 部分会生成一个迭代器 +# 这个迭代器在每次迭代时都会返回斐波那契数列的下一个数字,直到生成了前 10 个数字为止。 +for _, val in zip(range(10), fibonacci()): + print(val) +``` ## Lambda @@ -184,64 +253,14 @@ lambda 关键字用于创建小巧的匿名函数。Lambda 函数可用于任何 ```python # 语法 -lambda [arg1 [,arg2,.....argn]]:expression -# 实例 ->>> def make_incrementor(n): -... return lambda x: x + n -... ->>> f = make_incrementor(42) ->>> f(0) -42 ->>> f(1) -43 -``` - -## 全局、局部变量 - -**定义在函数内部的变量拥有一个局部作用域,定义在函数外的拥有全局作用域。** - -局部变量只能在其被声明的函数内部访问,而全局变量可以在整个程序范围内访问。调用函数时,所有在函数内声明的变量名称都将被加入到作用域中。如下实例: - -```python -total = 0 # 全局变量 - -def sum( arg1, arg2 ): - # 返回 2 个参数的和 - total = arg1 + arg2 # total在这里是局部变量. - print ("函数内是局部变量 : ", total) - return total - -#调用 sum 函数 -sum( 10, 20 ) -print ("函数外是全局变量 : ", total) -``` - -## global、nonlocal 关键字 - -内部作用域修改外部作用域的变量时,需要使用 global 关键字声明。反之要修改嵌套作用域(enclosing 作用域,外层非全局作用域)中的变量则需要 nonlocal 关键字。 - -```python -num = 1 - -def fun1(): - global num # 需要使用 global 关键字声明 - print(num) - num = 123 - print(num) - -fun1() -print(num) -``` - -```python -def outer(): - num = 10 - def inner(): - nonlocal num # nonlocal关键字声明 - num = 100 - print(num) - inner() - print(num) - -outer() +lambda arguments: expression + +# lambda 实例 +double = lambda x: x * 2 + +# 常规函数 +def double(x): + return x * 2 + +print(double(5)) # 输出:10 ``` diff --git a/wiki/programming-language/Python/入门/基础语法.md b/wiki/programming-language/Python/入门/基础语法.md index bcded518..99cebf2a 100644 --- a/wiki/programming-language/Python/入门/基础语法.md +++ b/wiki/programming-language/Python/入门/基础语法.md @@ -8,7 +8,7 @@ tags: - Python sidebar_position: 1 author: 7Wate -date: 2022-11-20 +date: 2023-08-03 --- ## 简介 @@ -27,7 +27,6 @@ Python 官网上免费提供了 Python 解释器和扩展的标准库,包括 Python 官网还包含许多**免费丰富的第三方 Python 模块**、程序和工具发布包及文档链接。 - ## 使用 ### 安装 @@ -116,3 +115,29 @@ else : suite ``` +此外,Python还支持一些更高级的特性,如函数式编程、面向对象编程和元编程等。随着学习的深入,你会发现Python的世界越来越广阔。 + +## 关键字 + +Python 的标准库提供了一个 keyword 模块,可以输出当前版本的所有关键字: + +```python +$ import keyword +$ print(keyword.kwlist) + +['False', 'None', 'True', 'and', 'as', 'assert', 'async', 'await', 'break', 'class', 'continue', 'def', 'del', 'elif', 'else', 'except', 'finally', 'for', 'from', 'global', 'if', 'import', 'in', 'is', 'lambda', 'nonlocal', 'not', 'or', 'pass', 'raise', 'return', 'try', 'while', 'with', 'yield'] +``` + +## 声明 + +**硬性规则:** + +- 变量名由**字母**(广义的 Unicode 字符,不包括特殊字符)、**数字**和**下划线**构成,**数字不能开头**。 +- **大小写敏感**(大写的`a`和小写的`A`是两个不同的变量)。 +- 不要跟**关键字**(有特殊含义的单词)和系统保留字(如函数、模块等的名字)冲突。 + +**PEP 8要求:** + +- 用小写字母拼写,多个单词用下划线连接。 +- 受保护的实例属性用单个下划线开头。 +- 私有的实例属性用两个下划线开头。 diff --git a/wiki/programming-language/Python/入门/控制语句.md b/wiki/programming-language/Python/入门/控制语句.md index a35896cb..d4871e7b 100644 --- a/wiki/programming-language/Python/入门/控制语句.md +++ b/wiki/programming-language/Python/入门/控制语句.md @@ -8,19 +8,23 @@ tags: - Python sidebar_position: 3 author: 7Wate -date: 2022-11-19 +date: 2023-08-03 --- -## 条件 +Python 中的控制语句有条件语句,循环语句,异常处理,以及其他一些特殊的控制语句。 -在 Python 中,判断的值可以分为: +## 条件语句 + +Python中的条件语句是通过一条或多条语句的执行结果(即True或False)来决定执行的代码块。 + +判断的值可以分为: - 假值 :None、空列表、空集合、空字典,空元组、空字符串、0、False 等。 - 真值 :非空列表、非空集合、非空字典,非空元组、非空字符串、非 0 数值、True 等。 ### if -在Python中,要构造分支结构可以使用`if`、`elif`和`else`关键字。 +在Python中,要构造分支结构可以使用`if`、`elif`和`else`关键字。`elif`和`else`都是可选的,可以根据需要进行使用。 ```python # 示例 @@ -41,7 +45,7 @@ Please enter an integer: 42 ### match -match 语句接受一个表达式并将它的值与以一个或多个 case 语句块形式给出的一系列模式进行比较。 这在表面上很类似 C, Java 或 JavaScript (以及许多其他语言) 中的 switch 语句,但它还能够从值中提取子部分 (序列元素或对象属性) 并赋值给变量。 +Python 3.10 引入了新的`match`语句,它是模式匹配的一种形式。`match`语句接受一个表达式并将它的值与一系列的模式进行比较。 每个模式都关联到一个代码块,当模式与表达式的值匹配时,该代码块将被执行。这在某种程度上类似于其他语言中的 switch 语句。 ```python def http_error(status): @@ -52,12 +56,11 @@ def http_error(status): return "Not found" case 418: return "I'm a teapot" - case _: + #"变量名" `_` 被作为 通配符 并必定会匹配成功 + case _: return "Something's wrong with the internet" ``` -最后一个代码块: **"变量名" `_` 被作为 通配符 并必定会匹配成功**。 如果没有任何 case 语句匹配成功,则任何分支都不会被执行。 - 你可以使用 `|` (“ or ”)在一个模式中组合几个字面值: ```python @@ -65,10 +68,14 @@ case 401 | 403 | 404: return "Not allowed" ``` -## 循环 +*注意,`match`语句和模式匹配的概念是Python 3.10中新增的特性,可能在更早版本的Python中无法使用。* + +## 循环语句 ### for +Python 的 for 语句用于遍历任何序列的项目,如列表或字符串。 + Python 的 for 语句与 C 或 Pascal 中的不同。**Python 的 for 语句不迭代算术递增数值**(如 Pascal),或是给予用户定义迭代步骤和暂停条件的能力(如 C),而是迭代列表或字符串等任意序列,元素的迭代顺序与在序列中出现的顺序一致。 ```python @@ -84,16 +91,17 @@ print(sum) **内置函数 range() 表示不可变的数字序列,通常用于在 for 循环中循环指定的次数。** -`range(101)`可以用来构造一个从 1 到 100 的范围,当我们把这样一个范围放到 `for-in` 循环中,就可以通过前面的循环变量 `x` 依次取出从 1 到 100 的整数。当然,`range` 的用法非常灵活,下面给出了一个例子: +在 Python 中,`range()` 是一个内置函数,用于生成一个不可变的数字序列。通常,这个函数在 for 循环中使用,用于指定循环的次数。`range(101)`可以用来生成一个包含0到100(不包含101)的整数序列。 -- `range(101)`:可以用来产生 0 到 100 范围的整数,需要注意的是取不到 101。 -- `range(1, 101)`:可以用来产生 1 到 100 范围的整数,相当于前面是闭区间后面是开区间。 -- `range(1, 101, 2)`:可以用来产生 1 到 100 的奇数,其中 2 是步长,即每次数值递增的值。 -- `range(100, 0, -2)`:可以用来产生 100 到 1 的偶数,其中 -2 是步长,即每次数字递减的值。 +你也可以根据需要更改`range()`函数的参数,例如: + +- `range(1, 101)`:生成一个包含1到100(不包含101)的整数序列。 +- `range(1, 101, 2)`:生成一个包含1到100的奇数序列,其中2是步长。 +- `range(100, 0, -2)`:生成一个包含100到1的偶数序列,其中-2是步长。 ### while -如果要构造不知道具体循环次数的循环结构,那么使用`while`循环通过一个能够产生或转换出`bool`值的表达式来控制循环,表达式的值为`True`则继续循环;表达式的值为`False`则结束循环。 +`while` 循环语句用于在条件满足的情况下重复执行一个代码块。条件表达式的结果为`True`时,继续循环;结果为`False`时,结束循环。 ```python """ @@ -120,91 +128,136 @@ if counter > 7: ### break -break 语句和 C 中的类似,用于跳出最近的 for 或 while 循环。 +`break` 语句可以提前退出循环。具体来说,**`break` 用于完全结束一个循环,并跳出该循环体。** ### continue -continue 语句也借鉴自 C 语言,表示继续执行循环的下一次迭代。 +`continue` 语句用于**跳过当前循环的剩余语句,然后继续进行下一轮循环。** ### else -循环语句支持 else 子句;for 循环中,可迭代对象中的元素全部循环完毕时,或 while 循环的条件为假时,执行该子句;break 语句终止循环时,不执行该子句。 +在 Python 中,`else` 子句可以与 `for` 循环和 `while` 循环一起使用。当循环正常完成(即没有碰到 `break` 语句)时,`else` 块的内容会被执行。这个特性在很多其他语言中都没有,因此对于初学者来说可能会感到有些不熟悉。 ```python -for n in range(2, 10): - for x in range(2, n): - if n % x == 0: - print(n, 'equals', x, '*', n//x) - break - else: - # loop fell through without finding a factor - print(n, 'is a prime number') - -""" -2 is a prime number -3 is a prime number -4 equals 2 * 2 -5 is a prime number -6 equals 2 * 3 -7 is a prime number -8 equals 2 * 4 -9 equals 3 * 3 -""" +# 使用 else 的 for 循环的示例 +for i in range(5): + if i == 10: + break +else: + print("循环正常完成") ``` -## 异常 +在这个示例中,循环会正常完成,因为没有任何一个元素使得 `i == 10` 为 `True`,所以 `break` 语句不会被执行,`else` 块的内容会被打印出来。 + +如果我们修改 `if` 语句的条件使得 `break` 语句被执行,那么 `else` 块的内容就不会被打印出来: + +```Python +for i in range(5): + if i == 3: + break +else: + print("循环正常完成") +``` + +在这个示例中,当 `i` 等于3时,`break` 语句就会被执行,所以循环没有正常完成,`else` 块的内容不会被打印出来。 + +同样的,`else` 也可以与 `while` 循环一起使用,当 `while` 循环的条件变为 `False` 时,`else` 块的内容会被执行。 + +```python +i = 0 +while i < 5: + if i == 3: + break + i += 1 +else: + print("循环正常完成") +``` + +在这个示例中,当 `i` 等于3时,`break` 语句就会被执行,所以循环没有正常完成,`else` 块的内容不会被打印出来。 + +## 异常语句 ### try、except、finally -![img](https://static.7wate.com/img/2022/11/20/9a4509b47ebe3.png) +```mermaid +graph TD + A(开始) + B[try 块] + C[except 块1] + D[except 块2] + E[else 块] + F[finally 块] + G(结束) + A --> B + B -- 异常1 --> C + B -- 异常2 --> D + B -- 无异常 --> E + C --> F + D --> F + E --> F + F --> G -1. `except`语句不是必须的,`finally`语句也不是必须的,但是二者必须要有一个,否则就没有`try`的意义了。 -2. `except`语句可以有多个,Python会按`except`语句的顺序依次匹配你指定的异常,如果异常已经处理就不会再进入后面的`except`语句。 -3. `except`语句可以以元组形式同时指定多个异常,参见实例代码。 -4. `except`语句后面如果不指定异常类型,则默认捕获所有异常,你可以通过logging或者sys模块获取当前异常。 -5. 如果要捕获异常后要重复抛出,请使用`raise`,后面不要带任何参数或信息。 -6. 不建议捕获并抛出同一个异常,请考虑重构你的代码。 -7. 不建议在不清楚逻辑的情况下捕获所有异常,有可能你隐藏了很严重的问题。 -8. 尽量使用内置的异常处理语句来替换`try/except`语句,比如`with`语句,`getattr()`方法。 +``` + +1. **确定需要的异常处理结构**:在开始编写异常处理结构时,首先确定你需要 `try`, `except`, `else` 和/或 `finally` 语句块。要记住的是,`except` 和 `finally` 块至少需要一个,否则 `try` 将失去其意义。 +2. **编写 `try` 语句块**:将可能抛出异常的代码放入 `try` 块。如果在 `try` 块中发生异常,Python 将停止执行 `try` 块的其余部分,并转到 `except` 块。 +3. **编写 `except` 语句块**:为每种可能抛出的异常类型编写一个 `except` 块。Python 会按照它们在代码中出现的顺序来检查这些 `except` 块。如果匹配到异常,Python 将执行相应的 `except` 块并停止查找。 +4. **处理多个异常**:你可以将多个异常类型放入一个元组中,然后使用一个 `except` 块来处理它们。例如:`except (TypeError, ValueError):`。 +5. **处理所有异常**:如果 `except` 块后面没有指定异常类型,那么这个 `except` 块将处理所有异常。你可以通过 `logging` 或 `sys` 模块获取异常的详细信息。 +6. **重新抛出异常**:如果你在 `except` 块中捕获了一个异常,然后想要再次抛出它,你可以使用 `raise` 语句而不需要附加任何参数或信息。 +7. **编写 `else` 语句块**:`else` 块中的代码只有在 `try` 块没有发生任何异常时才会被执行。 +8. **编写 `finally` 语句块**:无论是否发生异常,`finally` 块中的代码总是会被执行。这对于清理(例如关闭文件或网络连接)非常有用。 +9. **重构代码**:考虑使用 `with` 语句或 `getattr()` 方法等内置的异常处理语句,而不是 `try/except`。此外,如果可能,尽量避免在同一个 `except` 块中捕获和抛出相同的异常。最后,除非你确定需要处理所有可能的异常,否则不应该捕获所有异常,因为这可能会隐藏严重的问题。 ```python +# 示例 +try: + # 这里可能会抛出异常的代码 + do_something() +except SomeException as e: + # 当捕获到SomeException时的处理代码 + handle_exception(e) + +# 实例 def div(a, b): try: print(a / b) except ZeroDivisionError: - print("Error: b should not be 0 !!") + print("错误:b 不应为 0 !!") except Exception as e: - print("Unexpected Error: {}".format(e)) + print("意外错误:{}".format(e)) else: - print('Run into else only when everything goes well') + print('只有当一切正常时,才会运行 else') finally: - print('Always run into finally block.') + print('始终运行 finally 块。') -# tests +# 测试 div(2, 0) -div(2, 'bad type') +div(2, '错误的类型') div(1, 2) -# Mutiple exception in one line + +# 在一行中捕获多个异常 try: print(a / b) except (ZeroDivisionError, TypeError) as e: print(e) -# Except block is optional when there is finally + +# 当存在 finally 时,except 是可选的 try: open(database) finally: close(database) -# catch all errors and log it +# 捕获所有错误并记录下来 try: do_work() except: - # get detail from logging module - logging.exception('Exception caught!') + # 从 logging 模块获取详细信息 + logging.exception('捕获到异常!') - # get detail from sys.exc_info() method + # 从 sys.exc_info() 方法获取详细信息 error_type, error_value, trace_back = sys.exc_info() print(error_value) raise @@ -212,15 +265,15 @@ except: ### with -Python 的 with 语句支持通过上下文管理器所定义的运行时上下文这一概念。 此对象的实现使用了一对专门方法,**允许用户自定义类来定义运行时上下文**,在语句体被执行前进入该上下文,并在语句执行完毕时退出该上下文。 +Python 的 with 语句支持通过上下文管理器所定义的运行时上下文这一概念。 -通过上下文管理器,我们可以更好的控制对象在不同区间的特性,并且**可以使用 with 语句替代 try...except** 方法,使得代码更加的简洁,主要的**使用场景是访问资源,可以保证不管过程中是否发生错误或者异常都会执行相应的清理操作,释放出访问的资源。** +`with`语句是一种处理上下文管理器的语句,上下文管理器通常包含`__enter__`和`__exit__`这两个方法。在`with`语句的代码块被执行前,会首先执行`__enter__`方法,在执行完毕后,会调用`__exit__`方法。这在你需要管理资源,如文件,网络连接或锁定等情况非常有用,因为它可以**保证在任何情况下都会执行必要的清理操作。** ```python -# with 读写文件 -with open("myfile.txt") as f: - for line in f: - print(line, end="") +# 在这里,文件已经被关闭,无需再次手动关闭 +with open("file.txt", "r") as file: + for line in file: + print(line) ``` ```python @@ -228,109 +281,76 @@ with open("myfile.txt") as f: # 紧跟 with 后面的语句被求值后,返回对象的 enter() 方法被调用,并将返回值赋值给 as 后面的变量。 # 当 with 的代码块全部被执行完之后,将调用前面返回对象的 exit() 方法。 -class Sample: +class ManagedFile: + def __init__(self, filename): + self.filename = filename + def __enter__(self): - print("In __enter__()") - return "Foo" + self.file = open(self.filename, 'r') + return self.file - def __exit__(self, type, value, trace): - print("In __exit__()") + def __exit__(self, exc_type, exc_val, exc_tb): + if self.file: + self.file.close() - -def get_sample(): - return Sample() - - -with get_sample() as sample: - print("sample:", sample) +# 使用自定义的上下文管理器 +with ManagedFile("file.txt") as file: + print(file.read()) ``` ### raise -`raise`语句支持强制触发指定的异常。例如: +`raise`语句用于引发特定的异常。你可以定义异常类型并附加一个错误消息: ```python -raise NameError('HiThere') - -Traceback (most recent call last): - File "", line 1, in - -NameError: HiThere +raise ValueError("这是一个无效的值!") ``` -## 其他 +如果你在`except`块中使用`raise`语句,而不提供任何参数,它将默认重新引发最近的异常。 + +```python +try: + print(5/0) +except ZeroDivisionError as e: + print("发生了一个错误!") + raise # 重新引发最近的异常 +``` + +## 其他语句 ### assert -Python 断言(assert)用于判断一个表达式,在表达式条件为 false 的时候触发异常。 - -简单形式 `assert expression` 等价于: +`assert` 语句用于断言某个条件为真,如果条件为假,则会抛出 `AssertionError` 异常。它常常用于调试代码,确认代码的某些方面满足预期,例如: ```python -if __debug__: - if not expression: raise AssertionError +x = 1 +assert x == 1 # 条件为真,没有问题 + +# 条件为假,抛出 AssertionError,并附带错误信息 +assert x == 2, "x should be 2 but is actually " + str(x) ``` -扩展形式 `assert expression1, expression2` 等价于: - -```python -if __debug__: - if not expression1: raise AssertionError(expression2) -``` +*注意,`assert` 语句在优化模式下(使用 `-O` 参数启动 Python 时)会被全局禁用。* ### pass -pass 语句不执行任何操作。语法上需要一个语句,但程序不实际执行任何动作时,可以使用该语句。例如: +`pass` 语句是 Python 中的空语句,用于在需要语句的地方保持语法的完整性,但是**实际上不做任何事情**。通常,我们使用它作为未完成代码的占位符: ```python -while True: - pass # Busy-wait for keyboard interrupt (Ctrl+C) +def my_function(): + pass # TODO: implement this function -# 最小的类 class MyEmptyClass: pass - -# 三个点 等同于 pass -class MyEmptyClass: - ... -``` - -### del - -目标列表的删除将从左至右递归地删除每一个目标 - -```python -del a -del b[] ``` ### return -return 会离开当前函数调用,并以表达式列表 (或 None) 作为返回值。 - -### yield - -生成迭代器 +`return` 语句用于从函数返回一个值。所有函数都会返回一个值:如果函数执行到结尾而没有遇到 `return` 语句,它将返回特殊值 `None`: ```python -def foo(num): - print("starting...") - while num<10: - num=num+1 - yield num -for n in foo(0): - print(n) - -# 输出 -starting... -1 -2 -3 -4 -5 -6 -7 -8 -9 -10 +def add(a, b): + return a + b + +print(add(1, 2)) # 输出:3 ``` diff --git a/wiki/programming-language/Python/入门/数据类型.md b/wiki/programming-language/Python/入门/数据类型.md index 23c64cf1..43413a1b 100644 --- a/wiki/programming-language/Python/入门/数据类型.md +++ b/wiki/programming-language/Python/入门/数据类型.md @@ -8,47 +8,183 @@ tags: - Python sidebar_position: 2 author: 7Wate -date: 2022-11-19 +date: 2023-08-03 --- -## 内置类型 +在编程领域,理解和掌握各种数据类型是任何编程语言的基础,Python 也不例外。Python 3 提供了多种内置数据类型,包括但不限于数字(整型、浮点型、复数)、布尔型、列表、元组、字符串、集合、字典等,还有函数、模块等高级类型。每种数据类型都有其特定的特性和适用场景。 -**Python中的一切都是对象,变量是对象的引用!同时 Python 的动态语言特性变量和常量不需要事先声明类型。** +> 在Python中,变量可以被理解为一个标签(tag)或者标记,它是附着在特定对象上的名字。你可以将这个理念理解为在超市里的商品标签,标签告诉你这是什么商品,而商品是具体的物品。同样的,Python的变量名就像是一个标签,它告诉我们这个变量指向的是什么对象,而对象是存储在内存中的具体数据。 -Python 不同于其他程序设计语言使用**存储期(存储空间生命周期)** 对变量和对象进行管理,Python 使用**引用计数**,即引用对象的变量个数,对变量和对象进行管理。 +## 对象与类型 -Python 可以使用 **id 函数**获取标识值(伪指针)、**type 函数**获取类型。 +**在 Python 中,几乎所有的数据都可以被视为对象,每一个对象都有其相应的类型。**例如: -Python 中根据值是否可以改变,类型分为两类: +```python +print(type(123)) # +print(type(3.14)) # +print(type('hello')) # +``` -- **可变类型**:列表、字典、集合等。 -- **不可变类型**:数值、字符串、元组等。 +**Python 是动态类型语言,我们不需要预先声明变量的类型。**在程序运行过程中,变量的类型可以根据赋值而改变。例如: -如果对不可变类型的变量(引用的对象)的值进行变更、则会生成新的对象,然后变量重新引用新的对象。**赋值语句复制的是对象的引用而不是值。** *Python 的“变量”不同于其他程序语言的“变量”,Python 的“变量”翻译成“标志”更合适!* +```python +x = 123 # x 是一个整数 +print(type(x)) # -**Python 3 内置类型**如下,除了各种数据类型,Python 解释器内建了还有很多其他类型,比如上下文管理器类型,模块、方法、代码对象、类型对象、内部对象等类型。 +x = 'hello' # x 变成了一个字符串 +print(type(x)) # +``` -| 类型 | 可变性 | 描述 | 语法例子 | -| :------------------------: | :------: | :----------------------------------------------------------: | :----------------------------------------------------------: | -| `bool` | 不可变 | 布尔值 | `True` `False` | -| `int` | 不可变 | 理论上无限制大小的整数 | `42` | -| `float` | 不可变 | 双精度浮点数。精度是机器依赖的但实际上一般实现为 64 位IEEE 754 数而带有 53 位的精度 | `1.414` | -| `complex` | 不可变 | 复数,具有实部和虚部 | `3+2.7j` | -| `range` | 不可变 | 通常用在循环中的数的序列,规定在 for 循环中的次数 | `range(1, 10)` `range(10, -5, -2)` | -| `str` | 不可变 | 字符串,Unicode 代码点序列 | `'Wikipedia'` `"Wikipedia"` `"""Spanning multiple lines"""` | -| `bytes` | 不可变 | 字节序列 | `b'Some ASCII'` `b"Some ASCII"` `bytes([119, 105, 107, 105])` | -| `bytearray` | **可变** | 字节序列 | `bytearray(b'Some ASCII')` `bytearray(b"Some ASCII")` `bytearray([119, 105, 107, 105])` | -| `list` | **可变** | 列表,可以包含混合的类型 | `[4.0, 'string', True]` `[]` | -| `tuple` | 不可变 | 元组,可以包含混合的类型 | `(4.0, 'string', True)` `('single element',)` `()` | -| `dict` | **可变** | 键-值对的关联数组(或称字典);可以包含混合的类型(键和值),键必须是可散列的类型 | `{'key1': 1.0, 3: False}` `{}` | -| `set` | **可变** | 无序集合,不包含重复项;可以包含混合的类型,如果可散列的话 | `{4.0, 'string', True}` `set()` | -| `frozenset` | 不可变 | 无序集合,不包含重复项;可以包含混合的类型,如果可散列的话 | `frozenset([4.0, 'string', True])` | -| `types.EllipsisType` | 不可变 | 省略号占位符,用作 NumPy 数组的索引 | `...` `Ellipsis` | -| `types.NoneType` | 不可变 | 表示值缺席的对象,在其他语言中经常叫做 null | `None` | -| `types.NotImplementedType` | 不可变 | 可从重载运算符返回的占位符,用来指示未支持的运算数(operand)类型 | `NotImplemented` | +Python中有两个内置函数`id`和`type`。 + +- `id(obj)`函数:返回对象`obj`的唯一标识符,其实质上是该对象在内存中的地址。 +- `type(obj)`函数:返回对象`obj`的类型。 + +### 引用计数和垃圾收集 + +Python 不依赖存储期(即对象在内存中存在的时间)来管理变量和对象,而是**使用引用计数。每个对象都会计算有多少个变量引用了它,当引用计数为 0 时,对象就会被垃圾收集器删除**。可以使用内置的 `id` 函数获取对象的标识(这实际上是该对象的内存地址),使用 `type` 函数获取对象的类型。 + +```python +# 引用计数和垃圾收集的例子: +# 在代码中,`sys.getrefcount(a)`可以获得对象`a`的引用计数。 +# 当我们创建一个新的引用`b`时,`a`的引用计数增加1。 +# 当我们删除`b`时,`a`的引用计数减少1。 + +import sys + +a = [] # 创建一个空列表 + +print(sys.getrefcount(a)) # 输出:2,一个引用来自 a,一个来自 getrefcount 的参数 + +b = a # 增加一个引用 + +print(sys.getrefcount(a)) # 输出:3,新增一个引用来自 b + +b = None # 删除一个引用 + +print(sys.getrefcount(a)) # 输出:2,b 不再引用 +``` + +### 可变类型、不可变类型 + +Python 中的数据类型可以分为两大类:可变类型与不可变类型。 + +- **可变类型**:值可以更改,如列表、字典和集合。 +- **不可变类型**:值不可更改,如数字、字符串、元组等。 + +不可变类型的变量如果改变值,实际上是生成了一个新的对象,并使变量引用新的对象。Python的赋值语句复制的是对象的引用,而不是对象的值。因此,Python中的“变量”与其他编程语言中的“变量”不完全相同,将其翻译为“引用”可能更加合适。 + +```python +# 在代码中,`list1`是一个列表,是可变类型。 +# 我们可以通过`append`方法修改`list1`,但是`list1`的`id`并未改变,说明`list1`还是同一个对象。 +# `x`是一个整数,是不可变类型。当我们改变`x`的值时,`x`的`id`改变了,说明`x`现在是一个新的对象。 + +# 可变类型:列表 +list1 = [1, 2, 3] +print(id(list1)) # 输出 list1 的 id +list1.append(4) # 修改 list1 +print(id(list1)) # id 没有改变 + +# 不可变类型:整数 +x = 1 +print(id(x)) # 输出 x 的 id +x = x + 1 # 修改 x +print(id(x)) # id 改变了 +``` + +## Python3 内置类型 + +Python 3内置了多种数据类型,同时还内建了许多其他类型,如上下文管理器类型、模块、方法、代码对象、类型对象、内部对象等。 + +### 数字类型 + +数字类型主要用于存储和处理数值。这包括整数、浮点数、复数和布尔类型。 + +| 类型 | 描述 | 示例 | 可变性 | +| ------- | ---------------------------------- | ---------------------- | ------ | +| int | 整数,无论大小都可以是正数或负数。 | `123`, `-456`, `0` | 不可变 | +| float | 浮点数,包含小数部分的数字。 | `3.14`, `-0.01`, `9.0` | 不可变 | +| complex | 复数,包含实部和虚部的数字。 | `1+2j`, `3-4j` | 不可变 | + +### 布尔类型 + +| 类型 | 描述 | 示例 | 可变性 | +| ---- | ---------------------- | --------------- | ------ | +| bool | 布尔,表示真或假的值。 | `True`, `False` | 不可变 | + +### 序列类型 + +序列类型是一种有序的元素集合,包括字符串、列表和元组。每个元素都有一个相应的索引,可以通过索引来访问。 + +| 类型 | 描述 | 示例 | 可变性 | +| ----- | ---------------------------------------- | -------------------------- | ------ | +| str | 字符串,由零个或多个字符组成的文本。 | `'hello'`, `"world"`, `''` | 不可变 | +| list | 列表,由一系列按特定顺序排列的元素组成。 | `[1, 'two', 3.0]` | 可变 | +| tuple | 元组,类似于列表,但元素不可更改。 | `(1, 'two', 3.0)` | 不可变 | + +### 集合类型 + +集合类型是一个无序的元素集合,其中的元素都是唯一的。这包括集合和冻结集合。 + +| 类型 | 描述 | 示例 | 可变性 | +| --------- | ---------------------------------------- | ---------------------------- | ------ | +| set | 集合,一组无序的、不重复的元素。 | `{1, 'two', 3.0}` | 可变 | +| frozenset | 不可变集合,类似于集合,但元素不可更改。 | `frozenset({1, 'two', 3.0})` | 不可变 | + +### 映射类型 + +映射类型是一个存储键值对的元素集合,其中的键是唯一的。字典就是一个映射类型。 + +| 类型 | 描述 | 示例 | 可变性 | +| ---- | ---------------------------- | ----------------------------- | ------ | +| dict | 字典,包含键值对的数据结构。 | `{'name': 'John', 'age': 25}` | 可变 | + +### 特殊类型 + +| 类型 | 描述 | 示例 | 可变性 | +| ------------------ | ------------------------------------------------------ | ------------------- | ------ | +| NoneType | 表示None的特殊类型。 | `None` | 不可变 | +| EllipsisType | 表示省略的特殊类型,主要在切片和NumPy库中使用。 | `Ellipsis` 或 `...` | 不可变 | +| NotImplementedType | 表示未实现方法的特殊类型,主要在自定义比较方法中使用。 | `NotImplemented` | 不可变 | + +### 二进制类型 + +| 类型 | 描述 | 示例 | 可变性 | +| ---------- | ------------------------------------------------------------ | ------------------------- | -------------- | +| bytes | 字节,包含零个或多个范围为0<=x<256的整数的不可变序列。 | `b'hello'`, `b'\x01\x02'` | 不可变 | +| bytearray | 字节数组,包含零个或多个范围为0<=x<256的整数的可变序列。 | `bytearray(b'hello')` | 可变 | +| memoryview | 内存查看,用于访问其他二进制序列、打包的数组和缓冲区的内部数据。 | `memoryview(b'hello')` | 依据所查看对象 | + +### 类、实例和异常 + +| 类型 | 描述 | 示例 | 可变性 | +| --------- | ------------------------ | --------------------------- | ---------- | +| object | 对象,所有类的基类。 | `obj = object()` | 依据具体类 | +| exception | 异常,程序运行时的错误。 | `raise Exception('Error!')` | 不可变 | + +### 其他内置类型 + +| 类型 | 描述 | 示例 | 可变性 | +| --------- | ----------------------------------------------------------- | ------------------------------ | ------ | +| function | 函数,包含一系列指令的代码块。 | `def greet(): print('Hello!')` | 不可变 | +| type | 类型,表示对象的类型。 | `type(123)` | 不可变 | +| generator | 生成器,一种可迭代的对象,由函数定义并使用 `yield` 产生值。 | `(x**2 for x in range(10))` | 不可变 | ## 类型转换 +Python提供了多种函数,用于在不同类型之间进行转换: + +```Python +x = "123" # 这是一个字符串 +print(type(x)) # + +x = int(x) # 将字符串转为整数 +print(type(x)) # + +x = float(x) # 将整数转为浮点数 +print(type(x)) # +``` + | 函数 | 描述 | | :-------------------- | :-------------------------------------------------- | | int(x [,base]) | 将x转换为一个整数 | @@ -60,7 +196,7 @@ Python 中根据值是否可以改变,类型分为两类: | tuple(s) | 将序列 s 转换为一个元组 | | list(s) | 将序列 s 转换为一个列表 | | set(s) | 转换为可变集合 | -| dict(d) | 创建一个字典。d 必须是一个 (key, value)元组序列。 | +| dict(d) | 创建一个字典。d 必须是一个 (key, value)元组序列 | | frozenset(s) | 转换为不可变集合 | | chr(x) | 将一个整数转换为一个字符 | | ord(x) | 将一个字符转换为它的整数值 | @@ -71,6 +207,30 @@ Python 中根据值是否可以改变,类型分为两类: 在实际开发中,如果搞不清楚运算符的优先级,可以**使用括号来确保运算的执行顺序**。 +```python +# 运算符 +a = 10 +b = 20 + +print(a + b) # 加法,输出: 30 +print(a - b) # 减法,输出: -10 +print(a * b) # 乘法,输出: 200 +print(a / b) # 除法,输出: 0.5 +print(a ** 2) # 幂运算,输出: 100 +print(a % 3) # 取模,输出: 1 + +# 逻辑运算符 +print(a > b) # 大于,输出: False +print(a < b) # 小于,输出: True +print(a == b) # 等于,输出: False +print(a != b) # 不等于,输出: True + +# 成员运算符 +s = 'Hello World' +print('World' in s) # 输出: True +print('Python' not in s) # 输出: True +``` + | 运算符 | 描述 | | ------------------------------------------------------------ | ------------------------------ | | `[]` `[:]` | 下标,切片 | @@ -86,29 +246,4 @@ Python 中根据值是否可以改变,类型分为两类: | `is` `is not` | 身份运算符 | | `in` `not in` | 成员运算符 | | `not` `or` `and` | 逻辑运算符 | -| `=` `+=` `-=` `*=` `/=` `%=` `//=` `**=` `&=` `| =` `^=` `>>=` `<<=` | 赋值运算符 | - -## 关键字 - -Python 的标准库提供了一个 keyword 模块,可以输出当前版本的所有关键字: - -```python -$ import keyword -$ print(keyword.kwlist) - -['False', 'None', 'True', 'and', 'as', 'assert', 'async', 'await', 'break', 'class', 'continue', 'def', 'del', 'elif', 'else', 'except', 'finally', 'for', 'from', 'global', 'if', 'import', 'in', 'is', 'lambda', 'nonlocal', 'not', 'or', 'pass', 'raise', 'return', 'try', 'while', 'with', 'yield'] -``` - -## 声明 - -**硬性规则:** - -- 变量名由**字母**(广义的Unicode字符,不包括特殊字符)、**数字**和**下划线**构成,**数字不能开头**。 -- **大小写敏感**(大写的`a`和小写的`A`是两个不同的变量)。 -- 不要跟**关键字**(有特殊含义的单词,后面会讲到)和系统保留字(如函数、模块等的名字)冲突。 - -**PEP 8要求:** - -- 用小写字母拼写,多个单词用下划线连接。 -- 受保护的实例属性用单个下划线开头(后面会讲到)。 -- 私有的实例属性用两个下划线开头(后面会讲到)。 +| `=` `+=` `-=` `*=` `/=` `%=` `//=` `**=` `&=` `| =``^=``>>=``<<=` | 赋值运算符 | diff --git a/wiki/programming-language/Python/入门/数据结构.md b/wiki/programming-language/Python/入门/数据结构.md index 79c5fff6..424e350d 100644 --- a/wiki/programming-language/Python/入门/数据结构.md +++ b/wiki/programming-language/Python/入门/数据结构.md @@ -8,339 +8,348 @@ tags: - Python sidebar_position: 5 author: 7Wate -date: 2022-11-20 +date: 2023-08-03 --- ## 数值 -Python 数值数据类型用于存储数值。数据类型是不允许改变的,这就意味着如果改变数字数据类型的值,将重新分配内存空间。Python 支持三种不同的数值类型: +Python的数值数据类型是用来存储数值的。一旦创建,**数值对象的身份和类型是不可改变的**。如果你改变了数值对象的值,Python 将创建一个新的数值对象。Python 支持三种不同的数值类型: -- **整型(int)** - 通常被称为是整型或整数,是正或负整数,不带小数点。Python3 整型是没有限制大小的,可以当作 Long 类型使用,所以 Python3 没有 Python2 的 Long 类型。布尔(bool)是整型的子类型。 -- **浮点型(float)** - 浮点型由整数部分与小数部分组成,浮点型也可以使用科学计数法表示(2.5e2 = 2.5 x 102 = 250) -- **复数( (complex))** - 复数由实数部分和虚数部分构成,可以用 a + bj,或者 complex(a,b) 表示, 复数的实部 a 和虚部 b 都是浮点型。 +- **整型(int)**:被用来表示整数,包括正整数和负整数,不带小数点。在Python 3中,整数的长度是无限的,可以视为 Python 2 中的 Long 类型。此外,布尔类型(bool)实际上是整型的一种特殊形式,其中 True 和 False 分别被表示为 1 和 0。 +- **浮点型(float)**:用来表示实数,由整数部分和小数部分组成。浮点型还可以使用科学计数法表示。例如,2.5e2 表示的是 2.5 乘以 10 的平方,等于 250。 +- **复数(complex)**:复数由实部和虚部构成,表示形式可以是 a + bj,或者 complex(a,b)。在这里,实部 a 和虚部 b 都是浮点型。 ```python -num1 = -100 # 整数 +num1 = -100 # 整数 num2 = 200.01 # 浮点数 num3 = 0xA0F # 十六进制 num4 = 0o37 # 八进制 +# 引入库 +import math +import random + # 数学常量 PI 和 e -pi -e +pi = math.pi +e = math.e # 绝对值 -abs(num1) +abs_val = abs(num1) # 向上取整 -ceil(num2) +ceil_val = math.ceil(num2) # 返回最大数 -max(num1, num2) +max_val = max(num1, num2) # x**y 运算后的值 -pow(num1, num2) +pow_val = pow(num1, num2) # 随机数 -random.random() +random_val = random.random() # x弧度的正弦值。 -sin(x) +sin_val = math.sin(pi/2) ``` ## 字符串 -所谓**字符串**,就是由零个或多个字符组成的有限序列。在Python程序中,如果我们把单个或多个字符用单引号或者双引号包围起来,就可以表示一个字符串。 +字符串在计算机科学和编程中非常重要。在Python程序中,如果我们把单个或多个字符用单引号或者双引号包围起来,我们就创建了一个字符串。 -- 单引号 **'** 和双引号 **"** 使用完全相同。 -- 三引号(**'''** 或 **"""**)可以指定一个多行字符串。 -- 反斜杠 **\\** 可以用来转义,使用 **r** 可以让反斜杠不发生转义。 -- 字面意义级联字符串:如 **"this " "is " "string"** 会被自动转换为 **this is string**。 -- 字符串有两种索引方式,从左往右以 **0** 开始,从右往左以 **-1** 开始。 -- 字符串不能改变,一个字符就是长度为 1 的字符串。 -- 字符串的截取的语法格式如下:**变量[头下标:尾下标:步长]** - -![img](https://static.7wate.com/img/2022/11/20/7158615419c61.png) +### 定义 ```python -# 单引号、双引号字符串 +# 在 Python 中,可以使用单引号(')和双引号(")来定义字符串,它们的使用方式完全相同。 s1 = 'hello, world!' s2 = "hello, world!" -# 以三个双引号或单引号开头的字符串可以折行 +# 使用三个单引号(''')或者三个双引号(""")可以创建一个多行字符串。 s3 = """ hello, world! """ -# 在字符串中使用 \(反斜杠)来表示转义 +# 反斜杠(\)可以用来在字符串中插入特殊字符,这被称为转义字符。 s4 = '\n\t\141\u9a86\u660a' -# 字符串开头使用 r 来取消转义 +# 在字符串前加上字符 r 可以阻止反斜杠的转义效果。 s5 = r'\n\\hello, world!\\\n' -# 输出:\n\\hello, world!\\\n -# 级联字符串 +# 通过将字符串级联来创建新的字符串。 s6 = "this " "is " "string" -# 输出:this is string -# 字符串无法改变 -s6[2] = "c" -# 输出:错误! +# 字符串是不可变的,这意味着你不能改变一个字符串的内容。 +s6[2] = "c" # 这将引发一个错误 + +# 字符串可以通过索引进行访问,其中 0 是第一个字符的索引,-1 是最后一个字符的索引。 +s1 = 'hello, world!' +print(s1[0]) # 输出:h +print(s1[-1]) # 输出:! + +# Python也支持字符串的切片操作,你可以使用这种方式来获取字符串的一部分。 +s1 = 'hello, world!' +print(s1[0:5]) # 输出:hello + +# ===== 字符串切片示意图 ===== +| L | e | t | t | e | r | s | +|---|---|---|---|---|---|---| +| 0 | 1 | 2 | 3 | 4 | 5 | 6 | +|-7 |-6 |-5 |-4 |-3 |-2 |-1 | ``` ### 转义 -| 转义字符 | 描述 | 实例 | -| :---------- | :----------------------------------------------------------- | :----------------------------------------------------------- | -| \(在行尾时) | 续行符 | `>>> print("line1 \ ... line2 \ ... line3") line1 line2 line3 >>> ` | -| \\ | 反斜杠符号 | `>>> print("\\") \` | -| \' | 单引号 | `>>> print('\'') '` | -| \" | 双引号 | `>>> print("\"") "` | -| \a | 响铃 | `>>> print("\a")`执行后电脑有响声。 | -| \b | 退格(Backspace) | `>>> print("Hello \b World!") Hello World!` | -| \000 | 空 | `>>> print("\000") >>> ` | -| \n | 换行 | `>>> print("\n") >>>` | -| \v | 纵向制表符 | `>>> print("Hello \v World!") Hello World! >>>` | -| \t | 横向制表符 | `>>> print("Hello \t World!") Hello World! >>>` | -| \r | 回车,将 **\r** 后面的内容移到字符串开头,并逐一替换开头部分的字符,直至将 **\r** 后面的内容完全替换完成。 | `>>> print("Hello\rWorld!") World! >>> print('google runoob taobao\r123456') 123456 runoob taobao` | -| \f | 换页 | `>>> print("Hello \f World!") Hello World! >>> ` | -| \yyy | 八进制数,y 代表 0~7 的字符,例如:\012 代表换行。 | `>>> print("\110\145\154\154\157\40\127\157\162\154\144\41") Hello World!` | -| \xyy | 十六进制数,以 \x 开头,y 代表的字符,例如:\x0a 代表换行 | `>>> print("\x48\x65\x6c\x6c\x6f\x20\x57\x6f\x72\x6c\x64\x21") Hello World!` | -| \other | 其它的字符以普通格式输出 | | +| 转义字符 | 描述 | 示例 | +| :------- | :---------------------------- | :--------------------------------------------- | +| `\\` | 反斜杠符号 | `print("\\")` 输出:`\` | +| `\'` | 单引号 | `print('\'')` 输出:`'` | +| `\"` | 双引号 | `print("\"")` 输出:`"` | +| `\n` | 换行 | `print("\n")` 输出一个新行 | +| `\r` | 回车 | `print("Hello\rWorld!")` 输出:`World!` | +| `\t` | 横向制表符(Tab) | `print("Hello\tWorld!")` 输出:`Hello World!` | +| `\b` | 退格(Backspace) | `print("Hello \bWorld!")` 输出:`Hello World!` | +| `\f` | 换页 | `print("Hello \fWorld!")` 输出:`Hello World!` | +| `\a` | 响铃 | `print("\a")` 执行后电脑有响声 | +| `\000` | 空 | `print("\000")` 输出:`` | +| `\v` | 纵向制表符 | `print("Hello \vWorld!")` 输出:`Hello World!` | +| `\other` | 其他的字符以普通格式输出 | | +| `\xhh` | 二进制数,代表一个ASCII字符 | `print("\x48\x65\x6c\x6c\x6f")` 输出:`Hello` | +| `\ooo` | 八进制数,代表一个ASCII字符 | `print("\141\142\143")` 输出:`abc` | +| `\uhhhh` | 16进制数,代表一个Unicode字符 | `print("\u6211\u4eec")` 输出:`我们` | ### 运算 -| 操作符 | 描述 | 实例 | -| :----- | :----------------------------------------------------------- | :------------------------------ | -| + | 字符串连接 | a + b 输出结果: HelloPython | -| * | 重复输出字符串 | a*2 输出结果:HelloHello | -| [] | 通过索引获取字符串中字符 | a[1] 输出结果 **e** | -| [ : ] | 截取字符串中的一部分,遵循**左闭右开**原则,str[0:2] 是不包含第 3 个字符的。 | a[1:4] 输出结果 **ell** | -| in | 成员运算符 - 如果字符串中包含给定的字符返回 True | **'H' in a** 输出结果 True | -| not in | 成员运算符 - 如果字符串中不包含给定的字符返回 True | **'M' not in a** 输出结果 True | -| r/R | 原始字符串 - 原始字符串:所有的字符串都是直接按照字面的意思来使用,没有转义特殊或不能打印的字符。 原始字符串除在字符串的第一个引号前加上字母 **r**(可以大小写)以外,与普通字符串有着几乎完全相同的语法。 | `print( r'\n' ) print( R'\n' )` | +| 运算符 | 描述 | 示例 | +| :--------- | :------------------------------------------------- | :--------------------------------------------- | +| `+` | 连接字符串 | `"Hello " + "World!"` 输出:`Hello World!` | +| `*` | 重复字符串 | `"Hello " * 3` 输出:`Hello Hello Hello` | +| `[]` | 索引字符串 | `"Hello"[1]` 输出:`e` | +| `[:]` | 切片字符串 | `"Hello"[1:4]` 输出:`ell` | +| `in` | 成员运算符 - 如果字符串中包含给定的字符返回 True | `'H' in 'Hello'` 输出:`True` | +| `not in` | 成员运算符 - 如果字符串中不包含给定的字符返回 True | `'H' not in 'Hello'` 输出:`False` | +| `%` | 格式化字符串 | `"Hello, %s" % 'World!'` 输出:`Hello, World!` | +| `f-string` | 格式化字符串的新方式(Python 3.6+) | `f"Hello, {'World!'}` 输出:`Hello, World!` | + +### `f-strings` + +在 Python 3.6 及以后的版本中,格式化字符串引入了更为简洁的方式,即使用 `f` 字符作为字符串的前缀,创建所谓的格式化字符串字面值(f-string)。通过 f-string,可以在字符串中直接插入变量值或表达式,并可以通过冒号和数字来指定输出宽度等格式化选项。 ```python -a = "Hello" -b = "Python" - -print("a + b 输出结果:", a + b) # a + b 输出结果: HelloPython -print("a * 2 输出结果:", a * 2) # a * 2 输出结果: HelloHello -print("a[1] 输出结果:", a[1]) # a[1] 输出结果: e -print("a[1:4] 输出结果:", a[1:4]) # a[1:4] 输出结果: ell - -if( "H" in a) : # H 在变量 a 中 - print("H 在变量 a 中") -else : - print("H 不在变量 a 中") - -if( "M" not in a) : # M 不在变量 a 中 - print("M 不在变量 a 中") -else : - print("M 在变量 a 中") - -print (r'\n') # \n -print (R'\n') # \n -``` +# {a:10} 表示将变量 a 插入到字符串中,并占用 10 个字符的宽度。不足 10 个字符,将使用空格进行填充。 -### 格式化 - -在 Python 中,字符串格式化使用与 C 中 sprintf 函数一样的语法。尽管这样可能会用到非常复杂的表达式,但最基本的用法是将一个值插入到一个有字符串格式符 %s 的字符串中。 - -```python -print ("我叫 %s 今年 %d 岁!" % ('小明', 10)) # 我叫 小明 今年 10 岁! -``` - -#### 符号 - -| 符 号 | 描述 | -| :----- | :----------------------------------- | -| %c | 格式化字符及其ASCII码 | -| %s | 格式化字符串 | -| %d | 格式化整数 | -| %u | 格式化无符号整型 | -| %o | 格式化无符号八进制数 | -| %x | 格式化无符号十六进制数 | -| %X | 格式化无符号十六进制数(大写) | -| %f | 格式化浮点数字,可指定小数点后的精度 | -| %e | 用科学计数法格式化浮点数 | -| %E | 作用同%e,用科学计数法格式化浮点数 | -| %g | %f和%e的简写 | -| %G | %f 和 %E 的简写 | -| %p | 用十六进制数格式化变量的地址 | - -#### 辅助指令 - -| 符号 | 功能 | -| :---- | :----------------------------------------------------------- | -| * | 定义宽度或者小数点精度 | -| - | 用做左对齐 | -| + | 在正数前面显示加号( + ) | -| `` | 在正数前面显示空格 | -| # | 在八进制数前面显示零('0'),在十六进制前面显示'0x'或者'0X'(取决于用的是'x'还是'X') | -| 0 | 显示的数字前面填充'0'而不是默认的空格 | -| % | '%%'输出一个单一的'%' | -| (var) | 映射变量(字典参数) | -| m.n. | m 是显示的最小总宽度,n 是小数点后的位数(如果可用的话) | - -### `f`格式化输出 - -**Python 3.6** 以后,格式化字符串还有更为简洁的书写方式,就是在字符串前加上字母`f`,我们可以使用下面的语法糖来简化上面的代码。其可以通过变量后 冒号+数字 指定输出宽度,例如 {a:10} 将占用 10 个字符宽度。 - -```python a, b = 5, 10 print(f'{a:10} * {b:10} = {a * b}') ``` -### 常用函数 +#### PEP 701 的更新 + +在 Python 3.11 版本中,f-strings 还存在一些限制,导致在某些情况下使用起来不够灵活。PEP 701 对 f-strings 进行了一些更新,解除了一些限制,使得 f-strings 可以更灵活地使用,包括以下方面的更新: + +在 Python 3.12 版本中,对 f-strings 进行了更新,解除了之前的一些限制,使得 f-strings 更加灵活和强大。更新主要包括以下方面: + +##### 引号重用 + +在 Python 3.11 中,f-string 的表达式组件使用与包含 f-string 相同的引号会导致 SyntaxError。而在 Python 3.12 中,可以重用引号,不再限制引号的使用。 ```python -str1 = 'hello, world!' - -# 通过内置函数len计算字符串的长度 -len(str1) # 13 - -# 获得字符串首字母大写的拷贝 -str1.capitalize() # Hello, world! - -# 获得字符串每个单词首字母大写的拷贝 -str1.title() # Hello, World! - -# 获得字符串变大写后的拷贝 -str1.upper() # HELLO, WORLD! - -# 从字符串中查找子串所在位置 -str1.find('or') # 8 -str1.find('shit') # -1 - -# 与find类似但找不到子串时会引发异常 -# print(str1.index('or')) -# print(str1.index('shit')) - -# 检查字符串是否以指定的字符串开头 -str1.startswith('He') # False -str1.startswith('hel') # True - -# 检查字符串是否以指定的字符串结尾 -str1.endswith('!') # True - -# 将字符串以指定的宽度居中并在两侧填充指定的字符 -str1.center(50, '*') - -# 将字符串以指定的宽度靠右放置左侧填充指定的字符 -str1.rjust(50, ' ') - -str2 = 'abc123456' -# 检查字符串是否由数字构成 -str2.isdigit() # False - -# 检查字符串是否以字母构成 -str2.isalpha() # False - -# 检查字符串是否以数字和字母构成 -str2.isalnum() # True - -str3 = ' jackfrued@126.com ' -# 获得字符串修剪左右两侧空格之后的拷贝 -str3.strip() +songs = ['Take me back to Eden', 'Alkaline', 'Ascensionism'] +result1 = f"This is the playlist: {', '.join(songs)}" +print(result1) +# 输出:'This is the playlist: Take me back to Eden, Alkaline, Ascensionism' ``` +##### 多行表达式和注释 +在 Python 3.11 中,f-string 的表达式必须在一行内定义,即使外部表达式可以跨多行,这会使得代码可读性变差。而在 Python 3.12 中,可以定义跨多行的表达式,并且可以包含注释,提高了代码的可读性。 -## 列表(List) +```python +result2 = f"This is the playlist: {', '.join([ + 'Take me back to Eden', # My, my, those eyes like fire + 'Alkaline', # Not acid nor alkaline + 'Ascensionism' # Take to the broken skies at last +])}" +print(result2) +# 输出:'This is the playlist: Take me back to Eden, Alkaline, Ascensionism' +``` + +##### 反斜杠和 Unicode 字符 + +在 Python 3.11 中,f-string 的表达式不能包含任何反斜杠字符,也不能包含 Unicode 转义序列。而在 Python 3.12 中,可以包含反斜杠和 Unicode 字符,使得表达式更加灵活。 + +```python +songs = ['Take me back to Eden', 'Alkaline', 'Ascensionism'] + +result3 = f"This is the playlist: {'\n'.join(songs)}" +print(result3) +# 输出: +# This is the playlist: Take me back to Eden +# Alkaline +# Ascensionism + +result4 = f"This is the playlist: {'\N{BLACK HEART SUIT}'.join(songs)}" +print(result4) +# 输出:'This is the playlist: Take me back to Eden♥Alkaline♥Ascensionism' +``` + +以上代码展示了 Python 3.12 中 f-strings 的更新。可以看到,我们可以在 f-string 中使用引号重用,多行表达式和注释,以及包含反斜杠和 Unicode 字符的表达式,这些更新使得 f-strings 更加灵活和强大,更方便地进行字符串格式化。同时,通过使用 PEG 解析器实现的更新,使得错误消息更加准确,有助于代码调试。 + +### 常用方法 + +| 函数 | 描述 | 示例 | +| :-------------------------- | :----------------------------------------- | :----------------------------------------------------------- | +| len(str) | 返回字符串长度 | `len('hello, world!')` 输出结果:13 | +| str.capitalize() | 返回首字母大写的字符串 | `'hello, world!'.capitalize()` 输出结果:Hello, world! | +| str.title() | 返回每个单词首字母大写的字符串 | `'hello, world!'.title()` 输出结果:Hello, World! | +| str.upper() | 返回字符串变大写后的拷贝 | `'hello, world!'.upper()` 输出结果:HELLO, WORLD! | +| str.find(sub) | 查找子串在字符串中的起始位置,未找到返回-1 | `'hello, world!'.find('or')` 输出结果:8 | +| str.startswith(prefix) | 检查字符串是否以指定的前缀开头 | `'hello, world!'.startswith('He')` 输出结果:False | +| str.endswith(suffix) | 检查字符串是否以指定的后缀结尾 | `'hello, world!'.endswith('!')` 输出结果:True | +| str.center(width, fillchar) | 将字符串以指定宽度居中并在两侧填充指定字符 | `'hello'.center(10, '*')` 输出结果:***hello*** | +| str.rjust(width, fillchar) | 将字符串以指定宽度靠右并在左侧填充指定字符 | `'hello'.rjust(10, '*')` 输出结果:*****hello | +| str.isdigit() | 检查字符串是否由数字构成 | `'abc123456'.isdigit()` 输出结果:False | +| str.isalpha() | 检查字符串是否由字母构成 | `'abc123456'.isalpha()` 输出结果:False | +| str.isalnum() | 检查字符串是否由字母和数字构成 | `'abc123456'.isalnum()` 输出结果:True | +| str.strip() | 返回修剪左右两侧空格之后的字符串 | `' jackfrued@126.com '.strip()` 输出结果:'[jackfrued@126.com](mailto:jackfrued@126.com)' | + +## 列表 列表(`list`)是一种结构化的、非标量类型,它是值的有序序列,每个值都可以通过索引进行标识,定义列表可以将列表的元素放在`[]`中,多个元素用`,`进行分隔,可以使用`for`循环对列表元素进行遍历,也可以使用`[]`或`[:]`运算符取出列表中的一个或多个元素。 -### 操作 +### 定义 ```python # 列表定义 -list1 = [ 'abcd', 786 , 2.23, 'runoob', 70.2 ] +list1 = [ 'abcd', 786, 2.23, 'runoob', 70.2 ] tinylist = [123, 'runoob'] -print (list1) # 输出完整列表 -print (list1[0]) # 输出列表第一个元素 -print (list1[1:3]) # 从第二个开始输出到第三个元素 -print (list1[2:]) # 输出从第三个元素开始的所有元素 -print (tinylist * 2) # 输出两次列表 -print (list1 + tinylist) # 连接列表 +# 输出完整列表 +print(list1) # 输出结果:['abcd', 786, 2.23, 'runoob', 70.2] + +# 输出列表第一个元素 +print(list1[0]) # 输出结果:'abcd' + +# 从第二个开始输出到第三个元素 +print(list1[1:3]) # 输出结果:[786, 2.23] + +# 输出从第三个元素开始的所有元素 +print(list1[2:]) # 输出结果:[2.23, 'runoob', 70.2] + +# 输出两次列表 +print(tinylist * 2) # 输出结果:[123, 'runoob', 123, 'runoob'] + +# 连接列表 +print(list1 + tinylist) # 输出结果:['abcd', 786, 2.23, 'runoob', 70.2, 123, 'runoob'] # 通过循环用下标遍历列表元素 for index in range(len(list1)): print(list1[index]) - + # 通过for 循环遍历列表元素 for elem in list1: print(elem) - + # 通过 enumerate 函数处理列表之后再遍历可以同时获得元素索引和值 for index, elem in enumerate(list1): print(index, elem) - -# 添加元素 -list1.append(200) -list1.insert(1, 400) - -# 合并两个列表 -# list1.extend([1000, 2000]) -list1 += [1000, 2000] - -# 指定位置插入元素 -list1.insert(2,700) - -# 删除元素 -list1.remove(700) - -# 指定位置删除元素 -list1.pop(0) -list1.pop(len(list1) - 1) - -# 删除列表所有元素 -list1.clear() - -# 返回元素索引 -list1.index(700) - -# 返回元素出现次数 -list1.count(700) - -# 翻转列表中的元素 -list1.reverse() - -# 返回列表的浅拷贝 -list1.copy() ``` -### 切片 - -![img](https://static.7wate.com/img/2022/11/20/03b82ae504a58.png) +### 运算 ```python -fruits = ['grape', 'apple', 'strawberry', 'waxberry'] -fruits += ['pitaya', 'pear', 'mango'] +# 创建列表 +list1 = [1, 2, 3] +list2 = [4, 5, 6] -# 列表切片 -fruits2 = fruits[1:4] -print(fruits2) # apple strawberry waxberry +# 连接列表,返回一个新的列表 +list3 = list1 + list2 +print(list3) # 输出:[1, 2, 3, 4, 5, 6] -# 可以通过完整切片操作来复制列表 -fruits3 = fruits[:] -fruits4 = fruits[-3:-1] -print(fruits4) # ['pitaya', 'pear'] +# 复制列表,返回一个新的列表 +list4 = list1 * 3 +print(list4) # 输出:[1, 2, 3, 1, 2, 3, 1, 2, 3] -# 通过反向切片操作来获得倒转后的列表的拷贝 -fruits5 = fruits[::-1] -print(fruits5) # ['mango', 'pear', 'pitaya', 'waxberry', 'strawberry', 'apple', 'grape'] +# 计算元素个数 +length = len(list1) +print(length) # 输出:3 + +# 判断元素是否在列表中 +result = 4 in list1 +print(result) # 输出:False + +result = 2 in list1 +print(result) # 输出:True + +# 访问元素,可以使用索引,索引从0开始 +elem = list1[0] +print(elem) # 输出:1 + +# 切片操作 +list5 = list1[1:3] +print(list5) # 输出:[2, 3] + +# 切片时可以指定步长 +list6 = list1[0:3:2] +print(list6) # 输出:[1, 3] + +# 切片时可以省略开始或结束索引 +list7 = list1[:2] +list8 = list1[1:] +print(list7) # 输出:[1, 2] +print(list8) # 输出:[2, 3] + +# 反向切片 +list9 = list1[::-1] +print(list9) # 输出:[3, 2, 1] + +# 修改元素值 +list1[0] = 100 +print(list1) # 输出:[100, 2, 3] + +# 列表末尾添加元素 +list1.append(4) +print(list1) # 输出:[100, 2, 3, 4] + +# 列表末尾删除元素 +list1.pop() +print(list1) # 输出:[100, 2, 3] + +# 指定位置插入元素 +list1.insert(1, 200) +print(list1) # 输出:[100, 200, 2, 3] + +# 删除指定位置的元素 +del list1[2] +print(list1) # 输出:[100, 200, 3] + +# 清空列表 +list1.clear() +print(list1) # 输出:[] ``` +### 常用方法 + +| 函数 | 描述 | +| :----------------------------------- | :----------------------------------------------------------- | +| `len(list)` | 返回列表元素的个数。 | +| `list.append(x)` | 在列表末尾添加元素 `x`。 | +| `list.insert(i, x)` | 在指定位置 `i` 前插入元素 `x`。 | +| `list.extend(iterable)` | 将可迭代对象中的元素逐个添加到列表。 | +| `list.remove(x)` | 删除列表中第一个值为 `x` 的元素。 | +| `list.pop([i])` | 移除并返回列表中指定位置 `i` 处的元素。如果未指定索引,默认删除并返回最后一个元素。 | +| `list.clear()` | 清空列表中的所有元素。 | +| `list.index(x[, start[, end]])` | 返回列表中第一个值为 `x` 的元素的索引。如果未找到该元素,会引发 `ValueError` 异常。 | +| `list.count(x)` | 返回元素 `x` 在列表中出现的次数。 | +| `list.sort(key=None, reverse=False)` | 对列表进行排序,如果指定参数 `key` 则按照 `key` 进行排序,如果指定参数 `reverse` 则为降序。 | +| `list.reverse()` | 将列表中的元素倒序排列。 | +| `list.copy()` | 返回列表的浅拷贝,即创建一个新列表并复制原列表中的元素。 | + ## 元组 -Python 中的元组与列表类似也是一种容器数据类型,元组使用小括号 **( )**,列表使用方括号 **[ ]**;元组可以用一个变量(对象)来存储多个数据,不同之处在于**元组的元素不能修改**。 +Python 中的元组是一种容器数据类型,使用小括号 `()` 定义,类似于列表,但元组的元素不能修改,是不可变的。元组用于存储多个数据,可以包含不同类型的元素。与列表不同,元组在定义时可以省略括号,但在实际使用中建议加上括号,以增强代码的可读性。 ![img](https://static.7wate.com/img/2022/11/20/49dc8db289c04.png) -### 操作 +### 定义 ```python # 定义元组 @@ -349,17 +358,17 @@ t = ('骆昊', 38, True, '四川成都') # 定义空元组 t1 = () -# 元组中只包含一个元素时,需要在元素后面添加逗号 , ,否则括号会被当作运算符使用。 +# 元组中只包含一个元素时,需要在元素后面添加逗号 `,`,否则括号会被当作运算符使用。 tup1 = (50) # 不加逗号,类型为整型 tup1 = (50,) # 加上逗号,类型为元组 # 遍历元组中的值 for member in t: print(member) - + # 重新给元组赋值 -# t[0] = '王大锤' # TypeError -# 变量t重新引用了新的元组原来的元组将被垃圾回收 +# t[0] = '王大锤' # TypeError,元组不可修改 +# 变量t重新引用了新的元组,原来的元组将被垃圾回收 t = ('王大锤', 20, True, '云南昆明') # 将元组转换成列表 @@ -379,204 +388,280 @@ tup = ('Google', 'Runoob', 1997, 2000) del tup ``` -### 切片 - -![img](https://static.7wate.com/img/2022/11/20/62a764ad48690.png) +### 运算 ```python -tup = ('Google', 'Runoob', 'Taobao', 'Wiki', 'Weibo','Weixin') +# 创建元组 +tup1 = (1, 2, 3) +tup2 = (4, 5, 6) + +# 连接元组,返回一个新的元组 +tup3 = tup1 + tup2 +print(tup3) # 输出:(1, 2, 3, 4, 5, 6) + +# 复制元组,返回一个新的元组 +tup4 = tup1 * 3 +print(tup4) # 输出:(1, 2, 3, 1, 2, 3, 1, 2, 3) + +# 计算元素个数 +length = len(tup1) +print(length) # 输出:3 + +# 判断元素是否在元组中 +result = 4 in tup1 +print(result) # 输出:False + +result = 2 in tup1 +print(result) # 输出:True + +# 访问元素,可以使用索引,索引从0开始 +elem = tup1[0] +print(elem) # 输出:1 + +# 切片操作 +tup5 = tup1[1:3] +print(tup5) # 输出:(2, 3) + +# 切片时可以指定步长 +tup6 = tup1[0:3:2] +print(tup6) # 输出:(1, 3) + +# 切片时可以省略开始或结束索引 +tup7 = tup1[:2] +tup8 = tup1[1:] +print(tup7) # 输出:(1, 2) +print(tup8) # 输出:(2, 3) + +# 反向切片 +tup9 = tup1[::-1] +print(tup9) # 输出:(3, 2, 1) ``` -| Python 表达式 | 结果 | 描述 | -| :------------ | :---------------------------------------------- | :----------------------------------------------- | -| tup[1] | 'Runoob' | 读取第二个元素 | -| tup[-2] | 'Weibo' | 反向读取,读取倒数第二个元素 | -| tup[1:] | ('Runoob', 'Taobao', 'Wiki', 'Weibo', 'Weixin') | 截取元素,从第二个开始后的所有元素。 | -| tup[1:4] | ('Runoob', 'Taobao', 'Wiki') | 截取元素,从第二个开始到第四个元素(索引为 3)。 | +### 常用方法 + +| 函数 | 描述 | +| :------------------------------- | :----------------------------------------------------------- | +| `len(tuple)` | 返回元组中元素的个数。 | +| `tuple.count(x)` | 返回元组中元素 `x` 出现的次数。 | +| `tuple.index(x[, start[, end]])` | 返回元组中第一个值为 `x` 的元素的索引。如果未找到该元素,会引发 `ValueError` 异常。 | +| `tuple + tuple` | 将两个元组连接成一个新的元组。 | +| `tuple * n` | 将元组重复 `n` 次。 | +| `x in tuple` | 判断元素 `x` 是否在元组中,如果在返回 `True`,否则返回 `False`。 | +| `x not in tuple` | 判断元素 `x` 是否不在元组中,如果不在返回 `True`,否则返回 `False`。 | ## 集合 集合(set)是由不重复元素组成的无序容器。基本用法包括成员检测、消除重复元素。集合对象支持合集、交集、差集、对称差分等数学运算。 -创建集合用花括号或 set() 函数。注意,创建空集合只能用 set(),不能用 {},{} 创建的是空字典 +### 定义 -### 操作 +集合可以通过花括号 `{}` 或 `set()` 函数创建。注意,创建空集合只能用 `set()`,不能用 `{}`,因为 `{}` 创建的是空字典。 ```python -# 演示去重功能 -basket = {'apple', 'orange', 'apple', 'pear', 'orange', 'banana'} -print(basket) # 输出 {'orange', 'banana', 'pear', 'apple'} +# 创建集合 +set1 = {1, 2, 3, 4, 5} +set2 = {3, 4, 5, 6, 7} +set3 = {5, 6, 7, 8, 9} # 判断元素是否在集合内 -'orange' in basket # True -'crabgrass' in basket # Flase +3 in set1 # True +10 in set1 # False -# 添加 -basket.add(400) -basket.add(500) +# 添加元素 +set1.add(6) # {1, 2, 3, 4, 5, 6} +set1.add(7) # {1, 2, 3, 4, 5, 6, 7} +set1.add(3) # {1, 2, 3, 4, 5, 6, 7},重复的元素不会被添加 -# 删除 -basket.remove('apple') -basket.discard('orange') +# 删除元素 +set1.remove(4) # {1, 2, 3, 5, 6, 7} +set1.discard(6) # {1, 2, 3, 5, 7} +set1.discard(8) # {1, 2, 3, 5, 7},如果元素不存在,discard 方法不会引发异常 # 元素个数 -len(basket) +len(set1) # 5 -# 清空 -basket.clear() +# 清空集合 +set1.clear() # set() ``` ### 运算 ```python +set1 = {1, 2, 3, 4, 5} +set2 = {3, 4, 5, 6, 7} +set3 = {5, 6, 7, 8, 9} + # 交集 -print(set1 & set2) +set1 & set2 # {3, 4, 5} + # 并集 -print(set1 | set2) +set1 | set2 # {1, 2, 3, 4, 5, 6, 7} + # 差集 -print(set1 - set2) -# 对称差运算 -print(set1 ^ set2) +set1 - set2 # {1, 2} + +# 对称差运算(在 set1 或 set2 中,但不会同时出现在二者中) +set1 ^ set2 # {1, 2, 6, 7} # 判断子集和超集 -print(set2 <= set1) -print(set3 <= set1) -print(set1 >= set2) -print(set1 >= set3) +set2 <= set1 # False,set2 是 set1 的子集吗? +set3 <= set1 # False,set3 是 set1 的子集吗? +set1 >= set2 # False,set1 是 set2 的超集吗? +set1 >= set3 # False,set1 是 set3 的超集吗? ``` +### 常用方法 + +| 函数 | 描述 | +| :------------------------------------- | :----------------------------------------------------------- | +| set.add(x) | 将元素 x 添加到集合中 | +| set.remove(x) | 删除集合中的元素 x,如果 x 不在集合中会引发 KeyError | +| set.discard(x) | 删除集合中的元素 x,如果 x 不在集合中不会引发异常 | +| set.pop() | 随机删除集合中的一个元素,并返回删除的元素 | +| set.clear() | 清空集合中的所有元素 | +| len(set) | 返回集合中元素的个数 | +| x in set | 判断元素 x 是否在集合中,如果在返回 True,否则返回 False | +| set1.isdisjoint(set2) | 判断两个集合是否没有共同的元素,如果没有返回 True,否则返回 False | +| set1.issubset(set2) | 判断集合 set1 是否是集合 set2 的子集,如果是返回 True,否则返回 False | +| set1.issuperset(set2) | 判断集合 set1 是否是集合 set2 的超集,如果是返回 True,否则返回 False | +| set1.union(set2) | 返回集合 set1 和 set2 的并集 | +| set1.intersection(set2) | 返回集合 set1 和 set2 的交集 | +| set1.difference(set2) | 返回集合 set1 和 set2 的差集,即在 set1 中但不在 set2 中的元素 | +| set1.symmetric_difference(set2) | 返回集合 set1 和 set2 的对称差集,即在 set1 或 set2 中,但不会同时出现在二者中的元素 | +| set1.update(set2) | 将集合 set2 中的元素添加到 set1 中 | +| set1.intersection_update(set2) | 将集合 set1 中不在 set2 中的元素删除 | +| set1.difference_update(set2) | 将集合 set1 和 set2 的差集中的元素从 set1 中删除 | +| set1.symmetric_difference_update(set2) | 将集合 set1 和 set2 的对称差集中的元素添加到 set1 中 | +| set.copy() | 返回集合的浅拷贝 | +| set.pop() | 随机删除并返回集合中的一个元素 | + ## 字典 -字典是另一种可变容器模型,且**可存储任意类型对象**。字典与列表、集合不同的是,字典的每个元素都是由一个键和一个值组成的**键值对**,键和值通过冒号分开。键必须是唯一的,但值则不必。 - ![img](https://static.7wate.com/img/2022/11/20/6f5d818eb8092.png) +字典是另一种可变容器模型,且**可存储任意类型对象**。字典与列表、集合不同的是,它是一种键-值(key-value)对应的数据结构。每个元素都是**由一个键和一个值组成的键值对**,键和值通过冒号分开。键必须是唯一的,但值则不必。 + ![img](https://static.7wate.com/img/2022/11/20/0e8264c5acc5a.png) -字典实例: +### 定义 ```python -tinydict = {'name': 'runoob', 'likes': 123, 'url': 'www.runoob.com'} +# 使用大括号创建字典 +person = {'name': 'John', 'age': 30, 'city': 'New York'} + +# 使用 dict() 创建字典 +empty_dict = dict() + +# 访问字典中的元素 +print(person['name']) # 输出:John +print(person['age']) # 输出:30 +print(person['city']) # 输出:New York ``` -### 操作 +### 运算 ```python -# 使用大括号 {} 来创建空字典 -tinydict = {'Name': 'Runoob', 'Age': 7, 'Class': 'First'} -# 使用内建函数 dict() 创建字典 -emptyDict = dict() +# 修改字典中的元素 +person['age'] = 31 +print(person) # 输出:{'name': 'John', 'age': 31, 'city': 'New York'} -# 打印字典 -print(tinydict) - -# 查看字典的数量 -print("Length:", len(tinydict)) - -# 查看类型 -print(type(tinydict)) +# 添加新的键值对 +person['job'] = 'Engineer' +print(person) # 输出:{'name': 'John', 'age': 31, 'city': 'New York', 'job': 'Engineer'} -# 访问字典里的值 -print ("tinydict['Name']: ", tinydict['Name']) +# 删除字典中的元素 +del person['city'] +print(person) # 输出:{'name': 'John', 'age': 31, 'job': 'Engineer'} -# 修改字典 -tinydict['Age'] = 8 # 更新 Age -tinydict['School'] = "菜鸟教程" # 添加信息 +# 清空字典 +person.clear() +print(person) # 输出:{} + +# 遍历字典的键 +for key in person: + print(key) + +# 遍历字典的值 +for value in person.values(): + print(value) + +# 遍历字典的键值对 +for key, value in person.items(): + print(key, value) + +# 判断键是否存在 +if 'name' in person: + print("Name exists.") -# 删除字典元素 -del tinydict['Name'] # 删除键 'Name' -tinydict.clear() # 清空字典 -del tinydict # 删除字典 ``` -### 循环 +### 常用方法 -在字典中循环时,用 `items()` 方法可同时取出键和对应的值: - -```python -knights = {'gallahad': 'the pure', 'robin': 'the brave'} -for k, v in knights.items(): - print(k, v) - -# gallahad the pure -# robin the brave -``` - -在序列中循环时,用 `enumerate()` 函数可以同时取出位置索引和对应的值: - -```python -for i, v in enumerate(['tic', 'tac', 'toe']): - print(i, v) - -# 0 tic -# 1 tac -# 2 toe -``` - -同时循环两个或多个序列时,用 `zip()` 函数可以将其内的元素一一匹配: - -```python -questions = ['name', 'quest', 'favorite color'] -answers = ['lancelot', 'the holy grail', 'blue'] -for q, a in zip(questions, answers): - print(f'What is your {q}? It is {a}.') - -# What is your name? It is lancelot. -# What is your quest? It is the holy grail. -# What is your favorite color? It is blue. -``` +| 方法 | 描述 | +| :----------------------- | :--------------------------------------- | +| `keys()` | 返回字典所有键的列表 | +| `values()` | 返回字典所有值的列表 | +| `items()` | 返回字典所有键值对的元组列表 | +| `get(key, default=None)` | 返回指定键的值,如果键不存在,返回默认值 | +| `pop(key)` | 删除指定键的值,并返回该键对应的值 | +| `popitem()` | 随机删除一个键值对,并返回该键值对 | +| `clear()` | 删除字典中的所有键值对 | +| `update(other_dict)` | 将其他字典的键值对更新到当前字典 | +| `copy()` | 返回字典的浅拷贝(复制字典中的键值对) | ## 推导式 -Python 推导式是一种独特的数据处理方式,可以从一个数据序列构建另一个新的数据序列的结构体。 +推导式是一种强大的 Python 特性,可以快速、简洁地创建新的数据序列。它可以从一个数据序列构建另一个新的数据序列的结构体。 ### 列表 +列表推导式是最常用的推导式,它通过在一个可迭代对象上应用表达式来创建一个新的列表。 + ```python # 语法 -[out_exp_res for out_exp in input_list] -[out_exp_res for out_exp in input_list if condition] +new_list = [out_exp_res for out_exp in input_list] +new_list = [out_exp_res for out_exp in input_list if condition] -# 实例 +# 示例 multiples = [i for i in range(30) if i % 3 == 0] -print(multiples) # [0, 3, 6, 9, 12, 15, 18, 21, 24, 27] +print(multiples) # 输出:[0, 3, 6, 9, 12, 15, 18, 21, 24, 27] ``` ### 字典 ```python # 语法 -{ key_expr: value_expr for value in collection } -{ key_expr: value_expr for value in collection if condition } +new_dict = {key_expr: value_expr for value in collection} +new_dict = {key_expr: value_expr for value in collection if condition} -# 实例 +# 示例 dic = {x: x**2 for x in (2, 4, 6)} -print(dic) # {2: 4, 4: 16, 6: 36} +print(dic) # 输出:{2: 4, 4: 16, 6: 36} ``` ### 集合 ```python # 语法 -{ expression for item in Sequence } -{ expression for item in Sequence if conditional } +new_set = {expression for item in sequence} +new_set = {expression for item in sequence if conditional} -# 实例 +# 示例 a = {x for x in 'abracadabra' if x not in 'abc'} -print(a) # {'d', 'r'} +print(a) # 输出:{'d', 'r'} ``` ### 元组 ```python # 语法 -(expression for item in Sequence ) -(expression for item in Sequence if conditional ) +new_tuple = (expression for item in sequence) +new_tuple = (expression for item in sequence if conditional) -# 实例 -a = (x for x in range(1,10)) -print(a) # 返回的是生成器对象 -tuple(a) # 使用 tuple() 函数,可以直接将生成器对象转换成元组 +# 示例 +a = (x for x in range(1, 10)) +print(a) # 输出: at 0x7f900dfdd200> +print(tuple(a)) # 输出:(1, 2, 3, 4, 5, 6, 7, 8, 9) ``` ### 其他 @@ -587,28 +672,32 @@ print(f) f = [x + y for x in 'ABCDE' for y in '1234567'] print(f) -# 用列表的生成表达式语法创建列表容器 -# 用这种语法创建列表之后元素已经准备就绪所以需要耗费较多的内存空间 f = [x ** 2 for x in range(1, 1000)] print(sys.getsizeof(f)) # 查看对象占用内存的字节数 print(f) -# 请注意下面的代码创建的不是一个列表而是一个生成器对象 -# 通过生成器可以获取到数据但它不占用额外的空间存储数据 -# 每次需要数据的时候就通过内部的运算得到数据(需要花费额外的时间) +# 生成器表达式,不占用额外内存 f = (x ** 2 for x in range(1, 1000)) -print(sys.getsizeof(f)) # 相比生成式生成器不占用存储数据的空间 +print(sys.getsizeof(f)) # 相比列表生成式,生成器不占用存储数据的空间 print(f) for val in f: print(val) + ``` ## 迭代器 -可以利用 for 循环的对象,都叫可迭代对象。列表、元组、字典、字符串等都是可迭代对象。 +可迭代对象是 Python 中的一种数据结构,例如列表、元组、字典、字符串等都是可迭代对象。迭代器是一种特殊的可迭代对象,它可以通过调用 `iter()` 函数获取,然后使用 `next()` 函数逐个访问元素。迭代器在其生命周期中,会有四个状态:`GEN_CREATED`、`GEN_RUNNING`、`GEN_SUSPENDED` 和 `GEN_CLOSED`。 -迭代器非常普遍的使用并使得 Python 成为一个统一的整体。 在幕后,for 语句会在容器对象上调用 `iter()`。 该函数返回一个定义了 `__next__()` 方法的迭代器对象,此方法将逐一访问容器中的元素。 当元素用尽时,`__next__()` 将引发 `StopIteration` 异常来通知终止 `for` 循环。 可以使用 `next()` 内置函数来调用 __next__() 方法; +```mermaid +graph LR + A[生成器已创建] --> B[解释器执行中] + B --> C[在 yield 表达式处暂停] + C --> D[解释器执行中] + D --> C + D --> E[生成器执行结束] +``` - **iter()**:创建迭代器。 - **next()**:输出迭代器的下一个元素。 @@ -617,42 +706,40 @@ for val in f: s = 'abc' it = iter(s) -print(it) # -next(it) # 'a' -next(it) # 'b' -next(it) # 'c' -next(it) +print(it) # 输出: +print(next(it)) # 输出:'a' +print(next(it)) # 输出:'b' +print(next(it)) # 输出:'c' # 抛出异常 -Traceback (most recent call last): - File "", line 1, in - next(it) -StopIteration -``` +# 迭代器用尽后会引发 StopIteration 异常 +try: + print(next(it)) +except StopIteration: + print("迭代器用尽了。") -```python -# 为类添加迭代器 -class Reverse: - """Iterator for looping over a sequence backwards.""" - def __init__(self, data): - self.data = data - self.index = len(data) - - def __iter__(self): - return self - - def __next__(self): - if self.index == 0: - raise StopIteration - self.index = self.index - 1 - return self.data[self.index] ``` ## 生成器 -生成器(Generator)是一个可以像迭代器那样使用 for 循环来获取元素的函数。 +生成器是一种特殊的迭代器,它是一个可以像迭代器一样使用 for 循环来获取元素的函数。生成器函数使用 `yield` 关键字将一个普通函数改造成生成器函数,`yield` 相当于 `return` 但有所不同,`yield` 虽然返回了值但函数并没有结束,而是暂停在 `yield` 处,下次调用 `next()` 或循环迭代时会从 `yield` 处继续执行。生成器在其生命周期中会有四个状态:`GEN_CREATED`、`GEN_RUNNING`、`GEN_SUSPENDED` 和 `GEN_CLOSED`。 -Python 中还有另外一种定义生成器的方式,就是通过 yield 关键字将一个普通函数改造成生成器函数。yield 相当于 return 但有所不同,yield 虽然返回了但是函数并没有结束。当函数运行到 yield 后,函数暂停运行并把 yield 后表达式的值返回出去。若 yield 没有接任何值,则返回 None。 +```mermaid +graph LR + A[生成器函数被调用] --> B[生成器已创建] + B --> C[解释器执行中] + C --> D[在 yield 表达式处暂停] + D --> C + C --> E[生成器执行结束] + + F[生成器已创建] --> G[GEN_CREATED] + G --> H[生成器正在执行] + H --> I[GEN_RUNNING] + I --> J[在 yield 表达式处暂停] + J --> H + I --> K[生成器执行结束] + K --> L[GEN_CLOSED] +``` 生成器在其生命周期中,会有如下四个状态: @@ -662,15 +749,15 @@ Python 中还有另外一种定义生成器的方式,就是通过 yield 关键 - GEN_CLOSED:生成器执行结束。 ```python -def fibonacci(n): # 生成器函数 - 斐波那契 +def fibonacci(n): a, b, counter = 0, 1, 0 while True: - if (counter > n): + if counter > n: return yield a a, b = b, a + b counter += 1 for i in fibonacci(10): - print(i,end=" ") + print(i, end=" ") # 输出:0 1 1 2 3 5 8 13 21 34 55 ``` diff --git a/wiki/programming-language/Python/入门/模块和包.md b/wiki/programming-language/Python/入门/模块和包.md index 8a895bb4..f24c65fc 100644 --- a/wiki/programming-language/Python/入门/模块和包.md +++ b/wiki/programming-language/Python/入门/模块和包.md @@ -8,46 +8,37 @@ tags: - Python sidebar_position: 7 author: 7Wate -date: 2022-11-28 +date: 2023-08-03 --- -在编程语言中,代码块、函数、类、模块,一直到包,逐级封装,层层调用。**在 Python 中,一个`.py`文件就是一个模块,模块是比类更高一级的封装。**在其他语言,被导入的模块也通常称为库。 +在编程语言中,我们常常会看到各种不同级别的封装,如代码块、函数、类、模块,甚至包,每个级别都会进行逐级调用。**在Python中,一个`.py`文件就被看作是一个模块,这实际上是比类级别更高的封装。**在其他的编程语言中,被导入的模块通常被称为库。 ## 模块 -**模块可以分为自定义模块、内置模块和第三方模块**。使用模块有什么好处? +在 Python 中,模块可以分为**自定义模块、内置模块(标准模块)和第三方模块。**使用模块主要有以下几个好处: -- 首先,提高了代码的可维护性。 -- 其次,编写代码不必从零开始。当一个模块编写完毕,就可以被其他的模块引用。不要重复造轮子,我们简简单单地使用已经有的模块就好了。 -- 使用模块还可以避免类名、函数名和变量名发生冲突。相同名字的类、函数和变量完全可以分别存在不同的模块中。但是也要注意尽量不要与内置函数名(类名)冲突。 +- **提高了代码的可维护性。**通过将代码拆分为多个模块,可以降低每个模块的复杂性,更容易进行理解和维护。 +- **重用代码。**当一个模块编写完毕,就可以被其他的模块引用。这避免了“重复造轮子”,允许开发者更加专注于新的任务。 +- **避免命名冲突。**同名的类、函数和变量可以分别存在于不同的模块中,防止名称冲突。但也要注意尽量避免与内置函数名(类名)冲突。 ### 自定义模块 -自主开发完成复用的模块。 +自定义模块是开发者根据实际需求编写的代码,封装为模块方便在多个地方使用。 ### 标准模块 -Python 拥有一个强大的标准库。Python语言的核心只包含数值、字符串、列表、字典、文件等常见类型和函数,而由 Python 标准库提供了系统管理、网络通信、文本处理、数据库接口、图形系统、XML 处理等额外的功能。 - -Python 标准库的主要功能有: - -- 文本处理,包含文本格式化、正则表达式、文本差异计算与合并、Unicode 支援,二进制数据处理等功能。 -- 文件系统功能,包含文件和目录操作、建立临时文件、文件压缩与归档、操作配置文件等功能。 -- 操作系统功能,包含线程与进程支持、IO 复用、日期与时间处理、调用系统函数、日志(logging)等功能。 -- 网络通信,包含网络套接字,SSL 加密通信、异步网络通信等功能。支持 HTTP,FTP,SMTP,POP,IMAP,NNTP,XMLRPC 等多种网络协议,并提供了编写网络服务器的框架。 -- W3C 格式支持,包含 HTML,SGML,XML 的处理。 -- 其它功能,包括国际化支持、数学运算、HASH、Tkinter 等。 +Python 拥有一个强大的标准库。Python语言的核心只包含数值、字符串、列表、字典、文件等常见类型和函数,而由 Python 标准库提供了系统管理、网络通信、文本处理、数据库接口、图形系统、XML 处理等额外的功能。如文本处理、文件系统操作、操作系统功能、网络通信、W3C 格式支持等。 ### 第三方模块 -Python 拥有大量的第三方模块,这也是其核心优点之一。基本上,所有的第三方模块都会在[PyPI - the Python Package Index](https://pypi.python.org/)上注册,只要找到对应的模块名字,即可用 pip 安装。 +Python 拥有大量的第三方模块,这也是其核心优点之一。这些模块通常在 Python 包管理系统 [PyPI](https://pypi.python.org/) 中注册,你可以通过 pip 工具来安装。 ## 包 -Python 为了避免模块名冲突,又引入了按目录来组织模块的方法,称为包(Package),**包是模块的集合,比模块又高一级的封装。**包是一个分层次的文件目录结构,它定义了一个由模块及子包,和子包下的子包等组成的 Python 的应用环境。**包名通常为全部小写,避免使用下划线。** +Python 为了避免模块名冲突,又引入了按目录来组织模块的方法,称为包(Package)。**包是模块的集合,比模块又高一级的封装。**包是一个分层次的文件目录结构,它定义了一个由模块及子包,和子包下的子包等组成的 Python 的应用环境。**一般来说,包名通常为全部小写,避免使用下划线。** ### 标准包 -简单来说标准包就是文件夹下必须存在 `__init__.py` 文件,该文件的内容可以为空。如果没有该文件,Python 无法识别出标准包。Python 中导入包后会初始化并执行 `__init__.py` 进行初始化;在 `__init__.py` 中,如果将`__all__` 定义为列表,其中包含对象名称的字符串,程序就可以通过 * 的方式导入。 +标准包就是文件夹下必须存在 `__init__.py` 文件,该文件的内容可以为空。如果没有该文件,Python 无法识别出标准包。Python 中导入包后会初始化并执行 `__init__.py` 进行初始化;在 `__init__.py` 中,如果将`__all__` 定义为列表,其中包含对象名称的字符串,程序就可以通过 * 的方式导入。 ```markdown test.py @@ -85,80 +76,55 @@ runoob2() # I'm in runoob2 ``` -## import +## 模块和包的导入 -`import` 语句不带 `from` 会分两步执行: +Python 模块是一个包含 Python 定义和语句的文件,模块可以定义函数,类和变量。模块也可以包含可执行的代码。**包是一种管理 Python 模块命名空间的形式,采用"点模块名称"。** -1. 查找一个模块,如果有必要还会加载并初始化模块。 +### 导入方式 -2. 在局部命名空间中为 import 语句发生位置所处的作用域定义一个或多个名称。 +在Python中,可以通过以下四种方式导入模块或包: -from 形式使用的过程略微繁复一些: +#### `import xx.xx` -1. 查找 from 子句中指定的模块,如有必要还会加载并初始化模块; - -2. 对于 import 子句中指定的每个标识符: - 1. 检查被导入模块是否有该名称的属性。 - 2. 如果没有,尝试导入具有该名称的子模块,然后再次检查被导入模块是否有该属性。 - 3. 如果未找到该属性,则引发 ImportError。 - 4. 否则的话,将对该值的引用存入局部命名空间,如果有 as 子句则使用其指定的名称,否则使用该属性的名称。 - -```python -# Python 中,模块(包、类、函数)的导入方式有以下四种: -import xx.xx -from xx.xx import xx -from xx.xx import xx as rename -from xx.xx import * -``` - -### import xx.xx - -将对象(这里的对象指的是包、模块、类或者函数,下同)中的所有内容导入。如果该对象是个模块,那么调用对象内的类、函数或变量时,需要以`module.xxx`的方式。 +这种方式将整个模块导入。如果模块中有函数、类或变量,我们需要以`module.xxx`的方式调用。 ```python # Module_a.py def func(): - print("this is module A!") - + print("This is module A!") + # Main.py import module_a -module_a.func() # 调用方法 +module_a.func() # 调用函数 ``` -### from xx.xx import xx.xx +#### `from xx.xx import xx` -从某个对象内导入某个指定的部分到当前命名空间中,不会将整个对象导入。这**种方式可以节省写长串导入路径的代码,但要小心名字冲突**。 +这种方式从某个模块中导入某个指定的部分到当前命名空间,不会将整个模块导入。这种方式可以节省代码量,但需要注意避免名字冲突。 ```python # Main.py from module_a import func - -module_a.func() # 错误的调用方式 -func() # 这时需要直接调用 func +func() # 直接调用 func ``` -### from xx.xx import xx as rename +#### `from xx.xx import xx as rename` -为了避免命名冲突,在导入的时候,可以给导入的对象重命名。 +为了避免命名冲突,我们可以在导入时重命名模块。 ```python # Main.py from module_a import func as f - -def func(): # main 模块内部已经有了 func 函数 - print("this is main module!") - -func() -f() +f() # 使用新名称 f 来调用函数 ``` -### from xx.xx import * +#### `from xx.xx import \*` -将对象内的所有内容全部导入。非常容易发生命名冲突,请慎用! +这种方式将模块中的所有内容全部导入,非常容易发生命名冲突,因此需要谨慎使用。 ```python # Main.py @@ -166,88 +132,93 @@ f() from module_a import * def func(): - print("this is main module!") + print("This is the main module!") -func() # 从 module 导入的 func 被 main 的 func 覆盖了 +func() # func 从 module_a 导入被 main 中的 func 覆盖 ``` ### 模块路径搜索顺序 -**不管在程序中执行了多少次import,一个模块只会被导入一次。**导入一个模块,Python 解析器对模块位置的搜索顺序是: +当我们尝试导入一个模块时,Python 解释器对模块位置的搜索顺序是: -1. Python 项目当前目录 -2. Python 搜索在 shell 变量 PYTHONPATH 下的每个目录。 -3. Python 默认搜索路径。UNIX下,默认路径一般为 /usr/local/lib/python/。 +1. Python 项目的当前目录 +2. 在环境变量 PYTHONPATH 中列出的所有目录 +3. Python 的安装目录和其他默认目录 -模块搜索路径存储在 system 模块的 sys.path 变量中。变量里包含当前目录,PYTHONPATH 由安装过程决定的默认目录。 +模块搜索路径存储在`sys`模块的`sys.path`变量中。 ```python import sys print(sys.path) - -# ['/workspace/PythonStudy', '/usr/local/lib/python310.zip', '/usr/local/lib/python3.10', '/usr/local/lib/python3.10/lib-dynload', '/home/user/.local/lib/python3.10/site-packages'] ``` -## 命名空间 +## 命名空间和作用域 -命名空间(Namespace)是从名称到对象的映射,大部分的命名空间都是通过 Python 字典来实现的。 +在Python中,命名空间(Namespace)是从名称到对象的映射,主要用于避免命名冲突。命名空间的生命周期取决于对象的作用域,如果对象执行完成,则该命名空间的生命周期就结束。 -命名空间提供了在项目中避免名字冲突的一种方法。各个命名空间是独立的,没有任何关系的,所以一个命名空间中不能有重名,但不同的命名空间是可以重名而没有任何影响。 - -一般有三种命名空间: - -- **内置名称(built-in names**):Python 语言内置的名称,比如函数名 abs、char 和异常名称 BaseException、Exception 等等。 -- **全局名称(global names)**:模块中定义的名称,记录了模块的变量,包括函数、类、其它导入的模块、模块级的变量和常量。 -- **局部名称(local names)**:函数中定义的名称,记录了函数的变量,包括函数的参数和局部定义的变量。(类中定义的也是) +### 命名空间类型 ![img](https://static.7wate.com/img/2022/11/20/7ee3813629181.png) -假设我们要使用变量 runoob,则 **Python 的查找顺序为:局部的命名空间 -> 全局命名空间 -> 内置命名空间。** +Python有三种命名空间: -如果找不到变量 runoob,它将放弃查找并引发一个 NameError 异常:`NameError: name 'runoob' is not defined。` +- **内置名称(built-in names)**:Python语言内置的名称,如函数名`abs`、`char`和异常名称`BaseException`、`Exception`等。 +- **全局名称(global names)**:模块中定义的名称,包括模块的函数、类、导入的模块、模块级别的变量和常量。 +- **局部名称(local names)**:函数中定义的名称,包括函数的参数和局部定义的变量。 -### 生命周期 +### 作用域 -命名空间的生命周期取决于对象的作用域,如果对象执行完成,则该命名空间的生命周期就结束。因此,我们**无法从外部命名空间访问内部命名空间的对象。** - -![img](https://static.7wate.com/img/2022/11/20/8beaf2d3e4567.png) - -```python -# var1 是全局名称 -var1 = 5 -def some_func(): - - # var2 是局部名称 - var2 = 6 - def some_inner_func(): - - # var3 是内嵌的局部名称 - var3 = 7 -``` - -## 作用域 - -作用域就是一个 Python 程序可以直接访问命名空间的正文区域。 - -在一个 python 程序中,直接访问一个变量,会从内到外依次访问所有的作用域直到找到,否则会报未定义的错误。 - -Python 中,程序的变量并不是在哪个位置都可以访问的,访问权限决定于这个变量是在哪里赋值的。 - -变量的作用域决定了在哪一部分程序可以访问哪个特定的变量名称。Python 的作用域一共有4种,分别是: - -- **L(Local)**:最内层,包含局部变量,比如一个函数/方法内部。 -- **E(Enclosing)**:包含了非局部(non-local)也非全局(non-global)的变量。比如两个嵌套函数,一个函数(或类) A 里面又包含了一个函数 B ,那么对于 B 中的名称来说 A 中的作用域就为 nonlocal。 -- **G(Global)**:当前脚本的最外层,比如当前模块的全局变量。 -- **B(Built-in)**: 包含了内建的变量 / 关键字等,最后被搜索。 +作用域定义了命名空间可以直接访问的代码段,决定了在哪一部分程序可以访问特定的变量名。Python的作用域一共有4种,分别是: ![img](https://static.7wate.com/img/2022/11/20/1e3af056f1ac0.png) -规则顺序: **L –> E –> G –> B**。 +- **L(Local)**:最内层,包含局部变量,比如一个函数/方法内部。 +- **E(Enclosing)**:包含了非局部(non-local)也非全局(non-global)的变量。比如两个嵌套函数,一个函数(或类)A里面又包含了一个函数B,那么对于B中的名称来说A中的作用域就为nonlocal。 +- **G(Global)**:当前脚本的最外层,比如当前模块的全局变量。 +- **B(Built-in)**: 包含了内建的变量/关键字等,最后被搜索。 + +变量的查找顺序是:L --> E --> G --> B。 + +### 代码示例 ```python -g_count = 0 # 全局作用域 +# 全局变量 +x = 10 +z = 40 + +# 定义函数 foo +def foo(): + # 局部变量 + x = 20 + z = 50 + + def bar(): + nonlocal z + print("局部变量 z =", z) # 输出 "局部变量 z = 50" + + def baz(): + global z + print("全局变量 z =", z) # 输出 "全局变量 z = 40" + + print("局部变量 x =", x) # 输出 "局部变量 x = 20" + bar() + baz() + +# 定义函数 outer def outer(): - o_count = 1 # 闭包函数外的函数中 + y = 30 # 封闭作用域变量 + def inner(): - i_count = 2 # 局部作用域 + nonlocal y + print("封闭作用域 y =", y) # 输出 "封闭作用域 y = 30" + + inner() + +# 执行函数 +foo() +print("全局变量 x =", x) # 输出 "全局变量 x = 10" +outer() + +# 调用内置函数 +print(abs(-5)) # 输出 5 ``` diff --git a/wiki/programming-language/Python/入门/面对对象.md b/wiki/programming-language/Python/入门/面对对象.md index 0bfeb111..50716d87 100644 --- a/wiki/programming-language/Python/入门/面对对象.md +++ b/wiki/programming-language/Python/入门/面对对象.md @@ -8,313 +8,751 @@ tags: - Python sidebar_position: 6 author: 7Wate -date: 2022-11-20 +date: 2023-08-03 --- -面对对象把一组数据结构和处理它们的方法组成对象(object),把相同行为的对象归纳为类(class),通过类的封装(encapsulation)隐藏内部细节,通过继承(inheritance)实现类的特化(specialization)和泛化(generalization),通过多态(polymorphism)实现基于对象类型的动态分派 +面向对象编程(Object-Oriented Programming,OOP)是一种编程范式或模型,以「对象」作为核心来设计和实现软件。这种方法主要的目标是将数据和处理数据的函数结合在一起,封装成独立的软件模块,我们称之为「对象」。面向对象编程被广泛应用于多种编程语言中,包括 Python。 -## 类 +## 基本概念 -类(class)是具有相同特性(属性)和行为(方法)的对象(实例)的抽象模板。 +### 理念和用途 -在面向对象编程的世界中,一切皆为对象,对象都有属性和行为,每个对象都是独一无二的,而且对象一定属于某个类(型)。当我们把一大堆拥有共同特征的对象的静态特征(属性)和动态特征(行为)都抽取出来后,就可以定义出一个叫做类的东西。 +**面向对象编程是在更高的抽象层次上思考问题的一种方式。**面向对象编程的主要理念是,将现实世界中的对象抽象化,将对象的属性(也叫状态)和行为(也叫方法)封装在一起。面向对象编程的思考方式,更贴近我们对现实世界的认知。 -- **类(Class):** 用来描述具有相同的属性和方法的对象的集合。它定义了该集合中每个对象所共有的属性和方法。对象是类的实例。 -- **方法:**类中定义的函数。 -- **类变量:**类变量在整个实例化的对象中是公用的。类变量定义在类中且在函数体之外。类变量通常不作为实例变量使用。 -- **数据成员:**类变量或者实例变量用于处理类及其实例对象的相关的数据。 -- **方法重写:**如果从父类继承的方法不能满足子类的需求,可以对其进行改写,这个过程叫方法的覆盖(override),也称为方法的重写。 -- **局部变量:**定义在方法中的变量,只作用于当前实例的类。 -- **实例变量:**在类的声明中,属性是用变量来表示的,这种变量就称为实例变量,实例变量就是一个用 self 修饰的变量。 -- **继承:**即一个派生类(derived class)继承基类(base class)的字段和方法。继承也允许把一个派生类的对象作为一个基类对象对待。例如有这样一个设计:一个 Dog 类型的对象派生自 Animal 类,这是模拟"是一个(is-a)"关系(例 Dog 是一个 Animal)。 -- **实例化:**创建一个类的实例,类的具体对象。 -- **对象:**通过类定义的数据结构实例。对象包括两个数据成员(类变量和实例变量)和方法。 +面向对象编程的主要优点包括: -### 定义 +1. **代码复用性**:类的定义一旦完成,就可以在任何地方创建其对象,复用类的属性和方法,而无需重复编写代码。通过类之间的继承关系,子类可以继承父类的代码,从而实现代码的复用。 +2. **代码的可维护性和可扩展性**:面向对象编程的封装特性,保护了对象内部的数据,使得代码更安全,更易于维护。通过类的继承和多态,可以轻松实现对现有代码的扩展。 -Python中可以使用`class`关键字定义类,然后在类中通过之前学习过的函数来定义方法,这样就可以将对象的动态特征描述出来,代码如下所示。 +### 特点 + +**面向对象编程有三个主要的特点:封装、继承、和多态。** + +- **封装**:封装是指将对象的状态(数据)和行为(方法)包装在一起,隐藏对象的内部实现细节,仅对外提供必要的接口。这样可以保护对象的内部状态,提高代码的安全性,也提高了代码的可维护性。 +- **继承**:继承是指子类可以继承父类的属性和方法。继承让我们可以在父类的基础上进行扩展,实现代码的复用,提高代码的可扩展性。 +- **多态**:多态是指不同类的对象对同一消息会做出不同的响应。多态提供了统一的接口,使得我们可以使用一致的方式处理不同类的对象,提高了代码的灵活性。 + +## Python 中的类和对象 + +在 Python 中,我们使用`class`关键字来定义类。类是一种数据类型,它定义了一种新的对象,这种对象具有自己的属性和方法。 + +### 类和对象的概念 + +- **类(Class)**: 类是对象的蓝图或原型。你可以想象成是创建对象的模板,它定义了特定类型的对象的属性和方法。 ```python -# 语法 -class ClassName: - - . - . - . - - -# 实例 -class MyClass: - """一个简单的类实例""" - i = 12345 - def f(self): - return 'hello world' - -# 实例化类 -x = MyClass() - -# 访问类的属性和方法 -print("MyClass 类的属性 i 为:", x.i) -print("MyClass 类的方法 f 输出为:", x.f()) +# 一个简单的 Python 类的例子 +class Dog: + pass ``` -### 属性 - -属性引用使用和 Python 中所有的属性引用一样的标准语法:**obj.name**。类对象创建后,类命名空间中所有的命名都是有效属性名。 - -### 方法 - -在类的内部,使用 **def** 关键字来定义一个方法,与一般函数定义不同,**类方法必须包含参数 self,** 且为第一个参数,self 代表的是类的实例方法。 +- **对象(Object)**:对象是类的实例。具体来说,当我们根据类的定义创建了一个实例后,这个实例就是一个对象。每个对象都具有类定义的属性和方法。 ```python -class Animal: - def __init__(self, name): - self.name = name - - def run(self): - print(f"{self.name}跑起来啦") - ->>> dog=Animal(name="小黑") ->>> dog.run() -小黑跑起来啦 ->>> Animal.run(dog) -小黑跑起来啦 +# 创建Dog类的一个实例 +my_dog = Dog() ``` -### 类方法 +我们可以通过创建类的实例,也就是对象,来使用类的属性和方法。 -类方法在定义时,第一个参数固定是 cls,为 class 的简写,代表类本身。不管是通过实例还是类调用类方法,都不需要传入 cls 的参数。 +### 类的属性和方法 + +类的属性和方法是类的主要组成部分。属性用于描述类和其实例的状态,方法用于描述类和其实例可以进行的操作。 + +在Python中,我们可以在类中定义两种类型的属性和方法: + +- **实例属性和方法**:实例属性和方法属于类的实例。每个实例都有自己的实例属性,这些属性与其他实例的属性互不影响。实例方法可以通过实例来调用,它可以访问和修改实例的属性。 ```python -class Animal: +# 在Python类中定义实例属性和方法的例子 +class Dog: def __init__(self, name): - self.name = name + self.name = name # 实例属性 - def run(self): - print(f"{self.name}跑起来啦") + def bark(self): # 实例方法 + return f"{self.name} says woof!" + +``` + +- **类属性和方法**:类属性和方法是属于类本身的,所有的实例都会共享同一个类属性。类方法可以通过类名直接调用,也可以通过实例调用,但是它不能访问和修改实例属性。 + +```python +# 在Python类中定义类属性和方法的例子 +class Dog: + species = "Canis familiaris" # 类属性 @classmethod - def jump(cls, name): - print(f"{name}跳起来啦") - ->>> dog=Animal(name="小黑") ->>> dog.eat() -正在吃饭... ->>> Animal.eat() -正在吃饭... + def description(cls): # 类方法 + return "This class represents a dog." + ``` -### 静态方法 +### `__init__` -Python 类的静态方法在定义时,不需要 self 参数。 @staticmethod 装饰的函数就是静态方法,静态方法不需要实例化就可以调用。 +在Python中使用`__init__`方法来初始化类的实例。这个方法会在创建实例时自动调用,我们可以在这个方法中设置实例的初始状态,也就是定义实例的属性。 + +`__init__`方法的第一个参数始终是`self`,代表了实例本身。在`__init__`方法中,我们使用`self.属性名`来定义实例属性。 ```python -class Animal: - def __init__(self, name): - self.name = name - - def run(self): - print(f"{self.name}跑起来啦") - - @staticmethod - def eat(): - print("正在吃饭...") - ->>> dog=Animal(name="小黑") ->>> dog.jump("小黑") -小黑跳起来啦 ->>> Animal.jump("小黑") -小黑跳起来啦 -``` - -### 访问可见性 - -在Python中,属性和方法的访问权限只有两种,也就是公开的和私有的。如果希望属性是私有的,在给属性命名时可以参考以下方法 - -#### 私有属性、方法 - -##### 单下划线 - -Python 以单个下划线开头的变量或方法仅供内部使用,但是不做强制约束,依旧可以正常访问。 - -##### 双下划线 - -Python 以两个下划线开头会导致 Python解释器重写属性名称,以避免子类中的命名冲突 - -#### 强制访问 - -但是,Python 并没有从语法上严格保证私有属性或方法的私密性,它只是给私有的属性和方法换了一个名字来妨碍对它们的访问,事实上如果你知道更换名字的规则仍然可以访问到它们,下面的代码就可以验证这一点。 - -```python -class Test: - - def __init__(self, foo): - self.__foo = foo - - def __bar(self): - print(self.__foo) - print('__bar') - - -def main(): - test = Test('hello') - test._Test__bar() - print(test._Test__foo) - - -if __name__ == "__main__": - main() -``` - -### 封装 - -封装是指将数据与具体操作的实现代码放在某个对象内部,使这些代码的实现细节不被外界发现,外界只能通过接口使用该对象,而不能通过任何形式修改对象内部实现。 - -```python -############ 未封装 -class Person: +class Dog: def __init__(self, name, age): self.name = name self.age = age +``` -xh = Person(name="小红", age=27) -if xh.age >= 18: - print(f"{xh.name}已经是成年人了") -else: - print(f"{xh.name}还是未年人") +## 封装 -############ 封装后 -class Person: +### 概念和用途 + +封装,这是面向对象编程(OOP)中的基本概念之一,其目标是通过将数据(对象的状态)和对象的行为组合在一起,来**隐藏或封装对象内部的详细信息**。简单地说,封装就是把客户端代码(对象的使用者)从对象的内部细节中解耦出来。 + +封装的主要优势包括: + +1. **提高了代码的安全性**:封装可以防止对象状态的无意义或不合适的修改。只有通过定义好的方法(有时被称为 getter 和 setter )才能修改状态,这些方法可以控制对对象状态的更改,从而保证其正确和一致。 +2. **提高了代码的可维护性**:由于客户端代码从对象的内部实现中解耦出来,我们可以自由地改变对象的内部实现,而不会影响到使用该对象的代码。 + +### Python 中实现封装 + +#### 数据封装 + +在 Python 中,我们可以通过使用私有属性(私有变量)实现数据封装。**Python 中的私有属性名字前面带有两个下划线(例如`__name`)。**这些属性只能在类的内部被访问,不能在类的外部直接访问,从而实现了数据的封装。 + +```python +class Dog: def __init__(self, name, age): - self.name = name + # __name和__age是私有属性,我们不能在类的外部直接访问 + self.__name = name + self.__age = age +``` + +#### 方法封装 + +Python 同样可以通过私有方法实现方法的封装。私有方法的名字也以两个下划线开始,它们只能在类的内部被调用,不能在类的外部直接调用。 + +```python +class Dog: + def __init__(self, name, age): + self.__name = name self.__age = age - def is_adult(self): - return self.__age >= 18 - -xh = Person(name="小红", age=27) -xh.is_adult() + # __bark 是一个私有方法,我们不能在类的外部直接调用 + def __bark(self): + return f"{self.__name} says woof!" ``` -### 继承 - -继承可以在已有类的基础上创建新类,这其中的一种做法就是让一个类从另一个类那里将属性和方法直接继承下来,从而减少重复代码的编写。提供继承信息的我们称之为父类,也叫超类或基类;得到继承信息的我们称之为子类,也叫派生类或衍生类。 - -子类除了继承父类提供的属性和方法,还可以定义自己特有的属性和方法,所以子类比父类拥有的更多的能力,在实际开发中,我们经常会用子类对象去替换掉一个父类对象,这是面向对象编程中一个常见的行为,对应的原则称之为里氏替换原则。下面我们先看一个继承的例子。 +尽管私有属性和方法不能在类的外部直接访问,但是我们可以提供公共的getter和setter方法,让外部代码能够以受控的方式读写这些私有属性。这样我们就可以保证封装的安全性,同时也提供了一定的灵活性。 ```python -# 语法 -class DerivedClassName(modname.BaseClassName): - -# 实例 -class people: - #定义基本属性 - name = '' - age = 0 - #定义私有属性,私有属性在类外部无法直接进行访问 - __weight = 0 - #定义构造方法 - def __init__(self,n,a,w): - self.name = n - self.age = a - self.__weight = w - def speak(self): - print("%s 说: 我 %d 岁。" %(self.name,self.age)) - -#单继承示例 -class student(people): - grade = '' - def __init__(self,n,a,w,g): - #调用父类的构函 - people.__init__(self,n,a,w) - self.grade = g - #覆写父类的方法 - def speak(self): - print("%s 说: 我 %d 岁了,我在读 %d 年级"%(self.name,self.age,self.grade)) - -s = student('ken',10,60,3) -s.speak() +class Dog: + def __init__(self, name, age): + self.__name = name + self.__age = age + + # getter for __name + def get_name(self): + return self.__name + + # setter for __name + def set_name(self, name): + self.__name = name + + # getter for __age + def get_age(self): + return self.__age + + # setter for __age + def set_age(self, age): + self.__age = age + + # __bark 是一个私有方法,我们不能在类的外部直接调用 + def __bark(self): + return f"{self.__name} says woof!" + + # 公共方法可以调用私有方法 + def public_bark(self): + return self.__bark() ``` -#### 多继承 +## 继承 -Python 同样有限的支持多继承形式。多继承的类定义形如下例: +### 继承的概念和用途 + +继承是面向对象编程中的核心概念之一。继承允许我们定义一个新的类(子类或派生类)来继承现有类(父类或基类)的属性(变量)和方法(函数)。这样我们就可以在新类中复用父类的代码,避免了代码的重复,同时也可以在新类中添加新的方法或对父类的方法进行重写(override)。 + +继承的主要优点包括: + +1. **代码复用**:子类继承了父类的所有属性和方法,所以我们可以通过创建子类,来复用和扩展父类的代码,减少了代码的重复。 +2. **易于维护和修改**:由于子类可以重写父类的方法,这就意味着我们可以在不改变父类的情况下,改变子类的行为。这使得代码更容易维护和修改。 + +### Python 中实现继承 + +#### 创建子类 + +在 Python 中,我们可以通过在类定义时的括号中写入父类的名字来创建子类。新定义的类将会继承父类的所有属性和方法。 ```python -# 语法 -class DerivedClassName(Base1, Base2, Base3): - - . - -# 实例 -class people: - #定义基本属性 - name = '' - age = 0 - #定义私有属性,私有属性在类外部无法直接进行访问 - __weight = 0 - #定义构造方法 - def __init__(self,n,a,w): - self.name = n - self.age = a - self.__weight = w - def speak(self): - print("%s 说: 我 %d 岁。" %(self.name,self.age)) - -# 单继承示例 -class student(people): - grade = '' - def __init__(self,n,a,w,g): - #调用父类的构函 - people.__init__(self,n,a,w) - self.grade = g - #覆写父类的方法 - def speak(self): - print("%s 说: 我 %d 岁了,我在读 %d 年级"%(self.name,self.age,self.grade)) - -# 多重继承之前的准备 -class speaker(): - topic = '' - name = '' - def __init__(self,n,t): - self.name = n - self.topic = t - def speak(self): - print("我叫 %s,我是一个演说家,我演讲的主题是 %s"%(self.name,self.topic)) - -# 多重继承 -class sample(speaker,student): - a ='' - def __init__(self,n,a,w,g,t): - student.__init__(self,n,a,w,g) - speaker.__init__(self,n,t) - -test = sample("Tim",25,80,4,"Python") -test.speak() #方法名同,默认调用的是在括号中参数位置排前父类的方法 +class Animal: + def __init__(self, name): + self.name = name + +# Dog 是 Animal 的子类,它继承了 Animal 的所有属性和方法。 +class Dog(Animal): + pass ``` -### 多态 +#### `super` 函数 -面对对象的三大特征其一继承:子类在继承了父类的方法后,如果你的父类方法的功能不能满足你的需求,可以对父类已有的方法给出新的实现版本,这个动作称之为方法重写(override)。通过方法重写我们可以让父类的同一个行为在子类中拥有不同的实现版本,当我们调用这个经过子类重写的方法时,不同的子类对象会表现出不同的行为,这个就是多态(poly-morphism)。 +在 Python 中,`super`函数是一个内置函数,它可以用来调用父类的方法。这在你需要在子类中扩展父类的方法时特别有用。 ```python -class Parent: - def myMethod(self): - print ('调用父类方法') - -class Child(Parent): - def myMethod(self): - print ('调用子类方法') - -c = Child() # 子类实例 -c.myMethod() # 子类调用重写方法 -super(Child,c).myMethod() #用子类对象调用父类已被覆盖的方法 +class Animal: + def __init__(self, name): + self.name = name + +# Dog 的 __init__ 方法中使用了 super().__init__(name) 来调用 Animal 的 __init__ 方法。 +class Dog(Animal): + def __init__(self, name, breed): + super().__init__(name) # 调用父类的构造方法 + self.breed = breed # 新增的属性 ``` -### 关系 +## 多态 -简单的说,类和类之间的关系有三种:is-a、has-a和use-a关系。 +### 多态的概念和用途 -- is-a关系也叫继承或泛化,比如学生和人的关系、手机和电子产品的关系都属于继承关系。 -- has-a关系通常称之为关联,比如部门和员工的关系,汽车和引擎的关系都属于关联关系;关联关系如果是整体和部分的关联,那么我们称之为聚合关系;如果整体进一步负责了部分的生命周期(整体和部分是不可分割的,同时同在也同时消亡),那么这种就是最强的关联关系,我们称之为合成关系。 -- use-a关系通常称之为依赖,比如司机有一个驾驶的行为(方法),其中(的参数)使用到了汽车,那么司机和汽车的关系就是依赖关系。 +在面向对象编程(OOP)中,多态性是一种允许一个实体采取多种形态的能力。更具体地说,多态是指通过同一个接口,使用不同的实例,可以产生不同的结果。这使得我们可以在运行时确定我们正在使用的对象类型,然后执行相应的操作。 + +多态的优势主要有两点: + +1. **提高代码的灵活性**:多态能够让我们以更一般的方式编写代码,处理更广泛的数据类型,而不仅仅是特定的单一数据类型。这使得我们的代码更加灵活和可维护。 +2. **提高代码的可扩展性**:如果我们想要添加新的数据类型,我们只需要确保它们遵循现有的接口规定。这使得我们的代码更易于扩展和改进,而无需修改大量现有代码。 + +### Python 中实现多态 + +Python 是一种动态类型语言,意味着我们不需要明确地声明对象的类型。这就让我们在 Python 中能够非常容易地实现多态。以下是 Python 中实现多态的一些方法: + +#### 方法重写 + +在 Python 中,子类可以重写父类的方法。这就意味着,当我们在子类中调用一个父类的方法时,将会执行子类中定义的版本,而非父类中的原始版本。这使得我们可以改变子类中方法的行为,以符合我们的需求。 + +```python +class Animal: + def sound(self): + return "Generic animal sound" + +# Dog类重写了Animal类的sound方法,所以当我们调用Dog实例的sound方法时,会返回"Woof!",而不是"Generic animal sound"。 +class Dog(Animal): + def sound(self): + return "Woof!" +``` + +#### Duck Typing + +Python 支持一种编程概念叫做 duck typing,这也是实现多态的一种方法。Duck typing的核心思想是:如果一个对象能够像鸭子一样走路,像鸭子一样叫,那么我们就可以认为它是鸭子。对于Python来说,如果一个对象有我们需要的方法,我们就可以使用它,而不管它是什么类型的对象(运行时类型确定)。 + +```python +def animal_sound(animal): + # 这个函数只关心对象是否有sound方法,而不关心对象的具体类型 + return animal.sound() + +print(animal_sound(Dog())) # 输出"Woof!" +print(animal_sound(Animal())) # 输出"Generic animal sound" +``` + +## 特殊方法 + +### Python 的特殊方法 + +Python 的特殊方法是 Python 类中的一种特殊的方法,它们有固定的命名规则,即前后都有两个下划线(`__`)。这些方法在特定的情况下会被 Python 自动调用,因此它们有时也被称为魔术方法或者双下划线方法。特殊方法让我们可以自定义对象在特定情况下的行为,例如在进行算术运算,比较,迭代等操作时。 + +| 特殊方法 | 描述 | +| -------------------------------- | ---------------------------------------------------- | +| `__init__(self, ...)` | 构造函数,在创建新实例时调用。 | +| `__del__(self)` | 析构函数,在实例被销毁时调用。 | +| `__repr__(self)` | 定义该类的“官方”字符串表示。通常可以被`eval()`执行。 | +| `__str__(self)` | 定义该类的字符串表示,例如用于`print()`。 | +| `__bytes__(self)` | 定义`bytes()`的返回值。 | +| `__format__(self, format_spec)` | 定义`format()`的行为。 | +| `__lt__(self, other)` | 定义小于符号的行为。 | +| `__le__(self, other)` | 定义小于等于符号的行为。 | +| `__eq__(self, other)` | 定义等于符号的行为。 | +| `__ne__(self, other)` | 定义不等于符号的行为。 | +| `__gt__(self, other)` | 定义大于符号的行为。 | +| `__ge__(self, other)` | 定义大于等于符号的行为。 | +| `__hash__(self)` | 定义`hash()`的行为。 | +| `__bool__(self)` | 定义`bool()`的返回值。定义`True`和`False`的行为。 | +| `__getattr__(self, name)` | 定义当用户试图获取一个不存在的属性时的行为。 | +| `__setattr__(self, name, value)` | 定义对实例属性的赋值行为。 | +| `__delattr__(self, name)` | 定义对实例属性的删除行为。 | +| `__getattribute__(self, name)` | 定义属性访问行为。 | +| `__getitem__(self, key)` | 定义使用索引访问元素的行为。 | +| `__setitem__(self, key, value)` | 定义使用索引设置元素的行为。 | +| `__delitem__(self, key)` | 定义使用索引删除元素的行为。 | +| `__iter__(self)` | 定义迭代器行为,返回一个新的迭代器对象。 | +| `__reversed__(self)` | 定义`reversed()`返回值。定义逆序迭代的行为。 | +| `__len__(self)` | 定义`len()`返回值。定义对象包含元素的个数。 | +| `__add__(self, other)` | 定义加法的行为。 | +| `__sub__(self, other)` | 定义减法的行为。 | +| `__mul__(self, other)` | 定义乘法的行为。 | +| `__truediv__(self, other)` | 定义真除法的行为。 | +| `__floordiv__(self, other)` | 定义整除法的行为。 | +| `__mod__(self, other)` | 定义求模运算的行为。 | +| `__pow__(self, other[, modulo])` | 定义指数运算的行为。 | +| `__and__(self, other)` | 定义按位与运算的行为。 | +| `__xor__(self, other)` | 定义按位异或运算的行为。 | +| `__or__(self, other)` | 定义按位或运算的行为。 | + +#### `__init__` + +`__init__`方法是类的构造函数,当我们创建类的实例时,`__init__`方法会被自动调用。我们可以在`__init__`方法中初始化实例的属性。 + +```python +class MyClass: + def __init__(self, value): + self.value = value # 初始化实例属性 + +# 创建实例 +mc = MyClass(10) +print(mc.value) # 输出:10 +``` + +#### `__del__` + +`__del__`方法是类的析构函数,当一个实例被销毁时(例如被垃圾回收器回收时),`__del__`方法会被自动调用。注意,我们通常不需要在`__del__`方法中做清理工作,Python的垃圾回收器会自动清理对象的资源。 + +```python +class MyClass: + def __del__(self): + print("Instance is being destroyed.") + +mc = MyClass() # 创建实例 +del mc # 销毁实例 +``` + +#### `__repr__` + +`__repr__`方法返回一个表示该对象的官方字符串,这个字符串通常可以被`eval()`执行来重新得到这个对象。如果我们没有定义`__str__`方法,那么在调用`str()`或`print()`时也会使用`__repr__`的返回值。 + +```python +class MyClass: + def __repr__(self): + return "MyClass()" + +mc = MyClass() # 创建实例 +print(mc) # 输出:MyClass() +``` + +#### `__str__` + +`__str__`方法返回一个表示该对象的字符串,这个字符串通常用于给用户看。当我们调用`str()`或`print()`时,会使用`__str__`的返回值。 + +```python +class MyClass: + def __str__(self): + return "This is a MyClass instance." + +mc = MyClass() # 创建实例 +print(mc) # 输出:This is a MyClass instance. +``` + +*注意,`__repr__`和`__str__`的区别在于,**`__repr__`更侧重于开发,而`__str__`更侧重于用户。*** + +#### `__bytes__` + +`__bytes__`方法定义了当我们调用`bytes()`时的行为。它应该返回一个字节串。 + +```python +class MyClass: + def __bytes__(self): + return b'MyClass instance' + +mc = MyClass() # 创建实例 +print(bytes(mc)) # 输出:b'MyClass instance' +``` + +#### `__format__` + +`__format__`方法定义了当我们调用`format()`或使用格式化字符串(f-string)时的行为。`format_spec`是一个格式说明符,它是在格式化字符串中`:`后面的部分。 + +```python +class MyClass: + def __format__(self, format_spec): + if format_spec == 'fancy': + return 'This is a fancy MyClass instance.' + return 'This is a MyClass instance.' + +mc = MyClass() # 创建实例 +print(f"{mc:fancy}") # 输出:This is a fancy MyClass instance. +``` + +#### `__hash__` + +`__hash__`方法定义了当我们调用`hash()`时的行为。它应该返回一个整数,这个整数会被用于在字典等哈希表中快速比较键。 + +```python +class MyClass: + def __init__(self, value): + self.value = value + + def __hash__(self): + return hash(self.value) + +mc = MyClass(10) # 创建实例 +print(hash(mc)) # 输出:10 +``` + +#### `__bool__` + +`__bool__`方法定义了当我们调用`bool()`时的行为。它应该返回`True`或`False`。 + +```python +class MyClass: + def __init__(self, value): + self.value = value + + def __bool__(self): + return bool(self.value) + +print(bool(MyClass(0))) # 输出:False +print(bool(MyClass(1))) # 输出:True +``` + +#### `__getattr__` + +`__getattr__`方法定义了当我们试图获取一个不存在的属性时的行为。`name`是我们试图获取的属性的名称。 + +```python +class MyClass: + def __getattr__(self, name): + return f"{name} does not exist." + +mc = MyClass() # 创建实例 +print(mc.unknown_attr) # 输出:unknown_attr does not exist. +``` + +#### `__setattr__` + +`__setattr__`方法定义了对实例属性的赋值行为。`name`是属性的名称,`value`是我们试图赋给属性的值。 + +```python +class MyClass: + def __setattr__(self, name, value): + self.__dict__[name] = value # 在__dict__中设置属性 + print(f"Set {name} to {value}.") + +mc = MyClass() # 创建实例 +mc.attr = 10 # 输出:Set attr to 10. +``` + +注意,为了防止在`__setattr__`中赋值属性时再次调用`__setattr__`,导致无限递归,我们需要直接在实例的`__dict__`属性中设置属性。 + +#### `__delattr__` + +`__delattr__`方法定义了对实例属性的删除行为。`name`是我们试图删除的属性的名称。 + +```python +class MyClass: + attr = 10 + + def __delattr__(self, name): + del self.__dict__[name] # 在__dict__中删除属性 + print(f"Deleted {name}.") + +mc = MyClass() # 创建实例 +del mc.attr # 输出:Deleted attr. +``` + +注意,为了防止在`__delattr__`中删除属性时再次调用`__delattr__`,导致无限递归,我们需要直接在实例的`__dict__`属性中删除属性。 + +#### `__getattribute__` + +`__getattribute__`方法定义了属性访问行为。无论属性是否存在,只要我们试图访问属性,就会调用`__getattribute__`。 + +```python +class MyClass: + def __getattribute__(self, name): + return f"You are trying to access {name}." + +mc = MyClass() # 创建实例 +print(mc.attr) # 输出:You are trying to access attr. +``` + +注意,如果我们定义了`__getattribute__`方法,那么`__getattr__`就不会被调用。因为无论属性是否存在,`__getattribute__`都会被调用。 + +#### `__getitem__` + +`__getitem__`方法定义了使用索引访问元素的行为。`key`是索引。 + +```python +class MyClass: + def __getitem__(self, key): + return f"You are trying to access key {key}." + +mc = MyClass() # 创建实例 +print(mc[10]) # 输出:You are trying to access key 10. +``` + +#### `__setitem__` + +`__setitem__`方法定义了使用索引设置元素的行为。`key`是索引,`value`是我们试图设置的值。 + +```python +class MyClass: + def __setitem__(self, key, value): + print(f"Set key {key} to {value}.") + +mc = MyClass() # 创建实例 +mc[10] = "value" # 输出:Set key 10 to value. +``` + +#### `__delitem__` + +`__delitem__`方法定义了使用索引删除元素的行为。`key`是索引。 + +```python +class MyClass: + def __delitem__(self, key): + print(f"Deleted key {key}.") + +mc = MyClass() # 创建实例 +del mc[10] # 输出:Deleted key 10. +``` + +#### `__iter__` + +`__iter__`方法定义了迭代器行为,它应该返回一个新的迭代器对象。 + +```python +class MyClass: + def __init__(self): + self.data = [1, 2, 3] + + def __iter__(self): + return iter(self.data) + +mc = MyClass() # 创建实例 +for i in mc: # 输出:1 2 3 + print(i) +``` + +#### `__reversed__` + +`__reversed__`方法定义了`reversed()`的返回值。它应该返回一个新的反向迭代器对象。 + +```python +class MyClass: + def __init__(self): + self.data = [1, 2, 3] + + def __reversed__(self): + return reversed(self.data) + +mc = MyClass() # 创建实例 +for i in reversed(mc): # 输出:3 2 1 + print(i) +``` + +#### `__len__` + +`__len__`方法定义了`len()`的返回值。它应该返回一个整数,表示对象包含的元素的个数。 + +```python +class MyClass: + def __init__(self): + self.data = [1, 2, 3] + + def __len__(self): + return len(self.data) + +mc = MyClass() # 创建实例 +print(len(mc)) # 输出:3 +``` + +## 抽象类和接口 + +### 抽象类和接口的概念 + +抽象类是一种特殊的类,它不能被实例化,只能被继承。抽象类中可以定义抽象方法,这些方法在抽象类中没有实现,在子类中必须实现。 + +接口是一种特殊的抽象类,它只定义了一组方法的签名,没有提供任何实现。接口定义了一组行为,任何实现了这些行为的类都可以说是实现了这个接口。 + +### Python 的abc模块 + +在 Python 中,我们使用 abc 模块来创建抽象类和接口。abc 模块提供了`ABC`基类和`abstractmethod`装饰器,我们可以使用它们来定义抽象类和抽象方法。 + +### Python 中使用抽象类和接口 + +#### 创建抽象类 + +我们可以使用`ABC`基类和`abstractmethod`装饰器来创建抽象类。 + +```Python +from abc import ABC, abstractmethod + +# AbstractAnimal 是一个抽象类,它有一个抽象方法 sound。 +class AbstractAnimal(ABC): + @abstractmethod + def sound(self): + pass +``` + +#### 创建接口 + +在 Python 中,接口的概念可以通过完全由抽象方法构成的抽象类来实现。与抽象类类似,我们使用 ABC 基类和 abstractmethod 装饰器来定义接口。 + +```python +from abc import ABC, abstractmethod + +# AnimalBehaviour 是一个接口,它定义了两个抽象方法:eat 和 sleep。 +class AnimalBehaviour(ABC): + @abstractmethod + def eat(self): + pass + + @abstractmethod + def sleep(self): + pass + +``` + +#### 实现接口 + +我们可以通过继承抽象类并实现其所有的抽象方法来实现接口。 + +```Python +class Dog(AbstractAnimal, AnimalBehaviour): + def sound(self): + return "Woof!" + + def eat(self): + return "The dog is eating." + + def sleep(self): + return "The dog is sleeping." +``` + +## 异常处理 + +### 异常的概念 + +在 Python 中,异常是程序运行期间发生的错误事件,它会中断常规程序的执行流程。当程序执行过程中遇到错误时,Python 解释器会自动引发(raise)一个异常。 + +异常是一种特殊的对象,它包含了有关错误的详细信息,例如错误类型和错误发生时的程序状态。Python 内置了很多标准异常类型,如`ValueError`,`TypeError`,`IndexError`等,每种类型都对应了一类特定的错误。 + +我们可以使用异常处理机制来捕获(catch)异常。通过处理异常,我们可以决定在出现错误时程序如何响应,而不是让程序直接崩溃。这对于构建健壮和稳定的程序至关重要。 + +```mermaid +graph TB + BaseException --> SystemExit + BaseException --> KeyboardInterrupt + BaseException --> GeneratorExit + BaseException --> Exception + Exception --> StopIteration + Exception --> ArithmeticError + ArithmeticError --> FloatingPointError + ArithmeticError --> OverflowError + ArithmeticError --> ZeroDivisionError + Exception --> AssertionError + Exception --> AttributeError + Exception --> EOFError + Exception --> ImportError + Exception --> ModuleNotFoundError + Exception --> LookupError + LookupError --> IndexError + LookupError --> KeyError + Exception --> NameError + NameError --> UnboundLocalError + Exception --> OSError + OSError --> IOError + Exception --> RuntimeError + RuntimeError --> NotImplementedError + RuntimeError --> RecursionError + Exception --> SyntaxError + Exception --> SystemError + Exception --> TypeError + Exception --> ValueError + Exception --> UnicodeError +``` + +### 异常处理关键字 + +Python中处理异常的关键字主要有四个:`try`,`except`,`finally`,`else`。 + +- `try`: 你可以把可能会引发异常的代码放在`try`块中。 +- `except`: 当`try`块中的代码引发异常时,`except`块中的代码将被执行。你可以在`except`后面指定你想捕获的异常类型。一个`try`块后面可以跟随多个`except`块,用于捕获不同类型的异常。 +- `finally`: 无论`try`块中的代码是否引发异常,`finally`块中的代码都将被执行。这常用于执行一些无论异常是否发生都需要执行的清理操作,如关闭文件。 +- `else`: 如果`try`块中的代码没有引发异常,那么`else`块中的代码将被执行。`else`关键字是可选的。 + +```Python +try: + # 这里是可能抛出异常的代码 + result = 10 / 0 +except ZeroDivisionError: + # 这里是处理ZeroDivisionError异常的代码 + print("Cannot divide by zero!") +else: + # 这里是try代码块成功执行后的代码 + print("Operation successful.") +finally: + # 这里是无论是否发生异常都会执行的代码 + print("This is the finally block.") +``` + +### Python 内置标准异常 + +| 异常名 | 描述 | +| --------------------- | ----------------------------------- | +| `BaseException` | 所有异常的基类 | +| `SystemExit` | 解释器请求退出 | +| `KeyboardInterrupt` | 用户中断执行(通常是输入^C) | +| `Exception` | 常规错误的基类 | +| `StopIteration` | 迭代器没有更多的值 | +| `GeneratorExit` | 生成器(generator)发生异常来通知退出 | +| `SystemError` | 解释器发现内部错误 | +| `SyntaxError` | Python语法错误 | +| `IndentationError` | 缩进错误 | +| `TabError` | Tab和空格混用 | +| `NameError` | 未声明/初始化对象 (没有属性) | +| `UnboundLocalError` | 访问未初始化的本地变量 | +| `AttributeError` | 对象没有这个属性 | +| `TypeError` | 对类型无效的操作 | +| `AssertionError` | 断言语句失败 | +| `ImportError` | 导入模块/对象失败 | +| `ModuleNotFoundError` | 找不到模块 | +| `LookupError` | 无效数据查询的基类 | +| `IndexError` | 序列中没有此索引(index) | +| `KeyError` | 映射中没有这个键 | +| `ValueError` | 传入无效的参数 | +| `UnicodeError` | Unicode相关的错误 | +| `ArithmeticError` | 数学运算基类 | +| `FloatingPointError` | 浮点计算错误 | +| `OverflowError` | 数值运算超出最大限制 | +| `ZeroDivisionError` | 除(或取模)零 (所有数据类型) | +| `EnvironmentError` | 操作系统错误的基类 | +| `IOError` | 输入/输出操作失败 | +| `OSError` | 操作系统错误 | +| `EOFError` | 没有内建输入,到达EOF标记 | +| `RuntimeError` | 一般的运行时错误 | +| `NotImplementedError` | 尚未实现的方法 | +| `RecursionError` | 超过最大递归深度 | + +### 自定义异常的创建和抛出 + +Python允许你创建自定义的异常类型。为了创建自定义的异常类型,你需要定义一个类,它继承自`Exception`类或者它的子类。在你的类中,你可以定义任何你需要的方法,但是通常,自定义的异常类型会非常简单,只提供一些基本的信息。 + +要抛出你自定义的异常,你可以使用`raise`关键字。在`raise`语句后面,你可以指定要抛出的异常类型,以及一个可选的错误消息。 + +```Python +class CustomError(Exception): + """自定义的异常类型""" + + def __init__(self, message): + self.message = message + +try: + # 抛出自定义的异常 + raise CustomError("This is a custom error.") +except CustomError as e: + # 捕获并处理自定义的异常 + print("Caught an exception:", e.message) +```