python封装函数、实现将任意的对象序列化到磁盘上_Python 第4周 - Python基础-装饰器/生成器/迭代器/内置函数/序列化/目录开发规范...

本文深入介绍了Python中的装饰器、生成器和迭代器的概念和使用,包括它们的实现机制、特点和应用场景。此外,还讲解了内置函数的用法,特别是数据序列化中的Json和Pickle模块。最后,讨论了软件目录开发规范和不同目录间的模块调用。
摘要由CSDN通过智能技术生成

内容

装饰器

生成器

迭代器

内置函数讲解

Json and Pickle 数据序列化

软件目录开发规范

不同目录间的模块调用一、装饰器

装饰器:本质就是函数,定义函数器装饰其他函数,就是为其他函数添加附加功能。

原则:1、不能修改被装饰的函数的源代码

2、不能修改被装饰的函数的调用方式

实现装饰器,涉及到的知识量:

1、函数既“变量”

2、高阶函数

3、嵌套函数

高阶函数+嵌套函数 =》完成装饰器的使用

1、装饰器的调用方式:@

b90d550a4e2f3c679de5ac7ff066046f.png

2、函数既是“变量”的理解

举例说明:

python 内部回收的地址说明

3、高阶函数

函数返回值

4、嵌套函数

分清,嵌套跟函数内部调用的区别

5、根据不同验证输入,走不同的验证方式

装饰高潮版

二、生成器

列表生成式:可以直接创建一个列表,作用使代码更简洁,数据量大的话,不适合去用,比较受内存的限制。数据量大的话,不仅占用很大的存储空间,如果整个列表里面,我们只需要几元素,那么后面元素占用的空间都浪费了。

举例:目前有一列表[0,1,2,3,4,5,6,7,8,9],需要在每个元素上加1,看实现的方法

第一种方法:>>> a = [0,1,2,3,4,5,6,7,8,9]>>> b =[]>>> for i in a:b.append(i+1) #定义b的空列表,把a的值加1后赋給b

...>>> print(b)

[1, 2, 3, 4, 5, 6, 7, 8, 9, 10]>>> a =b>>>print(a)

[1, 2, 3, 4, 5, 6, 7, 8, 9, 10]

第二种方法:>>> a = [0,1,2,3,4,5,6,7,8,9]>>> for index,i inenumerate(a):

... a[index]+= 1 #利用索引的切片取值,在循环加1,打印

...>>> print(a)

[1, 2, 3, 4, 5, 6, 7, 8, 9, 10]

第三种方法:>>> a = [0,1,2,3,4,5,6,7,8,9]>>> b = [i+1 for i ina]>>> print(b)

[1, 2, 3, 4, 5, 6, 7, 8, 9, 10]

生成器(generator):列表生成式,是把列表里面的所有元素完整的进行打印。生成器就是这种不必创建完整的list,从而节 省大量的内存空间。在python环境中,这种一边循环一边计算的机制就成称之为生成器。

1、简单的生成器创建,只需要把列表生成式的[]换成(), 就创建完成:

1 列表生成式:2 >>> a = [ i*2 for i in range(10) ]3 >>> print(a)4 [0, 2, 4, 6, 8, 10, 12, 14, 16, 18]5

备注:作用使代码更简洁,数据量大的话,不适合去用,比较耗空间

8 ===============================

9 生成器:10 >>> b = ( i*2 for i in range(10) )11 >>> print(b)12 at 0x1021e0938>

13 >>> b.__next__()14 015 >>> b.__next__()16 2

17 >>> b.__next__()18 4

备注:只有调用时才会生成相应的数据;只记录当前的位置,而且方法只有一个__next__(),不能返回去取用,(python 2.7版本是 next()),直到把数值取完为止。数据取完之后,进行往下取数值,就会报错。

不断的使用__next()__,取数值,比较麻烦,可以结合for的循环使用,因为生成器可以是迭代的对象:

观察以下,两个的变化:

打印了内存的地址:>>> d = ( i +1 for i in range(10))>>> for i ind:

...print(d)

... at 0x1019e09e8>

at 0x1019e09e8>

at 0x1019e09e8>

at 0x1019e09e8>

at 0x1019e09e8>

at 0x1019e09e8>

at 0x1019e09e8>

at 0x1019e09e8>

at 0x1019e09e8>

at 0x1019e09e8>

打印输出内容:

>>> d = ( i +1 for i in range(10))>>> for i ind:

...print(i)

...1

2

3

4

5

6

7

8

9

10

所以,创建了一个生成器,基本上永远不会调用__next()__,而是通过for循环来迭代它,并且不需要关心StopIteration的错误。

2、利用函数进行算法推算,并改成生成器的形式

如有推算的算法比较复杂,用类似的列表生成式的for 循环无法实现的时候,还可以用函数来实现。

如下:除第一个数,第二个数外。任意一个数都可以由前两个数相加得到:

a = [1,1,2,3,5,8,13,21,34,55.......]

要计算得出以上的数学列表,单独使用列表生成式,很难实现。用函数进行打印:

deffib(max):

n,a,b= 0,0,1

while n

a, b = b, a+b #注意赋值的写法,其中左边"a"的值等于右边"b"的值,左边 ”b“的值等于右边“a+b”的值。

n= n + 1

return 'done'f=fib(10)

print(f)

输出:1

1

2

3

5

8

13

21

34

55

done

以上函数实现了数列的推算,但种逻辑还不是生成器的写法,以下把print(b)改为yield b就可以实现。如下:

deffib(max):

n,a,b= 0,0,1

while n

yieldb

a, b= b, a+b # n = n + 1

return 'done'f= fib(10)print(f)

输出:打印出生成器的内存地址。

注意:以上是生成器的另外一种定义的方式,如果一个函数的定义中包含yield关键字,那么这个函数不是再是普通的函数,而是一个生成器函数。

3、生成器和函数的执行流程

重点理解:生成器和函数的执行流程不一样。函数是顺序执行,遇到return 语句或者最后一句函数语句就返回。而变成生成器的函数,在每次调用__next__ 的时候执行,遇到yield语句返回,再次执行时会从上次返回的yield语句处继续执行。函数测试最好加入断点的方法验证。

对比,以下两个断点判断执行的例子,观察执行顺序:

A、使print(b) 打印

def fib(max): #第二步,函数体处理过程

n,a,b = 0,0,1 #第三步

while n < max: #第四步,执行while循环,直到计算10次,完成整个循环退出。

print(b)#yield b

a, b = b, a+b

n= n + 1

return 'done'f= fib(10) #熟悉调用函数体 ,第一步

print(f) #第五步,调用打印

print("来个断点判断")print(f) #第六步,只会打印函数的return返回值,并不执行while 循环。

输出:

1

1

2

3

5

8

13

21

34

55

done

来个断点

done

B、使yield来打印

deffib(max):

n,a,b= 0,0,1

while n

a, b= b, a+b

n= n + 1

return 'done'f= fib(10) #f:

print(f.__next__()) #第一步,执行输出,调用函数进行计算,第一次执行完之后跳出,执行第二print的输出

print(f.__next__()) #第二步,执行输出,接上个print 执行后,调用函数进行计算,往下继续计算,不重复。

print("来个断点") #第三步,加个隔断

print(f.__next__()) #第四步,接着后面没有打印数值,进行传参,进行打印

print("来个断点2")print(f.__next__())

输出:1

来个断点1

1

2

来个断点2

3

在循环过程中不断调用yield,就会不断中断。当然要给循环设置一个条件来退出循环,不然就会产生一个无限数列出来。

C、使用for循环来迭代,进行对生成器的取值:

把函数变成生成器,基本上不会使用__next()__来获取下一个返回值,都是通过for循环取值

deffib(max):

n,a,b= 0,0,1

while n

a, b= b, a+b

n= n + 1

return 'done'f= fib(10)for i in(f):print(i)

输出:1

1

2

3

5

8

13

21

34

55

备注:使用for循环调用生成器时,拿不到生成器的return语句的返回值。如果想要拿到返回值,必须捕获StopIteration错误,返回值包含在StopIteration的value中,要进行异常输出:

异常处理,异常捕获:

deffib(max):

n,a,b= 0,0,1

while n

a, b= b, a+b

n= n + 1

return 'done'f= fib(10)for i in(f):whileTrue:try:

x=next(f)print('f:',x)exceptStopIteration as e:print('Generator return value:', e.value)break输出:

f:1f:2f:3f:5f:8f:13f:21f:34f:55Generatorreturn value: done

D、yield实现在单线程的情况下实现并发运算的效果

如下:

8f900a89c6347c561fdf2122f13be562.png

961ddebeb323a10fe0623af514929fc1.png

1 importtime2 defconsumer(name):3 print("%s 准备吃包子啦!" %name)4 whileTrue:5 baozi = yield #定义生成器位置

6

7 print("包子[%s]来了,被[%s]吃了!" %(baozi,name))8

9

10 defproducer(name):11 c = consumer('A')12 c2 = consumer('B')13 print(dir(c))14 c.__next__()15 c2.__next__()16 print("老子开始准备做包子啦!")17 for i in range(5):18 time.sleep(1)19 c.send(i) #可以使用dir(c),查看生成器的调用方式

20 c2.send(i) #send跟baozi = yield是什么关系

21

22 producer("chen1203")23 输出:24 ['__class__', '__del__', '__delattr__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', '__gt__', '__hash__', '__init__', '__iter__', '__le__', '__lt__', '__name__', '__ne__', '__new__', '__next__', '__qualname__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__', 'close', 'gi_code', 'gi_frame', 'gi_running', 'gi_yieldfrom', 'send', 'throw']25 A 准备吃包子啦!26 B 准备吃包子啦!27 老子开始准备做包子啦!28 包子[0]来了,被[A]吃了!29 包子[0]来了,被[B]吃了!30 包子[1]来了,被[A]吃了!31 包子[1]来了,被[B]吃了!32 包子[2]来了,被[A]吃了!33 包子[2]来了,被[B]吃了!34 包子[3]来了,被[A]吃了!35 包子[3]来了,被[B]吃了!36 包子[4]来了,被[A]吃了!37 包子[4]来了,被[B]吃了!

双向运算

三、迭代器

1、迭代器定义

注意区分,可迭代对象与迭代器的区别:可以直接作用于 for 语句进行循环的对象称之为可迭代对象;可以被next()函数调用并不断返回下一个值的对象称为迭代器:Iterator。生成器一定是迭代器,但迭代器不一定是生成器。

a = [1,2]for i ina:print(i)

输出:1

2b= {"a":123,"b":456}for i inb:print(i)

输出:

a

b

由上可述,可以直接作用于for循环的数据类型有以下几种:一类是集合数据类型,如list、tuple、dict、set、str等; 一类是generator,包括生成器和带yield的generator function。可以直接作用于 for 语句进行循环的对象称之为可迭代对象。

内置的数据类型(列表、元组、字符串、字典等)可以通过 for 语句进行迭代,我们也可以自己创建一个容器,包含一系列元素,可以通过 for 语句依次循环取出每一个元素,这种容器就是 迭代器(iterator)。

创建迭代器对象的好处是当序列长度很大时,可以减少内存消耗,因为每次只需要记录一个值即刻。

2、使用isinstance()判断一个对象是否是迭代(Iterable)对象:

>>> from collections import Iterable #导入Iterable(可迭代对象模块)

>>>isinstance([],Iterable)

True>>>isinstance((),Iterable)

True>>> a = "abc"

>>> isinstance("abc",Iterable)

True>>> isinstance('b',Iterable)

True>>> isinstance(100,Iterable)

False

>>> isinstance((x for x in range(3)),Iterable)

True

使用isinstance()判断一个对象是否是迭代器:

from collections importIteratorprint(isinstance([],Iterator))print(isinstance((),Iterator))print (isinstance((x for x in range(3)),Iterator))

输出:

False

False

True

总结:由上得出,生成器、列表、元组、字符串都是可迭代对象,生成器一定是迭代器。数字是非可迭代对象。

3、除了用for遍历,迭代器还可以通过next()方法逐一读取下一个元素。要创建迭代器有3种方法,其中前两种分别是:

A、list、dict、str虽然是Iterable(可迭代对象),却不是Iterator(迭代器)。但内置函数 iter() 将可迭代对象转化为迭代器

列表迭代器

ita = iter([1,2,3,4])print(type(ita)) #列表迭代器

print(next(ita))print(next(ita))print(next(ita))

输出:

1

2

3逐一取值。注:把ita= iter([1,2,3,4])改为ita = iter((1,2,3,4)),则为元组迭代器。

B、 为容器对象添加 __iter__() 和 __next__() 方法(Python 2.7 中是 next());__iter__() 返回迭代器对象本身 self,__next__() 则返回每次调用 next() 或迭代时的元素;

classContainer:def __init__(self, start = 0, end =0):

self.start=start

self.end=enddef __iter__(self):print("[LOG] I made this iterator!")returnselfdef __next__(self):print("[LOG] Calling __next__ method!")if self.start

i=self.start

self.start+= 1

returnielse:raiseStopIteration()

c= Container(0, 3)for i inc:print(i)

输出:

[LOG] I made this iterator!

[LOG] Calling__next__method!

0

[LOG] Calling__next__method!1[LOG] Calling__next__method!2[LOG] Calling__next__ method!

总结:

凡是可作用于for循环的对象都是Iterable类型;

凡是可作用于next()函数的对象都是Iterator类型,它们表示一个惰性计算的序列(惰性计算,就是计算到的时候才打印出来,没计算到距不打印),查看迭代类型是否可以使用next(),使用dir()去判断;

集合数据类型如list、dict、str等是Iterable但不是Iterator,不过可以通过iter()函数获得一个Iterator对象。

四、内置函数

内置函数

9c7c2fa3b058f69a59657efa6f77ce0c.png

8f900a89c6347c561fdf2122f13be562.png

961ddebeb323a10fe0623af514929fc1.png

abs()#绝对值

all()#只要一个为假,都是假

any()#只要一个为真,都是真

ascii()#把一个内存形式打印成字符串的格式

bin()#一个整数,十进制转二进制

bool()#布尔值,判断真假

bytearray()

bytes()

callable()#判断可调用的信息

chr()#利用数字打印asscii码

ord()#利用asscii码取对应的数字

compile()#用于底层,把代码进行编译的工具

complex()

dict()#字典

dir()#查使用方法

divmod()#相除,返回余数

enumerate()

eval()exec()

filter()#一组数据里面,过滤你想要的数值,打印出来

如:

res= filter(lambda n:n>5,range(10))for i inres:print(i)

map()#把后面的值放给前面出来,并把结果打印

res = map(lambda n:n*n,range(10))for i inres:print(i)

reduce()#2.7版本还是可以直接调用,3版本以后都是放在模块里面了

importfunctools

res= functools.reduce(lambda x,y:x+y,range(10))print(res)

frozenset()#冻结,不可变的集合

globals()#返回当前整个程序里面所有变量的key/values模式

hash()#中文名散列,处理过程,需要找到一个固定的映射关系,通过递归的二分查找进行划分归类查找。

hex()#把一个数字转成16进制

locals()#用于函数内部本地打印

object()#对象,用类的时候使用。一个个体,就是对象。每个对象都有属于自己的属性。

oct()#八进制,逢8进1

pow()#多少次方

repr()#与ascii()一致

reversed()#反转

round()#round(1.222,2) 保留两位小数点

slice()#切片

如:d=range(10)

t= d[slice(2,5)]

sorted()#把字典排序,按key或values 来排序

如:

a= {6:2,9:0,3:4,6:5,10:7,11:22}print(sorted(a.items()))

输出:

[(3, 4), (6, 5), (9, 0), (10, 7), (11, 22)]print (sorted(a.items(),key = lambda n:n[1]))

输出:

[(9, 0), (3, 4), (6, 5), (10, 7), (11, 22)]

zip()#拉链式组合

如:

a= [1,2,3,4]

b= ["a","b","c","d"]for i inzip(a,b):print(i)

输出:

(1, 'a')

(2, 'b')

(3, 'c')

(4, 'd')__import__() #模块导入,

如:__import__("chen1203")

其他常见的内置函数不列举

关于对应内置函数理解

对应函数的官网链接:https://docs.python.org/3/library/functions.html?highlight=built#ascii

五、Json and Pickle 数据序列化

1、序列化与反序列化的作用:

有两种方式json和pickle:json适合用其他的平台,pickle只能在python中应用。pickle 与json 模块通常是将python对象序列化为二进制流或文件,计算机底层是跟的二进制进行交互的。几

可以被序列化的类型有:

* None,True 和 False;

* 整数,浮点数,复数;

* 字符串,字节流,字节数组;

* 包含可pickle对象的tuples,lists,sets和dictionaries;

* 定义在module顶层的函数:

* 定义在module顶层的内置函数;

* 定义在module顶层的类;

* 拥有__dict__()或__setstate__()的自定义类型;

注意:对于函数或类的序列化是以名字来识别的,所以需要import相应的module。

序列化与反序列化,就是在内存之间进行数据翻译。pickle与json的功能效果差不多。但json并不完全能序列化,所以出现python专用的pickle,pickle 只能在python中使用。

2、json 序列化与反序列化

A、第一步:通过序列化,把info信息从内存写到文件中底

#json 序列化

importjson

info={"name": "chenchangqing","work":"IT","number":"400-100-119"}

f= open("test.txt","w+")

f.write(json.dumps(info))#把info信息序列化

f.close()

B、第二步,查看test.txt 文件

#test.txt 文件内容的存储信息

{"number": "400-100-119", "work": "IT", "name": "chenchangqing"}

C、第三步、json 反序列化

#json 反序列化

importjson

file= open("test.txt","r")

data=json.load(file)#通过使用json.load对文件信息反序列化出来。print(data)

file.close()

3、pickle 序列化与反序列化

pickle 是python通用的包括类、字典、对象、列表都可以序列化,json \ xml 都是平台通用。

A、第一步、pickle 序列化

pickle 字典序列化 Pickle.dump操作,dumps()函数执行和dump() 函数相同的序列化。取代接受流对象并将序列化后的数据保存到磁盘文件,这个函数简单的返回序列化的数据。

#pickle 序列化语法

importpickle

info={"name": "chenchangqing","work": "IT","number": "400-100-119"}

f= open("test_pickle.txt", "wb") #由于pickle是使用二进制,所以使用wb

f.write(pickle.dumps(info))

f.close()

B、第二步、查看test_pickle 文件

#文件内容�}q(XnumberqX400-100-119qXworkqXITqXnameqX chenchangqingqu.

bba3dc0f895ebe080dcf21cb86ef4833.png

C、第三步、pickle 反序列化

pickle 字典反序列化 Pickle.load loads()函数执行和load() 函数一样的反序列化。取代接受一个流对象并去文件读取序列化后的数据,它接受包含序列化后的数据的str对象, 直接返回的对象。

#pikcel 反序列化

importpickle

file= open("test_pickle.txt","rb")

data=pickle.load(file)print(data)

file.close()

输出:

{'work': 'IT', 'name': 'chenchangqing', 'number': '400-100-119'}

总结:pickle 序列化,如下

448b40e2bad9f95e25c9cdd8589c6d28.png

六、软件目录开发规范

为什么要设计好目录结构?

设计一个层次清晰的目录结构,就是为了达到以下两点:

可读性高: 不熟悉这个项目的代码的人,一眼就能看懂目录结构,知道程序启动脚本是哪个,测试目录在哪儿,配置文件在哪儿等等。从而非常快速的了解这个项目。

可维护性高: 定义好组织规则后,维护者就能很明确地知道,新增的哪个文件和代码应该放在什么目录之下。这个好处是,随着时间的推移,代码/配置的规模增加,项目结构不会混乱,仍然能够组织良好。

目录组织方式

Foo/

|-- bin/

| |-- foo

|

|-- foo/

| |-- tests/

| | |-- __init__.py

| | |-- test_main.py

| |

| |-- __init__.py

| |-- main.py

|

|-- docs/

| |-- conf.py

| |-- abc.rst

|

|-- setup.py

|-- requirements.txt

|-- README

解析一下:

bin/: 存放项目的一些可执行文件,当然你可以起名script/之类的也行。

foo/: 存放项目的所有源代码。(1) 源代码中的所有模块、包都应该放在此目录。不要置于顶层目录。(2) 其子目录tests/存放单元测试代码; (3) 程序的入口最好命名为main.py。

docs/: 存放一些文档。

setup.py: 安装、部署、打包的脚本。

requirements.txt: 存放软件依赖的外部Python包列表。

README: 项目说明文件。

这里重点解析关于README的内容

它需要说明以下几个事项:

软件定位,软件的基本功能。

运行代码的方法: 安装环境、启动命令等。

简要的使用说明。

代码目录结构说明,更详细点可以说明软件的基本原理。

常见问题说明。七、不同目录间的模块调用

不同目录间的模块调用。使用 from  ..  import .. 的模式导入

8527ec3237a554365408fe4e417a934f.png

以上利用系统的环境变量,找到相对路径BASE_DIR ,使用from 导入模块。

八、作业

作业需求:

模拟实现一个ATM + 购物商城程序

额度 15000或自定义

实现购物商城,买东西加入 购物车,调用信用卡接口结账

可以提现,手续费5%

支持多账户登录

支持账户间转账

记录每月日常消费流水

提供还款接口

ATM记录操作日志

提供管理接口,包括添加账户、用户额度,冻结账户等。。。

用户认证用装饰器

作业规划:

8f900a89c6347c561fdf2122f13be562.png

961ddebeb323a10fe0623af514929fc1.png

shopping.py

buy

调用信用卡接口

直接扣款

ATM.py

提款

还款

查余额

查账单1

2

3

4

5允许多个账号登录

允许账号之间的登录

提供还款接口

记录操作日志

提供管理接口,包括添加账户、用户额度,冻结账户等

用户认证用装饰器

作业分步

8f900a89c6347c561fdf2122f13be562.png

961ddebeb323a10fe0623af514929fc1.png

实现ATM常用功能

功能全部用python的基础知识实现,用到了time\os\sys\json\open\logging\函数\模块知识, 主要帮给大家一个简单的模块化编程的示例

注意:只实现了"还款"和"取现功能"程序结构:

day5-atm/├── README

├── atm#ATM主程目录

│ ├── __init__.py

│ ├── bin#ATM 执行文件 目录

│ │ ├── __init__.py

│ │ ├── atm.py#ATM 执行程序

│ │ └── manage.py #ATM 管理端,未实现

│ ├── conf #配置文件

│ │ ├── __init__.py

│ │ └── settings.py

│ ├── core#主要程序逻辑都 在这个目录 里

│ │ ├── __init__.py

│ │ ├── accounts.py#用于从文件里加载和存储账户数据

│ │ ├── auth.py #用户认证模块

│ │ ├── db_handler.py #数据库连接引擎

│ │ ├── logger.py #日志记录模块

│ │ ├── main.py #主逻辑交互程序

│ │ └── transaction.py #记账\还钱\取钱等所有的与账户金额相关的操作都 在这

│ ├── db #用户数据存储的地方

│ │ ├── __init__.py

│ │ ├── account_sample.py#生成一个初始的账户数据 ,把这个数据 存成一个 以这个账户id为文件名的文件,放在accounts目录 就行了,程序自己去会这里找

│ │ └── accounts #存各个用户的账户数据 ,一个用户一个文件

│ │ └── 1234.json #一个用户账户示例文件

│ └── log #日志目录

│ ├── __init__.py

│ ├── access.log#用户访问和操作的相关日志

│ └── transactions.log #所有的交易日志

└── shopping_mall #电子商城程序,需单独实现

└── __init__.py

作业示例

作业讲解

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值