Python 装饰器,生成器,迭代器,json和pickle序列化,软件开发规范,不同目录间的调用...

本文深入探讨Python中的装饰器和生成器概念,讲解装饰器的实现原理与应用实例,介绍生成器的工作机制及如何利用生成器实现高效内存管理。

一、装饰器

1)本质就是函数,(装饰其他函数),为其他函数添加附加功能
2)原则:
1.不能修改被装饰的函数源代码
2.不能修改被装饰的函数的调用方式

实现装饰器知识储备:
1.函数即”变量“
2.高阶函数
3.嵌套函数
高阶函数+嵌套函数=装饰器

实现装饰器知识储备:

先看一个装饰器的例子,反正没学习下面的内容之前,我是看不懂:

import time
#定义装饰器
def timmer(func):
    def warrper(*args,**kwargs):
        start_time=time.time()
        func()
        stop_time=time.time()
        print("the func run time is %s"%(stop_time-start_time))
    return warrper
@timmer
def test1():
    time.sleep(3)
    print("in the test1")
test1()
View Code

如何一步一步实现装饰器:

1.函数即“变量”

有了上面的图,看下面的例子应该easy很多

 
1
2
3
4
5
def foo():
    print("in the foo")
    bar()
foo()
报错bar没有定义
1
2
3
4
5
6
7
def bar():
    print("in the bar")
def foo():
    print("in the foo")
    bar()
foo()
正常运行
1
2
3
4
5
6
7
def foo():
    print("in the foo")
    bar()
def bar():
    print("in the bar")
foo()
正常运行

运行该例子的时候,先将定义的函数foo、bar依次存储到了内存;

然后在运行foo(),调用bar是在内存中存在的,所以不会报错。

1
2
3
4
5
6
7
def foo():
    print("in the foo")
    bar()
foo()
def bar():
    print("in the bar")
会报错提示bar没有定义

执行foo(),先将foo函数存储到内存,然后就运行foo;

而此时bar函数还没有定义写到内存中,找不到bar所以会报错。

 

总结:

    定义一个函数就相当于将函数体赋值给函数名;

    定义变量/函数 是先定义,再执行;

    函数和变量一样都会被python内存回收机制回收;

    所以:函数即“变量”

 

 

1.1匿名函数

#函数没有名字,下面给函数定义了一个变量名来引用;

1
2
niming = lambda x:x*3
print (niming(3))

 

2.高阶函数

a:把一个函数名当做实参给另外一个函数;

(在不修改被装饰函数源代码的情况下为其添加功能)

b:返回值中包含函数名;

(不修改函数的调用方式)

把函数当作一个实参传递给形参。

 看一个例子:

def bar():
    print("in the bar")
def test1(func):
    print(func)
    func()
test1(bar)#等于func=bar,func()    bar是一个地址,相当门牌号func()就是bar()

稍作改变:

import time
def bar():
    time.sleep(0.3)
    print("in the bar")
def test1(func):
    start_time=time.time()
    func()
    stop_time=time.time()
    print("the func run time is %s" %(stop_time-start_time))
test1(bar)#等于func=bar,func()    bar是一个地址,相当门牌号func()就是bar()

总结:(在不修改被装饰函数源代码的情况下为其添加功能),为bar这个函数计算了执行时间,但是呢却改变了bar的调用方式,这个不是我们想要的,我们继续下一步的探索

import  time
def bar():
    time.sleep(0.3)
    print("in the bar")
def test2(func):
    print(func)
    return func
#运行1
#print(test2(bar))#最后打印的是bar的地址
#运行2
#t = test2(bar)
#t()#把bar的地址给了t,t()就是执行bar这个函数
#运行3
bar=test2(bar)
bar()#把t换成bar,就是执行bar这个函数,即没有改变函数的调用方式
#bar是原函数

总结:返回值中包含函数名; (不修改函数的调用方式),这个需要大家一步一步的执行,就比较好理解了

写一个装饰器(高阶函数)

import  time
def deco(func):
    star_time= time.time()
    return func
    stop_time=time.time()
    print("th func run time is %s"%(stop_time-star_time))
def test1():
    time.sleep(3)
    print("in the test1")
def test2():
    time.sleep(5)
    print("in the test2")
test1=deco(test1)
test1()
test2=deco(test2)
test2()
#结束时间没有运行;

虽然没有改变函数的调用方式,发现结束时间没有执行,装饰的效果没有实现,依旧是一个高阶函数

3.嵌套函数

高阶函数+嵌套函数= 装饰器

所以加上嵌套函数,就等于装饰器了,一步步终于要看到装饰器了

import time
def timmer(func):#timmer(test1)   func = test1
    def deco():
        star_time= time.time()
        func()#run test1
        stop_time=time.time()
        print("th func run time is %s"%(stop_time-star_time))
    return deco
def test1():
    time.sleep(0.3)
    print("in the test1")
test1=timmer(test1)# timmer(test1)是deco的内存地址
test1()

装饰器雏形就这样写出来了,再稍作修改,就是真正的装饰器

import time
def timmer(func):#timmer(test1)   func = test1
    def deco():
        star_time= time.time()
        func()#run test1
        stop_time=time.time()
        print("th func run time is %s"%(stop_time-star_time))
    return deco
@timmer # test1=timmer(test1)# timmer(test1)是deco的内存地址
def test1():
    time.sleep(0.3)
    print("in the test1")
test1()

再修改一点,就变成了高级的装饰器了

import time
def timmer(func):#timmer(test1)   func = test1
    def deco(*args,**kwargs):
        star_time= time.time()
        func(*args,**kwargs)#run test1
        stop_time=time.time()
        print("th func run time is %s"%(stop_time-star_time))
    return deco
@timmer # test1=timmer(test1)# timmer(test1)是deco的内存地址
def test1(*args,**kwargs):
    time.sleep(0.3)
    print("in the test1",9999)
test1(0,8,'ioio',{"name":"cathy"})

可以传递任何不固定个数和字典型的参数了

 下面就使用装饰器做个例子:

需求:网站有一些页面需要登录才能浏览,一些不需要浏览

usrename = "cathy"
password = "123abc"
def auth(func):
    def warpper(*args,**kwargs):
        user = input("input username:")
        passwd = input("input password:")
        if usrename == user and password == passwd:
            res = func(*args,**kwargs)#想要有返回值,这里也可以直接return
            print("-------center----")
            return res
        else:
            exit("wrong")
    return warpper  #而这里没有返回值;在上边return res之后就有数据了

def index():
    print("in the index")
@auth
#当home有return结果的时候,用上边的无法获取到home return的结果;wrapper加一个return;
def home():
    print("in the home")
    return "form home"
@auth
def bbs():
    print("in the home")
index()
print(home())#调用home相当于调用wrapper
bbs()

需求升级终极版装饰器,bbs需要用ldap验证方式

usrename = "cathy"
password = "123abc"
def auth(loal_type):
    def out_warp(func):
        def warpper(*args,**kwargs):
            if loal_type=="local":
                user = input("input username:")
                passwd = input("input password:")
                if usrename == user and password == passwd:
                    res = func(*args,**kwargs)#想要有返回值,这里也可以直接return
                    print("-------center----")
                    return res
                else:
                    exit("wrong")
            elif loal_type == "ldap":
                print("what is it?")
        return warpper  #而这里没有返回值;在上边return res之后就有数据了
    return out_warp
def index():
    print("in the index")
@auth(loal_type = "local")
#当home有return结果的时候,用上边的无法获取到home return的结果;wrapper加一个return;
def home():
    print("in the home")
    return "form home"
@auth(loal_type = "ldap")
def bbs():
    print("in the home")
index()
print(home())#调用home相当于调用wrapper
bbs()

 二、生成器

列表生成式:

a = [i*2 for i in range(10)]

使代码更简洁、还可以执行一个函数;

生成 :[0,2,4,6,8,12,14,16,18]

 [i*2 for i in range(10)]

相当于以下三句代码:

a = []
for i in range(10):
    a.append(i*2)

通过列表生成式,我们可以直接创一个列表。但是受到内存限制,列表容量肯定是有限的。而且创建一个包含100万个元素的列表,

不仅占用很大的存储空间,如果我们仅仅需要访问前面几个元素,那么后面绝大多数元素占用的空间是白白浪费的。

生成器定义

如果列表元素可以按照某种算法推算出来,那我们是否可以在循环的过程中不断推算出后续的元素,这样就不必创建完整的list,

从而节省大量的空间。在python中,这种一边循环一边计算的机制,称为生成器:generator。

生成器创建方法

要创建一个generator,有很多种方法。

第一种方法很简单,只要把一个列表生成式的[]改成(),就创建了一个generator:

 生成器内容深化

 生成器被调用的时候才生成所以列表元素

a= (i*2 for i in range(10))
print(a)

<generator object <genexpr> at 0x000002354C255AF0>

访问前两个数据:

a= (i*2 for i in range(10))
print(a)
print(a.__next__())
print(a.__next__())

<generator object <genexpr> at 0x00000270E78E5AF0>
0
2

生成器只记录当前位置,只存储一个数据;

只有一个__next__()方法。在python2.0 里边是next()

 取数据可以用for循环取数据;

当推算的算法比较复杂的时候,用for循环无法实现的时候,用函数来实现。

 fib例子:

先看一下斐波那契数列Fibonacci简写fib,除了第一个和第二个数外,任意一个数都可以由前两个数相加得到;

def fib(max):
    n,a,b = 0,0,1
    while n < max:
        print(b)
        a,b = b,a+b   #相当于t = (b,a+b) #t是一个tuple,a = t[0],b = t[1]
        n = n+1
    return "----done-----"
fib(10)

上面的函数和generator仅一步之遥,只需要把print(b)改成yield b 就可以了:

def fib(max):
    n,a,b = 0,0,1
    while n < max:
        yield b
        a,b = b,a+b   #相当于t = (b,a+b) #t是一个tuple,a = t[0],b = t[1]
        n = n+1
    return "----done-----"
f = fib(10)
print(f)
print(f.__next__())
print(f.__next__())
print(f.__next__())

当f.__next__取到第11个数据的时候,已经没有数据了,程序会报出一个错误done;

如果不想报错,就抓住这个异常。

def fib(max):
    n,a,b = 0,0,1
    while n < max:
        yield b
        a,b = b,a+b   #相当于t = (b,a+b) #t是一个tuple,a = t[0],b = t[1]
        n = n+1
    return "done"
g = fib(3)
print(g)
while True:
    try:
        x= next(g)
        print("g:",x)
    except StopIteration as e:
        print("generaotr return value:", e.value)
        break
print(g.__next__())
print(g.__next__())
print(g.__next__())
print(g.__next__())

通过装饰器可以实现并行效果,使单线程也可以并发。

通过yield实现在单线程的情况下实现并发运算的效果。

例子:典型的生产者消费者模型

import time
#消费者
def consumer(name):     #consumer 消费者
    print("%s 准备吃包子啦!"%name)
    while True:
        baozi = yield
        print("包子[%s]来了,被[%s]吃了!"%(baozi,name))
#c = consumer("123")    #只是变成一个生成器,而不执行;如果是函数是会被调用的。
#c.__next__()         #调用
# c.__next__()
#c.send("韭菜")
def producer(name):
    c = consumer("A")
    c2 = consumer("B")
    c.__next__()
    c2.__next__()
    print("开始准备吃包子啦!")
    for i in range(3):
        time.sleep(1)
        print("%s 做了1个包子,分两半!"%name)
        c.send(i)
        c2.send(i)
producer("Cathy")

 三、迭代器

这里面讲的迭代器是一个定义,具体使用场景后面或许用到,一般不用

可以直接作用for循环的数据类型

我们已经知道,可以直接作用for循环的数据类型有以下几种:

  一类是集合数据类型 如:list、tuple、dict、set、str等;

  一类是generator,包括生成器和带yield的generator function;

 

可迭代对象定义

这些可以直接作用for循环的对象称为可迭代对象:Iterable

 

可迭代对象判断

可以使用isinstance()判断一个对象是否是Iterable对象:

1
2
3
4
5
6
7
from collections import Iterable,Iterator
isinstance([],Iterable)
print(isinstance([],Iterable))
isinstance({},Iterable)
isinstance("abc",Iterable)
isinstance((x for in range(10)),Iterable)
isinstance(100,Iterable)    #返回False

 

迭代器定义

可以被next()函数调用并不断返回下一个值的对象称为迭代器:Iterator 。

1
2
isinstance((x for in range(10)),Iterator)
print(isinstance((x for in range(10)),Iterator))

 

把list、dict、str转成迭代器

生成器都是Iterator对象,但list、dict、str虽然是Iterable,却不是Iterator。

把list、dict、str等Iterable变成Iterator可以使用iter()函数:

1
2
3
4
isinstance(iter([]),Iterator)
print(isinstance(iter([]),Iterator))
isinstance(iter("abc"),Iterator)
print(isinstance(iter("abc"),Iterator))

 

 

为什么list、dict、str等数据类型不是Iterator?

 

这是因为python的Iterator对象表示的事一个数据流,Iterator对象可以被next()函数调用并不断返回一个序列,

但我们却不能提前知道序列的长度,只能不断通过next()函数实现按需计算下一个数据,所以Iterator的计算是惰性的,

只有在需要返回下一个数据时它才会计算。

 

Iterator甚至可以表示一个无限大的数据流,例如全体自然数。而使用list是永远不可能存储全体自然数的。

 

 

小结:

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

凡是可作用于next()函数的对象都是Iterator类型,它们表示一个惰性计算的序列;

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

 

python的for循环本质上就是通过不断调用next()函数实现的,

1
2
3
range(10)   
    python3.x 就是一个迭代器
    python2.x 就生成一个列表,要成为迭代器写成xrange(10) ;

 

例如:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
for in [1,2,3,4,5]:
    pass
就相当于下边:
-------------------
首先获得迭代器
it = iter([1,2,3,4,5])
循环
while True:
    try:
        #获得下一个值
        = next(it)
    except StopIteration:
        #遇到StopIteration
        break
----------------------

 

 

 四、json和pickle序列化
json   用于字符串和python数据类型间进行转换
pickle 用于python特有的类型 和 python的数据类型间进行转换

序列化:把字典或者字符串的内存对象 存到硬盘上;
反序列化:就是从硬盘上加载出来
 
json序列化与反序列化
序列化:把字典或者字符串的内存对象 存到硬盘上;
import json
info = {
        "name" : "aaa",
        "age" : 22
}
f = open("test.text","w",encoding="utf-8")
f.write(json.dumps(info))
f.close()

反序列化:就是从硬盘上加载出来

import json
f = open("test.text","r",encoding="utf-8")
data = json.load(f)  #data = json.loads(f.read())
print(data['age'])
f.close()

pickle序列化和反序列化

序列化:把字典或者字符串的内存对象 存到硬盘上;

# Author Cathy
import pickle
def hello(name):
        print('hello123',name)
info = {
        "name" : "aaa",
        "age" : 22,
        "new" : hello
}
f = open("test.text","wb")
f.write(pickle.dumps(info))
f.close()

反序列化:就是从硬盘上加载出来

# Author Cathy
import pickle
def hello(name):
    print('hello567', name)

f = open("test.text","rb")
data = pickle.load(f)  #data = json.loads(f.read())
print(data)#结果是{'name': 'aaa', 'age': 22, 'new': <function hello at 0x000002001C732E18>}
print(data['age'])#结果是22
##运行hello 函数,查看显示结果,这里只是表现了一种写代码的方式。
hello(data["name"])#结果是hello567 aaa
f.close()
json多次dumps和多次loads
序列化:把字典或者字符串的内存对象 存到硬盘上;
import json
info = {
        "name" : "aaa",
        "age" : 22
}
f = open("test01.text","w")
f.write(json.dumps(info))
info["age"] = 30
f.write(json.dumps(info))
f.close()
'''
test01.text内容如下:
{"name": "aaa", "age": 22}{"name": "aaa", "age": 30}
'''

反序列化:就是从硬盘上加载出来 ,在这里loads的时候会报错。

import json
f = open("test01.text","r")
data = json.loads(f.read())
print(data["age"])
f.close()

当学序列化只dumps一次,在loads就正确了 

序列化
import json
info = {
        "name" : "aaa",
        "age" : 22
}
f = open("test01.text","w")
#f.write(json.dumps(info))
info["age"] = 30
f.write(json.dumps(info))
f.close()
#反序列化
import json
f = open("test01.text","r")
data = json.loads(f.read())
print(data["age"])  #结果是:30
f.close()

总结:
json可以dumps多次,loads的时候报错;同时也不可以loads多次;
要想loads多次,就在dumps的时候dumps成不同的文件,laods不同文件。

pickle多次dumps和多次loads
序列化
import pickle
info = {
        "name" : "aaa",
        "age" : 22
}
f = open("test001.text","wb")
f.write(pickle.dumps(info))
info["age"] = 30
f.write(pickle.dumps(info))
f.close()

反序列化
f = open("test001.text","rb")
data = pickle.loads(f.read())
print(data["age"])  #结果是:22
f.close()

总结:

pickle可以dumps多次,loads的时候不报错,但得出的结果是第一次dumps的内容;同时也不可以loads多次;
要想loads多次,就在dumps的时候dumps成不同的文件,laods不同文件。


总结:

json   用于字符串和python数据类型间进行转换
pickle 用于python特有的类型 和 python的数据类型间进行转换

序列化:把字典或者字符串的内存对象 存到硬盘上;
反序列化:就是从硬盘上加载出来

当json dumps多次的时候,loads的时候报错;同时也不可以loads多次;
要想loads多次,就在dumps的时候dumps成不同的文件,laods不同文件。

当pickle dumps多次的时候,loads的时候不报错,但得出的结果是第一次dumps的内容;同时也不可以loads多次;
要想loads多次,就在dumps的时候dumps成不同的文件,laods不同文件。

 软件开发规范

1、可读性高

2、可维护性高

目录组织方式:

FOO/    项目名
                bin/    可执行文件放的目录
                        foo     可执行文件名字,当启动foo的时候调用foo/下的main.py;
                conf    配置文件
                foo/    主程序目录,源代码中的所有模块、包都应该放在此目录。
                        tests/      存放单元测试代码;
                        __init__.py  pycharm自动创建,是个空文件;
                        main.py     程序主入口,
                docs    存放文档
                        conf.py
                        abc.rst
                setup.py    安装、部署、打包的脚本。
                requirements.txt    存放软件依赖的外部Python包列表。
                README      项目说明文件。
        除此之外,有一些方案给出了更加多的内容。比如LICENSE.txt,ChangeLog.txt文件等,我没有列在这里,因为这些东西主要是项目开源的时候需要用到。

关于README的内容

这个我觉得是每个项目都应该有的一个文件,目的是能简要描述该项目的信息,让读者快速了解这个项目。
它需要说明以下几个事项:
        1.软件定位,软件的基本功能。
        2.运行代码的方法: 安装环境、启动命令等。
        3.简要的使用说明。
        4.代码目录结构说明,更详细点可以说明软件的基本原理。
        5.常见问题说明。
在软件开发初期,由于开发过程中以上内容可能不明确或者发生变化,并不是一定要在一开始就将所有信息都补全。但是在项目完结的时候,是需要撰写这样的一个文档的。
可以参考Redis源码中Readme的写法,这里面简洁但是清晰的描述了Redis功能和源码结构。

关于requirements.txt和setup.py

setup.py

一般来说,用setup.py来管理代码的打包、安装、部署问题。业界标准的写法是用Python流行的打包工具setuptools来管理这些事情。
这种方式普遍应用于开源项目中。不过这里的核心思想不是用标准化的工具来解决这些问题,而是说,
一个项目一定要有一个安装部署工具,能快速便捷的在一台新机器上将环境装好、代码部署好和将程序运行起来。
setuptools的文档比较庞大,刚接触的话,可能不太好找到切入点。学习技术的方式就是看他人是怎么用的,
可以参考一下Python的一个Web框架,flask是如何写的: setup.py  https://github.com/mitsuhiko/flask/blob/master/setup.py
当然,简单点自己写个安装脚本(deploy.sh)替代setup.py也未尝不可。

requirements.txt

这个文件存在的目的是:

方便开发者维护软件的包依赖。将开发过程中新增的包添加进这个列表中,避免在setup.py安装依赖时漏掉软件包。
方便读者明确项目使用了哪些Python包。
这个文件的格式是每一行包含一个包依赖的说明,通常是flask>=0.10这种格式,要求是这个格式能被pip识别,
这样就可以简单的通过 pip install -r requirements.txt来把所有Python包依赖都装好了。具体格式说明: 点这里。
https://pip.readthedocs.org/en/1.1/requirements.html

关于配置文件的使用方法

注意,在上面的目录结构中,没有将conf.py放在源码目录下,而是放在docs/目录下。
        很多项目对配置文件的使用做法是:

        配置文件写在一个或多个python文件中,比如此处的conf.py。
        项目中哪个模块用到这个配置文件就直接通过import conf这种形式来在代码中使用配置。
        这种做法不太好的方面:
                这让单元测试变得困难(因为模块内部依赖了外部配置)
                另一方面配置文件作为用户控制程序的接口,应当可以由用户自由指定该文件的路径。
                程序组件可复用性太差,因为这种贯穿所有模块的代码硬编码方式,使得大部分模块都依赖conf.py这个文件。
        所以,更好的方式是,
                模块的配置都是可以灵活配置的,不受外部配置文件的影响。
                程序的配置也是可以灵活控制的。
                能够佐证这个思想的是,用过nginx和mysql的同学都知道,nginx、mysql这些程序都可以自由的指定用户配置。

        所以,不应当在代码中直接import conf来使用配置文件。上面目录结构中的conf.py,是给出的一个配置样例,
        不是在写死在程序中直接引用的配置文件。可以通过给main.py启动参数指定配置路径的方式来让程序读取配置内容。
        当然,这里的conf.py你可以换个类似的名字,比如settings.py。或者你也可以使用其他格式的内容来编写配置文件,
        比如settings.yaml之类的。

 

不同目录间模块调用
import os
import sys
BASE_DIR= os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
sys.path.append(BASE_DIR)
from xxx import xxx

 

 

 

 
 

转载于:https://www.cnblogs.com/cathy-cc/p/8796154.html

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值