Python_Note1
参考资料
-
黑马 Python 教程
-
Python 进阶手册
-
Python CookBook
基本语法
先跳过 Python 安装和环境配置的内容
-
Hello world
1print('Hello world') -
注释
1# 单行注释1 2 3 4 5 6 7 8""" 多行注释 第一行注释 第二行注释 第三行注释 """ # 或者使用 ''' 也可以vscode 选中代码后,可以使用快捷键
Ctrl + /进行注释
缩进的重要性
Python 不同于其他语言使用大括号 {},而是通过缩进决定代码的逻辑结构
代码编辑器设置好 tab 键为 2/4 个空格后,可以使用 Tab 键进行缩进
(Tab 是制表符\t,可以用于对齐文本)
命名规范
| 规范 | 示例 | |
|---|---|---|
| 常量名 | 全大写 | DEFAULT_NAME |
| 变量名、函数名 | 小写加下划线 | user_name、get_user_msg() |
| 类名 | 大驼峰 | class UserAccount(object): |
| 文件名、模块名 | 小写加下划线,避免空格和特殊字符 | data_process.py |
| 库名 | 全小写,简短且不加下划线 | mypackage(可以回忆一下 numpy 或者 pandas 这类库的命名) |
名称都应该能展示出用途或者含义,见名知意,避免无意义的单字母
Python 中避免使用驼峰式命名法,例如 userName
常量通常定义在代码的前面部分,这样比较清晰且易维护,尽量不要穿插在中间
Python 中,约定了变量名前面加单下划线表示私有变量,在类中或者模块中会使用到,例如 _user_age
(双下划线。。。)
自定义的模块名不要使用 Python 的关键字或者内置模块名,例如 os.py、import.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} |
类型转换
|
|
|
|
|
|
可变与不可变类型变量
-
可变(mutable)类型
列表
字典
集合 -
不可变(immutable)类型
整型
浮点型
字符串
元组
|
|
|
|
字符串
|
|
字符串切片
|
|
上述报错的代码中,下标 -4 到 -1 选取是从左到右,而步长取 -1,表示从右向左,所以选不出数据
选取方向与步长方向不一致就切不了片
字符串的其他操作
|
|
|
|
|
|
|
|
|
|
运算符
算术和比较运算符
加减乘除乘方开方等等
大于小于等于
矩阵乘法
|
|
逻辑运算
若 (lens & 1) == 0,意味着 lens 的二进制表示的最低位为 0,因此它对应的十进制数是偶数
这是一种利用位运算来 判断一个数是否为偶数 的常用方法
三目运算符
|
|
-
递推表达式的应用
1 2 3 4 5 6tmp = [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 会把接收到的任意用户输入的数据都当做 字符串 处理
|
|
print() 函数的用法。。。
流程控制
条件语句
-
猜拳游戏
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17import 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 4a = [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 8i = 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
|
|
注意
若 while 循环体中存在 break 或者 return,那么就不会执行 else 中的内容
for ... else ...也是同理
数据结构
|
|
|
|
|
|
|
|
列表
列表是可变类型,可以修改其内部的元素
列表的增删元素
-
新增元素
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 25namelist = ['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
列表的其他操作
|
|
对于列表的遍历,优先选择 for 循环,而不是 while 循环
for 循环基于迭代器协议(__iter__和__next__),是 Python 为遍历序列优化的原生方式。它会自动处理迭代边界,无需担心索引越界(如 IndexError)
while 循环若忘记更新索引或条件错误(如i <= len(lst)),可能导致无限循环或越界崩溃
-
嵌套列表
1 2 3 4 5 6 7name_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 11import 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 3list1 = ['a', 'b', 'c', 'd', 'e'] for index, value in enumerate(list1, start=1): print("%d, %s" % (index, value))
元组
元组是不可变类型,不能直接对元组的元素进行修改
但是若元组中存放了列表,则可以对该列表的内部元素进行修改
|
|
注意,单个数据且末尾不加逗号的,类型为整型
1 2t3 = (10) print(type(t3)) # <class 'int'>
字典
定义字典的方法
|
|
|
|
字典常用操作
-
添加和删除元素
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 2print(dict1.get('id', '001')) # 当字典 dict1 中不存在键 id 时,则会返回给定的默认值 '001' -
遍历字典
1 2 3 4 5for key in dict1.keys(): for value in dict1.values(): for k, v in dict1.items():
字典与 json 格式
|
|
集合
集合是无序的(意味着没有下标,不可切片)
数据插入到集合的顺序,和打印出来的顺序很可能是不一样的
集合会自动去重,如果存储一个已有的值到集合中,集合会自动忽略它
|
|
注意
集合本身是可变对象,但是里面存放的元素必须是不可变类型(如整数、字符串、元组)
因为可变对象无法计算稳定哈希值
集合本质是“只有键没有值的字典”,内部用哈希表存储键的哈希值
集合查询元素是否存在的时间复杂度为 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 9s1 = {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 - s21 2a = {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
文件读取和写入
|
|
|
|
常用文件(夹)操作
-
os
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22import 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 24import 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}')
异常处理
|
|
若 try 中没有出现问题则 except 不会执行,而会执行 else 中的代码
即 except 和 else 二选一
此时若有 finally 语句,则系统在报错之前还会执行 finally 中的内容
一般 finally 中会时 释放文件句柄、内存空间 等操作
注意,若 try 的异常 没有通过 except 指出,系统 还是会报错 Traceback,且终止程序
使用 raise 关键字可以 显式地抛出异常,提供 更多的异常信息和上下文,帮助调试和定位问题
而不使用 raise 则会隐式地抛出异常
🔘 自定义异常
|
|
函数
函数是一个具有独立功能的代码块,可以在需要的位置进行调用,实现代码重用
-
内建函数
Python 提供了很多,比如print() -
自定义函数
1、def关键字开头
2、传入参数和自变量
3、return会将结果返回给调用该函数的地方,使得程序退出当前函数(层),并且后面的代码都不执行。return后面不加东西相当于返回None
|
|
函数的返回值
|
|
上述函数返回两个值 1 和 2
在 Python 中,当用逗号分隔多个返回值时,它们会自动打包成一个元组
所以这里实际上返回的是 (1, 2) 这个元组
|
|
函数的参数
|
|
上述函数有三个参数,其中 grade 给定了默认值 1
|
|
位置参数要求摆在关键字参数前面,否则会有以下的报错信息:
SyntaxError: non-default argument follows default argument.
关键字参数传入时是带有参数名的,所以前后顺序没有要求
可变参数
*args 和 **kwargs 被称为 匿名参数
*args 是 匿名位置参数,args 类型是 元组
**kwargs 是 匿名关键字参数,kwargs 类型是 字典
二者适用于 输入的参数个数不固定 的情况
|
|
-
两种写法
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16def 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 13from 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对象本身
|
|
在 Python 中,值是靠 引用 来传递的
在函数 定义 中,*表示 收集 任意多的位置参数
在函数 调用 语法中,*表示 展开 可迭代对象(例如列表、元组等)
函数递归
注意设置递归终止条件
|
|
匿名函数(内联函数)
可以用于实现一些简单的函数,或者简短的计算表达式
lambda 表达式典型的使用场景是 排序或数据 reduce
局限:只能指定单个表达式,不能包含其他操作,例如多个语句、条件表达式、迭代以及异常处理等等
|
|
|
|
-
指定匿名函数中变量的值
1 2x = 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 19import 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 3Test().__doc__ Test().__dict__ Test().__getattribute__('public') -
-
私有属性
私有属性一般只能 在类里面访问和修改,以双下划线开头
常常定义get_xx方法用来获取私有属性
定义set_xx方法用来修改私有属性值1 2 3 4 5 6 7 8class 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
当方法中 需要使用类对象(如访问私有类属性等)时,则考虑定义类方法
|
|
|
|
实例方法
与类方法类似,但是不使用 @classmethod 进行装饰
并且传入 self 作为第一个特殊参数
一般通过实例调用,不推荐通过类调用(尽管可行)
|
|
静态方法
使用装饰器 @staticmethod 将方法标识为静态方法
静态方法既不需要传递类对象也不需要传递实例对象(即不需要特殊参数 cls 和 self)
和类方法一样,也能够通过 类对象 和 实例对象 去调用
|
|
私有方法
私有方法名以双下划线开头,通过 _类名__方法名 访问
注意,直接使用 __方法名 是无法访问的
私有方法主要是为了 避免子类意外重写父类的方法,而不是为了严格的安全限制
双下划线是一种命名约定,Python 会进行名称改写(name mangling)
|
|
对比
-
函数和方法
1、本质上一样
函数和方法都是用于 完成特定功能的代码块2、调用方式不同
方法是通过 实例化后的对象 进行调用的,因此方法属于特定的实例对象,而不同的实例对象可以调用相同的方法
函数可以 直接被调用,不需要特定的对象3、参数的不同
在方法定义时,首个参数通常命名为 self,它指向调用该方法的实例对象本身。通过 self 参数,方法可以访问和操作实例对象的属性和方法
在函数定义时,不需要指定特定的参数,可以根据需要选择是否传入参数 -
class MyClass、class MyClass()和class MyClass(object)?写法 Python 3 结果 Python 2 结果 class MyClass新式类(隐式继承 object)旧式类(不继承 object)class MyClass()同 class MyClass同 class MyClassclass 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,代码更简洁