Python 学习笔记

1 基本概念

可变 (mutable)

  • 不可变对象:一旦创建就不可修改的对象,包括字符串、元组、数值类型

(该对象所指向的内存中的值不能被改变。当改变某个变量时候,由于其所指的值不能被改变,相当于把原来的值复制一份后再改变,这会开辟一个新的地址,变量再指向这个新的地址。)

  • 可变对象:可以修改的对象,包括列表、字典、集合

(该对象所指向的内存中的值可以被改变。变量(准确的说是引用)改变后,实际上是其所指的值直接发生改变,并没有发生复制行为,也没有开辟新的地址,通俗点说就是原地改变。)

可哈希 (hashable)

需要实现 __hash__() 方法

哈希表

约定:

  1. 一致性(consistent),在程序的一次执行过程中,对同一个对象必须一致地返回同一个整数。
  2. 如果两个对象通过 equals (Object)比较,结果相等,那么对这两个对象分别调用 hashCode 方法应该产生相同的整数结果。
  3. 如果两个对象通过 java.lang.Object.equals (java. lang. Ojbect)比较,结果不相等,不必保证对这两个对象分别调用 hashCode 也返回两个不相同的整数。

对于不可变类型而言,相同的值存储在相同的内存,如果将我们的不可变对象理解成哈希表中的 Key,将内存理解为经过哈希运算的哈希值 Value,这正好满足了哈希表的性质。

可哈希: 即不可变的数据结构 (数字类型(int,float,bool)字符串 str、元组 tuple、自定义类的对象)。
不可哈希: 即可变的数据结构 (字典 dict,列表 list,集合 set)

可迭代 (iterable)

在 Python 中,可迭代(iterable)是指可以被遍历的对象,可以通过 for 循环或者其他迭代方式逐个访问其元素。常见的可迭代对象包括字符串、列表、元组、字典等,以及一些 Python 模块中提供的生成器、迭代器对象。

在Python中,通过实现一个 __iter__() 方法,即可实现自定义的可迭代类型,如果对象同时实现了 __getitem__() 方法,那么它也可以被视为可迭代的,因为在 Python 中,迭代器和序列类型之间有着很强的关联性。

2 结构类型与程序控制结构

. 调用的是方法
不用 . 调用是内置函数

基本数据类型

数值

布尔

and 优先级比 or 高 会先运算
复数 a+bj
type ()只能输出一个变量的类型
多个变量同时赋值 a, b, c = 1, 0.5, 1+1j

字符串

方框调用
split (" ")依据空格分片为列表
title () 首字母大写其余小写

str1 = "i love you beijing"
str1.split(" ")
# ['i', 'love', 'you', 'beijing']

strip (‘abc’)删除字符串两端的 abc 默认空格
字符串前缀

  • b 后面字符串是 bytes 类型 网络编程中, 只认 bytes
  • u/U 表示 unicode 字符串
  • r/R 非转义的原始 (raw) 字符串, 去掉反斜杠的转义机制
  • f 格式化 (format) 字符串

格式化输出

  • %s 格式化 :和 c 语言类似
print ("我叫 %s 今年 %d 岁!" % ('小明', 10))
# 我叫 小明 今年 10 岁!
  • format 格式化
    {}内填
"{},{}".format("hello","world")
"{1},{0}".format("hello","world")
"{0},{1},{0},{0}".format("hello","world")
print("{:.2f}".format(3.1415926))
  • f-string 格式化
  • f-string 格式化字符串以 f 开头,后面跟着字符串,字符串中的表达式用大括号 {} 包起来,它会将变量或表达式计算后的值替换进去
  • {} 相当于 c 语言的百分号
print(f'hello world {str1}')
# hello world i love you beijing

大括号内外不能用同一种引号,需根据情况灵活切换使用单引号、双引号、单三引号、双三引号。
大括号内不能使用 \ 转义
需要显示大括号,则应输入连续两个大括号 {{ }} ;大括号内需要引号,使用引号即可。

print(f'i am {"hua wei"}')
print(f'{{5}}{"apples"}')
填充
冒号后写数据格式
记忆方法:括号口朝左边,就表示左填充;括号口朝右边,就表示右填充
print(f'{1:0>4}')
# 输出4个字符,不足补0
# 0001

数字符号相关格式描述符:仅针对数值有效

  • + 正负数都输出符号
  • - 负数输出符号
  • 空格 正数加空格 负数加负号
  • width. precision
print(f'{1:+}')
#+1
print(f'{1: }\n{-1: }')
# 1
#-1
a = 123.456
print(f"{a:.2f}")
#123.46

列表

反向索引 -i 表示倒数第 i 个元素 例如-1 表示倒数第一个元素,但是 0 表示第一个元素,要注意。
list[0:5], 左闭右开,取前五个元素 l ist1[:: 2] 步长为2
list1[::-1]步长为-1,即为倒着输出
访问单个元素注意返回的不是列表.

testlist = [1,3,3,677]
print(type(testlist[0]),type(testlist[1:3]))
#<class 'int'> <class 'list'>

+ 连接列表, * 整体复制列表

a = [1, 2, 3]
b = [4, 5, 6]
a + b # 连接[1, 2, 3, 4, 5, 6]
a * 3 # 整体复制3次[1, 2, 3, 1, 2, 3, 1, 2, 3]

list () 将字符串等可迭代对象转化成列表

list_ = list("hello world")
print(list_)
# ['h', 'e', 'l', 'l', 'o', ' ', 'w', 'o', 'r', 'l', 'd']

添加列表元素

  • append (): 在列表尾部添加一个新元素
  • insert (): 在指定索引位置插入一个元素
  • extend (): 把一个列表整体添加到另一个列表的尾部
a.append(9)
a.insert(2,0) 
# 在2号位插入数字0
a.extend(b)

id (a)返回 a 的地址
删除列表元素

id(a)
# 2932207713216
  • pop (): 默认删除尾部一个元素,或者删除指定索引元素
  • remove (x): 删除第一个与 x 相同的元素
  • clear (): 清空列表所有元素
stlist.pop()
stlist.pop(3)
stlist.remove('a')
stlist.clear()

del 删除到变量到对象的引用和变量名称本身
注意 del 语句作用在变量上,而不是数据对象上

del stlist[:2]
# 删除前2个元素
  • count (x):x 出现的个数
  • index (x):x 首次出现的位置
  • in / not in :是否存在
slist.count(1)
slist.index(2)
if 5 not in slist:
    print("hello")
else:
    print("aloha")

列表元素排序

  • sort () :按字典顺序排序
    当元素之间的类型不能够互相转换的时候,Python 就会报错,例如整数和字符串类型
  • reverse () :倒置
fruits.sort()
print(fruits)
fruits.reverse()
print(fruits)
print(fruits[::-1])
# 也可以逆序
  • sort (key = None, reverse = false): key 是指定比较的元素,reverse 为 True 则逆序,默认顺序。
  • sorted (iterable, key=None, reverse=False) :全局内置函数 复制原始列表的一个副本,再排序。而 sort ()会改变数据。
fruits.sort(cmp = None, key = None, reverse = True)
sorted(fruits,reverse = True)

其他全局内置函数

  • cmp (), 不可用 operator 模块的函数替代之
  • len () :元素个数
  • max (), min () :最大最小值
  • list () :转换成列表
  • range (start, stop[, step])返回整数序列的对象,而不是列表
  • zip ():用于将可迭代的对象作为参数,将对象中对应的元素打包成一个个元组,然后返回对象,而不是列表。如果各个迭代器的元素个数不一致,则返回列表长度与最短的对象相同,如果只有一个可迭代对象, 那么 zip ()会找到空元素来代替.
    利用 * 号操作符,可以将可迭代对象解包。
    * 号一般用在函数内.
list(zip(*verts))
#先把verts解包, 然后四个元组 再用zip函数[(0.0, 0.2, 1.0, 0.8), (0.0, 1.0, 0.8, 0.0)]
  • enumerate (sequence, [start=0]):用于将一个可遍历的数据对象 (如列表、元组或字符串)组合为一个索引序列 (索引, 元素),同时列出数据和数据下标,一般用在 for 循环当中。
  • zip ()可以完成多个类型的缝合,而 enumerate 只能提供数值类型
import operator
operator.lt(1,2)
# 是否小于
list(zip(fruits,range(len(fruits))))

'''
[('apple', 0),
 ('banana', 1),
 ('orange', 2),
 ('watermelon', 3),
 ('apple', 4),
 ('blueberry', 5),
 ('strawberry', 6)]
'''
list(zip(*zip(fruits,range(len(fruits)))))
'''
[('apple',
  'banana',
  'orange',
  'watermelon',
  'apple',
  'blueberry',
  'strawberry'),
 (0, 1, 2, 3, 4, 5, 6)]
'''
list(enumerate(fruits,start = 1) )
'''
[(1, 'apple'),
 (2, 'banana'),
 (3, 'orange'),
 (4, 'watermelon'),
 (5, 'apple'),
 (6, 'blueberry'),
 (7, 'strawberry')]
'''
for i,ele in enumerate(fruits,start = 1):
    print(i,ele)
'''
1 apple
2 banana
3 orange
4 watermelon
5 apple
6 blueberry
7 strawberry
'''

元组

一旦创建便不能修改
如果只有一个元素必须加逗号,必不可少,逗号是一个元组的核心标识
操作与列表类似
tuple ()将字符串,列表等可迭代对象转化为元组

tuple("hello world")
# ('h', 'e', 'l', 'l', 'o', ' ', 'w', 'o', 'r', 'l', 'd')
a = (1,2,3)
b = (4,5,6)
c = a + b
print(c)
# (1, 2, 3, 4, 5, 6)

实在想要修改的话

c = c[:2] + (7,) + c[2:]
# 但是此时c已经不是之前的c了

字典

键值对,键必须要独一无二
列表无法作为字典的键
字典无法作为字典的键或集合的元素

创建字典:

# 法一
dict1 = {'a':1,'2020':[1,2,3],100:('hello','world')}

# 法二 关键字参数
dict(color = 'b', edgecolor = 'r')
# {'color': 'b', 'edgecolor': 'r'}

# 法三 fromkeys方法
dict.fromkeys([1,3,5,5,1],(1,2,3))
# {1: (1, 2, 3), 3: (1, 2, 3), 5: (1, 2, 3)}

  • items ():显示所有键值对,一一封装在元组里
  • keys ():显示键
  • values ():显示值
dict1 = {'a':1,'2020':[1,2,3],100:('hello','world')}
dict1.items()
# dict_items([('a', 1), ('2020', [1, 2, 3]), (100, ('hello', 'world'))])
dict1.keys()
dict1.values()

输出某个键对应的值

  • dict1[x] x 是键的名字
dict1[100]
# ('hello','world')

get (key, default = None)方法

dict1.get('b','无此键')
# '无此键'

字典可变,修改直接赋值即可
增加元素,直接用方括号给出新键并赋值即可

dict1[(1,2)] = "asljdlas"
# {'a': 2, '2020': [1, 2, 3], 100: ('hello', 'world'), (1, 2): 'asljdlas'}

将一个字典整体增加到另一个字典

  • update (x) :相当于列表或者元组的 +
t1 = {'asd':[1,2],(1,2):'s'}
dict1.update(t1)
# {'a': 2, '2020': [1, 2, 3], 100: ('hello', 'world'), (1, 2): 's', 'asd': [1, 2]}
  • pop (x) :删除键为 x 的字典元素
  • popitem ():删除字典最后一个元素
dict1.pop(100)
dict1.popitem()
# {'a': 2, '2020': [1, 2, 3], (1, 2): 's'}

集合

{}围住,无序,唯一
集合元素只能包括数值,字符串,元组等不可变元素
不能包括列表,字典和集合等可变元素
即使强制重复也会自动去重

set1 = {1,2,2,3,3}
# {1, 2, 3}
  • set () 将可迭代对象转换为集合

可以用 | & - ^ 求两个集合的并,交,差,对称差
也可以用方法

程序控制结构

python 中的数值型非 0 为真,None、空字符串、空列表、空元组、空字典、空集合都是 false。

选择

if else
if elif…
if else 三元操作符

x = 10
y = 20
small = x if x < y else y
print(small)

循环

对字典 d 迭代可以迭代键,不能迭代 k,v
或者 k, v 解包 items()

for k in d():
	print(k,d[k])
	# 或者 print(k,d.get(k,0))
for k,v in d.items():
    print(k,v)
number = [1,55,123,-1,44,77,8]
even = []
odd = []
while len(number) > 0:
    a = number.pop()
    if a % 2 == 1:
        even.append(a)
    else:
        odd.append(a)
print(f'even: {even}\nodd: {odd}')
'''
even: [77, -1, 123, 55, 1]
odd: [8, 44]
'''

推导式

[生成表达式 for 变量 in 序列或迭代对象]

列表推导式

alist = [x**2 for x in range(1,11)]

过滤不符合条件的元素

int_list = [x for x in a if type(x) == int]

for 循环可以嵌套 也可以用多条 for if 构建

字典推导式

dic = {1:'a',2:'b',3:'c'}
reverse_dic = {v:k for k, v in dic.items()}
# {'a': 1, 'b': 2, 'c': 3}

集合推导式

sq = {x**2 for x in [1,-1,2,2,3]}
# {1, 4, 9}

例题

list1 = [1,2,3]
list2 = [2,7]
liste = []
for i in list1:
    for j in list2:
        if i != j:
#             t = [i,j]
            liste.append((i,j))
# 或者
difflist = [(a,b) for a in list1 for b in list2 if a != b]
  1. 删除列表中的重复元素
  • set ()最方便
duplist = [1,1,2,2,4,'a','b','a',3,1]
list3 = list(set(duplist))
  • 利用字典的键
dict4 = {}
for i in duplist:
    dict4[i] = 1
list5 = list(dict4.keys())
print(type(dict4.keys()),type(list(dict4.keys())))
# <class 'dict_keys'> <class 'list'>
# 注意keys()方法不是列表类型
print(list5)
  1. 删除小于 3 个字符的姓名 ,相同的名字合并
names = ['alice','bob','Bob','Alice','J','JOHN','Bob']
# uni_names = {i.title() for i in names if len(i) > 2}
uni_names = {name[0].upper() + name[1:].lower() for name in names if len(name) > 2}
# 花括号是集合
print(uni_names)

5 键不分大小写,将值合并

mcase = {'a':10, 'b':34, 'A':7, 'Z':3}
fcase = {key.lower():mcase.get(key.upper(),0) + mcase.get(key.lower(), 0) for key in mcase.keys()}
# {'a': 17, 'b': 34, 'z': 3}
# 

# 法二
f2case = {}
for key in mcase.keys():
    f2case[key.lower()] = mcase.get(key.lower(),0) + mcase.get(key.upper(), 0)
    # 没有则返回0
    #     f2case[key.lower()] = mcase[key.lower()] + mcase[key.upper()]
    # 直接读取不存在的键会报错

注:upper 和 lower 是字符串的方法
get 是字典的方法
keys ()整体是 dict_keys 类型,每个键的类型各有不同。

3 自建 Python 模块与第三方模块

导入 Python 标准库

import 模块名 as 别名
from 模块名 import 对象名 as 别名
模块名.函数名
别名.函数名
导入某个特定对象后 可以直接使用函数 全局变量等

编写自己的模块

一个. py 文件就可以称为一个模块。
假设当前模块声明了这个内置变量, 如果本模块直接执行, 那么这个__name__的值就为__main__.
如果它被第三方引用 (即通过 import 导入), 那么它的值就是这个模块名, 即它所在 python 文件的文件名.

__name__ 为模块名

parameters 模块

PI = 3.1415926
def paraTest():
    print('PI = ',PI)

if __name__ == '__main__' :
    paraTest()
from parameters import PI
def circleArea(radius):
    return radius ** 2 * PI
def run(radius):
    print('圆的面积为: ',circleArea(radius))
run(5)
import sys
sys.path

包 (Package)

模块一般就是我们日常随手用的一些规模较小的代码,而在比较大规模的任务一般需要用到大量的模块,此时我们可以使用包 (Package)来管理这些模块。我们平时下载的第三方包也就是这个,如 Numpy,
2.1 什么是包?包,就是里面装了一个__init__. py 文件的文件夹。init. py 脚本有下列性质:(1)它本身是一个模块;(2)模块名不是__init__,而是包的名字,也就是装着__init__. py 文件的文件夹名。(3)它的作用是将一个文件夹变为一个 Python 模块(4)它可以不包含代码,不过一般会包含一些 Python 初始化代码 (例如批量导入需要的模块),在这个包被 import 的时候,这些代码会自动被执行。
导入包的方法和导入模块比较类似,只不过由于层级比一般模块多了一级,所以多了一条导入形式.
import 包名. 模块名 import 包名. 模块名 as 模块别名

严格来说,Python中没有库(library)的概念,模块(module)和包(package)才是Python语法中有的概念。这个库的概念是从其他编程语言引进来的,库的概念是具有相关功能模块的集合,只是一个通俗的说法。平时说的库既可以是一个模块,也可以是一个包。

常用的内建模块

collections 模块

namedtuple

第二个参数 [“x”,“y”]也可以写成 “x y"或者"x, y”
变量名不一定要和第一个参数相同
不同属性用逗号隔开,用属性来访问命名元组中的某个元素。

from collections import namedtuple
Point = namedtuple('Point',['x','y'])
p = Point(3, 4)
print(p.x, p.y)
#3 4
p = p._replace(x = 5)
# p[0] 第一个元素 索引调用完全可以
# 修改需要调用_replace方法

x,y = p
# 解包 把一个包含多个元素的对象赋给多个变量
deque 双向队列

和 list 形式上很像,都是方括号,但是 list 的写操作当列表元素数据量很大时,效率低。
没有 sort 方法。

  • append 右边增加元素
  • appendleft 左边增加元素
  • pop 弹出最右边元素
  • popleft 弹出最左边元素
  • insert (index, x)插入到 index 号 元素为 x
  • remove (x) 移除第一个为 x 的元素
  • reverse 颠倒
  • rotate (x) 循环整体向右移动 x 位
  • extend (iterable)可迭代对象添加到尾部
  • 字符串也是可迭代对象,如直接添加字符串’ABC’,会将’A’、‘B’、'C’添加到队列中
  • extendleft 添加到首部
  • copy 列表元组都有此方法
  • count
  • index
  • maxlen
  • 指定队列的长度后,如果队列已经达到最大长度,此时从队尾添加数据,则队头的数据会自动出队。队头的数据相等于被队尾新加的数据“挤”出了队列,以保证队列的长度不超过指定的最大长度。反之,从队头添加数据,则队尾的数据会自动出队。
dq.extend('qbc')
# deque(['a', 'aa', 'c', 'b', 'x', 'abc', 'q', 'b', 'c'])

mdq = deque(maxlen = 5)
mdq.extend(['a','b','c','d','e','f'])
# deque(['b', 'c', 'd', 'e', 'f'], maxlen=5)
OrderedDict

有序字典,底层双向链表
update ()
move_to_end (x)将键为 x 的元素移到尾部

defaultdict
dd = defaultdict(lambda : 'N/A')
dd['a'] ='china'
dd['c']
dd['z']
# {'a': 'china', 'c': 'N/A', 'z': 'N/A', 'b': 'N/A'}
Counter

统计每个单词出现的次数
法 1

colors = ['red','yellow','blue','red','black','red','yellow']
dict1 = {}
for _ in colors:
    if dict1.get(_) == None:
        dict1[_] = 1
    else:
        dict1[_] += 1
print(dict1)

法 2 更方便
Counter 返回值和字典类似

dict2 = Counter(colors)
dict2
# Counter({'red': 3, 'yellow': 2, 'blue': 1, 'black': 1})

most_common (n)求出现频率最高的 n 个单词
print(dict2.most_common(1)) # 出现频率最高的1个单词
# [('red', 3)]

datetime 模块

一个模块可以有很多类

datetime 模块的 datetime 类

from datetime import datetime

获取当前时间日期

datetime.now()

利用 datetime 构造方法生成时间

date1 = datetime(21,1,5)
print(date1,type(date1))
date1.day
# 0021-01-05 00:00:00 <class 'datetime.datetime'>
# >>> 5

纪元时间
1970 年 1 月 1 日00:00:00 UTC+00:00 在计算机里记为 0.
当前时间就是相对于纪元时间流逝的秒数,称为 timestamp

时间日期调用 timestamp ()方法

datetime.now().timestamp()

将字符串转换为 datetime
strptime 方法
datetime.strptime (‘日期时间’,‘格式’)

cday = datetime.strptime('2023 4 16 Sun 11:30:00','%Y %m %d %a %H:%M:%S')
print(cday)
# 2023-04-16 11:30:00

%Y 四个数字表示的年份 %y 两个数字表示的年份
%m 月份
%M 分钟数
%H 24 小时 %I 12 小时
%d 天
%S 秒
%a 星期缩写 %A 星期全写
%b 月份缩写 %B 月份全写

将 datetime 转换为字符串
对象.strftime (格式)

date1 = now.strftime('%Y%m%d %a')
print(date1)
# 20230416 Sun

datetime 加减
引入 timedelta 时间差类, 参数英文要加s.

date2 = cday - timedelta(days = 2, hours = 12)
print(date2)
# 2023-04-13 23:30:00

求两个日期相差的天数
两个 datetime 变量相减自动成为 timedelta 类型

list1 = ['2023 4 16','2022-1-1']
lday1 = datetime.strptime(list1[0],'%Y %m %d')
lday2 = datetime.strptime(list1[1],'%Y-%m-%d')
deltadays = lday1 - lday2
print(type(deltadays))
print(deltadays)
# <class 'datetime.timedelta'>
# 470 days, 0:00:00

json 模块

JSON 书写格式类似于字典

  • json.dumps () 将 python 序列化为 JSON 格式字符串
  • json.loads () 将 JSON 格式字符串反序列化为 python 对象

random 模块

  • random () 同名方法, 生成 0-1 之间的随机数 范围[0,1)
  • uniform (x, y)生成[x, y]之内的随机数
  • seed (n) 设置随机数种子, 后续每次生成随机数都是相同的
  • randoint (x, y)生成 x, y 之内的整型数
  • randrange (start, end, step) 就是从 range (start, end, step)中随机选一个
  • choice (x)从 x (列表, 元祖, 字典)中随便选一个
  • choices (it, y)从 it 中选取多个, 放回采样
  • sample (it, y)从 it 中选取多个, 不重复
  • shuffle (x)打乱原有排序
random.seed(10)
random.random()

常用第三方模块

mpmath

“mpmath” 是一个Python第三方数学库,适用于高精度浮点计算。此库提供了操纵实数、复数、矩阵和多项式的函数和工具,也支持高精度算术、矩阵计算、数学函数、特殊函数和统计分布等功能。

“mp” 是这个库中一个由开发者提供的模块。这个模块提供了一种高精度的浮点数表示方法,称为 “mpf”(多精度浮点数)。它可以用于实现高精度数学计算,如数值积分、常微分方程和线性代数等。

mp.dps
from mpmath import mp

mp.dps = 101
print(mp.pi)
'''
3.141592653589793238462643383279502884197169399375105820974944592307816406286208998628034825342117068
'''

将 dps 设置为 100 + 1,即精度为 101 位小数,可以在计算过程中保证高精度,同时还有一位备用,以避免出现舍入误差。
显示小数点只有99位是因为, 进位的时候第100位是0所以就没有显示.

sympy

新建符号

使用symbols定义符号

x, y = symbols('x y')

利用 SymPy 的 abc 子模块导入所有拉丁、希腊字母

from sympy.abc import x, y

注意:希腊字母  λ \lambda λ (lambda) 是 Python 保留关键字,当用户需要使用这个字母时,请写成 lamda(不写中间的 ‘b’)。

subs()方法 替换符号
处理代数式的函数。

sympy.simplify()函数可以简化代数式。
sympy.expand() 可以用来展开代数式。
sympy.factor() 函数则可以对代数式进行因式分解。

# simplify mathematical expressions
expr_1 = sin(x)**2 - cos(x)**2
print(simplify(expr_1))

# expand polynomial expressions
expr_2 = (x + 1)**3
print(expand(expr_2))

# take a polynomial and factors it into irreducible factors
expr_3 = x**3 + 2*x**2 - x - 2
print(factor(expr_3))
'''
-cos(2*x)
x**3 + 3*x**2 + 3*x + 1
(x - 1)*(x + 1)*(x + 2)
'''

sympy.collect() 函数用来合并同类项。

collect(a*x**2 + b*x**2 + a*x - b*x + c, x)
# 𝑐+𝑥^2(𝑎+𝑏)+𝑥(𝑎−𝑏)
a*x**2 + b*x**2 + a*x - b*x + c
# 𝑎𝑥^2+𝑎𝑥+𝑏𝑥^2−𝑏𝑥+𝑐

lambdify() 将 sympy 表达式转化为函数
lambdify 形式上类似 python 的 lambda 表达式.
args : 参数 expr 表达式
modules 可以指定为 'numpy' 等数字库 默认已安装的所有的数字库.

lambdify(
    args: Union[Iterable, ForwardRef('sympy.core.expr.Expr')],
    expr: 'sympy.core.expr.Expr',
    modules=None,
    printer=None,
    use_imps=True,
    dummify=False,
    cse=False,
)
from sympy.utilities.lambdify import lambdify
f_x = lambdify(x, expr)
print(f_x(2))
多项式对象

Poly()

from sympy import Poly
expr = (x + 1)**n  
expr_expand = expr.expand()
expr_expand = Poly(expr_expand) # 转化为多项式对象
poly_coeffs = expr_expand.coeffs() # 多项式对象查看系数
解方程组

solve()
第一个参数是方程或方程组
第二个参数是要求解的变量

默认返回列表
可以设置 dict = True 或 set = True 使结果按字典或集合方式呈现.

roots = solve(-x**3 + x, x)
# [-1, 0, 1]
solve(x**2 - y**2, x, set=True)
# ([x], {(-y,), (y,)})
绘制参数方程

^9e459d

t = symbols('t')
# parametric equation of unit circle
x1 = cos(t)
x2 = sin(t)
# plot the circle
plot_parametric(x1, x2, (t, 0, 2*pi), size = (10,10))

itertools

用来操作迭代器的模块

无限迭代器
count([start=0, step=1]) 接收两个可选整形参数,第一个指定了迭代开始的值,第二个指定了迭代的步长。
cycle(iterable) 是用一个可迭代对象中的元素来创建一个迭代器,并且复制自己的值,一直无限的重复下去。
repeat(elem [,n]) 是将一个元素重复n遍或者无穷多遍,并返回一个迭代器。

组合迭代器
product(*iterables, repeat=1) 得到的是可迭代对象的笛卡儿积,iterables参数表示需要多个可迭代对象。这些可迭代对象之间的笛卡儿积,也可以使用for循环来实现,例如 product(A, B) 与 ((x,y) for x in A for y in B)就实现一样的功能。

repeat 参数则表示这些可迭代序列重复的次数。例如 product(A, repeat=2)与 product(A, A)实现的功能一样。

permutations(iterable,r=None) 返回 r 个元素的排列 默认全排列
combinations(iterable,r) 返回 r 个元素的组合

import itertools
letters = "ABCA"
# find all combinations containing 2 letters
cmb = itertools.combinations(letters, 2)
pem = itertools.permutations(letters,2)

4 Python 函数

Python 中的函数

函数返回多个值

实际上函数返回的是一个元组的引用

def increase(x,y):
    x += 1
    y += 1
    return x,y
a,b = increase(1,2)
print(type(increase(1,2)));
'''
(2, 3)
<class 'tuple'>
'''
return [a,b]
# 也可以输出列表

函数文档

'''
一个问号看文档 两个问号看代码
help(函数名)
函数名.__doc__
'''
increase?? 
'''
Signature: increase(x, y)
Source:   
def increase(x,y):
    '''
    功能,对输入的两个数字自增1 并返回增加后的数
    '''
    x += 1
    y += 1
    return x,y
File:      c:\users\2021\appdata\local\temp\ipykernel_10168\799339069.py
Type:      function
'''
help(increase)
'''
Help on function increase in module __main__:

increase(x, y)
    功能,对输入的两个数字自增1 并返回增加后的数
'''
increase.__doc__
'''
'\n    功能,对输入的两个数字自增1 并返回增加后的数\n    '
'''

函数参数的花式传递

关键字参数

指定参数

参数为 word = 'hello world'

可变参数

在形参前面加* ,不管实参有多少个, 在内部它们都存放在以形参名为标识符的元组中

*args

def varPara(num,*str):
    print(num)
    print(str)
varPara(5,'das','sadasd')
'''
5
('das', 'sadasd')
'''

可变参数例子

def mySum(*args):
    sum = 0
    for i in range(len(args)):
        sum += args[i]
    return sum
print(mySum(1,3,5,1.0 + 1j))
print(mySum(1.2,1.2,6.6))
# (10+1j)
# 9.0

可变关键字参数

两个星号

**kwargs

会把可变参数打包成字典
调用函数需要采用 arg1 = value1, arg2 = value2 的形式

def varFun(**args):
    if len(args) == 0:
#         print('None')
        return 'None'
    else :
#         print(args)
        return args
print(varFun())
print(varFun(a = 1,b = 2))
print(varFun(1,2))
'''
None
{'a': 1, 'b': 2}

---------------------------------------------------------------------------
TypeError                                 Traceback (most recent call last)
~\AppData\Local\Temp\ipykernel_14840\2103809882.py in <module>
      1 print(varFun())
      2 print(varFun(a = 1,b = 2))
----> 3 print(varFun(1,2))

TypeError: varFun() takes 0 positional arguments but 2 were given
'''

除了用等号给可变关键字参数赋值, 还可以直接用字典给可变关键字参数赋值

默认参数

设定一个默认值, 有就覆盖, 没有就用默认的
如果列表 L 作为默认参数, 列表是一个可变量, 每次调用函数时, 如果改变了 L 的内容, 则下次调用时, 默认参数就会改变, 不再是函数刚开始定义时的那个空列表了.
定义默认参数的时候务必让这个默认参数是不可变对象,例如数值,元组,字符串,不可变集合 (frozenset), None 等

def add_end(L = None):
    if L is None:
        L = []
    L.append('END')
    return L

参数序列的打包与解包

打包: 多个变量赋值给一个, 会被打包成一个元组
解包: 将一个可迭代对象元素分别赋值给分散对象

在可迭代对象前加一个*号, 就可以解包了
对字典加一个星号,只能解包键 (输出形参)
加二个星号,就可以正常解包了 (输出 values)

def fun(a,b,c,d):
    print(a,b,c,d)
list1 = [1,2,3,4]
fun(*list1)
dict1 = {'a':1,'x':2,'y':3,'d':4}
fun(*dict1)
# a x y d
fun(**dict1)
# 1 2 3 4

传值还是传引用

python 中所有的函数参数传递, 统统都是基于传递对象的引用进行的. 这是因为, 在 python 中, 一切皆对象. 而传对象, 实质上传的是对象的内存地址, 而地址即引用.
对象大致分为两大类, 可变和不可变对象. 可变包括字典, 列表及集合等. 不可变对象包括数值, 字符串, 元组等.
可变对象, 传递的就是地址. 就不需要 return 一个可变对象了.
不可变对象, 为了维护它的不可变属性, 函数不得不重构一个实参的副本, 相当于值传递.

函数的递归

def recursive_fact(n):
    if n <= 1:
        return 1
    return n * recursive_fact(n - 1)
num = 5
result = recursive_fact(num)
print(f'递归方法: {num}! = {result}')

函数式编程的高阶函数

函数也是对象,因此函数 (对象)可以作为参数,也可以返回一个函数 (对象)

lambda 表达式

只有一行代码的函数, 由于其太过短小, 用后即焚, 不值得起名字
lambda 参数: 对参数实施的操作
可以把 lambda 表达式赋值给一个变量, 由于赋值可以决定被赋值对象的类型, 所以这个变量在本质上也是一个函数, 当我们使用这个变量时, 实际上就是在调用 lambda 表达式. 也就是说, 上述操作, 让一个原本匿名的函数变得"有名"了.

new_add = lambda x,y:x+y
print(new_add(3.2,1+5j),type(new_add))
# (4.2+5j) <class 'function'>

filter ()函数

filter (function, iterable)
两个参数, 第一个为判断函数, 用于制定筛选规则, 第二个为序列, 作为第一个参数的数据.
序列中每个元素都将作为参数传递给函数进行判断,符合条件的 (即判定为 True 的)留下, 否则就淘汰出局

def fun(var):
    letters = list('aeiou')
    if var in letters:
        return True
    else:
        return False

sequence = list('sadasdwecsadisd')
filtered = filter(fun,sequence)
print(list(set(filtered)))
# ['i', 'a', 'e']

如果第一个函数参数功能非常简单, 可以使用 lambda 表达式来描述

a_list  = [1,34,12,54,136,314,212,13,22,9]
print(list(filter(lambda x: x % 3 == 0,a_list)))
# [12, 54, 9]

map ()函数

map (function, iterable, …)
参数:
function 是映射函数
iterable 是一个或多个可迭代序列
返回值:
迭代器
在第一个参数代表的函数加工下,第二个参数代表的数据源就会转换成一个新的序列

# 求字符串组成的列表的每个字符串的长度序列
def lenstr(n):
    return len(n)
list2  = list(map(lenstr,['hello','sad','byebye','china']))
print(list2)
#[5, 3, 6, 5]
# lambda改写
list3 = list(map(lambda x:len(x),('asd','qwewqeq','12334','xzcx')))
print(list3)
def myFunc(x,y):
    return x + y
# str_cat = map(myFunc,('1','ss','wxs'),('2','sda','wwww'))
str_cat = map(lambda x,y:x+y,('1','ss','wxs'),('2','sda','wwww'))
print(list(str_cat))
# ['12', 'sssda', 'wxswwww']

注: 使用列表推导式也能完成类似功能. 但是 map ()函数的出现, 使得效率高很多.

reduce ()函数

reduce 的本意就是规约或减少
reduce (function, iterable,[, initializer])
function 实现规约功能的二元函数
iterable 可迭代对象
initializer 初试参数

reduce 对一个可迭代对象中的所有数据执行下列操作, 取前两个参数, 利用 function 代表的规则进行计算, 得到的结果再与 iterable 的第三个数据拼成一堆, 然后再利用 function 代表的规则进行运算. 得到下一个结果… 以此类推. 直到计算出最后一个结果.
reduce 函数在 python3 的内建函数移除了,放入了 functools 模块

print((reduce(lambda x,y:x+y,range(101))))
# 5050

利用 reduce 函数求最大最小值

a_list = [1,5,234,888,2,1,0]
reduce(lambda x,y:x if x > y else y,a_list)
# 888

sorted ()函数

sorted (iterable, key=None, reverse=False)
如果指定了 key 参数,排序时会让 key 参数指定的函数作用于序列中的每一个元素,对作用后的结果进行比较,来决定排序的顺序。

a_list = [1,5,-234,-888,2,1,0]
sorted(a_list,key = abs, reverse = True)
# [-888, -234, 5, 2, 1, 1, 0]
# abs是函数名,不写圆括号

# key = len
# key = str.lower

sorted(a_list,key = lambda x:x%2)
# 对列表的每一个元素,模2,按照结果(要么0要么1)排序
# 因此实现了把偶数,奇数分离开
# [-234, -888, 2, 0, 1, 5, 1]

sorted(sorted(a_list),key = lambda x:x%2)
# [-888, -234, 0, 2, 1, 1, 5]

data = [['Bob', 24], ['Cindy', 28], ['Alice', 25], ['David', 23]]
sorted(data, key = lambda item:item[1])
# 按年龄排序
# [['David', 23], ['Bob', 24], ['Alice', 25], ['Cindy', 28]]

例题

zip ()

  • zip ():用于将可迭代的对象作为参数,将对象中对应的元素打包成一个个元组,然后返回对象,而不是列表。如果各个迭代器的元素个数不一致,则返回列表长度与最短的对象相同,如果只有一个可迭代对象, 那么 zip ()会找到空元素来代替.
    利用 * 号操作符,可以将可迭代对象解包。

matrix = [[1,2,3],[4,5,6]] list (zip (*matrix)) 和 list (zip (matrix))的结果分别是什么

matrix = [[1,2,3],[4,5,6]]
print(list(zip(*matrix)))
print(list(zip(matrix)))
'''
[(1, 4), (2, 5), (3, 6)] *将matrix解包成[1,2,3]和[4,5,6]然后zip函数一一缝合成元组
[([1, 2, 3],), ([4, 5, 6],)] 只有一个可迭代对象,zip找到空元素来代替. (,)必不可少,这是第二个可迭代对象缺位的标志.
'''

itemgetter ()

from operator import itemgetter
a = [1,2,3]
b = itemgetter(0,2) # 定义函数b, 获取对象第0个,第2个域的值
b(a) # 获取列表a的第0个,第2个域的值
# (1, 3)
x = [
	{"语文":80,"数学":90,"英语":70,"物理":92,"化学":83},
	{"语文":82,"数学":70,"英语":78,"物理":90,"化学":80},
	{"语文":86,"数学":89,"英语":73,"物理":82,"化学":88},
	{"语文":76,"数学":86,"英语":60,"物理":82,"化学":79}
	]
#使用itemgetter,按照语文成绩排序
x_yuwen = sorted(x, key = itemgetter("语文"))
#使用itemgetter,按照数学成绩排序
x_shuxue = sorted(x, key = itemgetter("数学"))
#使用匿名函数按照物理成绩排序
x_wuli = sorted(x, key = lambda x:x["物理"])
print(*sorted(x, key = itemgetter('数学')),sep = '\n')
"""
[
{'语文': 76, '数学': 86, '英语': 60, '物理': 82, '化学': 79}, 
{'语文': 80, '数学': 90, '英语': 70, '物理': 92, '化学': 83}, 
{'语文': 82, '数学': 70, '英语': 78, '物理': 90, '化学': 80}, 
{'语文': 86, '数学': 89, '英语': 73, '物理': 82, '化学': 88}
]
[
{'语文': 82, '数学': 70, '英语': 78, '物理': 90, '化学': 80}, 
{'语文': 76, '数学': 86, '英语': 60, '物理': 82, '化学': 79}, 
{'语文': 86, '数学': 89, '英语': 73, '物理': 82, '化学': 88}, 
{'语文': 80, '数学': 90, '英语': 70, '物理': 92, '化学': 83}
]
[
{'语文': 86, '数学': 89, '英语': 73, '物理': 82, '化学': 88}, 
{'语文': 76, '数学': 86, '英语': 60, '物理': 82, '化学': 79}, 
{'语文': 82, '数学': 70, '英语': 78, '物理': 90, '化学': 80}, 
{'语文': 80, '数学': 90, '英语': 70, '物理': 92, '化学': 83}
]"""

print ()

print(*objects, sep=' ', end='\n', file=sys.stdout)
'''
objects – 复数,表示可以一次输出多个对象。输出多个对象时,需要用 , 分隔。  
sep – 用来间隔多个对象,默认值是一个空格。  
end – 用来设定以什么结尾。默认值是换行符 \n,我们可以换成其他字符串。  
file – 要写入的文件对象。
'''

5 Python 高级特性

面向对象程序设计

面向过程和面向对象

  • 面向过程设计 POP 程序 = 算法 + 数据结构
  • 面向对象设计 OOP 程序 = 对象 + 消息传递

OOP, 用户首先自定义一个数据结构----类, 然后用该类下的对象组装程序. 对象之间通过’‘消息’'进行通信. 每个对象中既包括数据, 又包括对数据的处理. 每个对象都像一个封闭的小型机器, 彼此协作, 又不相互干扰.
但是执行效率可能比 POP 低 , 复杂度高
POP 怎么做
OOP 谁来做
对象 = 数据 + 方法
因此 OOP 程序 = 数据 + 方法 + 消息传递

类的定义与使用

将具有相同属性和相同行为封装在一起, 便创造了新的类, 这大大扩充了数据类型的概念.
类是对某一类事物的描述,它是抽象的,概念上的定义.而对象是实际存在的该类事物中的个体,因此对象也被称为实例 (instance).
在 python 中, 方法是于特定实例绑定的函数, 因此我们常把类中的函数称为方法, 而把不于实例绑定的普通功能块称为函数
类中的数据成员 (属性)可大致分为两类: 属于对象的数据成员和属于类的数据成员.
对象. 属性名
对象. 方法名

class Person:
    height = 140 # 定义类的数据成员
    # 定义构造方法
    def __init__(self, name, age, weight):
        self.name = name # 定义对象的数据成员属性
        self.age = age
        # 定义私有属性,私有属性在类外部无法访问
        self.__weight = weight
    def speak(self):
        print("%s 说: 我 %d 岁, 我体重为 %d kg, 身高为 %d cm" % (self.name, self.age, self.__weight, Person.height))

构造方法__init__必须以 self 为前缀, 用于对象的初始化.
属于类的数据成员为所有对象共享, 类似于 java 的静态数据成员. 在__init__方法中, 三个数据成员都以 self. 作为访问修饰, 这表明他们是属于对象的数据成员.__weight 代表它是一个私有成员.
私有数据成员在类外通常不能被直接访问的.如果想访问需要借助公有成员函数 (相当于类的外部接口). 同样, 如果某个方法是由两个下划线开始的, 则表明它是一个私有方法. 也只能在类的内部被调用.
python 可以为对象添加新的临时属性, 但该属性和其他对象无关.

p1 = Person('Alice',10,30)
p1.speak()
p1.weight = 60 # 给实例绑定了一个实例属性。
print(p1.weight)
print(p1._Person__weight)
# Alice 说: 我 10 岁, 我体重为 30 kg, 身高为 140 cm
# 60
# 30
# print(p1.__weight)
# 报错

p1.asad = 1
print(p1.asad)
# 1

dir (对象)可以查看对象的属性的方法
我们可以看到, p1 的属性 weight 和 asadp2没有.
其次私有变量的形式是: _类名__私有属性名

print(*dir(p1),sep = '\n')
'''
_Person__weight
__class__
__delattr__
__dict__
__dir__
__doc__
__eq__
__format__
__ge__
__getattribute__
__gt__
__hash__
__init__
__init_subclass__
__le__
__lt__
__module__
__ne__
__new__
__reduce__
__reduce_ex__
__repr__
__setattr__
__sizeof__
__str__
__subclasshook__
__weakref__
age
asad
height
name
speak
weight
'''
p2 = Person('Bob',11,40)

p2.speak()

print(*dir(p2),sep = '\n')
'''
Bob 说: 我 11 岁, 我体重为 40 kg, 身高为 140 cm
_Person__weight
__class__
__delattr__
__dict__
__dir__
__doc__
__eq__
__format__
__ge__
__getattribute__
__gt__
__hash__
__init__
__init_subclass__
__le__
__lt__
__module__
__ne__
__new__
__reduce__
__reduce_ex__
__repr__
__setattr__
__sizeof__
__str__
__subclasshook__
__weakref__
age
height
name
speak
'''

千万不要在实例上修改类属性,它实际上并没有修改类属性,而是给实例绑定了一个实例属性。

p1.height = 150
p1.speak()
Person.height = 150
p1.speak()
p2.speak()
# Alice 说: 我 10 岁, 我体重为 30 kg, 身高为 140 cm
# Alice 说: 我 10 岁, 我体重为 30 kg, 身高为 150 cm
# Bob 说: 我 11 岁, 我体重为 40 kg, 身高为 150 cm

_xxx 保护成员, 只对自己和子类开放访问权限
__xxx__ python 系统自定义的特殊成员
__xxx 私有成员, 这类成员只能供类内部使用, 不能被继承, 但可以通过
对象名._类名__xxx 的方式调用. 因此, 严格意义上, python 不存在私有成员.

类的继承

既有的类叫基类, 超类或父类 派生出的叫子类或派生类
python 支持多继承,容易导致菱形继承 (两个子类继承一个父类, 又有子类同时继承这两个子类, 最后子类的很多成员存在二义性问题, 不知道它们来自哪个父类)
class 子类 ([模块.]父类)

class Student(Person):
    grad = ''
    def __init__(self, name, age, weight, grad):
	    # 调用父类构造方法,初始化父类数据成员
        Person.__init__(self,name,age,weight)
        self.grad = grad
    # 重写父类同名方法
    def speak(self):
	    super().speak()
        print("%s 说: 我 %d 岁了, 我在读 %d 年级" % (self.name, self.age, self.grad))

s1 = Student('Alice',11,40,5)
s1.speak()
# Alice 说: 我 11 岁, 我体重为 40 kg, 身高为 150 cm
# Alice 说: 我 11 岁了, 我在读 5 年级

如果想在子类中调用父类的方法, 可以使用内置方法 super (). 方法 或者 父类名. 方法

生成器与迭代器

生成器

这些元素能不能按照某种算法推算出来, 然后在后续循环过程中, 根据这些元素不断推算出其他被访问的元素? 生成器可以.
本质上生成器就是一个生成元素的函数.
生成器表达式 圆括号不是元组推导式, 而更像是某个函数的标志

gen = (x for x in range(10) if x % 2 == 0)
print(type(gen))# <class 'generator'>

next (generator)输出生成器的下一个元素

利用 yield 创建生成器
如果推算的规则比较复杂, 难以利用列表推导式来生成, 这时候就可以使用含 yield 关键字的函数.

def fib(xterms):
    n = 0
    a,b = 0,1
    while n < xterms:
#         print(b,end = ' ')
        yield b # 表明这个函数是一个生成器
        a, b = b, a + b
        n += 1
    return '成功输出'
f1 = fib(1000000)

生成器的函数, 调用 next ()时候, 遇到 yield 就’半途而废’, 再次执行时, 会从上次返回的 yield 语句接着往下执行. 如果生成器还有 return 语句, 也不是用来正常返回的. 而是 StopIteration 的异常说明

迭代器

字符串, 列表, 元组, 字典, 集合等更确切的说实存储数据的容器.
迭代器内部维护着一个状态, 该状态用来记录当前迭代指针所在的位置, 以方便下次迭代时获取正确的元素. 一旦所有的元素都被遍历, 那么迭代器就会指向容器对象的尾部, 并触发停止迭代的异常.
只有当迭代至某个值时, 该元素才会被计算并获取. 因此迭代器特别适合用于遍历大文件或无限集合, 因为我们不用一次性将它们全部预存到内存之中, 用哪个再临时拿来即可.

next ()输出迭代器的下一个元素

x = [1,2,3]
# next(x) 报错
y = iter(x)
next(y) # 1

迭代器实际上是某个迭代类定义的对象

__iter__
__next__

islice (iterable, start, stop[, step]) 迭代分片
f 是一个可迭代对象, 因为实现了__iter__方法, 又是一个迭代器, 因为实现了__next__方法

  • next 会为下次调用 next ()修改状态
  • 为当前调用生成返回结果
from itertools import islice
class Fib:
    def __init__(self):
        self.previous = 0
        self.current = 1
    def __iter__(self):
        return self
    def __next__(self):
        value = self.current
        self.previous,self.current = self.current,self.previous + self.current
        # 多变量同时赋值 分开就错 
        return value

f = Fib()
a = list(islice(f,0,10))
print(a)
# [1, 1, 2, 3, 5, 8, 13, 21, 34, 55]

文件操作

打开文件

打开模式
'r’只读 不存在会报错
'w’只写 不存在则创建
'a’新内容写到尾部, 不存在文件则创建
't’以文本文件模式打开, 默认
'b’以二进制打开, 图片, 音频等
'+'打开文件并允许更新, 可读可写
以上模式可以组合

加入 encoding = 'utf-8’参数 以 utf-8 编码读入

fhand = open('D:\study\code\Python\srcs\chap05-Python-advanced\python.txt','r')
for line in fhand:
    print(line)
'''
In this tutorial, you鈥檒l learn about Python operator precedence and associativity. 

This topic is crucial for programmers to understand the semantics of Python operators.

After reading it, you should be able to know how Python evaluates the order of its operators. 

Some operators have higher precedence than others such as the multiplication operator has higher priority than the addition operator, 

so do multiplication before addition.
'''
fhand.read()
# ''

read ()方法, 将文本文件一次性读取出来. 如果文件太大, 那么内存就爆了
此时文件指针指向文件末尾, 再用 read 方法, 指针进无可进

利用 seek ()方法
0 代表文件开头, 1 代表从当前位置算起, 2 表示从文件末尾算起

fhand.seek(0)
# 0
fhand.read()
'In this tutorial, you鈥檒l learn about Python operator precedence and associativity. \nThis topic is crucial for programmers to understand the semantics of Python operators.\nAfter reading it, you should be able to know how Python evaluates the order of its operators. \nSome operators have higher precedence than others such as the multiplication operator has higher priority than the addition operator, \nso do multiplication before addition.'

tell 方法返回文件指针的当前位置

fhand.tell()  
# 444

由于文件指针在末尾, 因此给出整个文本的字节数.

read (x)表示读取 x 个字节的数据

读取一行与全部行

readline ()读取一行包括换行符 (‘\n’)本身.
readlines ()读取全部行 以行为单位的 最方便
read () 以字节为单位的

fhand.seek(0)
lines = fhand.readlines()
lines[:2]

['In this tutorial, you鈥檒l learn about Python operator precedence and associativity. \n',
 'This topic is crucial for programmers to understand the semantics of Python operators.\n']

close ()关闭文件 回收资源
文件读写会产生异常, 建议 try finally

避免忘记关文件 使用 with 语句自动 close.

with open('python.txt','r') as f:
	print(f.read())
	# 其他语句

写入文件

write ()

with open('D:\study\code\Python\srcs\chap05-Python-advanced\python.txt','w') as f:
    f.write('test1')
    '''
    文件内容都被清空了,只剩下test1.
    '''

异常处理

try-except-finally
try 管住代码, 发生异常则进入 except 语句块, 没有异常则不进入. 最终都要进入 finally 语句块.

try:
    print(1/0)
except ZeroDivisionError as err:
    print('发生',err,' 异常')
finally:
    print('演示代码')

抛出异常

x = 10
if x > 5:
    raise Exception('x应该小于5,当前值为%d' % x)

错误调试

断言
如果条件不成立则抛出异常

assert 条件
等同于
if not 条件:
	raise AssertionError
def avg(n):
    assert len(n) != 0,'列表为空!'
    return sum(n)/len(n)
# list1 = []
# print(avg(list1))
list2 = [1,45,2,3.2]
print(avg(list2))

6 其他

视图与副本

a[1:3] 得到的是原数组的视图,而 a[[1, 2]] 得到的是原数组的副本。具体来说:

  • 视图是对原数组的引用,或者自身没有数据,与原数组共享数据
  • 副本是对原数组的完整拷贝,虽然经过拷贝后的数组中的数据来自于原数组,但是它相对于原数组是独立的;

赋值, 浅拷贝, 深拷贝

首先需要了解下几个概念

  • 变量:是一个系统表的元素,拥有指向对象的连接空间
  • 对象:被分配的一块内存,存储其所代表的值
  • 引用:是自动形成的从变量到对象的指针
  • 类型:属于对象,而非变量
  • 不可变对象:一旦创建就不可修改的对象,包括字符串、元组、数值类型

(该对象所指向的内存中的值不能被改变。当改变某个变量时候,由于其所指的值不能被改变,相当于把原来的值复制一份后再改变,这会开辟一个新的地址,变量再指向这个新的地址。)

  • 可变对象:可以修改的对象,包括列表、字典、集合

(该对象所指向的内存中的值可以被改变。变量(准确的说是引用)改变后,实际上是其所指的值直接发生改变,并没有发生复制行为,也没有开辟新的地址,通俗点说就是原地改变。)
当我们写:

a = 'python'

Python解释器干的事情:

  1. 创建变量a

  2. 创建一个对象(分配一块内存),来存储值 ‘python’

  3. 将变量与对象,通过指针连接起来,从变量到对象的连接称之为引用(变量引用对象)

1.赋值: 只是复制了新对象的引用,不会开辟新的内存空间。

并不会产生一个独立的对象单独存在,只是将原有的数据块打上一个新标签,所以当其中一个标签被改变的时候,数据块就会发生变化,另一个标签也会随之改变。
2.浅拷贝: 创建新对象,其内容是原对象的引用。

copy.copy()

浅拷贝有三种形式: 切片操作,工厂函数,copy 模块中的 copy 函数。
如: lst = [1,2,[3,4]]
切片操作:lst1 = lst[:] 或者 lst1 = [each for each in lst]
工厂函数:lst1 = list(lst)
copy 函数:lst1 = copy.copy(lst)

浅拷贝之所以称为浅拷贝,是它仅仅只拷贝了一层,拷贝了最外围的对象本身,内部的元素都只是拷贝了一个引用而已,在 lst 中有一个嵌套的 list[3,4],如果我们修改了它,情况就不一样了。

浅复制要分两种情况进行讨论:

1)当浅复制的值是不可变对象(字符串、元组、数值类型)时和“赋值”的情况一样,对象的 id 值_(id()函数用于获取对象的内存地址)_与浅复制原来的值相同。

2)当浅复制的值是可变对象(列表、字典、集合)时会产生一个“不是那么独立的对象”存在。有两种情况:

第一种情况:复制的对象中无复杂子对象,原来值的改变并不会影响浅复制的值,同时浅复制的值改变也并不会影响原来的值。原来值的id值与浅复制原来的值不同。

第二种情况:复制的对象中有复杂子对象(例如列表中的一个子元素是一个列表),如果不改变其中复杂子对象,浅复制的值改变并不会影响原来的值。 但是改变原来的值中的复杂子对象的值会影响浅复制的值。

3.深拷贝:和浅拷贝对应,深拷贝拷贝了对象的所有元素,包括多层嵌套的元素。深拷贝出来的对象是一个全新的对象,不再与原来的对象有任何关联。

copy.deepcopy()

所以改变原有被复制对象不会对已经复制出来的新对象产生影响。
只有一种形式,copy 模块中的 deepcopy 函数

不可变对象类型,没有被拷贝的说法,即便是用深拷贝,查看 id 的话也是一样的,如果对其重新赋值,也只是新创建一个对象,替换掉旧的而已。一句话就是,不可变类型,不管是深拷贝还是浅拷贝,地址值和拷贝后的值都是一样的。

list1 = [1,3,[8, 9],5,5]
list2 = list1 # 只是传引用
list3 = list1[:]
list4 = copy.copy(list1)
list5 = copy.deepcopy(list1)
list1.append(0)

l
[62]:

[[1, 3, [8, 9], 5, 5, 0],
 [1, 3, [8, 9], 5, 5, 0],
 [1, 3, [8, 9], 5, 5],
 [1, 3, [8, 9], 5, 5],
 [1, 3, [8, 9], 5, 5]]

[63]:

for i in l:

    print(id(i))

1721422170880
1721422170880
1721422158400
1721421359744
1721413352256

[64]:

list1[2].append(0)

l

[64]:

[[1, 3, [8, 9, 0], 5, 5, 0],
 [1, 3, [8, 9, 0], 5, 5, 0],
 [1, 3, [8, 9, 0], 5, 5],
 [1, 3, [8, 9, 0], 5, 5],
 [1, 3, [8, 9], 5, 5]]

结论:
1.外层添加元素时,浅拷贝不会随原列表变化而变化;内层添加元素时,浅拷贝才会变化。
2.无论原列表如何变化,深拷贝都保持不变。
3.赋值对象随着原列表一起变化。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值