从头开始学python(python基础)

大家好!我是小困难,目前的方向是机器学习这一块,其实也算是半路出家,之前学过一次python,但是没有形成系统的知识体系。之前在写代码的时候,一个赋值操作引起的错误让我查了一个下午,我深感基础不牢,地动山摇。于是趁着这次寒假,根据几位大佬的学习资料,重新过了一遍python基础部分的知识,总结了一下我认为的重点。这里先感谢一下Jack Cui大佬的思维导图,可以说我的整体框架就是根据这个展开的。

可能有些部分的知识比较简略,大家想要了解更详细的,也可以参考一下我学习时用的两个网站:菜鸟教程廖雪峰老师的官方网站

从头开始学python

1 python基础

1.1 输入输出

输出

python中是以print()作为输出的,可以在括号中加上字符串就可以输出指定的文字。

print("hello, world") # 输出:hello, world

print()可以打印以,分隔的多个字符串,输出时会以空格分隔

print("one", "two", "three") # 输出:one two three

print()还可以直接打印数字,或者计算结果,结合起来我们可以实现下面的效果:

print(" 1 + 2 =", 1 + 2) # 输出: 1 + 2 = 3

print()默认是换行输出的,如果不想换行需要加上end="":

x = "hello,"
y = "world" 
# 换行输出
print(x)
print(y) 
print('-------')
# 不换行输出
print(x, end="") 
print(y, end="")
hello,
world
-------
hello,world

输入

python提供input()让用户输入字符串,并存放到一个变量中。我们试着将“小困难”存入变量name中:

name = input() # 输入:小困难

print()方法试着打印一下name

print(name) # 输出:小困难

但是这样的代码并没有告诉用户应该干什么,显得很没有交互性,我们可以试着修改为下面的样子:

name = input("请输入你的名字:")
print("你好,", name) 
请输入你的名字: 小困难


你好, 小困难

1.2 变量

类型例子
整数100
浮点数3.14159
复数1+2j
布尔型True, False
字符串'hello'
列表[1, 1.2, 'hello']
元组('ring', 1000)
集合{1, 2, 3}
字典{'dogs': 5, 'pigs': 3}
Numpy数组array([1, 2, 3])
Pandas类型DataFrame, Series
自定义Object Oriented Classes

变量赋值

使用=python中的变量赋值

a = 'hello' 

在这个过程中python解释器其实干了两件事:

  • 在内存中创建了一个'hello'的字符串
  • 在内存中创建了一个名为a的变量,并将其指向'hello'

python支持同时给多个变量赋值:

a = b = c = 1

python还允许在同一行为多个对象指定多个变量:

a, b, c = 1, 2, 'hello' 

不过,我并不推荐使用上面的两个赋值方法,这样的代码缺乏可读性,最好还是分开写。像下面这样:

# 名字
name = '小困难' 

# 年龄
year = 1 

python中还存在一种特殊的值:空值,用None表示。None不等同于0,因为0是有意义的,而None

赋值机制

上面在介绍变量赋值的时候,简单介绍了一下变量的赋值机制。不过光是知道上面的内容是不够的,下面是对python赋值机制的详细讲解:

首先,我们先根据是否可变将数据分为以下两种:

  • 不可变数据:number(数字)、string(字符串)、tuple(元组)

    不可变数据一旦赋值就存在内存中,不可以改变,当重新赋值后,会有一块新的内存储存新值,原来的值会由python自动调用垃圾处理机制将其回收。

  • 可变数据:list(列表)、dictionary(字典)、set(集合)

    可变数据赋值后可以改变其中元素的值,而整体在内存中的地址并没有改变。

我们先来看一个简单的例子:

x = 'hello world' 
print(x)            # hello world
print(id(x))        # 2991120268912
y = x 
print(y)            # hello world
print(id(y))        # 2991120268912
print('----------') # ----------
# 改变y
y = 'hello python' 
print(x)            # hello world
print(id(x))        # 2991120268912
print(y)            # hello python
print(id(y))        # 2991118407536
  • x = 'hello world'

python先分配了一个string大小的内存pos1用来储存'hello world'。然后,python在命名空间中让变量x指向了这一块内存,由于string是不可变数据,所以这块内存的内容是不可变的。

内存命名空间
pos1 : str('hello world') (不可变)x : pos1
  • y = x

python并没有使用新的内存来储存变量 y 的值,而是在命名空间中,让变量 y 与变量 x 指向了同一块内存空间。

内存命名空间
pos1 : str('hello world') (不可变)x : pos1
y : pos1
  • y = 'hello python'

改变y的值,python此时分配一个 str 大小的内存 pos2 来储存对象 'hello python' ,然后改变变量 y 所指的对象。所以最后,x的值没有改变,因为其仍指向pos1

内存命名空间
pos1 : str('hello world') (不可变)
pos2 : str('hello python') (不可变)
x : pos1
y : pos2

对上面的过程进行验证,使用 id() 函数返回变量 的内存地址,我们也可以看到整个的变化过程。

现在再来看另一段代码:

x = [1, 2, 3] 
y = x 
print(x) # [1, 2, 3]
print(y) # [1, 2, 3]

# 改变y[1] 
y[1] = 4 
print(x) # [1, 4, 3]
print(y) # [1, 4, 3]

# 改变y 
y = [5, 6] 
print(x) # [1, 4, 3]
print(y) # [5, 6]
  • x = [1, 2, 3]

首先,python为3个int分配内存 pos1pos2pos3 (不可变),然后为列表分配一段内存 pos4 ,它包含3个位置,分别指向这3个内存,最后再让变量 x 指向这个列表。注意这里的pos4,它储存的是一个包含三个地址的列表,由于列表是可变数据,才会产生后面的情况。

内存命名空间
pos1 : int(1) (不可变)
pos2 : int(2) (不可变)
pos3 : int(3) (不可变)
pos4 : list(pos1, pos2, pos3) (可变)
x : pos4
  • y = x

并没有创建新的对象,只需要将 y 指向 pos4 即可。

内存命名空间
pos1 : int(1) (不可变)
pos2 : int(2) (不可变)
pos3 : int(3) (不可变)
pos4 : list(pos1, pos2, pos3) (可变)
x : pos4
y : pos4
  • y[1] = 4

原来 y[1] 这个位置指向的是 pos2 ,由于不能修改 pos2 的值,所以首先为 4 分配新内存 pos5

再把 y[1] 指向的位置修改为 pos5 。此时,由于 pos2 位置的对象已经没有用了,python会自动调用垃圾处理机制将它回收。重点来了,pos4的值已经发生变化了,但是x,y仍都指向它,所以最后x,y的值都发生变化了。

内存命名空间
pos1 : int(1) (不可变)
pos2 : 垃圾回收
pos3 : int(3) (不可变)
pos4 : list(pos1, pos5, pos3) (可变)
pos5 : int(4) (不可变)
x : pos4
y : pos4
  • y = [5, 6]

这里与上面不同的在于,不是改变列表中的某一个元素,而是直接让y等于一个新的列表。所以python首先创建这个列表,然后将变量 y 指向它。

内存命名空间
pos1 : int(1) (不可变)
pos3 : int(3) (不可变)
pos4 : list(pos1, pos5, pos3) (可变)
pos5 : int(4) (不可变)
pos6 : int(5) (不可变)
pos7 : int(6) (不可变)
pos8 : list(pos6, pos7) (可变)
x : pos4
y : pos8

对这一过程进行验证:

x = [1, 2, 3] 
print(id(x[0])) # 140720957133608
print(id(x[1])) # 140720957133640
print(id(x[2])) # 140720957133672
print(id(x)) # 2991118049024

# y=x,y指向同一个列表的地址
y = x 
print(id(y)) # 2991118049024

# 改变y[1],y和x的地址都没有发生变化,但是y[1]的地址变化了 
y[1] = 4 
print(id(x)) # 2991118049024
print(id(y)) # 2991118049024
print(id(y[1])) # 140720957133704

# 改变y,y的地址发生了变化,其中储存的也是新的地址
y = [5, 6] 
print(id(y)) # 2991118044800
print(id(y[0])) # 140720957133736
print(id(y[1])) # 140720957133768

1.3 注释

python中单行注释以#开头

# 第一个注释
print("hello, world")

还有多行注释'''""",效果相同

'''
多行注释1
多行注释2
''' 

"""
多行注释3 
多行注释4 
"""
print("hello, world")

1.4 行与缩进

python与其他的代码相比,最大的特色就是使用缩进来表示代码块。同一个代码块的语句必须包含相同的缩进空格数。

a = int(input()) 
if a > 10: 
    print("a > 10") 
else:
    print("a <= 10")
    
# 输入:2  输出:a <= 10

1.5 多行语句

python通常是一行一条语句,不过如果语句很长,我们可以使用\实现多行语句,例如:

part1, part2, part3 = 1, 2, 3

all = part1 + \
      part2 + \
      part3

但是在[], {}, ()中的多行语句,不需要\也可以直接实现多行:

all = [part1, part2, 
       part3]

1.6 类型注解

类型注解是Python 3引入的一种机制,它允许程序员在变量、函数参数和返回值等地方添加对应的数据类型信息。这种注解的主要目的是提供更好的代码可读性、维护性,以及一些工具的辅助支持,如静态类型检查器。

1.6.1 变量类型注解

基础数据类型注解
var1: int = 1
var2: float = 3.14
var3: bool = True 
var4: str = 'hello' 
类对象类型注解
class Student:
    pass 

stu:Student = Student() 
基础容器类型注解
my_list: list = [1, 2, 3] 
my_tuple: tuple = (1, 2, 3) 
my_set: set = {1, 2, 3} 
my_dict: dict = {"name": 'LiHua'} 
my_str: str = "LiHua" 
容器类型详细注解
my_list: list[int] = [1, 2, 3] 
my_tuple: tuple[str, int, bool] = ('LiHua', 2, True) 
my_set: set[int] = {1, 2, 3} 
my_dict: dict[str, int] = {"age": 1} 

1.6.2 函数(方法)类型注解

def  函数方法名(形参名: 类型, 形参名: 类型...)-> 返回值类型:
    pass
# 对形参进行类型注解
def add(x: int, y: int): 
    return x + y 
# 对返回值进行类型注解
def add(x: int, y: int)-> int: 
    return x + y 

注意:类型注解都是提示性的,而非强制性的,就算没有按照注解的类型输入或输出也没有任何问题。

print(add('hello', ' world')) # 输出:hello world

1.6.3 Union联合类型注解

当变量中有很多类型,通常不太容易给每一种类型都注解上,我们需要使用Union联合类型注解。Union类型可以用来表示混合类型的变量,可以同时限定多种类型。

from typing import Union

my_list: list[Union[str, int]] = [1, 'hello']

my_dict: dict[str, Union[str, int]] = {'name':'小困难', 'age': 1} 

def func(data: Union[str, int])-> Union[int, str]: 
    pass

2 变量和数据类型

2.1 数字

2.1.1 基本的数字类型

python中,我们可以使用type()参看变量类型

a = 1 
print(type(a)) # 输出:int 
整型 int

在 64 位系统中,一个整型 8 个字节

最小值 -9,223,372,036,854,775,808

最大值 9,223,372,036,854,775,807

import sys 
print(sys.maxsize)
9223372036854775807

python中所有的数字类型都可以进行加减乘除,取余,幂运算等基本数值操作。需要注意的是,python3中,整数相除的结果不再是单纯地返回一个整数,而是返回一个浮点数。

# 加减乘除
print(1 + 1)  # 2
print(3 - 2)  # 1
print(2 * 3)  # 6
print(3 / 2)  # 1.5
print(3 / 3)  # 1.0
# 取余
print(5 % 2)  # 1
# 幂运算
print(2 ** 2) # 4
# 取整(往小取)
print(9//2)   # 4
浮点数 float
a = 1.2
print(type(a)) 
print('浮点数最大值:', sys.float_info.max)
<class 'float'>
浮点数最大值: 1.7976931348623157e+308

浮点数的运算和上面的整数运算相同,不过可能会出现不能精确计算的情况:

print(3.4 - 3.2) # 输出:0.19999999999999973
复数 complex

python中用j表示复数的虚部

a = 1 + 2j 
print(type(a)) # 输出:<class 'complex'>

复数有专门查看实部、虚部、共轭的方法:

# 实部
print(a.real)        # 1.0
# 虚部
print(a.imag)        # 2.0
# 共轭
print(a.conjugate()) # (1-2j)
布尔值 bool

布尔型变量分为两种,TrueFalse

b = True 
print(type(b)) # 输出:<class 'bool'>

一般可以搭配比较符号来构建布尔变量,比较符号有: <, >, <=, >=, ==, !=

b = 2 < 1 
print(b) # 输出:False

运算符的优先级如下表:

运算符描述
()、[]、{}括号表达式
x[index], x[index:index], x(arguments…), x.attribute读取,切片,调用,属性引用
await xawait 表达式
**乘方(指数)
+x, -x, ~x正,负,按位非 NOT
*, @, /, //, %乘,矩阵乘,除,整除,取余
+, -加和减
<<, >>移位
&按位与 AND
^按位异或 XOR
|按位或 OR
in, not in, is, is not, <, <=, >, >=, !=, ==比较运算,包括成员检测和标识号检测
not x逻辑非 NOT
and逻辑与 AND
or逻辑或 OR
if – else条件表达式
lambdalambda 表达式
:=赋值表达式

小编个人觉得最需要注意的有以下几点:

  • ** > * / > + -
  • & > ^ > |
  • not > and > or
  • if-else > lambda > 赋值

2.1.2 常见的数学函数

# 取绝对值
abs(-12)     # 12

# 取整(四舍五入)
round(2.4)   # 2

# 求最值
min(2, 3, 4) # 2
max(2, 3, 4) # 4

2.1.3 类型转换

可以使用int()float()分别将数字强制转换为整数、浮点数

浮点数转换为整数,只保留整数部分:

print(int(2.4)) # 2 

整数转换为浮点数

print(float(2)) # 2.0

2.1.4 原地计算 in-place

python可以使用原地计算代替表达繁琐的表达式

a += 1 # 相当于 a = a + 1 
a -= 1 # 相当于 a = a - 1 
a *= 2 # 相当于 a = a * 2
a /= 2 # 相当于 a = a / 2

2.2 字符串 string

2.2.1 字符串生成

python中的字符串可以使用一对'或者"来生成,它们的效果相同,不存在区别

s1 = 'hello, world.'
s2 = "hello, world." 
print(s1) # 输出:hello, world.
print(s2) # 输出:hello, world.

如果字符串过长,一行写不下,想要多行表示的话,可以使用'''

s = ''' hello, 
        world''' 
print(s)
 hello, 
        world

需要注意的是,当你想要表达I'm LiHua这样的字符串,要使用""括起来,因为原字符串中有'

print("I'm LiHua") # 输出:I'm LiHua

但是当你想要字符串中既有'又有"的时候,要怎么办呢?这时候我们就要用到转义字符\

转义字符

转义字符\可以转义很多字符,比如\n表示换行,\t表示制表符,也可以转义自身\\

print('I\'m \"LiHua\"')
print('hello \n world') 
print('hello \t world')
print('hello \\ world') 
I'm "LiHua"
hello 
 world
hello 	 world
hello \ world

同时,我们也可以使用r''来表示''内部的字符串不转义

print(r'hello \n world') # 输出:hello \n world

2.2.2 格式化字符串

有时候,我们希望将字符串传入指定的位置,这时候就可以使用格式化字符串:

print("Hi, I'm %s. I'm %d years old" % ('LiHua', 1)) # 输出:Hi, I'm LiHua. I'm 1 years old

%运算符就是用来格式化字符串的,%?表示占位符,前面有几个占位符,后面%()里就要跟几个变量,顺序也要对应。常见的占位符有:

  • %d 整数
  • %f 浮点数
  • %s 字符串
  • %x 十六进制整数

其中,还可以指定长度,是否补0,以及小数的位数:

print('%10s-%2d-%02d'%('hello', 1, 2)) 
print('%.2f'%(3.1415926))
     hello- 1-02
3.14

另一种方法是使用'{}'.format()格式化字符串,它会将format()中的参数传入{}中:

print('{} {} {}'.format('hello', ',', 'world'))
# 输出:hello , world

用数字指定传入参数的位置:

print('{2} {1} {0}'.format('hello', ',', 'world'))
# 输出:world , hello

指定传入参数的名称:

print('{color} {n} {x}'.format(x=1, n = 2, color = 'black'))
# 输出:black 2 1

使用{<field name>:<format>}指定格式,这里<format>一般用来指定字符串长度和数据类型,d表示整数,f表示浮点数,如果是浮点数的话可能还有.n表示n位小数

print('{0:10} {1:1d} {2:10.2f}'.format('hello', 3, 3.4))
# 输出:hello      3       3.40

最后一种格式化字符串的方法是f'{x}',如果变量x存在,就会以x替换{x}

r = 2
s = 3.14 * r ** 2

print(f'圆的半径是{r},则圆的面积为{s:.2f}')
# 输出:圆的半径是2,则圆的面积为12.56

2.2.3 字符串方法及函数

简单操作
# 加法
s = 'hello ' + 'world' # 'hello world'

# 乘法
s = 'hello ' * 3 # 'hello hello hello '

# 求长度 
length = len(s) # 返回一个int
分割

.split()默认以空格分割,也可以指定字符分割。分割后返回的结果是一个以字符串为元素的列表。

line = '1 2 3 4' 
numbers = line.split() 
print(numbers) # 输出:['1', '2', '3', '4']

# 以指定字符分割
line1 = '1,2,3,4' 
numbers1 = line1.split(',') 
print(numbers1) # 输出:['1', '2', '3', '4']
连接

''.join()''中的内容连接,将列表连接成字符串

numbers = ['1', '2', '3', '4']
s = ' ' 
s.join(numbers) # '1 2 3 4'

s = ',' 
s.join(numbers) # '1,2,3,4'
替换

s.replace(a, b)将字符串s中的a替换成b。这里需要注意的是,替换后s的值并没有改变,而是替换操作生成了一个新的字符串。

s = 'hello world' 
a = s.replace('world', 'python') 
print(s) # hello world
print(a) # hello python
大小写转换
  • 大写:.upper()
  • 小写:.lower()

该方法也不会改变原字符串的值

s = 'Hello World' 
print(s.lower()) # hello world
print(s.upper()) # HELLO WORLD
print(s)         # Hello World
去掉多余的空格

该方法同样不会改变原字符串

s = '   hello world   ' 

# 将两端的空格都去掉
print(s.strip())  # 'hello world'

# 将开头(左端)的空格去掉
print(s.lstrip()) # 'hello world   '

# 将结尾(右端)的空格去掉 
print(s.rstrip()) # '   hello world'

print(s)          # '   hello world   '
更多方法

我们不可能记住所有的函数和方法,这个时候我们就可以使用dir()函数查看一个变量所有可以使用的方法

dir(s)
'''输出:
['__add__',
 '__class__',
 '__contains__',
 '__delattr__',
 '__dir__',
 ....]
'''

2.2.4 字符串索引和分片

索引

字符串的索引和分片都是从0开始的,0代表字符串中的第一个字符,-1表示最后一个字符。

s = 'hello world' 
print(s[0])  # h
print(s[-1]) # d
分片

[a:b]分片主要有两部分,从a开始,到b结束。分片的结果包括a,但是不包括b。

s = 'hello world' 
print(s[1:3])  # 'el' 
print(s[1:-2]) # 'ello wor' 
print(s[:-2])  # 'hello wor'
print(s[:])    # 'hello world' 

# 每隔2个取一个值
print(s[::2])  # 'hlowrd' 

# 从结尾开始分片
print(s[::-2]) # 'drwolh'

2.3 列表 list

2.3.1 列表生成

列表中元素的类型可以不相同,它支持数字、字符串,甚至可以嵌套列表。

列表可以通过直接赋值生成:

l = [1, 2, 'hello'] 

想要生成空列表的话,可以使用[]或者list(),效果相同。

empty_list1 = [] 
empty_list2 = list() 

2.3.2 列表操作

求长度
len(l) 
加法乘法
a = [1, 2] 
b = [3, 4] 
print(a + b) # [1, 2, 3, 4]
print(a * 3) # [1, 2, 1, 2, 1, 2]
索引分片

列表的索引分片操作基本和字符串的操作相同

a = [1, 2, 3, 4] 
print(a[0])    # 1
print(a[1:-2]) # [2]
print(a[::2])  # [1, 3]

不过需要注意的是,列表可以通过索引修改元素,但是字符串不可以,强行使用的话会报错。

a[0] = 5
print(a) # [5, 2, 3, 4]
f = 'hello world'
f[0] = 'H'
print(f) 
---------------------------------------------------------------------------

TypeError                                 Traceback (most recent call last)

Cell In[77], line 2
      1 f = 'hello world'
----> 2 f[0] = 'H'
      3 print(f)

TypeError: 'str' object does not support item assignment

列表也可以通过分片来修改元素

整段替换的话,即使元素个数不相同也没有问题:

a = [1, 2, 3, 4] 
a[0:2] = [3, 2, 1] 
print(a) # [3, 2, 1, 3, 4]

但是对于使用不连续分片修改时,元素个数要相同:

a[::2] = [1, 2, 3] 
print(a) # [1, 2, 2, 3, 3] 
删除元素

列表删除元素可以使用分片的操作

a = [1, 2, 3, 4] 
a[1:2] = [] 
print(a) # [1, 3, 4] 

但是注意,索引方法不可以。a[0] = []仍会在位置0处有一个[]

a = [1, 2, 3, 4] 
a[0] = [] 
print(a) # [[], 2, 3, 4] 

当然,列表也有专门的方法用来删除元素。

一是使用del(ls[index])方法,通过指定索引删除元素:

a = [1, 2, 3, 4] 
del(a[0])
print(a) # [2, 3, 4] 

二是使用ls.remove(ob)属性,删除第一个出现的ob

a = [1, 2, 3, 4] 
a.remove(1) 
print(a) # [2, 3, 4] 
弹出元素

ls.pop(idx)返回ls中索引idx处的元素,并从列表中删除:

a = [1, 2, 3] 
print(a.pop(0)) # 1
print(a)        # [2, 3]
测试从属关系

可以使用in判断某个元素是否在列表中,用not in判断是否不在,返回的是布尔值:

a = [1, 2, 3] 
print(1 in a)     # True
print(1 not in a) # False

该方法也适用于字符串:

s = 'hello world' 
print('h' in s)     # True
print('h' not in s) # False
元素个数

ls.count(ob)返回列表ls中有几个元素ob:

a = [1, 2, 2, 4] 
print(a.count(2)) # 2
元素位置

ls.index(ob)返回元素ob在列表ls中第一次出现的位置:

a = [1, 2, 2, 3] 
print(a.index(2)) # 1
添加元素

ls.append(ob)在列表的末尾添加单个元素ob,该属性会直接改变原列表的值:

a = [1, 2] 
a.append(3)
print(a) # [1, 2, 3]

ls.extend(ls1)在列表ls的末尾直接添加列表ls1:

a = [1, 2] 
a.extend([3, 4]) 
print(a) # [1, 2, 3, 4] 
插入元素

ls.insert(idx, ob)在索引idx处,插入元素ob:

a = [1, 2, 3] 
a.insert(1, 4) 
print(a) # [1, 4, 2, 3] 
排序
  • ls.sort()会改变原来的列表
  • =sorted(ls)不会改变原来的列表
ls1 = [2, 3, 1] 
ls1.sort() 
print(ls1) # [1, 2, 3]
ls2 = [2, 3, 1] 
ls = sorted(ls2)
print(ls)  # [1, 2, 3]
print(ls2) # [2, 3, 1]
反向

ls.reverse()会将列表反向排序

a = [1, 2, 3] 
a.reverse() 
print(a) # [3, 2, 1]

当然,如果你不想改变原列表的值,这里有一种巧妙的方法:

a = [1, 2, 3, 4, 5, 6] 
b = a[::-1] 
print(a) # [1, 2, 3, 4, 5, 6]
print(b) # [6, 5, 4, 3, 2, 1]

2.4 元组 tuple

2.4.1 元组生成

元组可以直接使用()生成,元组中的元素没必要是同一类型的元素:

t = (1, 2, 'hello') 
print(t) # (1, 2, 'hello')

元组也是有序列表,但是这个有序指的是索引,所以元组也可以使用索引分片操作:

print(t[0])   # 1
print(t[0:2]) # (1, 2)

不过,有一点需要注意的是,元组是不可变的,一旦初始化就不能修改。

t[0] = 3
---------------------------------------------------------------------------

TypeError                                 Traceback (most recent call last)

Cell In[103], line 1
----> 1 t[0] = 3

TypeError: 'tuple' object does not support item assignment

当然,元组的不变指的是内部每个元素指向的位置不变,而不是内容不变。下面的例子中,我们改变了元组中列表的元素,可以看到最后元组也发生变化了,因为对于元组而言,它索引2处储存的是一个列表的位置,我们当然没有改变这个位置,而是改变了列表的元素,这样是可以操作的。

t = (1, 2, [1, 2, 3]) 
t[2][1] = 1 
print(t) # (1, 2, [1, 1, 3])

单个元组的生成需要和单纯的一个元素区分开,不能只是单纯的使用(10),而是要(10,)

t = (10,) 
print(type(t)) # <class 'tuple'>

a = (10) 
print(type(a)) # <class 'int'>

2.4.2 元组操作

元组长度
len(t)
元素个数
t.count(10) 
元素位置
t.index(10) 

2.5 字典 dictionary

2.5.1 字典生成

空字典的生成有两种方法:

  • {}
  • dict()
a = {} 
print(type(a)) # <class 'dict'>

b = dict() 
print(type(b)) # <class 'dict'>
插入键值

字典由两部分组成 键(key):值(value)

a['name'] = 'LiHua' 
a['age'] = 1 

print(a) # {'name': 'LiHua', 'age': 1}
查看键值

我们可以通过键查找其对应的值:

print(a['name']) # LiHua
更新键值

字典更新值也是通过键来操作的:

a['age'] = 2 
print(a) # {'name': 'LiHua', 'age': 2}

注意,字典是没有顺序的,所以此处不能使用索引和分片。

a[0]
---------------------------------------------------------------------------

KeyError                                  Traceback (most recent call last)

Cell In[120], line 1
----> 1 a[0]

KeyError: 0
使用dic()初始化字典
inventory = dict(
    [('one',1),
     ('two',2), 
     ('three',3)])
print(inventory) # {'one': 1, 'two': 2, 'three': 3}

2.5.2 字典操作

查找值

我们可以直接通过键(key)找到对应的值,但是当字典中没有这个键时会报错

a['name'] # 'LiHua'
a['name1']
---------------------------------------------------------------------------

KeyError                                  Traceback (most recent call last)

Cell In[123], line 1
----> 1 a['name1']

KeyError: 'name1'

如果想要避免报错,我们可以使用d.get(key, default=None),当key不存在时,默认返回None;也可以通过指定返回的值。

print(a.get('name1'))             # None
print(a.get('name1', 'undefind')) # undefind
删除元素
del(a['name']) 
弹出元素
print(a.pop('name')) # LiHua
print(a)             # {'year': 2}

弹出不存在的键,也可以指定返回的值

print(a.pop('name1', 'not exit')) # not exit
更新字典

d.update(newd)会将字典newd中的内容更新到d

如果有相同的键,则将该键的值更新为newd中的值;如果newd中有d没有出现过的键,则直接添加。

person = {}
person['first'] = "Jmes"
person['last'] = "Maxwell"
person['born'] = 1831
print(person) # {'first': 'Jmes', 'last': 'Maxwell', 'born': 1831}

person_add = {'first':'James', 'middle':'Mark'}
person.update(person_add) 
print(person) # {'first': 'James', 'last': 'Maxwell', 'born': 1831, 'middle': 'Mark'}
测试从属关系

in查询字典中是否有某个键,not in查询是否不在字典中,返回一个bool值。

print('one' not in person) # True
print('first' in person)   # True
键值对方法

d.keys()返回一个由所有键组成的列表

print(person.keys()) # dict_keys(['first', 'last', 'born', 'middle'])

d.values()返回一个由所有值组成的列表

print(person.values()) # dict_values(['James', 'Maxwell', 1831, 'Mark'])

d.items()返回一个由所有键值对元组组成的列表

print(person.items()) # dict_items([('first', 'James'), ('last', 'Maxwell'), ('born', 1831), ('middle', 'Mark')])

注意运用时要先明确为列表再使用比较保险

a = list(person.values()) 
print(a[0]) # James

2.6 集合 set

2.6.1 集合生成

集合的生成使用set(),也可以用{}指定已知道元素的集合。但是注意不能使用{}生成空集合,因为{}生成的是字典。

a = set() 
print(type(a)) # <class 'set'>
a = {1, 2, 'hello'} 
print(a) # {1, 2, 'hello'}

集合是无序的,不能使用索引分片。

同时放入两个相同的元素时,只会保留一个(唯一性)

放入其中的元素不可改变(确定性)

a = {1, 1, 2, 3} 
print(a) # {1, 2, 3}

2.6.2 集合操作

集合的并、交等操作并不会改变原来的集合

并集
a = {1, 2, 3, 4} 
b = {3, 4, 5, 6} 

print(a.union(b)) # {1, 2, 3, 4, 5, 6}
print(a|b)        # {1, 2, 3, 4, 5, 6}
交集
print(a.intersection(b)) # {3, 4}
print(a&b)               # {3, 4}
差集

a和b的差集,返回的是在a但不在b的元素组成的集合

print(a.difference(b)) # {1, 2}
print(a-b)             # {1, 2}
对称差

a和b的对称差,返回的是在a中或在b中,但是不同时在a和b中的元素组成的集合

print(a.symmetric_difference(b)) # {1, 2, 5, 6}
print(a^b)                       # {1, 2, 5, 6}
包含关系

要判断b是不是a的子集,可以用b.issubset(a)方法,或者直接用b<=a

a = {1, 2, 3} 
b = {1, 2} 

print(b.issubset(a)) # True
print(b <= a)        # True

同时,可以用a.issuperset(b)或者a>=b判断母集

print(a.issuperset(b)) # True
print(a >= b)          # True

方法只能用来判断子集,但是操作符可以用来判断真子集

print(a.issubset(a)) # True
print(a < a)         # False

2.6.3 集合方法

添加元素
  • s.add(ob) 添加单个元素
  • s.update(list) 添加多个元素
s = {1, 2} 

s.add(3) 
print(s) # {1, 2, 3}

s.update([4, 5,6])
print(s) # {1, 2, 3, 4, 5, 6}
删除元素
  • s.remove(ob)移除单个元素,如果元素不存在会报错。
s = {1, 2, 3} 
s.remove(2) 
print(s) # {1, 3}
  • s.pop()由于集合没有顺序,所以不能按照索引弹出元素,该方法会删除并返回集合中任意一个元素,如果没有元素就会报错。
s = {1, 2, 3} 
print(s.pop()) # 1
print(s)       # {2, 3}
  • s.discard(ob)直接删除指定的元素,与remove()不同的是,如果集合中不存在元素ob,并不会报错。
s = {1, 2, 3} 
s.discard(1) 
print(s) # {2, 3}
s.discard(5) # 这里就正常运行了,没有报错
  • a.difference_update(b)从集合a中删除所有属于b的元素。
a = {1, 2, 3, 4} 
b = {1, 2} 
a.difference_update(b) 
print(a) # {3, 4}

2.7 总结速查表

操作含义
数字
a.real查看复数的实部
a.imag查看复数的虚部
a.conjugate()查看复数的共轭
abs()求绝对值
round()取整
max()、min()求最值
字符串
len()求长度
s.split()分割(默认以空格分割s)
s.join()连接(将列表以s连接)
s.replace(a,b)替换(将s中的字符a替换为b)
s.upper()、s.lower()大小写转换
s.strip()、s.lstrip()、s.rstrip()去除空格
%、‘{}’.format()、f’’格式化字符串
列表
[]、list()生成列表
in、not in判断从属关系
ls.count(ob)返回ls中有几个元素ob
ls.index(ob)返回ob第一次出现的位置
del(ls[0])根据索引删除元素
ls.remove(ob)删除第一个出现的ob
ls.append(ob)在列表末尾添加元素ob
ls.extend(list)在ls末尾添加序列list
ls.insert(idx, ob)在idx出添加元素ob
ls.sort()列表排序,改变原列表
sored(ls)列表排序,不改变原列表
元组
()、tuple()元组生成
len()求长度
t.count(ob)求元素ob的个数
t.index(ob)求元素ob的位置
字典
{}、dict()字典生成
d[‘name’]查找键name的值,如果不存在会报错
d.get(‘name’, error)查找name键的值,如果不存在则返回error
d[‘name’]=value插入键值
d[‘name’]=value1更新键值
d.update(newd)把字典newd中的内容更新到d中
del(d[‘name’])删除元素
d.pop(‘name’,error)弹出元素
in、not in判断
d.keys()返回所有的键
d.values()返回所有的值
d.items()返回所有的键值对
集合
set()集合生成
a.union(b) a | b并集
a.intersection(b) a & b交集
a.difference(b) a - b差集
a.symmetric_difference(b) a ^ b对称差
b.issubset(a) b<=ab是不是a的子集
b.issuperset(a) b>=ab是不是a的母集
s.add(ob)添加单个元素
s.update(list)添加多个元素
s.remove(ob)移除元素ob,不存在会报错
s.pop()弹出元素
s.discard(ob)移除元素,不存在不会报错
a.difference_update(b)从a中去除所有属于b的元素

3 判断与循环

3.1 判断

基本用法

if-else语句是从上往下判断的,如果第一个不符合则检查下一个,注意缩进。

def if_statement(x): 
    if x > 0:  
        print('hello') 
    elif x == 0: 
        print('x = 0') 
    else: 
        print('sorry')

if_statement(1)  # hello
if_statement(0)  # x = 0
if_statement(-1) # sorry

还可以使用andornot等关键词结合多个判断条件

x = 10 
y = -5
print(x > 0 and y < 0) # True
print(not x > 0)       # False
print(x < 0 or y < 0)  # True

值的测试

python不仅仅可以使用布尔型变量作为条件,还可以使用任何表达式作为条件

大部分表达式的值会被当做True,但是以下会被当作False

  • False
  • None
  • 0
  • 空字符串、空列表、空字典、空集合
mylist = [12, 21, 32, 4] 
if mylist: 
    print('hello')
else:
    print('sorry')
# 输出:hello
mylist = []
if mylist: 
    print('hello')
else:
    print('sorry')
# 输出:sorry

模式匹配

有时候使用if-else语句进行判断可能会有点繁琐。我们可以使用模式匹配:

score = 87

match score: 
    case x if x >= 90 : 
        print('A') 
    case x if 80 <= x < 90: 
        print('B') 
    case 60: 
        print('刚好及格,还需努力啊!') 
    case _: 
        print('没及格,要加油!') 
# 输出:B

3.2 循环

while循环

i = 0 
total = 0 
while i < 1000000: 
    total += i 
    i += 1 
print(total) # 499999500000

空容器会被判断为False,所以可以用while循环读取容器中所有的元素

plays = {'hello', 'nihao', 'sorry'} 
while plays: 
    play = plays.pop() 
    print('Perform', play)
Perform hello
Perform sorry
Perform nihao

for循环

plays = {'hello', 'nihao', 'sorry'} 
for play in plays:
    print('Perform', play)
Perform hello
Perform sorry
Perform nihao
total = 0 
for i in range(1000000): 
    total += i 
print(total) # 499999500000

range(a, b, n)可以遍历数字序列,从a开始到b,步长为n,n默认为1。

for i in range(0, 10, 2): 
    print(i) 
0
2
4
6
8

continue语句

遇到continue的时候,程序会返回循环的最开始执行

value = [1, 2, 3, 4, 5] 
for i in value: 
    if i % 2 != 0: 
        # 忽略奇数
        continue
    print(i)
2
4

break语句

遇到break时,程序会直接跳出循环

value = [1, 2, 3, 4, 5] 
for i in value: 
    if i == 3:
        break
    print(i)
1
2

else语句

if 一样, whilefor 循环后面也可以跟着 else 语句,不过要和break一起连用。

  • 当循环正常结束时,循环条件不满足, else 被执行;
  • 当循环被 break 结束时,循环条件仍然满足, else 不执行。

不执行:

values = [7, 6, 4, 7, 19, 2, 1]
for x in values:
    if x <= 10:
        print('Found:', x)
        break
else:
    print('All values greater than 10')
输出:Found: 7

执行:

values = [11, 12, 13, 100]
for x in values:
    if x <= 10:
        print('Found:', x)
        break
else:
    print('All values greater than 10')
输出:All values greater than 10

列表推导式

循环可以用来生成列表:

values = [1, 2, 3, 4, 5]
squares = []
for x in values: 
    squares.append(x ** 2) 
print(squares) # [1, 4, 9, 16, 25]

不过,这样的耗费的时间长,代码也比较复杂。

这里我们可以使用列表推导式更简单地创建这个列表:

values = [1, 2, 3, 4, 5]
squares = [x ** 2 for x in values] 
print(squares) # [1, 4, 9, 16, 25]

还可以加入条件筛选

squares = [x ** 2 for x in values if x <= 3]
print(squares) # [1, 4, 9]

4 函数

4.1 普通函数

  • 使用def关键字定义一个函数
  • def后面是函数的名称,()中是函数的参数,不同的参数用,隔开,参数可以为空
  • """包含的字符串,用来解释函数的用途,可省略
  • return返回特定的值,如果省略,返回None
def add(x, y): 
    """ 两数相加""" 
    result = x + y 
    return result

函数调用是使用函数名,然后补齐参数就可以了,如果return指定的有值,运行后会返回该值。

print(add(1, 2)) # 3

也可以通过关键词,直接显式指定参数的值:

print(add( y = ' world' , x = 'hello')) # hello world

函数也可以同时返回多个值

def two_print(x, y): 
    r = x ** y 
    w = x + y 
    return r, w 

r, w = two_print(2, 3)
print(r, w) # 8 5

函数传参

函数可以接受参数,参数也可以设定默认参数:

def power(base, exponent=2): # 默认设置为平方
    return base ** exponent

print(power(2)) # 4

不定参数

函数可以在参数的前面加上*表示不定参数,相当于传入的是元组

def add(x, *args): 
    total = x 
    for i in args: 
        total += i 
    return total

print(add(1, 2, 3, 4)) # 10

还可以使用**,相当于传入的是字典

def add(x, **kwargs):
    total = x 
    for arg, value in kwargs.items(): 
        print('adding', arg) 
        total += value
    return total

print(add(1, a = 2, b = 3, c = 4))
adding a
adding b
adding c
10

作用域

使用函数,我们要先清楚变量的作用域。变量的作用域指的是变量在代码中可被访问的范围。全局变量和局部变量是两种不同作用域的变量。

  • 全局变量在整个代码中都可以被访问到
  • 局部变量只能在设定这个变量的函数内部被访问到
# 作用域
global_var = 42  # 全局变量

def example_function():
    local_var = 10  # 局部变量
    print(global_var + local_var)

example_function() # 52

闭包

闭包是一个函数对象,它包含了在函数定义时存在的引用环境。换句话说,闭包允许函数访问定义时的作用域,即使在函数被调用的位置已经不存在。

在理解闭包之前,首先需要了解两个概念:内部函数和外部函数。

  • 内部函数:在一个函数内部定义的函数称为内部函数。
  • 外部函数:包含内部函数的函数称为外部函数。

闭包通常由以下三个要素组成:

  • 内部函数:在外部函数内部定义的函数。
  • 引用环境:内部函数引用了外部函数的变量(局部变量或参数)。
  • 返回值:外部函数返回内部函数的引用。

下面是一个简单的闭包示例:

def outer_function(x):
    def inner_function(y):
        return x + y
    return inner_function

closure = outer_function(10)
result = closure(5)
print(result) # 15

在这个例子中,outer_function是外部函数,inner_function是内部函数。
inner_function引用了外部函数的变量x。当outer_function(10)被调用时,它返回了inner_function的引用,形成了闭包。然后,通过closure(5)调用闭包,实际上是在调用inner_function(5),并得到了结果15。

简单来说,我们可以将closure = outer_function(10)理解为声明了一个设置了默认参数的函数(默认参数x=10),然后result = closure(5)是调用了函数closure()。

闭包的用途:

  • 保存状态:由于闭包可以保留引用环境,内部函数可以访问外部函数的局部变量,因此闭包可用于保存状态信息。
def counter():
    count = 0 # 外部函数的局部变量
    def increment():
        nonlocal count # 使用nonlocal声明count不是在increment声明的,而是在外部函数中声明的
        count += 1
        return count
    return increment

counter_func = counter()
print(counter_func())  # 输出:1
print(counter_func())  # 输出:2

  • 函数工厂:闭包可以用作函数工厂,动态生成函数。
def power_generator(exponent):
    def power(base):
        return base ** exponent
    return power

square = power_generator(2)
cube = power_generator(3)

print(square(5))  # 输出:25
print(cube(5))    # 输出:125

4.2 匿名函数

匿名函数(lambda函数)是一种不使用def关键字定义的小型函数。

# 匿名函数
add = lambda x, y: x + y
result = add(3, 5)

4.3 迭代器和生成器

迭代器

迭代器是Python中用于遍历可迭代对象的一种机制。可迭代对象包括列表、元组、集合、字符串等。

迭代器有两个基本的方法:iter()next()

ls = [1, 2, 3] 
it = iter(ls) # 生成迭代器对象
print(next(it)) # 输出迭代器的下一个对象
print(next(it)) 
1
2

迭代器对象必须实现__iter__()和__next__()方法。

自定义迭代器:

class MyIterator:
    def __init__(self, data):
        self.data = data
        self.index = 0

    def __iter__(self):
        return self

    def __next__(self):
        if self.index < len(self.data):
            result = self.data[self.index]
            self.index += 1
            return result
        else:
            raise StopIteration

# 使用自定义迭代器
my_iterator = MyIterator([1, 2, 3, 4, 5])
for item in my_iterator:
    print(item)
1
2
3
4
5

生成器

生成器是一种更简洁、更优雅的迭代器实现方式。它使用yield关键字来暂停函数执行,并返回一个值。生成器在每次迭代时会从上次离开的地方继续执行。

示例生成器函数:

# 生成器函数
def square_numbers(n):
    for i in range(n):
        yield i ** 2

# 使用生成器
squares = square_numbers(5)
for square in squares:
    print(square)
0
1
4
9
16

在上面的示例中,square_numbers是一个生成器函数,它通过yield语句生成每个数字的平方。当我们使用for循环迭代生成器时,函数会在每次调用yield时暂停,并返回当前的值,直到下一次迭代。

生成器表达式

生成器表达式是一种简化生成器的语法,类似于列表推导式。

示例生成器表达式:

# 生成器表达式
squares = (x ** 2 for x in range(5))

# 使用生成器表达式
for square in squares:
    print(square)
0
1
4
9
16

4.4 装饰器

装饰器是Python中一种强大而灵活的工具,用于修改或扩展函数的行为。装饰器本质上是一个函数,它接受一个函数作为输入,并返回一个新的函数,通常用于在不修改原函数代码的情况下添加额外功能。

装饰器的定义

装饰器通常由一个函数来定义,该函数接受一个函数作为参数,并返回一个新的函数。

def my_decorator(func):
    def wrapper():
        print("Something is happening before the function is called.")
        func()
        print("Something is happening after the function is called.")
    return wrapper

在这个例子中,my_decorator是一个装饰器,它接受一个函数(func)作为参数,并返回一个新的函数wrapperwrapper函数在调用func前后添加了额外的操作。

装饰器的使用

使用装饰器的语法是在函数定义前使用@decorator_name,将装饰器应用到函数上。

@my_decorator
def say_hello():
    print("Hello!")

say_hello()
Something is happening before the function is called.
Hello!
Something is happening after the function is called.

多个装饰器的叠加

可以使用多个装饰器叠加在同一个函数上,它们的执行顺序是从内到外。

@decorator1
@decorator2
def my_function():
    # 函数体

在这个例子中,my_function首先被decorator2装饰,然后再被decorator1装饰。

带参数的装饰器

装饰器本身也可以带有参数,这样可以使装饰器更加灵活。带有参数的装饰器实际上是一个返回装饰器的函数。

def parametrized_decorator(param):
    def actual_decorator(func):
        def wrapper():
            print(f"Decorator parameter: {param}")
            func()
        return wrapper
    return actual_decorator

@parametrized_decorator("example")
def my_function():
    print("Hello!")

my_function()
Decorator parameter: example
Hello!

4.5 高阶函数

map 函数

函数定义完了,但是我们怎么快速的将函数运用到一个序列的每一个元素上呢? 这时可以使用map方法。

map(aFun, aSeq)将函数aFun应用到序列aSeq上的每一个元素上,返回一个列表

def sqr(x):
    return x **2 

a = [2, 3, 4, 5]
print(list(map(sqr, a))) # [4, 9, 16, 25]

也可以在多个序列间使用:

def add(x, y):
    return x + y

a = (2, 3, 3)
b = [1, 3, 3]
print(list(map(add, a, b))) # [3, 6, 6]

通常,我们也可以结合lambda函数使用:

numbers = [1, 2, 3, 4] 
square_numbers = list(map(lambda x: x ** 2, numbers)) 
print(square_numbers) # [1, 4, 9, 16]

reduce 函数

reduce(aFun, aSeq)先将函数aFun应用到序列aSeq上的第一个元素上,然后依次应用到下一个元素,做累积运算。

from functools import reduce

def add(x, y): 
    return x + y 

reduce(add, [1, 2, 3]) # 6

filter 函数

filter(aFun, aSeq)将函数aFun应用到aSeq的每一个元素上,不过这里aFun返回的应该是bool值,然后filter()根据这个布尔值决定是否丢弃这个元素。

# 只保留奇数
def is_ood(n): 
    return n % 2 == 1 

print(list(filter(is_ood, [1, 2, 3, 4, 5]))) # [1, 3, 5]

偏函数 partial

偏函数的性质和闭包有些类似,不过其主要是使用了partial()函数。

当函数的某个参数是提前知道的,我们可以将其固定住,但是每次更改又很麻烦,我们就可以使用偏函数。

# 定义一个求和函数,其中y值提前知道
def add(x, y = 1):
    return x + y 

# 简单调用
print(add(1)) # 2

# 但是当我们想要y=2时,我们要重新指定 
print(add(1, y = 2)) # 3

# 使用偏函数 
from functools import partial 
add_3 = partial(add, y = 3)
print(add_3(1)) # 4
2
3
4

5 正则表达式

5.1 正则表达式的基本概念

正则表达式是由字符和操作符组成的字符串,它描述了一种字符串匹配的模式。以下是一些基本的概念:

  • 普通字符: 普通字符包括字母、数字和一些特殊字符,它们表示它们自身。例如,字母 "a" 匹配字符串 "a"

  • 元字符: 元字符是具有特殊含义的字符,如 .、*、+、? 等。

  • 字符集: 使用方括号[]表示一个字符集,匹配其中的任意一个字符。例如,[aeiou] 匹配任意一个元音字母。

  • 量词: 量词指定前面的字符或字符集的重复次数。常见的量词包括*(零次或多次)、+(一次或多次)、?(零次或一次)。

  • 转义字符: 使用反斜杠 \ 可以将特殊字符转义成普通字符。例如,\. 匹配实际的点号。

5.2 常用的正则表达式操作

compile 方法

compile 方法用于将正则表达式模式编译成一个正则表达式对象,这个对象具有正则表达式的方法和属性,可以在后续的匹配中复用。

import re

# 使用compile方法编译正则表达式模式
pattern = re.compile(r'\d+')

# 待匹配的文本
text = 'The price is $42.99. '

# 使用compile后的正则表达式对象进行匹配
matches = pattern.findall(text)
print(matches) # ['42', '99']

search 方法

search 方法用于在文本中搜索匹配正则表达式的第一个位置。

import re

pattern = re.compile(r'\d+')
text = 'The price is $42.99.'
match = pattern.search(text)

if match:
    print(f'Match found: {match.group()}')
else:
    print('No match found.')
# 输出:Match found: 42

在最后获取文本的时候使用了group()方法。group()方法用于获取与正则表达式匹配的字符串。当使用 search() 方法找到匹配时,可以通过 group() 方法获取匹配的文本。

如果正则表达式中有多个用括号括起来的部分,group() 方法还可以接受一个参数,用于指定获取哪个匹配的部分。

pattern = re.compile(r'(\d+).(\d+)')
text = 'The price is $42.99.'
match = pattern.search(text)

if match:
    whole_match = match.group()      # 获取整个匹配的部分,即 '42.99'
    first_group = match.group(1)     # 获取第一个括号匹配的部分,即 '42'
    second_group = match.group(2)    # 获取第二个括号匹配的部分,即 '99'
    print(f'Match found: {whole_match}, First Group: {first_group}, Second Group: {second_group}')
else:
    print('No match found.')
# 输出:Match found: 42.99, First Group: 42, Second Group: 99

match 方法

match 方法用于检查文本的开头是否匹配正则表达式。

pattern = re.compile(r'\d+')
text = '42 is the answer.'
match = pattern.match(text)

if match:
    print(f'Match found: {match.group()}')
else:
    print('No match found.')
# 输出:Match found: 42

findall 方法

findall 方法用于找到文本中所有匹配正则表达式的部分,并返回一个列表。

pattern = re.compile(r'\d+')
text = 'The price is $42.99. The discount is 15%.'
matches = pattern.findall(text)

print(matches) # ['42', '99', '15']

sub 方法

使用 sub 方法可以替换匹配正则表达式的文本。

pattern = re.compile(r'\d+')
text = 'The price is $42.99. The discount is 15%.'
new_text = pattern.sub('X', text)

print(new_text) # The price is $X.X. The discount is X%.

5.3 示例

匹配数字

使用正则表达式匹配一个或多个数字。

  • \d 表示匹配任意数字。
  • +表示匹配前面的元素(这里是数字)一次或多次。
pattern = re.compile(r'\d+')
text = 'The price is $42.99.'
result = pattern.findall(text)
print(result) # ['42', '99']

匹配邮箱地址

使用正则表达式匹配邮箱地址。

  • \b 表示单词边界,确保匹配的是完整的邮箱地址。
  • [A-Za-z0-9._%+-]+ 匹配用户名部分。
  • @ 匹配邮箱地址中的 @ 符号。
  • [A-Za-z0-9.-]+ 匹配域名部分。
  • \. 匹配域名和顶级域之间的点号。
  • [A-Z|a-z]{2,} 匹配顶级域,至少包含两个字母。
pattern = re.compile(r'\b[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Z|a-z]{2,}\b')
text = 'Contact us at support@example.com or info@company.org.'
result = pattern.findall(text)
print(result) # ['support@example.com', 'info@company.org']

6 文件

6.1 文件读取与操作

文件操作模式包括:

  • 'r':读取模式,用于读取文件内容。
  • 'w':写入模式,用于写入文件内容。如果文件已存在,则清空文件;如果文件不存在,则创建文件。
  • 'a':追加模式,用于在文件末尾追加内容。如果文件不存在,则创建文件。
  • 'b':二进制模式,用于处理二进制文件。
  • 'x':排它性创建,用于创建文件,如果文件已存在则会抛出FileExistsError。
  • '+':读写模式,用于同时进行读取和写入操作。
  • 't':文本模式,用于处理文本文件。这是默认模式,通常可以省略。
  • 'rb':以二进制模式读取文件。
  • 'w+':以读写模式打开文件,如果文件不存在则创建。
  • 'a+':以读写模式打开文件,如果文件不存在则创建,写入操作追加在文件末尾。
  • 'wb':以二进制模式写入文件。

在ipython环境下写入测试文件:

%%writefile test.txt
this is a test file.
hello world! 
python is good! 
today is a good day.
Writing test.txt

读文件

使用open函数读文件,使用文件名的字符串作为输入参数

f = open('test.txt')

可以使用read方法来读入文件中的所有内容

text = f.read()
print(text)
f.close()
this is a test file.
hello world! 
python is good! 
today is a good day.

事实上,文件对象有一个指针,指向文件中当前的位置。读写操作会移动指针,我们就可以实现读取指定个数的字符:

f = open('test.txt')
text = f.read(10) 
print(text)
f.close()
# 输出:this is a 

也可以按照行读入内容,readlines方法返回一个列表,每个元素代表文件中每一行的内容

f = open('test.txt')
lines = f.readlines()
print(lines)
f.close()
# 输出:['this is a test file.\n', 'hello world! \n', 'python is good! \n', 'today is a good day.\n']

事实上,也可以将f放在一个循环中,得到每一行的内容

f = open('test.txt')
for line in f:
    print(line)
f.close()
this is a test file.

hello world! 

python is good! 

today is a good day.

删除刚创建的文件

import os
os.remove('test.txt')

写文件

我们使用open函数的写入模式来写文件

f = open('myfile.txt', 'w')
f.write('hello world')
f.close()

使用w模式时,如果文件不存在会被创建

print(open('myfile.txt').read()) # hello world

如果文件已经存在,w模式会覆盖之前写的所有内容

f = open('myfile.txt', 'w')
f.write('another hello world')
f.close()
print(open('myfile.txt').read()) # another hello world

追加模式a,追加模式不会覆盖之前的内容,而是接着写入

f = open('myfile.txt', 'a')
f.write('... and more')
f.close()
print(open('myfile.txt').read()) # another hello world... and more

写入结束后一定要用close关闭文件,否者可能出现内容没有完全写入的情况。

还可以使用读写模式w+

f = open('myfile.txt', 'w+')
f.write('hello world!')
f.seek(6)
print(f.read()) # world!
f.close()

这里 f.seek(6) 移动到文件的第6个字符处,然后 f.read() 读出剩下的内容。

with 语句

在实际情况中,我们使用更多的其实是with语句。

使用with语句可以确保文件在使用后正确关闭,无论是否发生异常,这样就可以避免忘记使用close

with open('myfile.txt', 'r') as f: 
    text = f.read() 
    print(text)
输出:hello world!

6.2 异常与警告

6.2.1 异常

try & except 块
import math

while True:
    text = input()
    if text[0] == 'q':
        break
    x = float(text)
    y = math.log10(x)
    print("log10({0}) = {1}".format(x, y))
 1
 log10(1.0) = 0.0
 0
 ---------------------------------------------------------------------------

ValueError                                Traceback (most recent call last)

Cell In[21], line 8
      6     break
      7 x = float(text)
----> 8 y = math.log10(x)
      9 print("log10({0}) = {1}".format(x, y))
ValueError: math domain error

当输入为数字是,计算它的对数并输出,直到输入值为q为止。

乍看没有问题,但当我们输入0或负数时:log10函数会报错,因为不能接收非正值。

一旦报错,程序就会停止执行,如果不希望程序停止执行,那么可以添加try & except

import math

while True:
    try:
        text = input()
        if text[0] == 'q':
            break
        x = float(text)
        y = math.log10(x)
        print("log10({0}) = {1}".format(x, y))
    except ValueError:
        print("the value must be greater than 0")
 0
 the value must be greater than 0
 1
 log10(1.0) = 0.0
 -1
 the value must be greater than 0
 q
捕捉所有异常

常见的异常有:

  • FileNotFoundError: 文件未找到异常,当尝试打开或操作一个不存在的文件时抛出。
  • TypeError: 类型错误异常,当执行操作的对象类型不符合预期时抛出。
  • ValueError: 值错误异常,当函数接收到一个类型正确但值不合适的参数时抛出。
  • ZeroDivisionError: 除零错误异常,当尝试除以零时抛出。
  • IndexError: 索引错误异常,当使用无效的索引访问序列中的元素时抛出。
  • KeyError: 键错误异常,当尝试使用字典中不存在的键访问值时抛出。
  • IOError: 输入/输出错误异常,当发生文件或流操作错误时抛出。

当我们想要捕捉所有的异常可以将except的值改为Exception类:

import math

while True:
    try:
        text = input()
        if text[0] == 'q':
            break
        x = float(text)
        y = 1 / math.log10(x)
        print("log10({0}) = {1}".format(x, y))
    except Exception:
        print("invalid value")
 1
 the value must be greater than 0
 0
 invalid value
 -1
 invalid value
 q
指定特定值
import math

while True:
    try:
        text = input()
        if text[0] == 'q':
            break
        x = float(text)
        y = 1 / math.log10(x)
        print("log10({0}) = {1}".format(x, y))
    except (ValueError, ZeroDivisionError):
        print("invalid value")
 1
 invalid value
 q

或者另加处理

import math

while True:
    try:
        text = input()
        if text[0] == 'q':
            break
        x = float(text)
        y = 1 / math.log10(x)
        print("log10({0}) = {1}".format(x, y))
    except ValueError:
        print("the value must be greater than 0")
    except ZeroDivisionError:
        print("the value must not be 1")
 1
 the value must not be 1
 -1
 the value must be greater than 0
 q
得到异常的具体信息

可以使用exc.args得到异常的具体信息

import math

while True:
    try:
        text = input()
        if text[0] == 'q':
            break
        x = float(text)
        y = 1 / math.log10(x)
        print("1 / log10({0}) = {1}".format(x, y))
    except ValueError as exc:
        if exc.args == "math domain error":
            print("the value must be greater than 0")
        else:
            print("could not convert '%s' to float" % text)
    except ZeroDivisionError:
        print("the value must not be 1")
    except Exception as exc:
        print("unexpected error:", exc.args)
 1
 the value must not be 1
 -1
 could not convert '-1' to float
 0
 could not convert '0' to float
 q

6.2.2 警告

在python中,warnings模块用于发出警告消息。警告是一种轻量级的消息,通常用于指示潜在的问题或不推荐使用的代码。Python中的警告有时被用于向用户提供关于过时功能、不推荐使用的模块或即将废弃的特性的信息。

常见的警告
  • DeprecationWarning(废弃警告): 用于指示某个功能、模块或方法即将被弃用。应该逐步停止使用这些被标记为废弃的元素,以避免未来版本的不兼容性。
  • FutureWarning(将来的警告): 用于指示某些使用方式在未来版本中可能会发生变化。这个警告类型通常用于提醒开发者他们的代码在未来版本可能需要修改。
  • RuntimeWarning(运行时警告): 值错误异常,当函数接收到一个类型正确但值不合适的参数时抛出。这通常用于提醒用户某些操作可能导致潜在问题,但不会中断程序执行。
  • UserWarning(用户警告): 用于向用户发出一般性的警告信息,提醒用户可能会影响程序行为的一些因素。

警告的使用如下:

import warnings

def month_warning(m): 
    if not 1 <= m <= 12:
        msg = 'month (%d) is not between 1 and 12' % m 
        warnings.warn(msg, RuntimeWarning) 

month_warning(13)
C:\Users\xxx\AppData\Local\Temp\ipykernel_18108\3826929804.py:6: RuntimeWarning: month (13) is not between 1 and 12
  warnings.warn(msg, RuntimeWarning)
忽略警告

有时候我们想要忽略特定的警告,可以使用warningsfilterwarnings函数:`filterwarnings(action, category)

action设置为ignore便可以忽略特定类型的警告

warnings.filterwarnings(action='ignore', category=RuntimeWarning) 

month_warning(13)

6.3 数据存储

pickle模块

pickle模块用于序列化和反序列化对象,将对象保存为文件。

import pickle

# pickle模块
data = {'name': 'Alice', 'age': 25}

# 序列化
with open('data.pkl', 'wb') as file:
    pickle.dump(data, file)

# 反序列化
with open('data.pkl', 'rb') as file:
    loaded_data = pickle.load(file)
    print(loaded_data)

json模块

json模块用于处理JSON格式的数据。

import json

# json模块
data = {'name': 'Bob', 'age': 30}

# 编码为JSON字符串
json_str = json.dumps(data)

# 解码JSON字符串
decoded_data = json.loads(json_str)
print(decoded_data)

ini格式文件处理

configparser模块用于处理INI格式的配置文件。

import configparser

# ini格式文件处理
config = configparser.ConfigParser()

# 写入配置
config['User'] = {'name': 'John', 'email': 'john@example.com'}
with open('config.ini', 'w') as config_file:
    config.write(config_file)

# 读取配置
config.read('config.ini')
user_name = config['User']['name']
print(user_name)

csv格式文件处理

csv模块用于处理CSV格式的文件。

import csv

# csv格式文件处理
data = [['Name', 'Age'], ['Alice', 25], ['Bob', 30]]

# 写入CSV文件
with open('data.csv', 'w', newline='') as csv_file:
    csv_writer = csv.writer(csv_file)
    csv_writer.writerows(data)

# 读取CSV文件
with open('data.csv', 'r') as csv_file:
    csv_reader = csv.reader(csv_file)
    for row in csv_reader:
        print(row)

当然,CSV格式文件更为常见的操作方式是使用pandas

import pandas as pd 

# DataFrame类型的数据
data = pd.DataFrame({'name': 'John', 'email': 'john@example.com'}) 

# 保存为CSV文件
file_path = 'files/data.csv'
data.to_csv(file_path, index=False) # index设置为False表示不保存索引列

# 读取CSV文件
data = pd.read_csv(file_path) 

7 面向对象

7.1 对象、类、实例

面向对象编程(Object-Oriented Programming,OOP)的基础思想是通过构建对象来组织和管理代码。对象是一种封装了数据和行为的实体,它将数据和对数据的操作结合在一起。在OOP中,代码被组织成类(Class)和实例(Instance)。

类(Class): 类是对象的模板,定义了对象的属性和行为。类中包含了数据成员(属性)和方法(函数)。

实例(Instance): 实例是根据类创建的具体对象。每个实例都是类的一个独立个体,拥有类定义的属性和行为。

# 设计类
class Student: 
    name = None    # 姓名
    gender = None  # 性别
    age = None     # 年龄

# 创建对象
stu1 = Student() 

# 创建实例(对对象中的属性赋值)
stu1.name = '小困难' 
stu1.gender = '男'
stu1.age = 1 

# 输出实例
print(stu1.name, stu1.gender, stu1.age) # 小困难 男 1

7.1.1 类的定义和使用

类定义的基础语法:

class 类名称:
    类的属性   定义在类中的变量(成员变量)
    类的行为   定义在类中的函数(成员方法)

成员方法的定义语法:

def 方法名(self, 形参1, ..., 形参N):
    方法体

self关键字是成员方法定义的时候必须存在的,可能完全使用不上,但是也必须存在。

当我们在方法内部想要访问类的成员变量的时候,必须使用self

class Student: 
    name = None    # 姓名
    gender = None  # 性别
    age = None     # 年龄

    def say_hi(self): 
        print('hello')

    def say_name(self): 
        print(f'hello, i\'m {self.name}.') 

    def say_words(self, words): 
        print(f'hello, {words}.') 

stu1 = Student() 

stu1.name = '小困难' 
stu1.gender = '男'
stu1.age = 1 

# 调用成员方法
stu1.say_hi()           # hello
stu1.say_name()         # hello, i'm 小困难.
stu1.say_words('world') # hello, world.

7.1.2 构造方法

如果每一次创建实例都需要一个一个属性去定义,会非常的繁琐。我们可以使用__init__()方法,也称构造方法。

构造方法可以实现:

  • 在创建类对象的时候,该方法会自动执行
  • 在创建类对象的时候,将传入的参数自动传递给__init__方法使用
class Student: 
    name = None    # 姓名
    gender = None  # 性别
    age = None     # 年龄

    def __init__(self, name, gender, age): 
        self.name = name 
        self.gender = gender 
        self.age = age 
        print('该对象已成功创建') 

stu = Student('小困难', '男', 1) 

print(stu.name, stu.gender, stu.age) 
该对象已成功创建
小困难 男 1

7.1.3 魔术方法

python中有很多内置的类方法,我们将其称为:魔术方法

__str__字符串方法

如果我们直接print一个实例的话,其输出的其实是这个类对象的内存地址,但是我们想要的并不是对象,而是字符串,这时就可以用到__str__字符串方法。

class Student: 
    def __init__(self, name , age): 
        self.name = name 
        self.age = age 

    def __str__(self): 
        return f"Student类, name={self.name}, age={self.age}"

stu = Student('小困难', 1) 
print(stu) # Student类, name=小困难, age=1 
__lt__小于符号比较方法

可以根据某一个属性进行比较,一般用于<比较。

  • 传入参数:self, other
  • 返回值: True, False
class Student: 
    def __init__(self, name , age): 
        self.name = name 
        self.age = age 

    def __lt__(self, other):
        return self.age < other.age 

stu1 = Student('小困难', 1) 
stu2 = Student('坤哥', 2) 

print(stu1 < stu2) # True
print(stu1 > stu2) # False
__le__小于等于符号比较方法

可以用于<=, >= 两种比较运算

  • 传入参数:self, other
  • 返回值: True, False
class Student: 
    def __init__(self, name , age): 
        self.name = name 
        self.age = age 

    def __le__(self, other):
        return self.age <= other.age 

stu1 = Student('小困难', 1) 
stu2 = Student('坤哥', 2) 

print(stu1 <= stu2) # True
print(stu1 >= stu2) # False
__eq__等于符号比较方法

可以用于 == 比较运算

  • 传入参数:self, other
  • 返回值: True, False
class Student: 
    def __init__(self, name , age): 
        self.name = name 
        self.age = age 

    def __eq__(self, other):
        return self.age <= other.age 

stu1 = Student('小困难', 1) 
stu2 = Student('坤哥', 1) 

print(stu1 == stu2) # True

7.2 面向对象三要素

7.2.1 封装

封装:将现实世界中的属性和方法,封装为类中的成员变量和成员方法。

现实世界中有对用户开放的属性和行为和对用户隐藏的属性和行为,那么类中也要有对不公开事物的支持,这就是私有成员

  • 私有成员变量 变量名以__开头(2个下划线)
  • 私有成员方法 方法名以__开头

尝试调用私有成员会报错:

class Student: 
    __weight = None # 体重

    def __printWeight(self): 
        print(f'My weight is {self.__weight}') 
    
stu = Student() 
stu.__printWeight() 
print(stu.__weight)
---------------------------------------------------------------------------

AttributeError                            Traceback (most recent call last)

Cell In[17], line 8
      5         print(f'My weight is {self.__weight}') 
      7 stu = Student() 
----> 8 stu.__printWeight() 
      9 print(stu.__weight)
AttributeError: 'Student' object has no attribute '__printWeight'

私有成员没有办法被类对象使用,但是可以被其他的成员使用。通俗来说,就是其他的成员方法可以调用。

class Student:
    __weight = 76 # 体重

    def __printWeight(self): 
        print(f'You weight is {self.__weight}') 

    def is_weight(self): 
        if self.__weight <= 75:
            print('Normal weight') 
        else:
            self.__printWeight() 
            print("You are overweight")

stu = Student() 
stu.is_weight() 
You weight is 76
You are overweight

7.2.2 继承

继承是一种类之间共享属性和方法的机制。一个类(子类)可以继承另一个类(父类)的属性和方法,并可以在此基础上进行扩展或修改。

在Python中,通过在类定义中的括号内指定父类的名称,来创建一个继承自父类的子类。

# 父类
class Animal:
    def __init__(self, name):
        self.name = name

    def speak(self):
        return "I'm a "
        
# 子类
class Dog(Animal):
    def speak(self):
        return f"{self.name} says Woof!"

my_dog = Dog('Buddy')
print(my_dog.speak()) # Buddy says Woof!
调用父类方法

在子类中,可以使用super()函数来调用父类的方法。这在子类要扩展而不是完全覆盖父类方法时非常有用。

class Animal:
    def __init__(self, name):
        self.name = name

    def speak(self):
        return "I'm a "

class Bird(Animal):
    def speak(self):
        return super().speak() + "bird."

my_bird = Bird('Tweety')
print(my_bird.speak()) # I'm a bird.
方法重写

方法重写是指在子类中重新定义父类的方法。当子类中定义了与父类同名的方法时,子类的方法将覆盖(重写)父类的方法。

# 方法重写
class Animal:
    def speak(self):
        return "Animal speaks."

class Dog(Animal):
    def speak(self):
        return "Dog barks."

my_dog = Dog()
print(my_dog.speak())  # Dog barks.
多重继承

Python支持多重继承,即一个子类可以继承自多个父类。多重继承可以通过在类定义时指定多个父类来实现。

# 多重继承
class A:
    def method(self):
        return "Method from class A"

class B:
    def method(self):
        return "Method from class B"

class C(A, B):
    pass

instance_c = C()
print(instance_c.method())  # Method from class A

在多重继承中,当一个类继承自多个父类,它的方法解析顺序由其继承的父类顺序决定。在这个例子中,类 C 继承自类 A 和类 B,因此它的方法解析顺序为先查找类 A,再查找类 B。

在 C 类中,虽然没有定义 method 方法,但由于继承自 A 类,它会首先在类 A 中查找是否存在 method 方法。因为 A 类中有定义 method 方法,所以它会返回 “Method from class A”。

另外,在类的定义中,pass通常被用作一个占位符,表示类体(类的主体部分)不能为空。当你正在设计一个类的框架,但还没有完全实现类的方法和属性时,可以使用pass来保持语法正确性,同时表示这个地方暂时不需要执行任何具体的操作。这就引入了抽象类的概念。

抽象类

抽象类是不能被实例化的类,其目的是为了被其他类继承。抽象类通常包含一些抽象方法,子类必须实现这些方法。

from abc import ABC, abstractmethod

# 抽象类与接口
class Shape(ABC):
    @abstractmethod
    def area(self):
        pass

class Circle(Shape):
    def __init__(self, radius):
        self.radius = radius

    def area(self):
        return 3.14 * self.radius * self.radius

my_circle = Circle(5)
print(my_circle.area()) # 78.5

在上面的例子中,Shape是一个抽象类,包含一个抽象方法areaCircle类继承自Shape类,并实现了area方法。抽象类的目的是强制子类实现特定的方法,以确保派生类具有特定的行为。如果没有实现该方法,就会导致实例化失败。

@abstractmethod是一个装饰器,作用是通知 Python 解释器,该方法是抽象方法,需要在子类中被具体实现。

还有另一种实现方式:

class Animal:
    def __init__(self, name):
        self.name = name

    def speak(self):
        raise NotImplementedError("Subclasses must implement this method.")

class Dog(Animal):
    def speak(self):
        return f"{self.name} says Woof!"

my_dog = Dog('Buddy')
print(my_dog.speak()) # Buddy says Woof!

raise NotImplementedError("Subclasses must implement this method.")是一种在抽象类中的抽象方法中常见的做法。这是在Python中用于明确指示子类必须覆盖(实现)这个方法的一种方式。

7.2.3 多态

多态是一种对象可以在不同的上下文中具有不同行为的能力。

同样的行为(函数),传入不同的对象,得到不同的状态。

class Animal: 
    def speak(self): 
        pass 

class Dog(Animal): 
    def speak(self): 
        print('汪汪汪') 

class Cat(Animal): 
    def speak(self): 
        print('喵喵喵') 

def animal_speak(animal: Animal):
    animal.speak()

dog = Dog() 
cat = Cat() 

animal_speak(dog) # 汪汪汪
animal_speak(cat) # 喵喵喵

7.3 类方法与静态方法

7.3.1 类方法

类方法是与类相关联的方法,而不是与类的实例相关联。它们由类调用,而不是由类的实例调用。类方法使用@classmethod装饰器来声明,它的第一个参数通常被命名为cls

class MathOperations:
    PI = 3.14  # 类变量

    @classmethod
    def circle_area(cls, radius):
        return cls.PI * radius**2

# 调用类方法
area = MathOperations.circle_area(5)
print(area) # 78.5

7.3.2 静态方法

静态方法与类或实例无关,它们不引用类或实例的任何属性。它们由类调用,但与类没有直接的关系。静态方法使用@staticmethod装饰器来声明。

class MathOperations:
    @staticmethod
    def add(x, y):
        return x + y

# 调用静态方法
result = MathOperations.add(3, 5)
print(result) # 8

7.4 访问控制与属性装饰器

7.4.1 访问控制

在面向对象编程中,访问控制是一种通过限制对类或对象的属性和方法的访问来确保数据的封装性和安全性的机制。

在Python中,通过以下的命名约定来表示属性或方法的访问权限:

  • 公有属性/方法: 以正常命名规则定义,没有特殊前缀。例如,self.name。

  • 受保护属性/方法: 以一个下划线_开头,例如,self._name。这只是一个约定,表示该属性或方法是类内部使用的,不建议在类外部直接访问。

  • 私有属性/方法: 以两个下划线__开头,例如,self.__age。同样,这只是一个约定,表示该属性或方法不应在类外部直接访问。在实际情况下,Python 使用名称修饰(name mangling)来在属性名前加上_classname的形式,以防止直接访问。

class MyClass:
    def __init__(self, name, age):
        self.name = name          # 公有属性
        self._age = age           # 受保护属性
        self.__secret = 'hidden'  # 私有属性

    def get_secret(self):
        return self.__secret

# 创建实例
obj = MyClass('小困难', 1)

# 访问公有属性
print(obj.name)    # 输出: 小困难

# 访问受保护属性
print(obj._age)    # 输出: 1

# 访问私有属性
# print(obj.__secret)  # 这将引发 AttributeError

# 使用名称修饰来访问私有属性
print(obj._MyClass__secret)  # 输出: hidden

7.4.2 属性装饰器

属性装饰器提供了更灵活的方式来控制类属性的访问和修改。常见的装饰器有@property@<attribute>.setter@<attribute>.deleter

@property

@property装饰器用于将一个方法转化为只读属性。这允许你在不改变类接口的情况下,以属性的方式访问方法。

class Rectangle:
    def __init__(self, width, height):
        self._width = width
        self._height = height

    @property
    def area(self):
        return self._width * self._height

# 创建实例
rect = Rectangle(5, 10)

# 访问只读属性
print(rect.area)  # 输出: 50
@.setter

@<attribute>.sette 装饰器用于定义设置属性值的方法。这使得我们可以在设置属性时执行额外的逻辑。

class Person:
    def __init__(self, name):
        self._name = name

    @property
    def name(self):
        return self._name

    @name.setter
    def name(self, value):
        if not isinstance(value, str):
            raise ValueError("Name must be a string.")
        self._name = value

# 创建实例
person = Person('小困难')

# 设置属性值
person.name = '李华'
print(person.name)  # 输出: 李华

# 尝试设置非字符串值,将引发 ValueError
# person.name = 42
@.deleter

@<attribute>.delete 装饰器用于定义删除属性值的方法。这使得我们可以在删除属性时执行额外的逻辑。

class Car:
    def __init__(self, brand):
        self._brand = brand

    @property
    def brand(self):
        return self._brand

    @brand.deleter
    def brand(self):
        print(f"Deleting {self._brand} brand.")
        del self._brand

# 创建实例
car = Car('Toyota')

# 删除属性值
del car.brand  # 输出: Deleting Toyota brand.

7.5 面向对象高级方法

7.5.1 属性查看

dir()函数或__dict__属性用于查看对象的所有属性和方法。

# 属性查看
class Person:
    def __init__(self, name, age):
        self.name = name
        self.age = age

little_difficult = Person('小困难', 1)

# 使用dir()函数
attributes = dir(little_difficult)

# 使用__dict__属性
attributes_dict = little_difficult.__dict__
print(attributes)
['__class__', '__delattr__', '__dict__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', '__getstate__', '__gt__', '__hash__', '__init__', '__init_subclass__', '__le__', '__lt__', '__module__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__', '__weakref__', 'age', 'name']
print(attributes_dict)
{'name': '小困难', 'age': 1}

7.5.2 实例化、可视化和Hash

实例化是创建类的实例(对象)的过程。在类的定义中,__init__ 方法用于初始化对象的属性。看的。

# 实例化
class Person:
    def __init__(self, name, age):
        self.name = name
        self.age = age

# 创建实例
little_difficult = Person('小困难', 1)

在 Python 中,我们可以通过两个特殊方法来控制对象的可视化表示:

  • __repr__方法用于返回对象的“官方”字符串表示,通常是一个可以用来重新创建对象的字符串。
  • __str__方法用于返回对象的“友好”字符串表示,通常是给用户看的。
# 可视化
class Person:
    def __init__(self, name, age):
        self.name = name
        self.age = age

    def __repr__(self):
        return f"Person('{self.name}', {self.age})"

    def __str__(self):
        return f"{self.name}, {self.age} years old"

little_difficult = Person('小困难', 1)

# 调用 __repr__
print(repr(little_difficult)) # Person('小困难', 1)

# 调用 __str__
print(str(little_difficult))  # 小困难, 1 years old

Hash是一个用于唯一标识对象的数值,对于哈希可变的对象(如列表)来说,哈希值可能会发生变化。在 Python 中,我们可以通过实现__hash__方法来定义对象的哈希值。

__hash__方法返回一个由对象属性组成的元组的哈希值。这确保了相同属性的对象将具有相同的哈希值,适用于对象被用作字典键或集合成员的情况。

# Hash
class Person:
    def __init__(self, name, age):
        self.name = name
        self.age = age

    def __hash__(self):
        return hash((self.name, self.age))

little_difficult = Person('小困难', 1)

# 获取哈希值
hash_value = hash(little_difficult)
print(hash_value) # -1017972499074875378

7.5.3 运算符重载

运算符重载是指在类中定义特殊方法,使得类的对象可以对内置运算符进行自定义操作。通过运算符重载,我们可以定义类的行为,使其更符合特定的需求。

在Python中,运算符重载通过定义类的特殊方法来完成。这些特殊方法都以双下划线__开头和结尾。例如,__add__ 方法用于重载加法运算符 +__sub__ 用于重载减法运算符 -,以此类推。

下面是一些常见的运算符重载方法:

  • __add__(self, other): 定义加法运算符 + 的行为。
  • __sub__(self, other): 定义减法运算符 - 的行为。
  • __mul__(self, other): 定义乘法运算符 * 的行为。
  • __truediv__(self, other): 定义真除法运算符 / 的行为。
  • __floordiv__(self, other): 定义地板除法运算符 // 的行为。
  • __mod__(self, other): 定义取模运算符 % 的行为。
  • __pow__(self, other): 定义幂运算符 ** 的行为。

通俗来说,就是重新定义+ - * /这些运算符号在这个类中的意义。

让我们通过一个复数类的例子来详细了解运算符重载:

class ComplexNumber:
    def __init__(self, real, imaginary):
        self.real = real
        self.imaginary = imaginary

    # 重载加法运算符
    def __add__(self, other):
        real_sum = self.real + other.real
        imaginary_sum = self.imaginary + other.imaginary
        return ComplexNumber(real_sum, imaginary_sum)

    # 重载减法运算符
    def __sub__(self, other):
        real_diff = self.real - other.real
        imaginary_diff = self.imaginary - other.imaginary
        return ComplexNumber(real_diff, imaginary_diff)

    # 重载乘法运算符
    def __mul__(self, other):
        real_product = self.real * other.real - self.imaginary * other.imaginary
        imaginary_product = self.real * other.imaginary + self.imaginary * other.real
        return ComplexNumber(real_product, imaginary_product)

    # 重载字符串表示
    def __str__(self):
        return f"{self.real} + {self.imaginary}j"

# 创建复数对象
complex_num1 = ComplexNumber(2, 3)
complex_num2 = ComplexNumber(1, 4)

# 使用重载的加法运算符
result_add = complex_num1 + complex_num2
print("Addition Result:", result_add)       # Addition Result: 3 + 7j

# 使用重载的减法运算符
result_sub = complex_num1 - complex_num2
print("Subtraction Result:", result_sub)    # Subtraction Result: 1 + -1j

# 使用重载的乘法运算符
result_mul = complex_num1 * complex_num2
print("Multiplication Result:", result_mul) # Multiplication Result: -10 + 11j

7.5.4 容器化

在面向对象编程中,容器化是一种使类对象具备容器的特性的技术。通过实现一系列特殊方法,我们可以使自定义的类对象像内置容器类型(如列表、字典等)一样,支持常见的操作,例如索引、切片等。

len 方法

__len__方法用于返回容器中元素的数量,类似于内置函数len()

class MyList:
    def __init__(self):
        self.items = []

    def __len__(self):
        return len(self.items)

my_list = MyList()
my_list.items = [1, 2, 3]

print(len(my_list))  # 输出: 3
getitem 方法

__getitem__方法用于实现容器的索引访问,类似于通过obj[key]获取元素。

class MyList:
    def __init__(self):
        self.items = []

    def __getitem__(self, index):
        return self.items[index]

my_list = MyList()
my_list.items = [1, 2, 3]

print(my_list[1])  # 输出: 2
setitem 方法

__setitem__方法用于实现容器的索引赋值,类似于通过obj[key] = value修改元素。

class MyList:
    def __init__(self):
        self.items = []

    def __setitem__(self, index, value):
        self.items[index] = value

my_list = MyList()
my_list.items = [1, 2, 3]

my_list[1] = 10
print(my_list.items)  # 输出: [1, 10, 3]

下面是一个完整的例子,展示了如何通过容器化让自定义的类对象表现得像一个简单的列表:

class MyList:
    def __init__(self):
        self.items = []

    def __len__(self):
        return len(self.items)

    def __getitem__(self, index):
        return self.items[index]

    def __setitem__(self, index, value):
        self.items[index] = value

    def append(self, value):
        self.items.append(value)

my_list = MyList()
my_list.append(1)
my_list.append(2)
my_list.append(3)

print(len(my_list))    # 输出: 3
print(my_list[1])      # 输出: 2

my_list[1] = 10
print(my_list.items)   # 输出: [1, 10, 3]

7.5.5 反射

反射是一种在运行时检查和操作对象的能力,通常涉及检查对象的属性和调用对象的方法。

hasattr() 函数

hasattr()函数用于检查对象是否具有指定名称的属性。它接受两个参数,第一个是对象,第二个是属性名。如果对象具有指定属性,返回True,否则返回False

class Person:
    def __init__(self, name, age):
        self.name = name
        self.age = age

little_difficult = Person('小困难', 1)

# 检查属性是否存在
has_name = hasattr(little_difficult, 'name')
has_gender = hasattr(little_difficult, 'gender')

print(has_name)    # True
print(has_gender)  # False
getattr() 函数

getattr()函数用于获取对象的属性值。它接受两个参数,第一个是对象,第二个是属性名。如果对象具有指定属性,返回属性的值;如果属性不存在,可以提供一个默认值。

如果第二个是方法名的话,最后返回的是这个方法,相当于调用这个方法。

class Person:
    def __init__(self, name, age):
        self.name = name
        self.age = age

little_difficult = Person('小困难', 1)

# 获取属性值
name_value = getattr(little_difficult, 'name')
gender_value = getattr(little_difficult, 'gender', 'Not specified')

print(name_value)       # 小困难
print(gender_value)     # Not specified
setattr() 函数

setattr()函数用于设置对象的属性值。它接受三个参数,第一个是对象,第二个是属性名,第三个是属性值。如果属性不存在,会创建一个新的属性。

class Person:
    def __init__(self, name, age):
        self.name = name
        self.age = age

little_difficult = Person('小困难', 1)

# 设置属性值
setattr(little_difficult, 'age', 2)
setattr(little_difficult, 'gender', '男')

print(little_difficult.age)    # 2
print(little_difficult.gender) # 男

反射的应用之一是通过字符串调用对象的方法。这在某些动态的场景下非常有用,例如从配置文件中读取方法名并调用相应的方法。

class Calculator:
    def add(self, x, y):
        return x + y

    def subtract(self, x, y):
        return x - y

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

    def divide(self, x, y):
        if y != 0:
            return x / y
        else:
            raise ValueError("Cannot divide by zero.")

calculator = Calculator()

# 通过字符串调用对象的方法
operation = 'add'
result = getattr(calculator, operation)(3, 4)
print(result)  # 7

7.5.6 上下文管理

上下文管理是一种在进入和离开某个上下文时执行特定操作的机制。在Python中,上下文管理通常与with语句一起使用,它提供了一种清晰、简单的方式来管理资源,比如文件、网络连接或数据库连接,确保在使用完资源后正确地释放它们。

with语句

with语句用于创建一个运行时上下文,确保在进入和离开上下文时资源得到正确的管理。它的一般语法如下:

with expression as variable:
    # 在这个块内执行操作,variable 将引用 expression 的结果
    # 在离开块时,确保资源被正确释放

expression通常是返回上下文管理器的对象,这个对象需要实现__enter____exit__方法。

上下文管理器

为了使用with语句,你需要创建一个上下文管理器。一个上下文管理器是一个包含__enter____exit__方法的对象。

  • __enter__方法在进入with代码块之前调用,它负责准备资源并返回一个值,这个值将被赋给 as 后面的变量。
  • __exit__方法在离开with代码块时调用,它负责清理或释放资源。如果代码块正常执行完毕__exit__方法的参数将为None, None, None。如果发生异常,参数将包含异常信息,可以用于异常处理。
class MyFileReader:
    def __init__(self, file_path):
        self.file_path = file_path

    def __enter__(self):
        self.file = open(self.file_path, 'r')
        return self.file

    def __exit__(self, exc_type, exc_value, traceback):
        if self.file:
            self.file.close()

# 使用上下文管理器
with MyFileReader('example.txt') as file:
    content = file.read()
    print(content)

7.5.7 描述器

描述器是一种强大的工具,用于定制属性的访问、修改和删除行为。在Python中,描述器是一个实现了__get____set____delete__方法中至少一个的对象。描述器常用于创建具有自定义行为的属性,例如对属性的类型进行验证或执行其他定制操作。

描述器的基本原理涉及到三个特殊方法:

  • __get__(self, instance, owner):用于获取属性的值。
  • __set__(self, instance, value):用于设置属性的值。
  • __delete__(self, instance):用于删除属性。

这三个方法中的参数含义如下:

  • self:描述器实例本身。
  • instance:拥有描述器的实例。
  • owner:拥有描述器的类。

接下来我们通过一个简单的例子来创建一个描述器:

class Celsius:
    def __get__(self, instance, owner):
        return instance._temperature

    def __set__(self, instance, value):
        if value < -273.15:
            raise ValueError("Temperature below absolute zero is not possible.")
        instance._temperature = value

class Temperature:
    def __init__(self, initial_temperature):
        self._temperature = initial_temperature
    celsius = Celsius()

# 使用描述器
temperature_obj = Temperature(25)
print(temperature_obj.celsius)  # 输出摄氏度     输出:25
temperature_obj.celsius = 30    # 设置摄氏度
print(temperature_obj.celsius)  # 再次输出摄氏度  输出:30
类型验证

描述器可以用于对属性的类型进行验证。例如,我们可以创建一个描述器确保属性的值为整数类型。

class IntegerValue:
    def __set__(self, instance, value):
        if not isinstance(value, int):
            raise ValueError("Value must be an integer.")
        instance._value = value

class Example:
    value = IntegerValue()

# 使用描述器进行类型验证
example_obj = Example()
example_obj.value = 42  # 正确
example_obj.value = "invalid"  # 报错,值必须是整数类型
---------------------------------------------------------------------------

ValueError                                Traceback (most recent call last)

Cell In[70], line 1
----> 1 example_obj.value = "invalid"
Cell In[69], line 4, in IntegerValue.__set__(self, instance, value)
      2 def __set__(self, instance, value):
      3     if not isinstance(value, int):
----> 4         raise ValueError("Value must be an integer.")
      5     instance._value = value
ValueError: Value must be an integer.
惰性计算

描述器可以用于实现属性的惰性计算,延迟计算属性值直到首次访问。

class LazyProperty:
    def __init__(self, method):
        self.method = method
        self.name = method.__name__

    def __get__(self, instance, owner):
        if instance is None:
            return self
        value = self.method(instance)
        setattr(instance, self.name, value)
        return value

class Circle:
    def __init__(self, radius):
        self._radius = radius

    @LazyProperty
    def diameter(self):
        print("Calculating diameter.")
        return 2 * self._radius

# 使用描述器进行惰性计算
circle_obj = Circle(5)
print(circle_obj.diameter)  # 首次访问,触发计算
Calculating diameter.
10
print(circle_obj.diameter)  # 不再计算,直接返回已经计算过的值
10
实现特定行为

描述器还可以用于实现特定行为,例如属性的只读或只写,或者实现属性值的特定操作。

class ReadOnly:
    def __set__(self, instance, value):
        raise AttributeError("Can't set attribute.")

class Person:
    def __init__(self, name):
        self._name = name

    name = ReadOnly()

# 使用描述器实现只读属性
person_obj = Person("小困难")
print(person_obj.name)  # 可读  输出:<__main__.ReadOnly object at 0x0000015AF078DB10>
person_obj.name = "新名字"  # 不可写,抛出异常
---------------------------------------------------------------------------

AttributeError                            Traceback (most recent call last)

Cell In[75], line 1
----> 1 person_obj.name = "新名字"
Cell In[74], line 3, in ReadOnly.__set__(self, instance, value)
      2 def __set__(self, instance, value):
----> 3     raise AttributeError("Can't set attribute.")
AttributeError: Can't set attribute.

8 模块

8.1 模块导入

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

主要的格式有下面几种:

  • import modulename : 导入整个模块

  • from modulename import functionname : 导入模块中的某个函数

  • from modulename import func1, func2 : 导入模块中的多个函数

  • from modulename import * : 导入某个模块的全部函数

  • import modulename as anothername : 导入某个模块并赋予别名

# 导入整个模块
import time 

print('hello') 
time.sleep(5)  # 这里会停止5秒
print('world') 
# 导入模块中的某个函数
from time import sleep 

print('hello') 
sleep(5) 
print('world') 
# 导入模块的全部函数 
from time import * 

print('hello') 
sleep(5) 
print('world') 
# 导入某个模块并赋予别名
import time as t 

print('hello') 
t.sleep(5) 
print('world')

from time import sleep as sl 

print('hello') 
sl(5) 
print('world') 

8.2 自定义模块

导入自定义模块

自定义模块需要将函数定义在一个.py文件中,例如my_module.py中有一个test函数。当导入的时候,模块名就是该文件名:

import my_model

from my_model import test

注意:当我们导入不同模块的同名功能时,新的导入会覆盖旧的导入。

__main__变量

当我们导入自定义模块的时候,相当于运行了一遍该文件,但是我们不是想运行这个模块文件,只是需要其中的函数。这时候就需要用到__main__变量,以下面的模块文件为例:

def add(x, y):
    print(x + y)

if __name__ == '__main__':
    add(1, 2) 

这样,当导入该模块文件的时候就不会执行if __name__ == '__main__':语句中的代码。

__all__变量

如果一个模块文件中有__all__变量,当我们使用from my_model import *导入时,只能导入这个列表中的元素

__all__ = ['test_a'] 

def test_a(): 
    print('test_a')

def test_b():
    print('test_b') 

8.3 包

如果我们有很多的模块文件,可能会造成管理混乱的情况,这时候就引入了的概念。

我们可以将包理解为一个文件夹,这个文件夹下必须有一个__init__.py文件,然后才是my_model.py这样的模块文件。

包的构造如下:

my_package
    __init__.py 
    my_model1.py
    my_model2.py 

导入方式:

import my_package.my_model1

from my_package import my_model1

from my_package.my_model1 import function

包中也有__all__变量,不过这个变量定义在__init__.py这个文件中,列表中列举的也应该是该包下的模块文件。

__all__ = ['my_model1']

这个变量限制的是from my_package import * 这个语句。

  • 42
    点赞
  • 49
    收藏
    觉得还不错? 一键收藏
  • 4
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值