持久化数据¶
到目前为止,我们只知道如何编写“临时”程序,
- 程序停止运行后数据就会消失。
文件允许数据持久化,
- 可以将程序的工作保存到磁盘上,
- 以便之后再次使用。
持久程序的例子
- 操作系统
- 数据库
- 服务器
读文件¶
- 使用
open()
函数打开文件,并通过readline()
方法逐行读取文件内容。
In [1]:
f = open('baseball.csv')
f.readline()
Out[1]:
'"id","year","stint","team","lg","g","ab","r","h","X2b","X3b","hr","rbi","sb","cs","bb","so","ibb","hbp","sh","sf","gidp"\n'
In [2]:
f.readline()
print(f.closed)
False
读文件¶
with
关键字用于处理文件时,可以自动进行错误检查和清理。- 自动关闭资源:当 with 语句结束时,Python 会自动关闭资源,如文件、连接等。
- 处理异常:如果在 代码块 中发生异常,Python 会自动调用上下文管理器的 exit() 方法,确保资源被正确关闭。
In [6]:
with open('baseball.csv') as f:
lines = f.readlines()
for line in lines:
#print(line)
pass
print(f.closed)
True
写文件¶
- 以写入模式打开文件,如果文件已存在,则会创建新文件并删除旧内容。写入文件时,
\n
代表新行,并且每次写入操作都会追加到文件末尾。
In [130]:
with open('demo.txt', 'w') as f:
f.write('cat\n')
f.write('dog\n')
print(f.closed)
True
字符串格式化¶
- Python提供了格式化字符串的工具,例如使用
%d
、%s
、%f
来格式化整数、字符串和浮点数。
In [131]:
# 使用操作符 “%” 格式化字符串
name = "John"
age = 30
print("My name is %s and I am %d years old." % (name, age))
My name is John and I am 30 years old.
In [132]:
# 使用str.format()方法格式化字符串
pi = 3.14159
print("The value of pi is {:.2f}".format(pi))
The value of pi is 3.14
对象持久化:pickle¶
pickle.dumps()
可以将对象转换为二进制字符串,这个字符串可以存储到文件中以供后续使用。
In [133]:
import pickle
t1 = [1, 'two', 3.0]
s = pickle.dumps(t1)
print(s)
with open('demo.pkl', 'wb') as f:
f.write(s)
with open('demo2.pkl', 'wb') as f:
pickle.dump(t1, f)
b'\x80\x04\x95\x16\x00\x00\x00\x00\x00\x00\x00]\x94(K\x01\x8c\x03two\x94G@\x08\x00\x00\x00\x00\x00\x00e.'
pickle.loads()
可以将这个字符串转换回对象。
In [134]:
t2 = pickle.loads(s)
t1 == t2, t2 is t1
Out[134]:
(True, False)
定位文件:os模块¶
os
模块允许我们与操作系统交互,例如获取当前工作目录、列出目录内容、更改工作目录等。路径可以是绝对路径或相对路径。
In [135]:
import os
os.getcwd(), os.path.exists('demo.pkl')
Out[135]:
('/mnt/c/Users/ASUS/Desktop/PPT', True)
错误处理:try/catch语句¶
- 使用
try/except
语句可以在发生错误时尝试恢复,而不是直接失败。- 例如,尝试打开一个文件,如果失败(例如文件不存在),则在其他地方寻找文件。
In [136]:
import os
try:
t = pickle.load(open('demo.pkl', 'rb'))
print(t)
except:
print('File not found')
[1, 'two', 3.0]
错误处理:try/catch语句¶
In [137]:
import math
def my_sqrt(x):
try:
return math.sqrt(x)
except TypeError:
print('x is not a number')
return None
except ValueError:
print('x is not a positive number')
return None
except:
print('Something else went wrong')
return None
x = my_sqrt('cat')
print(x)
x is not a number None
编写模块¶
- 我们可以编写自己的模块,并使用相同的语法导入它们。
In [138]:
import os
print(os.path.isfile('prime.py'))
True
In [139]:
import subprocess
cmd = 'cat prime.py'
subprocess.run(cmd, shell=True)
import math def is_prime(n): if n <= 1: return False elif n == 2: return True else: ulim = math.ceil(math.sqrt(n)) for k in range(2, ulim + 1): if n % k == 0: return False return True
Out[139]:
CompletedProcess(args='cat prime.py', returncode=0)
编写模块¶
In [140]:
import prime
prime.is_prime(1)
Out[140]:
False
In [141]:
from prime import is_prime
is_prime(2)
Out[141]:
True
类:程序员定义的类型¶
- 有时我们使用一组变量来表示一个特定的对象。类可以将数据类型封装起来,例如表示二维空间中的点。
- 定义一个类后,我们可以创建该类型的新变量。
In [142]:
class Point:
'''Represent a 2-d point.'''
pass
p = Point()
p
Out[142]:
<__main__.Point at 0x7faf8c12d030>
In [143]:
p.x = 3.0
p.y = 4.0
print(p.x)
3.0
嵌套¶
- 一个对象作为另一个对象的属性存在,叫做嵌套。
In [144]:
class Rectangle:
'''Represent a rectangle.
attribute: width, height, corner
'''
box = Rectangle()
box.width = 100.0
box.height = 200.0
box.corner = Point()
box.corner.x = 0.0
box.corner.y = 0.0
复制¶
- 别名的使用会让程序变得复杂, 因为一处改动可能会影响到其他地方.
In [145]:
import copy
q1 = copy.copy(p)
q1, p
Out[145]:
(<__main__.Point at 0x7faf8c12fd90>, <__main__.Point at 0x7faf8c12d030>)
复制¶
- 浅拷贝:仅仅复制对象和其内部引用,而不会复制内嵌对象;
- 深拷贝:不仅复制对象,同时也会复制其引用的对象,以及其引用对象内部引用的对象等等。
In [146]:
print(box, box.corner)
box2 = copy.copy(box)
print(box2, box2.corner)
box3 = copy.deepcopy(box)
print(box3, box3.corner)
<__main__.Rectangle object at 0x7faf8c12ef80> <__main__.Point object at 0x7faf8c12f400> <__main__.Rectangle object at 0x7faf8c12dde0> <__main__.Point object at 0x7faf8c12f400> <__main__.Rectangle object at 0x7faf8c12e530> <__main__.Point object at 0x7faf8c12dd80>
纯函数和修改器¶
- 纯函数返回一个对象,并且不修改其任何参数。
- 修改器函数会修改其获取的参数对象。
In [147]:
# 纯函数
def double_sides(r):
rdouble = Rectangle()
rdouble.corner = r.corner
rdouble.height = 2.0 * r.height
rdouble.width = 2.0 * r.width
return(rdouble)
# 修改器
def shift_rectangle(rec, dx, dy):
rec.corner.x = rec.corner.x + dx
rec.corner.y = rec.corner.y + dy
方法¶
- 方法是一种特殊类型的函数,它由对象提供。每个方法必须包含
self
作为其第一个参数。
In [148]:
class Time:
def print_time(self):
print(self.hour, ':', self.minute, ':', self.second)
t = Time()
t.hour, t.minute, t.second = 11, 59, 30
t.print_time()
11 : 59 : 30
创建对象:__init__方法¶
__init__
是一个特殊方法,在我们实例化对象时被调用。如果向__init__
提供少于三个参数,它将默认填充其余参数。
In [149]:
class Time:
def __init__(self, h=None, m=None, s=None):
self.hour = h
self.minute = m
self.second = s
def print_time(self):
print(self.hour, ':', self.minute, ':', self.second)
t = Time(11, 59, 30)
t.print_time()
11 : 59 : 30
继承¶
In [150]:
class Card:
""""Represents a playing card."""
def __init__(self, suit=0, rank=2):
self.suit = suit
self.rank = rank
# 类属性
suit_names = ['Clubs', 'Diamonds', 'Hearts', 'Spades']
rank_names = [None, 'Ace', '2', '3', '4', '5', '6', '7', '8', '9', '10', 'Jack', 'Queen', 'King']
# __str__ 是 Python 中的一个特殊方法,用于返回对象的字符串表示形式。
# 当你使用 print() 函数或 str() 函数来打印一个对象时,Python 会自动调用该对象的 __str__ 方法来获取其字符串表示形式
def __str__(self):
return '%s of %s' % (self.rank_names[self.rank], self.suit_names[self.suit])
# 在Python中,__lt__是特殊方法之一,用于定义对象之间的比较行为。具体来说,__lt__方法用于实现小于(less than)比较运算符<。
# 当你使用 print() 函数或 str() 函数来打印一个对象时,Python 会自动调用该对象的 __str__ 方法来获取其字符串表示形式。
def __lt__(self, other):
if self.suit < other.suit: return True
if self.suit > other.suit: return False
return self.rank < other.rank
card1 = Card(2, 11)
card2 = Card(3, 10)
print(card1, card1<card2)
Jack of Hearts True
In [151]:
# 整幅牌
class Deck:
def __init__(self):
self.cards = []
for suit in range(4):
for rank in range(1, 14):
card = Card(suit, rank)
self.cards.append(card)
def __str__(self):
res = []
for card in self.cards:
res.append(str(card))
return '\n'.join(res)
def pop_card(self):
return self.cards.pop()
def add_card(self, card):
self.cards.append(card)
def shuffle(self):
import random
random.shuffle(self.cards)
def move_card(self, hand, num):
for i in range(num):
hand.add_card(self.pop_card())
继承¶
- 继承是一种基于已有类,进行修改,从而定义新的类的能力。
- 我们想要一个类来表示 ‘手牌’, 即玩家手中持有的牌. 一副手牌和整副牌相差无几: 都是由卡牌构成, 并且都需要支持添加和移除牌等操作.
In [152]:
class Hand(Deck):
"""Represents a hand of playing cards."""
def __init__(self, label=''):
self.cards = []
self.label = label
def __str__(self):
res = []
res.append(self.label)
for card in self.cards:
res.append(str(card))
return '\n'.join(res)
函数式编程¶
- 面向对象编程的思想
- 一切都是对象
- 每个操作都是某个类/对象的责任
- 使用副作用(例如,修改属性)为我们带来好处
- 在函数式编程中,函数是中心概念,而不是对象
- 一切都是函数, 数据是不可变的
- 尽量避免副作用
- 使用纯函数
- 相同输入始终返回相同输出:对于相同的输入参数,纯函数总是返回相同的结果。这意味着函数的输出仅依赖于其输入,而与外部状态无关。 * HTTP协议:HTTP 是一个无状态协议,每个请求(例如网页请求)都与之前的请求无关。
- 没有副作用:纯函数在执行过程中不会对外部状态产生任何影响。这包括不修改全局变量、不改变传入对象的状态、不进行 I/O 操作(如文件读写或打印输出)等。
- 允许把函数本身作为参数传入另一个函数,还允许返回一个函数!
面向对象编程和函数式编程¶
函数式编程适用于计算逻辑较复杂且需要高并发的场景;
而面向对象编程更适合建模现实世界的复杂系统
Python是一种多范式编程语言
Python对函数式编程提供部分支持。由于Python允许使用变量,因此,Python不是纯函数式编程语言。(尽管许多函数式编程语言(如Haskell、Lisp)可以使用变量,但它们倾向于使用不可变数据结构和函数,避免改变变量的值。)
迭代器¶
函数式编程强调函数的组合性,迭代器支持对数据流的逐个处理。
迭代器 (Iterator):
- 迭代器是一个对象,它实现了两个方法:iter() 和 next()。iter() 返回自身,next() 返回序列中的下一个元素,并在元素耗尽时抛出 StopIteration 异常。
- 迭代器的特点是:元素只能被遍历一次,且它们不会存储所有的元素,而是按需逐个生成。
可迭代对象 (Iterable):
- 一个可迭代对象是实现了 iter() 方法的对象,该方法返回一个迭代器。例如:list、tuple、str、dict 等 Python 内置容器类型都是可迭代对象。
- 可迭代对象可以被 for 循环遍历,但它本身不是迭代器,不能直接调用 next() 方法。
In [153]:
t = [1, 2]
titer = iter(t) # 列表不是迭代器,所以我们首先必须使用函数iter()将列表t转换为迭代器。
next(titer), next(titer)
Out[153]:
(1, 2)
In [154]:
next(titer)
--------------------------------------------------------------------------- StopIteration Traceback (most recent call last) /tmp/ipykernel_95373/302089432.py in <module> ----> 1 next(titer) StopIteration:
In [155]:
t = [1,2]
for x in t:
print(x)
for x in iter(t):
print(x)
# 这两个for循环是等效的。第一个隐藏了iter()的调用,而在第二个中,我们正在做Python本来会为我们做的工作,通过将t转换为迭代器。
1 2 1 2
生成器¶
- 生成器是通过生成器函数或生成器表达式来创建的。
- 生成器函数与普通函数的区别在于,它使用 yield 而不是 return 来返回值。每次调用生成器的 next() 方法时,函数会从上一次暂停的地方继续执行。
In [156]:
def my_generator():
yield 1
yield 2
yield 3
gen = my_generator()
print(next(gen)) # 输出 1
print(next(gen)) # 输出 2
print(next(gen)) # 输出 3
# 再次调用 next 会引发 StopIteration 异常
print(next(gen))
1 2 3
--------------------------------------------------------------------------- StopIteration Traceback (most recent call last) /tmp/ipykernel_95373/1948705562.py in <module> 10 print(next(gen)) # 输出 3 11 # 再次调用 next 会引发 StopIteration 异常 ---> 12 print(next(gen)) StopIteration:
- 生成器表达式与列表推导式类似,但生成器表达式使用圆括号 (),而列表推导式使用方括号 []。生成器表达式是按需生成元素的,不会一次性生成所有元素。
In [157]:
gen_exp = (x * x for x in range(5)) # 生成一个生成器
print(next(gen_exp)) # 输出 0
print(next(gen_exp)) # 输出 1
print(next(gen_exp)) # 输出 4
0 1 4
高阶函数:map, reduce, and filter¶
map()函数接收两个参数,一个是函数,一个是Iterable,map将传入的函数依次作用到序列的每个元素,并把结果作为新的Iterator返回。
In [158]:
def f(x):
return x * x
r = map(f, [1, 2, 3, 4, 5, 6, 7, 8, 9])
list(r)
Out[158]:
[1, 4, 9, 16, 25, 36, 49, 64, 81]
reduce¶
再看reduce的用法。reduce把一个函数作用在一个序列[x1, x2, x3, ...]上,这个函数必须接收两个参数,reduce把结果继续和序列的下一个元素做累积计算,其效果就是:
reduce(f, [x1, x2, x3, x4]) = f(f(f(x1, x2), x3), x4)
In [159]:
from functools import reduce
def fn(x, y):
return x * 10 + y
reduce(fn, [1, 3, 5, 7, 9])
Out[159]:
13579
lambda¶
- lambda函数是一个可以传递给其他函数或作为参数的匿名函数。
- lambda 参数1, 参数2, ... : 表达式
- 匿名:lambda函数不需要名字,可以直接使用。
- 简洁:适合定义简单的函数,只能包含一个表达式,不能包含多条语句。
- 返回值:lambda函数会自动返回表达式的值。
- lambda 参数1, 参数2, ... : 表达式
In [160]:
list(map(lambda x: x * x, [1, 2, 3]))
Out[160]:
[1, 4, 9]
filter¶
- filter()也接收一个函数和一个序列
In [161]:
list(filter(lambda x: x % 2 == 1, [1, 2, 3, 4, 5, 6, 7, 8, 9]))
Out[161]:
[1, 3, 5, 7, 9]