Contents

Python_Note1

Beautiful landscape

Nature's Beauty

Explore the wonders of the world

参考资料

基本语法

先跳过 Python 安装和环境配置的内容

  • Hello world

    1
    
    print('Hello world')
    
  • 注释

    1
    
    # 单行注释
    
    1
    2
    3
    4
    5
    6
    7
    8
    
    """
    多行注释
    第一行注释
    第二行注释
    第三行注释
    """
    
    # 或者使用 ''' 也可以
    

    vscode 选中代码后,可以使用快捷键 Ctrl + / 进行注释

缩进的重要性

Python 不同于其他语言使用大括号 {},而是通过缩进决定代码的逻辑结构
代码编辑器设置好 tab 键为 2/4 个空格后,可以使用 Tab 键进行缩进
(Tab 是制表符\t,可以用于对齐文本)

命名规范

规范 示例
常量名 全大写 DEFAULT_NAME
变量名、函数名 小写加下划线 user_nameget_user_msg()
类名 大驼峰 class UserAccount(object):
文件名、模块名 小写加下划线,避免空格和特殊字符 data_process.py
库名 全小写,简短且不加下划线 mypackage(可以回忆一下 numpy 或者 pandas 这类库的命名)

名称都应该能展示出用途或者含义,见名知意,避免无意义的单字母
Python 中避免使用驼峰式命名法,例如 userName

常量通常定义在代码的前面部分,这样比较清晰且易维护,尽量不要穿插在中间

Python 中,约定了变量名前面加单下划线表示私有变量,在类中或者模块中会使用到,例如 _user_age
(双下划线。。。)

自定义的模块名不要使用 Python 的关键字或者内置模块名,例如 os.pyimport.py

数据类型概览

常见数据类型

数据类型 示例
int 整型 a = 1
float 浮点型 b = 1.1
bool 布尔型 c = True
str 字符串 d = '12345'
list 列表 e = [10, 20, 30]
tuple 元组 f = (10, 20, 30)
set 集合 h = {10, 20, 30}
dict 字典 j = {"name": "TOM", "age": 18}

类型转换

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
num1 = 1
str1 = '10'


# 整型 ---> 浮点型
print(float(num1))  # 1.0
# 字符串类型 ---> 浮点型
print(float(str1))  # 10.0
# 整型 ---> 字符串类型
print(str(num1))  # '1'
1
2
3
4
5
6
7
8
list1 = [10, 20, 30]
t1 = (100, 200, 300)


# 序列 ---> 元组
print(tuple(list1))  # (10, 20, 30)
# 序列 ---> 列表
print(list(t1))  # [100, 200, 300]
1
2
3
4
5
6
7
8
9
# eval() 将字符串中的数据转换成 Python 表达式原本类型
str2 = '1'
str3 = '1.1'
str4 = '(1000, 2000, 3000)'
str5 = '[1000, 2000, 3000]'
print(eval(str2))  # 1
print(eval(str3))  # 1.1
print(eval(str4))  # (1000, 2000, 3000)
print(eval(str5))  # [1000, 2000, 3000]

可变与不可变类型变量

  • 可变(mutable)类型
    列表
    字典
    集合

  • 不可变(immutable)类型
    整型
    浮点型
    字符串
    元组

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
# 对于不可变类型的变量来说,变量只是指向了值所在的地址
a = 1
b = a
print(b)
print(id(a))
print(id(b))    # a 和 b 同时指向了值 1 的地址,所以 id 值相同


a = 2
print(b)  # 这里的 b 没有随 a 改变,它还是指向原来 1 的地址
print(id(a))
print(id(b))
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
# 可变类型的变量
aa = [10, 20]
bb = aa
print(bb)
print(id(aa))
print(id(bb))


aa.append(30)
print(bb)    #这里的 bb 会跟着 aa 改变,因为 aa 是在原地址上进行修改的
print(id(aa))
print(id(bb))

字符串

1
2
3
4
5
6
7
8
9
# 三对双引号定义的字符串支持回车换行
d = """I
am tom"""
print(d)


# 注意转义字符 \
f = 'I\'m tom'
print(f)

字符串切片

1
2
3
4
5
6
tmp = 'abcdefg'


print(tmp[:-2])    # 'abcde'
# print(tmp[-4:-1:-1])    # 报错
print(tmp[-1:-4:-1])    # 'gfe'

上述报错的代码中,下标 -4-1 选取是从左到右,而步长取 -1,表示从右向左,所以选不出数据

选取方向与步长方向不一致就切不了片

字符串的其他操作

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
mystr = "hello world and cess and success and Python"


# find()
print(mystr.find('and'))  # 12,即第一次出现 and 的下标
print(mystr.find('and', 15, 30))  # 21,限制寻找范围
print(mystr.find('ands'))  # 没找到,但是返回 -1


# index()
print(mystr.index('and'))  # 12
print(mystr.index('and', 15, 30))  # 21
# print(mystr.index('ands', 15, 30)) # 效果基本同 find 函数,但是没找到就会直接报错


# count()
print(mystr.count('and'))  # 3,寻找 and 的出现次数
print(mystr.count('and', 15, 30))  # 1,限制寻找范围
print(mystr.count('ands'))  # 0

# 注意:
# rfind 和 rindex 同理,只是从右往左查询
# 除了字符串,列表也可以进行 index 和 count
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
new_str = mystr.replace('and', 'he', 10)    #只将前 10 个 and 替换成 he
# 【字符串是不可变数据类型,所以只能重新赋值给新的变量】


list2 = mystr.split('and', 2)    #只分割前 2 个 and


print(mystr.capitalize())    # 首字母大写,其他都改小写
print(mystr.title())    # 每个单词的首字母均大写
print(mystr.lower())    # 全部小写
print(mystr.upper())    # 全部大写
print(mystr.lstrip())    # 删除左侧空白
print(mystr.rstrip())    # 删除右侧空白
print(mystr.strip())    # 删除两侧空白


# 字符串根据某个规则进行转换
intab = "aeiou"
outtab = "12345"
trantab = str.maketrans(intab, outtab)
mystr.translate(trantab)


.startswith()    # 字符串以什么开头
.endswith()    # 字符串以什么结尾
.isalpha()    #是否全为字母
.isdigit()    #是否全为数字
.isalnum()    #是否是字母或数字
.isspace()    #是否全为空格
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
mystr = 'hello'


# ljust() 左对齐
print(mystr.ljust(10, '*'))  # hello*****

# rjust() 右对齐
print(mystr.rjust(10, '*'))  # *****hello

# center() 居中
# 若左右居中不了,则左侧会短于右侧
print(mystr.center(20, '*'))  # **hello***

# 返回指定长度的字符串,原字符串右对齐,前面填充 0
mystr.zfill(10)
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
a = ['172', '168', '1', '1']
b = ['2022', '01', '01']
c = 'Hello'
d = 'World'
e = [1, 2, 3]
f = [4, 5, 6]


print('.'.join(a))    # 172.168.1.1
print('/'.join(b))    # 2022/01/01
print(c + d)    # HelloWorld
print(e + f)    # [1, 2, 3, 4, 5, 6]

# 注意:append 和 extend 返回 None,不是列表本身
e_copy = e.copy()
e_copy.append(f)
print(e_copy)    # [1, 2, 3, [4, 5, 6]]

e_copy2 = e.copy()
e_copy2.extend(f)
print(e_copy2)    # [1, 2, 3, 4, 5, 6]
1
2
3
4
5
6
7
8
import string


string.punctuation    #所有的标点字符
string.whitespace    #所有的空白字符
string.ascii_lowercase    #所有的小写字符
string.hexdigits    #所有的十六进制字符
string.printable    #给出所有的标点符号,数字,ascii_letters和空格

运算符

算术和比较运算符
加减乘除乘方开方等等
大于小于等于

矩阵乘法

1
2
3
4
5
6
7
8
import numpy as np

# 矩阵点乘,即对应位置元素相乘
a = np.array([[1, 2, 3], [4, 5, 6], [7, 8, 9]])
b = np.array([[1, 2, 3]])

result = a * b
print(result)

逻辑运算

(lens & 1) == 0,意味着 lens 的二进制表示的最低位为 0,因此它对应的十进制数是偶数
这是一种利用位运算来 判断一个数是否为偶数 的常用方法

三目运算符

1
2
3
4
5
6
7
a = 1
b = 2


# 如果 a 大于 b,则将 a 赋值给 c;否则将 b 赋值给 c
c = a if a > b else b
print(c)
  • 递推表达式的应用

    1
    2
    3
    4
    5
    6
    
    tmp = [66,9,31,2,3,34,13,4,78,8,55]
    
    
    # result = [1 for i in tmp if i>10]
    result = [1 if i>10 else 0 for i in tmp]
    print(result)
    

    此外还支持多重 for 循环的递推表达式
    不只列表支持这个表达式,字典、集合也可以类似地这样写

输入输出

input 会阻塞以等待用户输入,输入完成之后程序才继续向下执行
在 Python 中,input 会把接收到的任意用户输入的数据都当做 字符串 处理

1
2
3
password = input('请输出您的密码:')
print(f'您输入的密码是{password}')
print(type(password))    # str

print() 函数的用法。。。

流程控制

条件语句

  • 猜拳游戏

     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    14
    15
    16
    17
    
    import random
    
    
    # 玩家出拳
    player = int(input('请出拳:0-石头,1-剪刀,2-布'))
    # 电脑出拳
    computer = random.randint(0, 2)
    print(f'电脑出{computer}')
    
    
    # 判断输赢
    if (player == 0 and computer == 1) or (player == 1 and computer == 2) or (player == 2 and computer == 0):
        print('玩家获胜')
    elif player == computer:
        print('平局')
    else:
        print('电脑获胜')
    

条件语句还可以嵌套

注意

1
2
3
4
a = [1,2,3,4,5,6,7,11]
if a.pop() < 10:    #这个 if 条件一判断,pop 就会执行
    print('hello')
print(a)  # [1,2,3,4,5,6,7]

循环语句

while

  • 循环打印九九乘法表

    1
    2
    3
    4
    5
    6
    7
    8
    
    i = 1
    while i <= 9:
        j = 1
        while j <= i:
            print('%d*%d=%d' % (j, i, i * j), end='\t')
            j += 1
        print()
        i += 1
    

在循环语句中,还会用到 break 和 continue
break 跳出整个循环
continue 只忽略本次

while、else

1
2
3
4
while 条件:
    条件成立时需要重复执行的代码
else:
    循环正常结束之后要执行的代码

注意
若 while 循环体中存在 break 或者 return,那么就不会执行 else 中的内容
for ... else ... 也是同理

数据结构

1
2
3
# list 列表
e = [10, 20, 30]
print(type(e))
1
2
3
# tuple 元组
f = (10, 20, 30)
print(type(f))
1
2
3
# set 集合
h = {10, 20, 30}
print(type(h))
1
2
3
# dict 字典
j = {"name": "TOM", "age": 18}
print(type(j))

列表

列表是可变类型,可以修改其内部的元素

列表的增删元素

  • 新增元素

     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    
    # append 添加元素
    name_list = ['tom', 'lily']
    name_list.append('big')
    print(name_list)    # ['tom', 'lily', 'big']
    
    
    # extend 追加元素
    name_list = ['tom', 'lily']
    # name_list.extend('big')    # 与预想不符,'big' 会被分成一个一个字符导入
    # 'big' 应该放在列表中传入
    name_list.extend(['big'])
    print(name_list)    # ['tom', 'lily', 'big']
    
    
    # insert 指定下标位置插入元素
    name_list = ['tom', 'lily']
    name_list.insert(1, 'big')
    print(name_list)    # ['tom', 'big', 'lily']
    
  • 删除元素

     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    
    namelist = ['tom', 'lily', 'big']
    del namelist[2]
    print(namelist)
    
    
    namelist = ['tom', 'lily', 'big']
    namelist.pop(-1)    # 若不指定下标,则默认弹出最后一个元素
    print(namelist)
    
    
    namelist = ['tom', 'lily', 'big']
    namelist.remove('big')    # 删除指定元素
    print(namelist)
    
    
    # 清空链表
    namelist = ['tom', 'lily', 'big']
    namelist.clear()
    print(namelist)
    
    
    # 删除链表
    namelist = ['tom', 'lily', 'big']
    del namelist
    # print(namelist)    # name 'namelist' is not defined
    

列表的其他操作

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
# 判断某元素是否在列表内
'hello' in ['hello', 'world', '!!!']


# 列表逆序
.reverse()


# 原地排序
.sort(key= , reverse=)

对于列表的遍历,优先选择 for 循环,而不是 while 循环
for 循环基于迭代器协议(__iter____next__),是 Python 为遍历序列优化的原生方式。它会自动处理迭代边界,无需担心索引越界(如 IndexError)
while 循环若忘记更新索引或条件错误(如 i <= len(lst)),可能导致无限循环或越界崩溃

  • 嵌套列表

    1
    2
    3
    4
    5
    6
    7
    
    name_list = [['小明', '小红', '小绿'],
                ['Tom', 'Lily', 'Rose'],
                ['张三', '李四', '王五']]
    
    
    print(name_list[0])    # ['小明', '小红', '小绿']
    print(name_list[1][1])    # Lily
    
  • numpy 数组切片

     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    
    import numpy as np
    a = np.arange(9).reshape(3,3)
    
    
    a1 = a[:2, 1:]
    
    a2 = a[2]    # 取出第 3 行
    
    a3 = a[2, :]    # 等价于 a2
    
    a3 = a[[2], :]    # 等价于 a2
    
  • enumerate 函数
    遍历的时候为元素添加下标
    start 控制下标开始的位置

    1
    2
    3
    
    list1 = ['a', 'b', 'c', 'd', 'e']
    for index, value in enumerate(list1, start=1):
        print("%d, %s" % (index, value))
    

元组

元组是不可变类型,不能直接对元组的元素进行修改
但是若元组中存放了列表,则可以对该列表的内部元素进行修改

1
2
3
4
5
6
7
8
# 多个数据的元组
t1 = (10, 20, 30)
print(type(t1))  # <class 'tuple'>


# 单个数据的元组,注意逗号
t2 = (10,)
print(type(t2))  # <class 'tuple'>

注意,单个数据且末尾不加逗号的,类型为整型

1
2
t3 = (10)
print(type(t3))  # <class 'int'>

字典

定义字典的方法

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
# 1、花括号中存放键值对
mydict1 = {
    'name': 'Tom',
    'interests': 'observe things',
}

# 2、将两个序列 zip 后,利用关键词 dict 转换成字典
a = ['name', 'interests']
b = ['Tom', 'observe things']
mydict2 = dict(zip(a, b))
1
2
3
4
5
6
7
8
9
# 以下几种情况都是可行的
a = [['hello', 3], ['world', 5]]
b = [['hello', 3], ('world', 5)]
c = (['hello', 3], ('world', 5))
d = (('hello', 3), ('world', 5))
print(dict(a))
print(dict(b))
print(dict(c))
print(dict(d))

字典常用操作

  • 添加和删除元素

    1
    2
    3
    4
    5
    6
    7
    8
    9
    
    # 字典新增元素
    dict1 = {'name': 'tom', 'age': 20, 'gender': '男'}
    dict1['id'] = 110
    print(dict1)
    
    
    # 删除指定的键值对
    del dict1['age']
    print(dict1)
    

    同样可以像列表一样使用 clear 方法

  • 获取字典元素
    一般情况下直接通过中括号访问,例如 dict1['name']
    但是若字典中找不到对应的 key 就会报错
    而使用 get 方法在找不到 key 的时候会返回给定的默认值

    1
    2
    
    print(dict1.get('id', '001'))
    # 当字典 dict1 中不存在键 id 时,则会返回给定的默认值 '001'
    
  • 遍历字典

    1
    2
    3
    4
    5
    
    for key in dict1.keys():
    
    for value in dict1.values():
    
    for k, v in dict1.items():
    

字典与 json 格式

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
import json
# loads 方法可以当作是
# JSON 格式的字符串类型 ---> 字典
# 注意里面的键值用的是【双引号】
json_str = '{"name": "Mike", "age": "30"}'
dict_data = json.loads(json_str)


# dumps 方法可以当作是上述的逆过程
dict_data = {"name": "Mike", "age": "30"}
json_str = json.dumps(dict_data)

集合

集合是无序的(意味着没有下标,不可切片)
数据插入到集合的顺序,和打印出来的顺序很可能是不一样的

集合会自动去重,如果存储一个已有的值到集合中,集合会自动忽略它

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

注意
集合本身是可变对象,但是里面存放的元素必须是不可变类型(如整数、字符串、元组)
因为可变对象无法计算稳定哈希值

集合本质是“只有键没有值的字典”,内部用哈希表存储键的哈希值
集合查询元素是否存在的时间复杂度为 O(1)
集合和字典的键特性一致,也遵循不可变规则

  • 创建集合

    1
    2
    3
    4
    5
    6
    7
    8
    
    # 1、创建一个空集合
    s = set()
    
    
    # 2、用可迭代对象初始化
    s1 = set("hello")  # {'e', 'h', 'l', 'o'}
    s2 = {1, 2, 3}  # 直接字面量
    s3 = set([1, 2, 2])  # 从列表去重: {1, 2}
    

注意到上述第 2 种创建集合方法中,s1 的输出展示了集合的无序性
实际上每次运行的输出顺序可能不同,但元素组成不变

集合常用操作

  • 添加元素

    1
    2
    3
    4
    5
    6
    7
    8
    9
    
    s1 = {10, 20}
    s1.add(100)
    print(s1)    # {10, 20, 100}
    
    
    # s1.add([11,22,33])    # 报错
    # 若需要往集合中一次性追加多个元素,则使用 update
    s1.update([11,22,33,])
    print(s1)    # {10, 20, 100, 11, 22, 33}
    
  • 删除元素

    1
    2
    3
    4
    5
    6
    7
    
    # 不存在对应的元素则报错
    s1.remove(4)
    
    # 即使没有对应的元素也不会报错
    s1.discard(33)    # {10, 20, 100, 11, 22}
    
    # 还有 pop 方法
    
  • 集合运算(并/交/差)
    s1 | s2, s1 & s2, s1 - s2

    1
    2
    
    a = {1, 2}; b = {2, 3}
    print(a - b)# 差集: {1}
    

同样可以使用 in 或者 not in 判断数据是否在集合中

字符串、列表、元组都支持 加号合并,也都支持 乘号翻倍
字符串、列表、元组、集合、字典都支持通过 len() 求得长度
列表、元组、集合可以通过关键字相互转换 list() tuple() set()

文件操作

文件的访问模式 以二进制格式打开 可读可写
只读 r rb r+ rb+ 不会新建 文件,没有对应文件就报错
写入 w wb w+ wb+ 会自动新建文件,但是会 先清空 再写入
追加 a ab a+ ab+ 会自动新建文件,末尾追加写入

凡是有 b 就是 以二进制格式打开
凡是有 + 就变为 可读可写
若不指定的话,默认访问模式为 r

文件读取和写入

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
with open('文件名', 'r') as f:
    f.read(10)
    # 10 表示读取【10 字节】的数据,若不指定,则读取全部数据

    f.readline()
    # 只读取【一行】数据

    f.readlines()
    # readlines 按照行的方式把整个文件中的内容进行一次性读取,并返回一个【列表】

    f.tell()
    # 返回当前指针位置,还能控制指针的位置
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
# 二进制格式读取旧文件
old_file = open('文件名', 'rb')

# 二进制格式覆盖写入新文件
new_file = open('文件名', 'wb')

while True:
    # 每次从旧文件中读取 1024 字节,即 1KB 的数据
    content = old_file.read(1024)

    # 如果旧文件的内容读取完毕就跳出循环
    if len(content) == 0:
        break

    # 将旧文件数据写入新文件中
    new_file.write(content)

# 手动关闭文件
old_file.close()
new_file.close()

常用文件(夹)操作

  • os

     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    
    import os
    
    os.rename(目标文件名, 新文件名)
    # 文件重命名
    
    os.remove(目标文件名)
    # 删除文件
    
    os.mkdir(文件夹名字)
    # 创建文件夹,如果文件夹已存在,则会报错
    
    os.rmdir(文件夹名字)
    # 删除文件夹,若目录不为空,则无法删除
    
    os.getcwd()
    # 获取当前目录
    
    os.chdir(目录)
    # 切换目录
    
    os.listdir(目录)
    # 获取目录中的文件,返回文件名的列表
    
  • pathlib

     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    
    import os
    import shutil
    import pathlib
    
    
    def create_dir(dirname):
        """
        创建文件夹
        """
        if dirname not in os.listdir():
            cache_path = os.path.join(os.getcwd(), dirname)
            # print(cache_path)
            pathlib.Path(cache_path).mkdir(parents=True, exist_ok=True)
            print(f'文件夹 {dirname} 创建成功(或已存在)')
    
    def remove_dir_files(dirname):
        """
        删除文件夹
        """
        if dirname in os.listdir():
            shutil.rmtree(dirname)
            print(f'文件夹 {dirname} 删除成功')
        else:
            print(f'未找到文件夹 {dirname}')
    

异常处理

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
# 异常传递
try:
    。。。
    try:
        。。。
    except:
        。。。
    else:
        。。。
    finally:
        。。。
except:
    。。。

若 try 中没有出现问题则 except 不会执行,而会执行 else 中的代码
exceptelse 二选一 此时若有 finally 语句,则系统在报错之前还会执行 finally 中的内容
一般 finally 中会时 释放文件句柄、内存空间 等操作

注意,若 try 的异常 没有通过 except 指出,系统 还是会报错 Traceback,且终止程序

使用 raise 关键字可以 显式地抛出异常,提供 更多的异常信息和上下文,帮助调试和定位问题
而不使用 raise 则会隐式地抛出异常

🔘 自定义异常

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
# 通过继承 Exception 来自定义
class myError(Exception):
    def __init__(self, ):
        pass

    def __str__(self):    # 抛出异常时打印的内容
        return 。。。

try:
    。。。
    if 。。。:
        # 抛出自定义异常,raise 异常类对象
        raise myError()
except Exception as e:
    print(e)
else:
    。。。

函数

函数是一个具有独立功能的代码块,可以在需要的位置进行调用,实现代码重用

  • 内建函数
    Python 提供了很多,比如 print()

  • 自定义函数
    1、def 关键字开头
    2、传入参数和自变量
    3、return 会将结果返回给调用该函数的地方,使得程序退出当前函数(层),并且后面的代码都不执行。return 后面不加东西相当于返回 None

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
def myfunc(a, b):
    """
    函数说明
    :param a: 参数1
    :param b: 参数2
    :return: 返回值
    """
    pass


help(myfunc)

函数的返回值

1
2
def return_num2():
    return 1, 2

上述函数返回两个值 12
在 Python 中,当用逗号分隔多个返回值时,它们会自动打包成一个元组
所以这里实际上返回的是 (1, 2) 这个元组

1
2
3
4
5
6
7
# 直接接收返回值
result = return_num2()
print(result)  # (1, 2)


# 使用解包(unpacking)方式接收返回值
result1, result2 = return_num2()  # 1、2分别被赋值给两个变量

函数的参数

1
2
def user_info(name, age, grade=1):
    print(f'您的名字是{name}, 年龄{age}, 年级{grade}')

上述函数有三个参数,其中 grade 给定了默认值 1

1
2
3
4
5
6
7
8
9
# 全部通过【位置参数】的方法进行调用
user_info('amy', 18, 2)

# 全部通过【关键字参数】的方法进行调用
user_info(name='tom', age=19, grade=3)

#【混用】,注意位置参数需要在关键字参数前面
user_info('rola', grade=3, age=18)
# user_info(name='bob', 18, grade=18)  # 报错

位置参数要求摆在关键字参数前面,否则会有以下的报错信息:
SyntaxError: non-default argument follows default argument.
关键字参数传入时是带有参数名的,所以前后顺序没有要求

可变参数

*args**kwargs 被称为 匿名参数
*args匿名位置参数args 类型是 元组
**kwargs匿名关键字参数kwargs 类型是 字典
二者适用于 输入的参数个数不固定 的情况

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
def user_info(*args, **kwargs):
    # **args 收集所有位置参数,并作为一个元组
    # **kwargs 收集关键字参数,并作为一个字典
    print(args)
    print(kwargs)
    # 都是⼀个组包的过程


user_info('TOM', 18, id='001', grade=3)
# ('TOM', 18)
# {'id': '001', 'grade': 3}
  • 两种写法

     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    14
    15
    16
    
    def myfunc(*arg, **kwargs):
        callback = kwargs.pop('callback')
        print('arg:', arg)
        callback()
    
    def mycallback():
        print('callback called')
    
    kwargs = {'callback': mycallback}
    
    
    # 调用
    myfunc(1, 2, 3, **kwargs)    # 收集 1,2,3 作为元组并赋值给 arg
    # 运行结果
    arg: (1, 2, 3)
    callback called
    

    等价写法:

     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    
    # 通过星号隔开位置参数和关键字参数
    def myfunc(arg, *, callback):
        print('arg:', arg)
        callback()
    
    def mycallback():
        print('callback called')
    
    # 调用
    myfunc((1, 2, 3), callback=mycallback)    # 手动传入元组
    # 运行结果
    arg: (1, 2, 3)
    callback called
    
  • partial() 方法
    在调用函数的时候,用于给参数设置固定的值

     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    
    from functools import partial
    
    
    def test(a, b, c, d):
        print(a, b, c, d)
    
    # 一般调用函数
    s1 = test(1, 2, 3, 4)
    
    # 利用 partial() 固定参数
    s2 = partial(test, a=1)
    s2(2, 3, 4)
    # 参数 a 被固定为 1,故调用的时候只需要传入参数 b、c、d
    

    用途:有时可以让原本不兼容的代码一起工作

注意事项

Python 函数默认参数的值,应该是 不可变对象,比如 None、True、False、数字或字符串
尽量不要将 可变类型 的变量作为参数传入
因为在函数定义时,默认参数的值会保存在函数的定义中,并在每次 函数调用时重用
如果使用可变对象(如列表)作为默认值,可能会导致 默认值在函数调用之间意外地被修改,导致程序出错

函数默认参数的值最好不要用空列表 [ ],如需要则先设默认值为 None,之后在函数中再通过 if 条件语句判断赋以 [ ]

  • 可变类型传参
    例如 fun(list_a)
    在函数内部将 list_a 修改后,函数外部的 list_a 也会受影响

  • 不可变类型传参
    例如 fun(a)
    传入函数中的只是 a 的值
    在函数内部修改 a 的值,只是修改另一个复制的对象,不会影响函数外部 a 对象本身

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
def test(a):
    print(a, id(a))

    a += a
    print(a, id(a))


c = [11, 22]
test(c)
print(c, id(c))  # 这里 c 的值发生了改变

在 Python 中,值是靠 引用 来传递的
在函数 定义 中,* 表示 收集 任意多的位置参数
在函数 调用 语法中,* 表示 展开 可迭代对象(例如列表、元组等)

函数递归

注意设置递归终止条件

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
# 计算 1 到 n 的数字累加和
def sum_numbers(n):
    if n == 1:
        return 1
    else:
        return n + sum_numbers(n - 1)


sum = sum_numbers(10)
print(sum)

匿名函数(内联函数)

可以用于实现一些简单的函数,或者简短的计算表达式
lambda 表达式典型的使用场景是 排序或数据 reduce

局限:只能指定单个表达式,不能包含其他操作,例如多个语句、条件表达式、迭代以及异常处理等等

1
2
3
4
5
fn2 = lambda: 200
print(fn2)
print(fn2())

print((lambda a, b:a + b)(1, 2))
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
# 对英文名进行字典排序
names = [
    'David Beazley',
    'Brian Jones',
    'Raymond Hettinger',
    'Ned Batchelder'
]
sorted(
    names,
    key=lambda name: name.split(' ')[0].lower()
)
  • 指定匿名函数中变量的值

    1
    2
    
    x = 2
    b = lambda y, x=x: x+y
    
  • 递推表达式结合匿名函数

     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    
    import random
    a = [[100,200], [300,600], [10,20]]
    
    
    # 方法一
    b = [
        lambda params=values: random.randint(*params)
        for values in a
    ]
    # 匿名函数中指定了 params 为列表中 values 值
    
    # 方法二
    b = []
    for i in a:
        b.append( lambda x=i: random.randint(*x) )
    
    # b 列表中的元素都是匿名函数
    # 尝试调用第二个元素,生成一个 300~600 之间的随机整数
    b[1]()
    

  • __init__() 函数
    在启动类时会自动调用
    一般在 __init__() 函数中进行一些初始化操作,例如赋值等等

  • self
    在 Python 的类定义中,self 是一个特殊的参数,用于表示实例化后对象自身
    约定将 self 作为第一个参数传递给类方法或实例方法,虽然在 Python 中 self 这个名称并不是关键字,可以选择其他名称,但是为了 遵循约定和与其他 Python 开发者保持一致,一般而言习惯将该参数命名为 self
    相当于 C++ 里面的 self 指针,Java 里面的 this 关键字
    它们都指代当前对象实例本身,并使得对象能够访问自己的属性和方法

  • cls

类属性和实例属性

  • 类属性
    是每个实例的共有属性
    类属性为 全类所共有,仅占用一份内存
    要修改类属性,需要通过 类名.类属性 = ... 进行修改 类属性只能通过类对象修改,不能通过实例对象修改

  • 实例属性(对象属性)
    不同实例的实例属性可以是不同的值
    每个对象 会为实例属性 单独 开辟一份内存空间来记录数据
    属性相当于 class 中的变量,方法相当于函数

  • 常用的属性

    • __doc__,类的说明

    • __dict__,返回对象或者类的属性或者方法

    1
    2
    3
    
    Test().__doc__
    Test().__dict__
    Test().__getattribute__('public')
    
  • 私有属性
    私有属性一般只能 在类里面访问和修改,以双下划线开头
    常常定义 get_xx 方法用来获取私有属性
    定义 set_xx 方法用来修改私有属性值

    1
    2
    3
    4
    5
    6
    7
    8
    
    class myc(object):
        def __init__(self, name, age):
            self.name = name
            self.__age = age    # 私有属性
        def get_age(self):
            return self.__age    # 获取私有属性
        def set_age(self):
            self.__age = ...
    

类方法

使用装饰器 @classmethod 将方法标识为类方法
对于类方法,第一个参数必须是类对象,一般命名为 cls
当方法中 需要使用类对象(如访问私有类属性等)时,则考虑定义类方法

1
2
3
4
5
6
7
class Student(object):
    __passing_mark = 60  # 私有类属性

    @classmethod
    def get_line(cls):
        return cls.__passing_mark
    # 类方法一般和类属性配合使用
1
2
3
4
5
6
7
8
# 通过类直接调用
Student.get_line()

# 通过实例调用
a = Student()
a.get_line()
# Python 的解释器会注意到你调用的是一个 @classmethod
# 自动忽略实例本身,仍然将类作为第一个参数 (cls) 传入

实例方法

与类方法类似,但是不使用 @classmethod 进行装饰
并且传入 self 作为第一个特殊参数
一般通过实例调用,不推荐通过类调用(尽管可行)

1
2
3
# 通过类调用,需要手动传入一个实例
s1 = Student()
Student.instance_method(s1)

静态方法

使用装饰器 @staticmethod 将方法标识为静态方法
静态方法既不需要传递类对象也不需要传递实例对象(即不需要特殊参数 cls 和 self
和类方法一样,也能够通过 类对象实例对象 去调用

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
class Student(object):
    @staticmethod
    def info_print():
        print('这是一个Student类,用于创建Student实例....')


s1 = Student()
# 静态方法能够通过类对象、实例对象访问
Student.info_print()
s1.info_print()

私有方法

私有方法名以双下划线开头,通过 _类名__方法名 访问
注意,直接使用 __方法名 是无法访问的

私有方法主要是为了 避免子类意外重写父类的方法,而不是为了严格的安全限制

双下划线是一种命名约定,Python 会进行名称改写(name mangling)

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
class myc(object):
    def __init__(self, length, width):
        self.length = length
        self.width = width
    def __cal_area(self):    # 定义私有方法
        return self.length * self.width


tmp = myc(length=2, width=3)

tmp.__cal_area()
# 报错,在类的内部可以通过 self.__cal_area() 来调用私有方法,但是在类的外部这样是不行的

tmp._myc__cal_area()
# 成功调用,但是注意这样做一定程度会破坏类的封装性

对比

  • 函数和方法
    1、本质上一样
    函数和方法都是用于 完成特定功能的代码块

    2、调用方式不同
    方法是通过 实例化后的对象 进行调用的,因此方法属于特定的实例对象,而不同的实例对象可以调用相同的方法
    函数可以 直接被调用,不需要特定的对象

    3、参数的不同
    在方法定义时,首个参数通常命名为 self,它指向调用该方法的实例对象本身。通过 self 参数,方法可以访问和操作实例对象的属性和方法
    在函数定义时,不需要指定特定的参数,可以根据需要选择是否传入参数

  • class MyClassclass MyClass()class MyClass(object) ?

    写法 Python 3 结果 Python 2 结果
    class MyClass 新式类(隐式继承 object 旧式类(不继承 object
    class MyClass() class MyClass class MyClass
    class MyClass(object) 新式类(显式继承 object 新式类(显式继承 object

    这三种写法在 Python 3 中基本是等价的,但在 Python 2 中有区别

    class MyClass 在 Python 3 中会隐式继承自 object(即使不写也会自动继承),等价于 class MyClass(object)
    而在 Python 2 中,这种写法会创建一个“旧式类”(old-style class),不继承自 object
    旧式类在方法解析、super() 等行为上与新式类不同,且功能受限 方法解析顺序是按照 深度优先搜索 的方式进行的

    class MyClass() 该写法在语法上是合法的,但括号内没有显式指定父类,故是冗余的

    class MyClass(object) 在 Python 3 中会显式继承 object
    在 Python 2 中这种写法会创建一个“新式类”(new-style class),继承自 object
    新式类支持 super()、属性描述符(@property)、__slots__ 等现代特性 方法解析顺序是按照 广度优先搜索 的方式进行的

    综上,如果要写兼容 Python 2 和 3 的代码,使用 class MyClass(object) 显式继承 object 是安全的
    否则 Python3 可以直接写 class MyClass,代码更简洁