当前仅显示指定条件回帖 [ 展开查看全部 ]
文件名称:
2.2.2.2.3 Notes.md
所在目录:
2.编程模块 / 2.2 Python 模块 / 2.2.2 Project Perspective / 2.2.2.2 Proj-01
文件大小:
13.94 KB
下载地址:
文本预览:
# 补注喵
for Proj-01
## 关于虚拟环境
### 虚拟环境是什么?
是一种用于隔离项目依赖关系的工具。每个虚拟环境都有自己的解释器和库。
> 通常虚拟环境中的解释器会指向全局环境中的解释器,也就是说在爆改全局环境(主要是更改解释器位置)后别忘了重建你的虚拟环境。
### 为什么需要虚拟环境
主要是能够避免不同项目的依赖的冲突。比如有一个项目需求低版本的`numpy`但是另一个需求高版本,把它们放在同一个环境中尝试安装依赖时就会出问题。
### 手动创建虚拟环境
通过VSCode创建详见正文。这里尝试讲解手动使用内置模块`venv`进行创建。
在项目文件夹中打开终端,使用下面的指令创建虚拟环境。
```bash
python -m venv ./.venv
```
大概就是调用Python的一个叫`venv`的模块,在当前目录下的`.venv`文件夹下创建虚拟环境。
> 将虚拟环境所在文件夹命名为`.venv`是一个约定俗成的做法,很多开发工具的默认行为就是这样,它们会自动识别并使用这个文件夹中的虚拟环境。
稍等一下就能完成。很快地,项目文件夹下多出了一个叫`.venv`的文件夹。
在终端下执行`./.venv/Scripts/activate`就能手动激活虚拟环境。
> 若遇到`powershell`阻止,按照提示进行设置即可。
要检查是否真正处于虚拟环境,使用powershell指令`Get-Command`查找`python`解释器的位置,判断一下是否处于正确的位置(`.venv/Scripts/python`)就行了。
## 关于`requests`库
很优雅的一个多线程安全的第三方网络请求库。
不过你也可以尝试只用Python标准库实现这个项目,这是可以做到的!
## 关于语法
> *Python很容易入门,语法限制也很少。但这不意味着它很简单,更不意味着它很随便。*
>
> —— MelodyEcho
这里仅尝试讲解正文中使用到的。
快去看[官方文档](https://docs.python.org/zh-cn/3/reference/index.html)
### 注释
Python中使用`#`符进行注释,在当前行中非字符串内容的`#`及其后的内容都会被当成注释。
有时能在一些文件看到以`#!`开头的首行注释,这被称作Shebang。
> Shebang这一语法特性由`#!`开头。在开头字符之后,可以有一个或数个空白字符,后接解释器的绝对路径,用于调用解释器。在直接调用脚本时,调用者会利用Shebang提供的信息调用相应的解释器,从而使得脚本文件的调用方式与普通的可执行文件类似。[^1]
你说得对 ☝️🤓,但是`'''`,即连续的三个引号包围的内容不是注释,而是多行字符串。它也常被用在[docstring](https://peps.python.org/pep-0257/)中。
### 控制流
参见 [Grammar_playground](./2.2.2.2.4%20Grammar%20Playground.md)
### 导入模块
`import`语句用于导入模块。
> 有一个叫`importlib`的库也可以用来导入模块,而且动态
常用的写法:
```python
# 直接导入
import math
import os.path
# 从某个模块中导入对象
from lxml import etree
# 导入某个模块中的所有对象到当前命名空间中,非必要不使用
from tkinter import *
# 换个名称导入
from tkinter import messagebox as msgbox
# 导入多个对象
from urllib import request, parse
# 上述指定了模块名的叫做绝对导入,从项目的根目录开始导入模块
# 下面的是相对导入,一般用在包的内部文件之间。
from . import utils
from ..module_a import func
```
[PEP8](https://peps.python.org/pep-0008/#imports) 中对于导入语句的写法有推荐,可以看看。
关于机制,Python会按顺序搜索`sys.path`列表中的每个目录,查找与模块名匹配的文件。
`sys.path`默认包括:
- 当前文件所在目录
- `%PYTHONPATH%`环境变量指定的目录
- 标准库目录,通常是`python/Lib`
- 安装的第三方包的目录,通常是`python/Lib/site-packages`
> 优先级:当前目录 -> site-packages -> 内置模块
你可以手动为`sys.path`添加路径,让它去额外的目录找模块;也可以自定义模块加载的各个步骤,总之就是自由度十分~~甚至九分~~地高。
更多内容参见[官方文档](https://docs.python.org/zh-cn/3/reference/simple_stmts.html#import),或者去问问AI。
### 函数
#### 函数的定义
使用`def`或者`lambda`定义,由后者定义的函数叫做匿名函数。
> 函数定义是一条**可执行语句**。它执行时会在当前局部命名空间中将函数名称绑定到一个函数对象(函数可执行代码的包装器)。这个函数对象包含对当前全局命名空间的引用,作为函数被调用时所使用的全局命名空间。(摘自官方文档)
嗯你问函数它本身是什么…… ~~函数它就是函数啊~~
Python中的函数是**封装**了代码块的**对象**。使用它可以复用代码,减少重复。
快去看[官方文档](https://docs.python.org/zh-cn/3/reference/compound_stmts.html#function)ww
一个典型的函数大概长这样:
```python
# 形式参数 参数类型标注(可选)
# vv vvvvvvvvvvv
def join(ss : list[str]) -> str:
# ^^^^---------------- ^^^^^^
# 函数名 形式参数列表 返回值类型标注(可选)
return "".join(ss)
```
> 关于形参与实参
>
> 形参是函数在定义的时候写好的参数,实参是函数在被实际调用时传入的参数
> 为什么要做类型标注(Type Hints)?
>
> 你说得对🤓,虽然Python是一种动态类型语言,但是类型标注也有很多好处。它能提升代码的可维护性和可读性,以及便于生成文档。此外在面对稍微复杂一些的代码时单靠IDE进行类型推导会显得有些吃力,但要是加上类型标注的话就可以帮助~~思维阻滞的~~IDE进行自动补全和错误检测等工作。
>
> 不过它完全是可选的,最终写不写还是取决于你就是了。
>
> 关于类型标注的详细讲解,见[Proj-02的补注](../2.2.2.3%20Proj-02/2.2.2.3.3%20Notes.md)。
#### 关于函数的默认参数
**不要将默认参数设定为可变对象**。作为替代,可以将默认参数设定为`None`,并在函数内部进行处理。
因为函数的默认参数只会在定义时被计算一次,后续每次调用时它们都会被共享。如果默认参数被修改,这些修改会影响到后续的函数调用。它在默认参数被实参覆盖时不会有问题,但在默认参数留空时便会出现意料外的情况。
来看例子。
```python
>>> def append_to_list(value, my_list=[]):
... my_list.append(value)
... return my_list
...
>>> print(append_to_list(1))
[1]
>>> print(append_to_list(2))
[1, 2]
>>> print(append_to_list(3))
[1, 2, 3]
```
替换为使用`None`:
```python
>>> def append_to_list(value, my_list=None):
... if my_list is None:
... my_list = []
... my_list.append(value)
... return my_list
...
>>> print(append_to_list(1))
[1]
>>> print(append_to_list(2))
[2]
>>> print(append_to_list(3))
[3]
```
### 异常
[官方文档](https://docs.python.org/zh-cn/3/reference/compound_stmts.html#the-try-statement)
#### 异常捕获
完全体的异常处理大概像这样:
```python
try:
# 可能会出错的代码
x = 1 / int(input("Your Number:"))
assert 1 == 0, "oops, 1 != 0"
except ZeroDivisionError as e:
... # 发生除零错误时会执行的代码
except AssertionError as e:
... # 发生断言错误时会执行的代码
except Exception as e:
... # 发生一般错误时会执行的代码
else:
... # 没有发生错误时会执行的代码
finally:
... # 不管发生了什么都会执行的代码
```
异常匹配顺序从上到下,匹配成功后便不再继续匹配。若最终都没有匹配上则错误还是会被抛出(但`finally`块仍然有效)
> 关于裸`except:`
>
> 等价于`except BaseException:`,可以捕获**所有**异常,包括系统退出和中断的异常,像 `SystemExit`、`KeyboardInterrupt` 和 `GeneratorExit`。因为经常会捕获到不该捕获的东西,语义不明确所以不建议使用。
>
> 一般日常建议使用`except Exception:`。就算你真的想捕获系统级异常,也应该使用`except BaseException:`进行平替,因为它的语义更明确一些。
> `...`?
>
> 没错`...`也是一个对象,叫做`Ellipsis`,通常用来表示省略或占位。在某些特殊的场景很有用(比如`numpy`中创建多维数组)。
`except ... as ...`可以将捕获的异常实例保存到一个变量中,方便后续做处理
#### 异常抛出
使用`raise`语句抛出一个异常**对象**。
> 在抛出之前,先要将异常类实例化,在实例化时可以传递具体的错误消息。(不实例化就抛出是py2的做法)
```python
raise RuntimeError("A runtime error.")
```
`raise ... from ...`可以从一个已有的异常实例抛出一个新异常,原有异常会被保存在新异常的`__cause__`属性中。常用在`except`块下。
### 变量作用域与生命周期
Python中的局部变量的作用域是它直接所在的函数,全局变量的则是它所在的模块。
在一个变量超出它的作用域,且它的引用计数变为`0`时,它就会被删除。
> 对于循环引用,Python的垃圾回收器负责清理它们。可以通过调用`gc.collect()`来手动执行垃圾回收。
> Python没有像C/C++或Java那样的块级作用域(Block Scope),在控制结构 (`if`/`while`/`match`/`for`/ ...) 中声明的变量在控制结构之外依然可以访问,只要是在相同的函数或全局作用域中;也没有像 js 那样的[变量提升](https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Guide/Grammar_and_types#%E5%8F%98%E9%87%8F%E6%8F%90%E5%8D%87),在变量被声明并赋值之前访问它会报错。
~~来点C语言笑话:~~
```c
void just_a_test(void){
// 我 有 异 域 症
{int i;}
{i = 0;}
}
```
### 引用与拷贝
Python中的**所有数据都是对象**。变量名实际上是指向这些对象的引用。当你赋值一个变量时,你只是将这个变量名绑定到某个对象上,并不复制对象本身。
```python
>>> a = [1, 2, 3]
>>> a
[1, 2, 3]
>>> b = a
>>> b.append(4)
>>> a
[1, 2, 3, 4]
```
> 要复制对象,可使用`copy`库中的`copy()`和`deepcopy()`函数,或某些对象内置的`copy()`方法
>
> - `copy()`: 浅拷贝,仅复制顶层结构,产生的新对象内层仍然共享。
> - `deepcopy()`: 深拷贝,复制整个对象,产生的新对象完全独立
Python中的对象分为可变(e.g.列表/字典/集合)和不可变(e.g.整数/字符串/元组)两种。
对于不可变的对象,对它做修改或赋值会在原地生成一个新的对象,再将这个对象绑定到变量上。这就是平时在数字/字符串变量之间进行赋值看起来像拷贝的原因。
```python
>>> a = 1
>>> b = a
>>> b = 2
>>> a
1
>>> b
2
```
> 常见的不可变对象包括:
>
> - 整数`int`
> - 浮点数`float`
> - 字符串`str`
> - 元组`tuple`
> - 布尔值`bool`
> - 字节串`bytes`
> - 冻结集合`frozenset`
>
> 详见[官方文档](https://docs.python.org/zh-cn/3/reference/datamodel.html)
### with语句
[官方文档](https://docs.python.org/zh-cn/3/reference/compound_stmts.html#with)
对于带有上下文管理器的对象,可以使用`with`来进行上下文管理。
进入`with`语句块时,对象的`__enter__()`方法会被自动调用;离开时,对象的`__exit__()`方法也会被自动调用,无论出错与否。保证能够正确关闭句柄、结束会话等。
```python
with open("./result.txt", "w+", encoding="utf-8") as fp:
fp.write("Pretending to be data")
```
> `requests`中的请求也可以用噢,看看[本节的代码](https://github.com/NingmengLemon/hcw-pyproj/tree/main/src/Proj-01)?
## 格式化字符串
格式化字符串是指将变量或表达式的值嵌入到字符串中的一种方法,各个编程语言都有各自的格式化字符串方法。对于Python,[官方文档](https://docs.python.org/zh-cn/3/tutorial/inputoutput.html)中列举有四种,选择适合的即可。
### f-string
通过在以`f""`包括的字符串中使用`{expression}`来嵌入值,在表达式后紧接`:` `!` `=`等格式说明符可以指定格式。重复的花括号可以得到花括号本身。详见官方文档。
```python
>>> a = 11.4514
>>> f"{a}"
'11.4514'
>>> f"ww{a:08.2f}" # 格式化
'ww00011.45'
>>> f"{a=}" # 自说明
'a=11.4514'
>>> f"{int!r}" # 转换
""
>>> f"}}{a}{{" # 花括号转义
'}11.4514{'
```
### format
`format()`是字符串对象的一个方法[^2]。同样通过花括号发挥效用。在格式说明符`:` `!`上的语法与`f-string`相同,但其余部分略有不同。
```python
>>> a = 11.4514
>>> "{}".format(a)
'11.4514'
>>> "{=}".format(a) # 没有`=`说明符
Traceback (most recent call last):
File "", line 1, in
KeyError: '='
>>> "{:08.2f}".format(a)
'00011.45'
>>> b = 191.981
>>> "{1:.2f} {0:.3f}".format(a, b) # 指定位置
'191.98 11.451'
>>> "{b:.2f} {a:.3f}".format(a=a, b=b) # 指定关键字
'191.98 11.451'
```
这意味着你可以在`format`方法中使用参数解包等很帅的操作 —— cool!
更多信息参见[官方文档](https://docs.python.org/zh-cn/3/tutorial/inputoutput.html#the-string-format-method)
### 旧式格式化
Python支持通过在字符串后紧跟`%`符进行格式化。当只有一个值时括号可省。
又被称作`printf`风格格式化,详见[官方文档](https://docs.python.org/zh-cn/3/library/stdtypes.html#old-string-formatting)。C语言中的相关内容参见 [cppreference](https://zh.cppreference.com/w/cpp/header/cstdio)。
```python
>>> "%.2f" % (a)
'11.45'
```
### 手动格式化
详见[官方文档](https://docs.python.org/zh-cn/3/tutorial/inputoutput.html#manual-string-formatting)
[^1]:
[^2]: 同时也是一个内置函数,若感兴趣可以自行查阅[文档](https://docs.python.org/zh-cn/3/library/functions.html#format)。
for Proj-01
## 关于虚拟环境
### 虚拟环境是什么?
是一种用于隔离项目依赖关系的工具。每个虚拟环境都有自己的解释器和库。
> 通常虚拟环境中的解释器会指向全局环境中的解释器,也就是说在爆改全局环境(主要是更改解释器位置)后别忘了重建你的虚拟环境。
### 为什么需要虚拟环境
主要是能够避免不同项目的依赖的冲突。比如有一个项目需求低版本的`numpy`但是另一个需求高版本,把它们放在同一个环境中尝试安装依赖时就会出问题。
### 手动创建虚拟环境
通过VSCode创建详见正文。这里尝试讲解手动使用内置模块`venv`进行创建。
在项目文件夹中打开终端,使用下面的指令创建虚拟环境。
```bash
python -m venv ./.venv
```
大概就是调用Python的一个叫`venv`的模块,在当前目录下的`.venv`文件夹下创建虚拟环境。
> 将虚拟环境所在文件夹命名为`.venv`是一个约定俗成的做法,很多开发工具的默认行为就是这样,它们会自动识别并使用这个文件夹中的虚拟环境。
稍等一下就能完成。很快地,项目文件夹下多出了一个叫`.venv`的文件夹。
在终端下执行`./.venv/Scripts/activate`就能手动激活虚拟环境。
> 若遇到`powershell`阻止,按照提示进行设置即可。
要检查是否真正处于虚拟环境,使用powershell指令`Get-Command`查找`python`解释器的位置,判断一下是否处于正确的位置(`.venv/Scripts/python`)就行了。
## 关于`requests`库
很优雅的一个多线程安全的第三方网络请求库。
不过你也可以尝试只用Python标准库实现这个项目,这是可以做到的!
## 关于语法
> *Python很容易入门,语法限制也很少。但这不意味着它很简单,更不意味着它很随便。*
>
> —— MelodyEcho
这里仅尝试讲解正文中使用到的。
快去看[官方文档](https://docs.python.org/zh-cn/3/reference/index.html)
### 注释
Python中使用`#`符进行注释,在当前行中非字符串内容的`#`及其后的内容都会被当成注释。
有时能在一些文件看到以`#!`开头的首行注释,这被称作Shebang。
> Shebang这一语法特性由`#!`开头。在开头字符之后,可以有一个或数个空白字符,后接解释器的绝对路径,用于调用解释器。在直接调用脚本时,调用者会利用Shebang提供的信息调用相应的解释器,从而使得脚本文件的调用方式与普通的可执行文件类似。[^1]
你说得对 ☝️🤓,但是`'''`,即连续的三个引号包围的内容不是注释,而是多行字符串。它也常被用在[docstring](https://peps.python.org/pep-0257/)中。
### 控制流
参见 [Grammar_playground](./2.2.2.2.4%20Grammar%20Playground.md)
### 导入模块
`import`语句用于导入模块。
> 有一个叫`importlib`的库也可以用来导入模块,而且动态
常用的写法:
```python
# 直接导入
import math
import os.path
# 从某个模块中导入对象
from lxml import etree
# 导入某个模块中的所有对象到当前命名空间中,非必要不使用
from tkinter import *
# 换个名称导入
from tkinter import messagebox as msgbox
# 导入多个对象
from urllib import request, parse
# 上述指定了模块名的叫做绝对导入,从项目的根目录开始导入模块
# 下面的是相对导入,一般用在包的内部文件之间。
from . import utils
from ..module_a import func
```
[PEP8](https://peps.python.org/pep-0008/#imports) 中对于导入语句的写法有推荐,可以看看。
关于机制,Python会按顺序搜索`sys.path`列表中的每个目录,查找与模块名匹配的文件。
`sys.path`默认包括:
- 当前文件所在目录
- `%PYTHONPATH%`环境变量指定的目录
- 标准库目录,通常是`python/Lib`
- 安装的第三方包的目录,通常是`python/Lib/site-packages`
> 优先级:当前目录 -> site-packages -> 内置模块
你可以手动为`sys.path`添加路径,让它去额外的目录找模块;也可以自定义模块加载的各个步骤,总之就是自由度十分~~甚至九分~~地高。
更多内容参见[官方文档](https://docs.python.org/zh-cn/3/reference/simple_stmts.html#import),或者去问问AI。
### 函数
#### 函数的定义
使用`def`或者`lambda`定义,由后者定义的函数叫做匿名函数。
> 函数定义是一条**可执行语句**。它执行时会在当前局部命名空间中将函数名称绑定到一个函数对象(函数可执行代码的包装器)。这个函数对象包含对当前全局命名空间的引用,作为函数被调用时所使用的全局命名空间。(摘自官方文档)
嗯你问函数它本身是什么…… ~~函数它就是函数啊~~
Python中的函数是**封装**了代码块的**对象**。使用它可以复用代码,减少重复。
快去看[官方文档](https://docs.python.org/zh-cn/3/reference/compound_stmts.html#function)ww
一个典型的函数大概长这样:
```python
# 形式参数 参数类型标注(可选)
# vv vvvvvvvvvvv
def join(ss : list[str]) -> str:
# ^^^^---------------- ^^^^^^
# 函数名 形式参数列表 返回值类型标注(可选)
return "".join(ss)
```
> 关于形参与实参
>
> 形参是函数在定义的时候写好的参数,实参是函数在被实际调用时传入的参数
> 为什么要做类型标注(Type Hints)?
>
> 你说得对🤓,虽然Python是一种动态类型语言,但是类型标注也有很多好处。它能提升代码的可维护性和可读性,以及便于生成文档。此外在面对稍微复杂一些的代码时单靠IDE进行类型推导会显得有些吃力,但要是加上类型标注的话就可以帮助~~思维阻滞的~~IDE进行自动补全和错误检测等工作。
>
> 不过它完全是可选的,最终写不写还是取决于你就是了。
>
> 关于类型标注的详细讲解,见[Proj-02的补注](../2.2.2.3%20Proj-02/2.2.2.3.3%20Notes.md)。
#### 关于函数的默认参数
**不要将默认参数设定为可变对象**。作为替代,可以将默认参数设定为`None`,并在函数内部进行处理。
因为函数的默认参数只会在定义时被计算一次,后续每次调用时它们都会被共享。如果默认参数被修改,这些修改会影响到后续的函数调用。它在默认参数被实参覆盖时不会有问题,但在默认参数留空时便会出现意料外的情况。
来看例子。
```python
>>> def append_to_list(value, my_list=[]):
... my_list.append(value)
... return my_list
...
>>> print(append_to_list(1))
[1]
>>> print(append_to_list(2))
[1, 2]
>>> print(append_to_list(3))
[1, 2, 3]
```
替换为使用`None`:
```python
>>> def append_to_list(value, my_list=None):
... if my_list is None:
... my_list = []
... my_list.append(value)
... return my_list
...
>>> print(append_to_list(1))
[1]
>>> print(append_to_list(2))
[2]
>>> print(append_to_list(3))
[3]
```
### 异常
[官方文档](https://docs.python.org/zh-cn/3/reference/compound_stmts.html#the-try-statement)
#### 异常捕获
完全体的异常处理大概像这样:
```python
try:
# 可能会出错的代码
x = 1 / int(input("Your Number:"))
assert 1 == 0, "oops, 1 != 0"
except ZeroDivisionError as e:
... # 发生除零错误时会执行的代码
except AssertionError as e:
... # 发生断言错误时会执行的代码
except Exception as e:
... # 发生一般错误时会执行的代码
else:
... # 没有发生错误时会执行的代码
finally:
... # 不管发生了什么都会执行的代码
```
异常匹配顺序从上到下,匹配成功后便不再继续匹配。若最终都没有匹配上则错误还是会被抛出(但`finally`块仍然有效)
> 关于裸`except:`
>
> 等价于`except BaseException:`,可以捕获**所有**异常,包括系统退出和中断的异常,像 `SystemExit`、`KeyboardInterrupt` 和 `GeneratorExit`。因为经常会捕获到不该捕获的东西,语义不明确所以不建议使用。
>
> 一般日常建议使用`except Exception:`。就算你真的想捕获系统级异常,也应该使用`except BaseException:`进行平替,因为它的语义更明确一些。
> `...`?
>
> 没错`...`也是一个对象,叫做`Ellipsis`,通常用来表示省略或占位。在某些特殊的场景很有用(比如`numpy`中创建多维数组)。
`except ... as ...`可以将捕获的异常实例保存到一个变量中,方便后续做处理
#### 异常抛出
使用`raise`语句抛出一个异常**对象**。
> 在抛出之前,先要将异常类实例化,在实例化时可以传递具体的错误消息。(不实例化就抛出是py2的做法)
```python
raise RuntimeError("A runtime error.")
```
`raise ... from ...`可以从一个已有的异常实例抛出一个新异常,原有异常会被保存在新异常的`__cause__`属性中。常用在`except`块下。
### 变量作用域与生命周期
Python中的局部变量的作用域是它直接所在的函数,全局变量的则是它所在的模块。
在一个变量超出它的作用域,且它的引用计数变为`0`时,它就会被删除。
> 对于循环引用,Python的垃圾回收器负责清理它们。可以通过调用`gc.collect()`来手动执行垃圾回收。
> Python没有像C/C++或Java那样的块级作用域(Block Scope),在控制结构 (`if`/`while`/`match`/`for`/ ...) 中声明的变量在控制结构之外依然可以访问,只要是在相同的函数或全局作用域中;也没有像 js 那样的[变量提升](https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Guide/Grammar_and_types#%E5%8F%98%E9%87%8F%E6%8F%90%E5%8D%87),在变量被声明并赋值之前访问它会报错。
~~来点C语言笑话:~~
```c
void just_a_test(void){
// 我 有 异 域 症
{int i;}
{i = 0;}
}
```
### 引用与拷贝
Python中的**所有数据都是对象**。变量名实际上是指向这些对象的引用。当你赋值一个变量时,你只是将这个变量名绑定到某个对象上,并不复制对象本身。
```python
>>> a = [1, 2, 3]
>>> a
[1, 2, 3]
>>> b = a
>>> b.append(4)
>>> a
[1, 2, 3, 4]
```
> 要复制对象,可使用`copy`库中的`copy()`和`deepcopy()`函数,或某些对象内置的`copy()`方法
>
> - `copy()`: 浅拷贝,仅复制顶层结构,产生的新对象内层仍然共享。
> - `deepcopy()`: 深拷贝,复制整个对象,产生的新对象完全独立
Python中的对象分为可变(e.g.列表/字典/集合)和不可变(e.g.整数/字符串/元组)两种。
对于不可变的对象,对它做修改或赋值会在原地生成一个新的对象,再将这个对象绑定到变量上。这就是平时在数字/字符串变量之间进行赋值看起来像拷贝的原因。
```python
>>> a = 1
>>> b = a
>>> b = 2
>>> a
1
>>> b
2
```
> 常见的不可变对象包括:
>
> - 整数`int`
> - 浮点数`float`
> - 字符串`str`
> - 元组`tuple`
> - 布尔值`bool`
> - 字节串`bytes`
> - 冻结集合`frozenset`
>
> 详见[官方文档](https://docs.python.org/zh-cn/3/reference/datamodel.html)
### with语句
[官方文档](https://docs.python.org/zh-cn/3/reference/compound_stmts.html#with)
对于带有上下文管理器的对象,可以使用`with`来进行上下文管理。
进入`with`语句块时,对象的`__enter__()`方法会被自动调用;离开时,对象的`__exit__()`方法也会被自动调用,无论出错与否。保证能够正确关闭句柄、结束会话等。
```python
with open("./result.txt", "w+", encoding="utf-8") as fp:
fp.write("Pretending to be data")
```
> `requests`中的请求也可以用噢,看看[本节的代码](https://github.com/NingmengLemon/hcw-pyproj/tree/main/src/Proj-01)?
## 格式化字符串
格式化字符串是指将变量或表达式的值嵌入到字符串中的一种方法,各个编程语言都有各自的格式化字符串方法。对于Python,[官方文档](https://docs.python.org/zh-cn/3/tutorial/inputoutput.html)中列举有四种,选择适合的即可。
### f-string
通过在以`f""`包括的字符串中使用`{expression}`来嵌入值,在表达式后紧接`:` `!` `=`等格式说明符可以指定格式。重复的花括号可以得到花括号本身。详见官方文档。
```python
>>> a = 11.4514
>>> f"{a}"
'11.4514'
>>> f"ww{a:08.2f}" # 格式化
'ww00011.45'
>>> f"{a=}" # 自说明
'a=11.4514'
>>> f"{int!r}" # 转换
"
>>> f"}}{a}{{" # 花括号转义
'}11.4514{'
```
### format
`format()`是字符串对象的一个方法[^2]。同样通过花括号发挥效用。在格式说明符`:` `!`上的语法与`f-string`相同,但其余部分略有不同。
```python
>>> a = 11.4514
>>> "{}".format(a)
'11.4514'
>>> "{=}".format(a) # 没有`=`说明符
Traceback (most recent call last):
File "
KeyError: '='
>>> "{:08.2f}".format(a)
'00011.45'
>>> b = 191.981
>>> "{1:.2f} {0:.3f}".format(a, b) # 指定位置
'191.98 11.451'
>>> "{b:.2f} {a:.3f}".format(a=a, b=b) # 指定关键字
'191.98 11.451'
```
这意味着你可以在`format`方法中使用参数解包等很帅的操作 —— cool!
更多信息参见[官方文档](https://docs.python.org/zh-cn/3/tutorial/inputoutput.html#the-string-format-method)
### 旧式格式化
Python支持通过在字符串后紧跟`%`符进行格式化。当只有一个值时括号可省。
又被称作`printf`风格格式化,详见[官方文档](https://docs.python.org/zh-cn/3/library/stdtypes.html#old-string-formatting)。C语言中的相关内容参见 [cppreference](https://zh.cppreference.com/w/cpp/header/cstdio)。
```python
>>> "%.2f" % (a)
'11.45'
```
### 手动格式化
详见[官方文档](https://docs.python.org/zh-cn/3/tutorial/inputoutput.html#manual-string-formatting)
[^1]:
[^2]: 同时也是一个内置函数,若感兴趣可以自行查阅[文档](https://docs.python.org/zh-cn/3/library/functions.html#format)。
点赞
回复
X