【学习笔记】Python语法细节总结

173 阅读23分钟

Python Notes

Python 3.12 基础语法

数据类型转换

隐式类型转换:python变量类型不是固定的,会在运行时,根据变量当前值自动转换,即为python的隐式类型转换。在对数值进行运算时,数据类型会自动向更高精度的类型转换。

显示类型转换:手动使用数据类型同名函数,对变量数据类型进行强制转换,但强制转换后并不会影响原变量的数据类型。

Python的数据类型分类

  • 不可变数据类型:数值类型 (int, float)、布尔 (bool)、字符串 (str)、元组 (tuple)
  • 可变数据类型:列表 (list)、集合 (set)、字典 (dict)
  • 改变数据元素的值,其内存地址不变,则为可变数据类型,反之为不可变数据类型。

运算符

:x and y(若x为真,则返回y;若x为假,则返回x)

:x or y(若x为真,则返回x;若x为假,则返回y)

:not x(与x的结果相反)

三目运算:max = a if a>b else b

位运算符

名称表示功能
按位与a & b二进制数据的补码,同1异0
按位或a | b二进制数据的补码,有1为1
按位异或a ^ b二进制数据的补码,相异为1
按位非~ a二进制数据的补码,按位取反
位左移a << n符号位不变,高位舍去,低位补0
位右移a >> n符号位不变同时补给高位,低位舍去

运算优先级:圆括号>算术运算>位运算>比较运算>逻辑运算>赋值运算

关键字

全局/局部变量

全局变量:在整个程序范围内都可访问,定义在函数体外,作用域在全局。

局部变量:仅在被声明的函数内部使用,定义在函数体内。

若有同名的全局变量和局部变量,则根据就近原则使用。若在函数内部使用global关键字声明变量,则该变量指定使用全局变量。

函数使用的注意事项

  • 同名函数调用时,遵循就近原则。
  • 定义形参不用指定数据类型,会根据传入的实参决定。
  • 定义函数时也可以给参数提供默认值。默认参数需排在参数列表后方。
  • 函数支持可变参数,传入的多个实参将会存储为一个元组tuple。定义时,参数列表使用*args表示。
  • 函数的可变参数支持多个关键字参数,即多个“形参名=实参值”,传入的多个关键字参数存储为一个字典dict。定义时,参数列表使用**args表示。
  • 调用函数时,根据函数定义时的参数位置来传递参数,传递的实参与定义的形参顺序与个数必须一致。
  • 调用函数时,支持关键字参数,即通过“形参名=实参值”的方式传递参数,这样不受参数传递顺序的限制。
  • 函数可拥有多个返回值,且返回数据类型不受限制。
  • 字符串和数值是不可变的数据类型,当值发生变化时,其对应内存地址将会发生改变。
  • 函数可作为参数进行传递,一个函数可接受多个函数作为参数。

类型注解type-hint

# 在定义变量时,变量名称后面使用冒号标明变量的类型`value : type`,type的类型包括容器和自定义类。
value_name: int

# 在定义数据容器时,可对容器内存储的数据进行类型注解。
list_name: list[int] = [100, 200]
tuple_name: tuple[int, bool, float] = [9, true, 3.14]
set_name: set[str] = ["John", "Rose"]

# 在定义后加注释进行注解`# type: int`。
value_name = "Jack" # type: str

# 定义函数时,对函数参数进行类型注解
def func_name(attribute1: int, attribute2: bool) -> return_type:
    # Function body
    
# 变量和函数都可以使用Union进行注解
from typing import Union

a: Union[int, str] = 100 
	# a可以是int或者str类型
list_name: list[Union[int, str]] = [22, 33, "Jack"]
	# list内部元素可以是int或str
def func1(num1: Union[int, float],
          num2: Union[int, float]) -> Union[int, float]:
    return num1 + num2
	# 函数参数使用联合体注解


    

​ 类型注解不是强制性的,编译器只是会提示类型不一致的情况

Union类型

定义方式Union[x, y] 等价于 x | y,表示联合体内只有两种类型。联合体参数至少有一个且必须是某种类型。

联合体使用前必须先导入Union模块:from typing import Union

# 联合体内部若包含另一个联合体,则会被展平。
Union[Union[int, str], float] == Union[int, str, float]

# 单参数联合体的类型就是参数本身。
Union[int] == int

# 多个同种类型参数只显示一个。
Union[int, str, int] == Union[int, str] == int | str

# 联合体的比较运算不涉及参数顺序。
Union[bool, float] == Union[float, bool]

lambda匿名函数

用于创建临时函数

lambda a, b: a if a > b else b
    # lambda关键字声明为匿名函数
    # a, b为捕捉参数列表
    # :后为函数体,只能单行
    # 不需要return,运算结果即返回值

常用函数及魔术方法

type()		# 返回变量类型
id()		# 返回对象内存地址
len()		# 返回对象长度或元素个数
ord()		# 返回单个字符Unicode编码值

zip()		# 将可迭代对象作为参数,将对象内所有元素打包成一个元组,返回由这些元组组成的列表

range(start, stop, step)		# 返回一个有序的不可变序列
	# start默认0,step默认1。
    # range()生成数列为前闭后开,stop指定的结尾不在序列内。

魔术方法:所有使用__包裹的方法,不需要调用,在类或对象的某些事件发生时,自动执行,魔术方法支持自定义。

unittest.mock --- 模拟对象库 — Python 3.12.6 文档

数据容器

列表

创建方式

list1 = [], list2 = list()				# 使用[]或list()来定义一个空列表
list3 = [1, 2, 3]						# 使用方括号创建

# 使用列表推导式
# [列表元素表达式 for 变量 in 可迭代对象]
list_name = [x for x in iterable]
list4 = [ele * 2 for ele in range(1,5)] # 得到列表[2, 4, 6, 8]

重要操作

list(seq)					# 将元组转换为列表
list.count(x)				# 统计x在列表中出现次数
list1.extend(list2)			# 将list2追加到list1末尾
list.insert(index, x)		# 将x插入到指定的index位置

注意事项

  • 列表索引也可从尾部开始,其下标为-1,往前一位为-2,依此类推。
  • 列表是可变序列,即列表元素可以修改,修改后列表元素内存地址改变,但整个列表地址不变。
  • 列表作为参数,传递给函数时,会将地址传递给函数,在函数内部修改列表,列表本身会被修改。

元组

元组是不可变序列,可存放多个不同数据类型,通常用于存储异构数据的集合,也可以用于存储同构数据的不可变序列。

创建方式

() or tuple()			# 表示空元组
a, or (a,)				# 表示单元组,定义时必须加逗号
a, b, c or	(a, b, c)	# 逗号分隔多项
tuple()					# 使用内置的tuple

重要操作

tuple_name[index]		# 使用访问元组内元素
tuple_name.index(obj)	# 返回某值的索引

注意事项

  • 元组创建之后就无法修改。
  • 元组元素数据类型无限制,可嵌套元组。元素内元素可重复,并且有序。
  • 元组内若包含list类型的元素,则list可以修改,但不能整个删除。
  • 元组可从尾部遍历,最后一个元素索引为-1,往前一位是-2,以此类推。
  • 在多线程环境中,不可变对象本身是线程安全的,可以减少线程同步开销,方便共享。元组在创建效率及内存占用上都由于列表,同时对数据元素进行写保护。

字符串

用于存储文本数据,字符串是由Unicode码位构成的不可变序列,创建之后不可修改。在python中字符串没有长度限制。

一对三个引号(单引号或双引号),可以存储多行文本数据。

字符串支持索引访问单个字符,返回类型仍是字符串类型。

字符串可使用运算符>, <=, ==, !=进行比较,两个字符串逐个对比内部源码。

字符串重要操作

str_name.replace[oldstr, newstr, count]		# 返回副本,将old替换成new,替换count次,默认全部替换。
str_name.split(sep=None, maxsplit=-1)		# 返回一个列表,使用sep分割,进行maxsplit次拆分。
str_name.strip([chars])						# 移除字符串头尾的chars字符。
str_name.capitalize()						# 返回副本,使字符串首字母大写。
str_name.casefold()							# 返回副本,消除字符串大小写格式,用于忽略大小写的匹配

字符串格式化输出三种方式

print("%s %d %0.0f" % (string, int, float))					# %操作符
print("输出内容:{} {} {}".format(string, int, float))		# format()函数
print(f"输出内容:" {string} {int} {float})					# f-strings

字符串输出注意事项:

  1. 原样输出:使用一对'''或""",即三对单引号或双引号包裹输出内容。
  2. 在字符串前加r或R做前缀,防止字符串内容被转义。

字符串驻留机制

  • 在原生Python中,仅保存一份相同且不可变字符串,存放在驻留池中,后续创建相同的字符串仅赋给变量字符串地址。该机制在编译时发生,而非运行时。
  • 字符串驻留机制触发条件:仅由26个大小写英文字母及数字组成;长度为0或1;大小在[-5, 256]内的整数数字。
  • 在PyCharm中,该机制会有优化。

集合

集合是由不重复元素组成的无序容器,可用于成员检测、消除重复元素。集合对象支持合集、交集、差集、对称差分等数学运算。集合不支持索引,不可使用while只能使用for进行遍历。创建集合使用花括号{}或set()函数,创建空集合只能用set()函数。

创建方式

set_name = {100, 200, 300, 400}				# 花括号内逗号分隔
set_name = set('a', 'b', 'c')				# 类型构造器

# 集合生成式
{集合元素表达式 for 自定义变量 in 可迭代对象}
set_name = {c for c in 'abbccc' if c not in 'abc'}		# {a, b, c}
set_name = {ele * 2 for ele in range(1,5)}				# {2,4,6,8}

重要操作

set_3 = set_1.union(set_2)				# 两集合合并
set_3 = set_1.intersection(set_2)		# 两集合求交集
set_3 = set_1.difference(set_2)			# 两集合做差,c=a-b

字典

字典是一种映射类型,处理通过key存储或寻找value的需求。字典中存储的是多个键值对,并以唯一的key为索引,key通常是字符串或数字等不可变的数据类型,value可以是任意数据类型。

  • 如果一个元组只包含字符串、数字和元组,此时也可以作为key。列表以及包含可变数据类型的元组不能作为key。
  • 字典不支持索引,不支持while遍历,可以使用for循环进行遍历。
  • 字典key必须唯一,如果指定了多个相同key,其对应的value会被新的覆盖。

创建方式

dict_name = {}
dict_name = dict()								# 创建空字典
dict_name = {key1:value1, key2:value2, ...}		# 普通创建

# 字典生成式:
# { key表达式:value表达式 for elem1, elem2 in zip(可迭代对象1, 可迭代对象2)}
keys = ["key1", "key2", "key3"]
values = ["value1", "value2", "value3"]
dict_name = { key_name:value_name for keys, values in zip(keys, values) }
# {'key1':'value1', 'key2':'value2', 'key3':'value3'}

遍历方式

for key in dict_name:
    print(f"key: {key}, value:{dict_name[key]}")
    
for value in dict_name.values():
    print(f"{value}")
    
for k,v in dict_name.items():
    print(f"key: {k}, value: {v}")

重要操作

keys()						# 返回字典所有key,类型是dict_keys
key in dict_name			# 判断字典中是否有key
pop(key[, default])			# 返回字典中key的value并弹出

序列类容器的切片操作

对序列类容器如列表、元组、字符串等,内容连续、有序且可使用索引的数据容器,从序列中取出一个子序列。

语法:序列[起始索引:结束索引:步长]。

  • 切片操作前闭后开,起始索引包含在切片后的子序列中,结束索引不是。
  • 起始索引默认为0,结束索引默认到结尾,步长默认为1。
  • 反向切片则参数均为负数。
  • 切片操作不会影响原序列,而是生成新序列。
str_name[start:end:step]				# 字符串
list_name[start:end:step]				# 列表
tuple_name[start:end:step]				# 元组

数据容器特点总结

比较项列表(list)元组(tuple)字符串(str)集合(set)字典(dict)
是否支持多个元素YYYYY
元素类型任意任意只支持字符任意Key:通常是字符串或数字
Value:任意
是否支持元素重复YYYNKey不能重复
Value可以重复
是否有序YYYN3.6版本之后开始支持元素有序的功能
是否支持索引YYYNN
可修改性/可变性YNNYY
使用场景可修改、可重复的多个数据不可修改、可重复的多个数据字符串不可重复的多个数据通过关键字查询对应数据的需求
定义符号[]()""/"{}{ key:value }

数据容器的传参机制

在Python中,数据容器传参是通过值传递还是通过引用传递(传递数据的内存地址),主要取决于数据容器是可变(mutable)还是不可变(immutable)的。

  • 对于不可变数据类型,整数、浮点数、布尔值作参数时,实际上是通过值传递传参;字符串作参数,传递其副本;元组作为参数,传递的是元组的引用,但在函数内部不可修改元组的内容。
  • 对于可变数据类型,列表、字典、集合作为参数时,传递的是对象的引用,在函数内部可以修改对象的内容。

模块

6. 模块 — Python 3.12.6 文档

导入模块

[ from 模块名 ] import ( 函数 | 类 | 变量 | * ) [ as 别名 ]

from module_name import object_name		# 完整导入模块的某功能
from module_name import *				# 导入模块所有功能
import module_name as _name				# 给导入模块取名
from module_name import object_name as _name
										# 给导入模块的某功能取名

python模块的特殊变量

  1. __name__:

在模块内部,文件的文件名保存在全局变量__name__中,__name__一般是filename.py文件去掉.py后缀剩下的名称。如果一个文件是包的组成部分,则__name__也将包括父包的路径。如果模块是在最高层级代码环境中执行的,则它的__name__会被设为字符串__main__。在主程序执行代码时,不希望导入的模块中同名函数被同时运行,则在执行前加上:if __name__ == "__main__":的判断语句。

  1. __all__:

模块中的字符串列表,用于开放模块的特定功能,当在其它模块使用 from module import * 导入本模块时,定义在__all__中的功能才能被导入,如__all__ = ['type1', 'type2']。如果不定义 __all__,那么导入语句会导入模块顶级命名空间中除了以下划线开头的名称以外的所有名称。

__all__ 变量并不阻止显式导入任何名称,它只影响使用星号(*)的导入。如果使用 from moduleA import _name 指定导入某功能,那么即使 __all__ 没有显式列出,这个导入操作也会成功,只要某功能确实存在于被导入模块中。

包的管理

包是指通过使用“带点号模块名”来构造Python模块命名空间的一种方式,如模块A.B表示A模块中名为B的子模块。

在编译器中,使用Python Package创建包,在包文件夹下,同时会自动生成__init__.py文件,在包文件夹下,__init__.py文件来控制包导入的模块。

import package_name.module_name			# 导入包模块某功能
from package_name import module_name	# 导入包模块某功能
	# 这种导入方法在使用时,不用带包名
from package_name import *				# 导入包模块的所有功能
	# 这种导入方法在使用时,不用带包名

__init__.py文件中,通过__all__变量控制允许导入的模块。该方法仅对from package_name import *方式生效。例:__all__ = [ 'module_name' ]

第三方库

python常用第三方库总结 - 知乎 (zhihu.com)

命令行pip工具安装第三方库

pip install -i https://... lib_name/package_name
	# -i 参数指定pip源										

​ Python pip常用源地址:

Pycharm管理第三方库

​ 设置 -> 项目:pythonProject -> Python解释器

类提供了把数据和功能绑定在一起的方法。创建新类时创建了新的对象类型,从而能够创建该类型的新实例。实例具有能维持自身状态的属性,还具有能修改自身状态的方法(由其所属的类来定义)。

  • Python类的继承机制支持多个基类,支持派生的类能覆盖基类的方法,支持类的方法能调用基类中的同名方法。
  • 对象可包含任意数量和类型的数据。和模块一样,类也支持 Python 动态特性:在运行时创建,创建后还可以修改。
  • 类成员(包括数据成员)通常为 public (例外的情况见下文 私有变量),所有成员函数都为 virtual
  • 没有用于从对象的方法中引用本对象成员的简写形式:方法函数在声明时,有一个显式的第一个参数代表本对象,该参数由方法调用隐式提供。
  • Python 的内置类型可以用作基类,供用户扩展。此外,与 C++ 一样,具有特殊语法的内置运算符(算术运算符、下标等)都可以为类实例重新定义。
  • Python中一切皆为对象,所有对象都有一个布尔值,可以通过内置函数bool()获取。

操作规范

定义类和创建类的实例:

class C:
    成员变量(属性)
    成员函数(方法)
c1 = C()

声明和定义类的成员函数:

# 声明 
def func_name(self, arg1, ...) -> return_type:
    pass # 未定义方法体
# 定义
def func_name(self, arg1, ...) -> return_type:
    self.class_value1 # 方法体

# 方法定义的参数列表中,self表示当前对象本身,定义时必须添加。
# 当通过对象调用方法时,self参数会隐式传入
# 在成员函数内部使用self才能访问到成员变量及成员函数。

动态给实例对象添加方法:

​ 假设p1为类P的一个实例,func()为类P中未定义的方法,将func()添加到实例p1中,即类实例方法关联。

p1.new_func = func()
p1.new_func()

将对象方法声明为静态方法:

class C:
    @staticmethod
    def func(arg1, arg2, ...) -> return_type:
        ...
# 静态方法不会接受隐式的第一个参数,即参数不需要添加self

C.func()
# 静态方法可通过类名来调用。
  • 类也是一个对象,通过class对象可以访问属性。
  • 通过类名也可以调用非静态的成员方法,但需要将类本身作为参数传给调用的方法。

抽象类

默认情况下Python不提供抽象类,可以使用abc模块,令抽象基类继承abc模块中的ABC类,在这个抽象基类中使用@abstractmethod(abc模块中的装饰器)来声明抽象方法,然后声明子类继承该抽象基类即可。

  • 抽象类含有抽象方法就不能实例化,没有抽象方法就可实例化。
  • 抽象基类需要继承ABC类,并需要至少一个抽象方法。
  • 抽象类也可以有普通方法。
  • 如果一个类继承了抽象类,那么它必须实现所有抽象方法,否则它仍是一个抽象类。

模板设x计模式:抽象类体现了模板设计模式的思想,利用抽象类作为多个子类通用的模板,子类可以在此基础上改造扩展,同时子类总体上保留抽象父类的行为方式。

构造器的使用

def __init__(self, arg1, ...):
    # do something

初始化对象时,会自动执行Python预定义的__init__方法,参数将自动传递给__init__方法,在__init__方法内可以自定义一系列的操作,这些操作会在构造类实例时一并完成。一个类中只有一个__init__方法,并且它不能有返回值。

在Python中,可以使用__init__来给具体实例动态生成对象属性,即在创建类实例的同时,给类实例添加原本类中不存在的成员变量,但这些成员变量只能在本实例中使用。

类的继承

class derived_class_name(base_class_name):
    # statement...

子类继承了父类所有属性和方法,非私有的子类可以直接访问,私有的子类必须通过父类提供的公共方法去访问,子类不能直接访问父类的私有成员。

Python中object是所有类的基类,Python支持多重继承,如果有同名的成员,遵守从左到右优先级递减的继承原则,对于多个父类共有的同名函数,父类优先级高的同名函数将被继承下来。

如果子类有父类的同名成员,可以通过父类名super()来访问父类成员。如果有多重继承,访问同名成员需从子类向上逐级查找。

x# 访问父类成员方式
base_name.attribute_name
base_name.method_name
super().attribute_name
super().method_name
# 建议使用super()方式,因为父类名称不固定

子类可以在构造器中,调用父类构造器完成父类属性的初始化;也可以override重写继承自父类的属性和方法(Pycharm编译器会自动标记子类重写父类的属性或方法)。

class derived_class_name(base_class_name):
    def __init__(self, derived_attribute_name, ...):
        super().__init__(base_attribute_name)

类的多态

向上转型 -- Python中,子类实例对象可以传递给父类类型的参数。

Python不要求严格的继承体系,关注点不在对象本身,而是对象是否具有要调用的方法。

当调用对象成员的时候,会和对象本身动态关联

isinstance():用于判断对象是否为某个类或者其子类的对象。

isinstance(object, classinfo)
# object: 对象
# classinfo: 基本类型、类名或由它们组成的元组

# 加入B继承自A,b为B的实例对象,则:
isinstance(b, A) == isinstance(b, B) == True

num = 9
isinstance(num, int) # True
isinstance(num, str) # False
isinstance(num, (int, str, list)) # True
isinstance(num, (str, list)) # False

类成员的私有化

默认情况下,类的成员都是公有的,名称前都没有下划线,在类的内部和外部都可以访问公有的成员变量及成员函数。

在类中使用双下划线__开头命名成员函数或成员变量,将其设置为私有,访问私有变量或方法,需提供公共方法。

Python作为动态语言,会出现伪私有属性的情况,当在类外部尝试给私有属性赋值的时候,编译器会自动给类的实例添加与私有变量同名的变量。

使用自定义对象传参的机制

将一个实例对象作为参数传递给一个函数时,实际上传递的是对象的引用。函数接收到的是指向同一个对象内存地址的引用。如果在这个函数中修改了实例对象的属性,那么这些修改将会影响对象本身的值。

有关成员变量的注意事项

错误和异常

内置异常 — Python 3.12.6 文档

内置异常

Python中,所有异常都必须继承自BaseException类实例。try语句的except子句,只处理继承自BaseException类的异常类。通过子类化创建的两个不相关异常类永远是不等效的,既使它们具有相同的名称。python支持继承Exception或其子类来自定义一个新异常。

如果一个异常发生,但没有捕获处理,那么这个异常将传递给调用者,若所有调用者都不处理,系统将接手。

异常处理

try/except语句:

  • 执行try主句下的代码时,如果未出现异常,则跳出异常处理;如果出现异常,则执行except子句。
  • except子句可以有多个处理方式,但每次最多一个处理执行;也可以在一个except子句内使用元组来指定多个异常。
  • try ... except 语句具有可选的else子句,该子句如果存在,它必须放在所有except子句之后。 它适用于try子句没有引发异常但又必须要执行的代码。
  • filnally子句下,不管有无异常,代码都会执行。
  • 异常处理程序不仅会处理在try主句中立刻发生的异常,还会处理调用(包括间接调用)的函数的异常。
try 
	# 尝试执行的代码
except (异常名称) A:
    pring("A")
except (Exception, RuntimeError, TypeError) as B:
    print("Error info: ", B)
except (ValueError):
    pass
else:
    print("hhh")

抛出异常

使用raise语句强制触发指定的异常。raise唯一的参数就是要触发的异常,这个参数必须是异常实例或异常类。如果传递的是异常类,将通过调用没有参数的构造函数来隐式实例化。

try:
    # function
    raise ZeroDivision # 主动触发
except ZeroDivision as z:
    print(f"捕获到异常:{z},类型:{type(e)}")

文件处理

7. 输入与输出 — Python 3.12.6 文档

常用文件操作

# 打开文件,返回一个文件对象file object
open(file, mode='r', buffering=-1, encoding=None, errors=None, newline=None, closefd=True, opener=None)

# 读取文件内容
f.read(size)	
	# 文件对象是文本模式返回字符串,是二进制模式返回字节串对象
    # size省略或者是负数时,读取并返回整个文件内容
    
# 读取文件单行数据
f.readline()
	# 字符串末尾保留换行符
    
# 列表形式读取文件所有行
list(f)
f.readlines()

# 逐行遍历整个文件
for line in f:
    
# 数据写入文件,返回写入字符数
f.write(string)

# 刷新文件流的写入缓冲区
f.flush()

# 刷新并关闭文件流,释放文件占用的系统资源
f.close()

# with关键字处理文件对象
with open() as f:
    # 子语句体结束后会自动关闭文件
    
# 删除文件,使用os模块
import os
if os.path.exists("d://full path"):
    os.remove("d://full path")
    

目录操作

import os

# 创建文件目录
os.mkdir(path, mode=0o777, dir_fd=None)

# 创建多级目录
os.makedirs(name, mode=0o777, exist_ok=False)

# 删除目录
os.rmdir(path, *, dir_fd=None)

# 删除多级目录
os.removedirs(name)

# 判断某路径是否是现有的目录
os.path.isdir(path)

获取文件相关信息

import os

# 获取文件或fd的状态,返回一个stat_result对象
os.stat(path, *, dir_fd=None, follow_symlinks=True)

# 用例
import os
import time
f_stat = os.stat("d:/python/hello.py")
print("--------文件信息--------")
print(f"文件大小-> {f_stat.st_size}\n"
     f"最近的访问时间-> {time.ctime(f_stat.st_atime)}\n"
     f"最近的修改时间-> {time.ctime(f_stat.st_mtime)}\n"
     f"文件创建时间-> {time.ctime(f_stat.st_ctime)}")