Python学习笔记 day4 基础4

1.列表生成式,迭代器&生成器

列表生成式

列表生成式:a = [i+1 for i in range(10)]
列表[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
[1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
>>> a = b
>>> a
[1, 2, 3, 4, 5, 6, 7, 8, 9, 10]

第二种:
a = [1,3,4,6,7,7,8,9,11]
for index,i in enumerate(a):
    a[index] +=1
print(a)

第三种:
>>> a
[1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
>>> a = map(lambda x:x+1, a)
>>> a
<map object at 0x101d2c630>
>>> for i in a:print(i)
... 
3
5
7
9
11

第四种:列表生成式
>>> a = [i+1 for i in range(10)]
>>> a
[1, 2, 3, 4, 5, 6, 7, 8, 9, 10]

生成器
通过列表生成式,我们可以直接创建一个列表。但是,受到内存限制,列表容量肯定是有限的。而且,创建一个包含100万个元素的列表,不仅占用很大的存储空间,如果我们仅仅需要访问前面几个元素,那后面绝大多数元素占用的空间都白白浪费了。

所以,如果列表元素可以按照某种算法推算出来,那我们是否可以在循环的过程中不断推算出后续的元素呢?这样就不必创建完整的list,从而节省大量的空间。在Python中,这种一边循环一边计算的机制,称为生成器:generator。

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

>>> L = [x * x for x in range(10)]
>>> L
[0, 1, 4, 9, 16, 25, 36, 49, 64, 81]
>>> g = (x * x for x in range(10))
>>> g
<generator object <genexpr> at 0x1022ef630>

创建L和g的区别仅在于最外层的[]和(),L是一个list,而g是一个generator。

我们可以直接打印出list的每一个元素,但我们怎么打印出generator的每一个元素呢?

如果要一个一个打印出来,可以通过next()函数获得generator的下一个返回值: next(g)
每次调用next(g),就计算出g的下一个元素的值,直到计算到最后一个元素,没有更多的元素时,抛出StopIteration的错误。

不断调用next(g)实在是太变态了,正确的方法是使用for循环,因为generator也是可迭代对象:

>>> g = (x * x for x in range(10))
>>> for n in g:
...     print(n)
...
0
1
4
9
16
25
36
49
64
81


生成器

生成器,只有在调用时才会生成相应的数据
只记录当前位置,只有一个next()方法

函数 实现生成器
斐波拉契数列,除第一个和第二个数外,任意一个数都可由前两个数相加的得到
1,1,2,3,5,8,13,21,34,…

斐波拉契数列用列表生成式写不出来,但是,用函数把它打印出来却很容易:

def fib(max):
    n, a, b = 0, 0, 1
    while n < max:
        print(b)
        a, b = b, a + b
        n = n + 1
    return 'done'

注意,赋值语句:a, b = b, a + b
相当于

t = (b, a + b) # t是一个tuple
a = t[0]
b = t[1]

调用

>>> fib(10)
1
1
2
3
5
8
13
21
34
55

仔细观察,可以看出,fib函数实际上是定义了斐波拉契数列的推算规则,可以从第一个元素开始,推算出后续任意的元素,这种逻辑其实非常类似generator。

也就是说,上面的函数和generator仅一步之遥。要把fib函数变成generator,只需要把print(b)改为yield b就可以了

def fib(max):
    n,a,b = 0,0,1

    while n < max:
        #print(b)
        yield  b
        a,b = b,a+b

        n += 1
	return 'done'

这就是定义generator的另一种方法。如果一个函数定义中包含yield关键字,那么这个函数就不再是一个普通函数,而是一个generator:

f = fib(6)
print(f.__next__())   结果为:1
print(f) 结果为:<generator object fib at 0x104feaaa0>

这里,最难理解的就是generator和函数的执行流程不一样。函数是顺序执行,遇到return语句或者最后一行函数语句就返回。而 变成generator的函数,在每次调用next()的时候执行,遇到yield语句返回,再次执行时从上次返回的yield语句处继续执行。 调用一次才执行一次。想用就用,中间还可以插入别的语句

data = fib(10)
print(data)

print(data.__next__())
print(data.__next__())
print("干点别的事")
print(data.__next__())
print(data.__next__())
print(data.__next__())
print(data.__next__())
print(data.__next__())

#输出
<generator object fib at 0x101be02b0>
1
干点别的事
3
8

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

同样的,把函数改成generator后,我们基本上从来不会用next()来获取下一个返回值,而是直接使用for循环来迭代:
 

>>> for n in fib(6):
...     print(n)
...
1
3
8

但是用for循环调用generator时,发现拿不到generator的return语句的返回值。如果想要拿到返回值,必须捕获StopIteration错误,返回值包含在StopIteration的value中:
取的数据多于实际数据,如何更友好?

def fib(max):
    n, a, b = 0, 0, 1
    while n < max:
        # print(b)
        yield b
        a, b = b, a + b
        n += 1
    return 'done'

g = fib(6)
while True:
    try:
        x = next(g)
        print('g:', x)
    except StopIteration as e:
        print('Generator return value:', e.value)
        break
g: 1
g: 1
g: 2
g: 3
g: 5
g: 8
Generator return value: done

next回到 生成器函数里的 yield位置,即之前中断的位置,只是唤醒 (第一次是回到生成器函数,从头开始执行到yield,第二次是回到yield)
yield 是 中断,返回到外面执行外面的,当外面遇到next又继续执行之前的。即保存当前状态,等回到yield又可以执行,通过send可以传值,唤醒生成器然后传值,成为当前yield表示式结果
 
还可通过yield实现在单线程的情况下实现并发运算的效果 单线程下利用生成器并发运算

#_*_coding:utf-8_*_
__author__ = 'Alex Li'

import time
def consumer(name):
    print("%s 准备吃包子啦!" %name)
    while True:
       baozi = yield
       print("包子[%s]来了,被[%s]吃了!" %(baozi,name))

def producer(name):
    c = consumer('A')
    c2 = consumer('B')
    c.__next__()
    c2.__next__()
    print("老子开始准备做包子啦!")
    for i in range(10):
        time.sleep(1)
        print("做了2个包子!")
        c.send(i)   # 发送过去就是yield接收到,然后yield又赋值给baozi
        c2.send(i)

producer("果子哥")

通过生成器实现协程并行运算,两个消费者,一个厨师。三个任务看得像是并行

结果:
A 准备吃包子啦!
B 准备吃包子啦!
老子开始准备做包子啦!
做了2个包子!
包子[0]来了,[A]吃了!
包子[0]来了,[B]吃了!
。。。。。。。。。。。

迭代器

可以直接作用于for循环的数据类型有以下几种:
一类是集合数据类型,如list、tuple、dict、set、str等;
一类是generator,包括生成器和带yield的generator function。
这些可以直接作用于for循环的对象统称为可迭代对象:Iterable
可以使用isinstance()判断一个对象是否是Iterable对象:

>>> from collections import Iterable
>>> isinstance([], Iterable)
True
>>> isinstance({}, Iterable)
True
>>> isinstance('abc', Iterable)
True
>>> isinstance((x for x in range(10)), Iterable)
True
>>> isinstance(100, Iterable)
False

而生成器不但可以作用于for循环,还可以被next()函数不断调用并返回下一个值,直到最后抛出StopIteration错误表示无法继续返回下一个值了。

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

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

>>> from collections import Iterator
>>> isinstance((x for x in range(10)), Iterator)
True
>>> isinstance([], Iterator)
False
>>> isinstance({}, Iterator)
False
>>> isinstance('abc', Iterator)
False

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

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

>>> isinstance(iter([]), Iterator)
True
>>> isinstance(iter('abc'), Iterator)
True

为什么list、dict、str等数据类型不是Iterator?
这是因为Python的Iterator对象表示的是一个数据流,Iterator对象可以被next()函数调用并不断返回下一个数据,直到没有数据时抛出StopIteration错误。可以把这个数据流看做是一个有序序列,但我们却不能提前知道序列的长度,只能不断通过next()函数实现按需计算下一个数据,所以Iterator的计算是惰性的,只有在需要返回下一个数据时它才会计算。

Iterator甚至可以表示一个无限大的数据流,例如全体自然数。而使用list是永远不可能存储全体自然数的。
  
小结
1、凡是可作用于for循环的对象都是Iterable类型;
2、凡是可作用于next()函数的对象都是Iterator类型,它们表示一个惰性计算的序列;
3、集合数据类型如list、dict、str等是Iterable但不是Iterator,但可以通过iter()函数获得一个Iterator对象。
 
Python的for循环本质上就是通过不断调用next()函数实现的,例如:

for x in [1, 2, 3, 4, 5]:
    pass

实际上完全等价于:

# 首先获得Iterator对象:
it = iter([1, 2, 3, 4, 5])
# 循环:
while True:
    try:
        # 获得下一个值:
        x = next(it)
    except StopIteration:
        # 遇到StopIteration就退出循环
        break

 

2.装饰器

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

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

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

def logger():
	print('logging')
def test1():
	pass
	logger()
def test2():
	pass
	logger()

1.函数即“变量”
匿名函数:lambda x:x*3
没有函数名,会立马被回收

calc = lambda x:x*3 跟变量,就有内存回收机制,如果没有引用变量,就立马回收

变量的在使用的时候又两步:
第一步:定义
第二步:调用
在调用前就被Python解释了,解释了内存就存在,内存存在就立马能用了。

函数就像是变量,函数也是先定义再调用

2.高阶函数
a:把一个函数名当做实参传给另外一个函数(在不修改被装饰函数源代码的情况下为其添加功能)
b:返回值中包含函数名(不修改函数的调用方式)

import time
def bar():
	time.sleep(3)
	print('in the bar')
def test2(func):
	print(func)
	return func
print(test2(bar))

结果:
<function bar at 0x0000027AB0C57C18>
<function bar at 0x0000027AB0C57C18>

test2(bar)  传的是 bar内存地址
test2(bar())  传的是 bar的返回值

import time
def bar():
	time.sleep(3)
	print('in the bar')
def test2(func):
	print(func)
	return func
bar=test2(bar)
bar()
结果:
<function bar at 0x000001FDCFDB7C18>
in the bar

3.嵌套函数

def foo():
    print('in the foo')
    def bar(): # 具有局部变量的特性,局部变量只能在内部调用
        print('in the bar')
    bar()

foo()

结果:
in the foo
in the bar

 

装饰器 统计两个函数的运行时间
不能改变test代码,也没有改变test调用方式
普通版本:无参数

# 装饰器 统计两个函数的运行时间
# 前提是不能改变调用方式
# 如何给test1 在不修改源代码的情况下新增功能
import time

# 把func写外面;内嵌函数找不到变量就会去外面找,所以不会报错,参数也会传递到内嵌的函数,也就是deco里面去
def timer(func):  # timer(test1)  func=test1
    def deco():
        start_time = time.time()
        func()  #run test1
        stop_time = time.time()
        print("the func run time is %s" % (stop_time-start_time))
    return deco   # 返回这个函数的内存地址

@timer   # 等同于test1 = timer(test1) = deco,@timer,想要给哪个函数新增功能,就写在哪个函数头部
# 可以通过debug断点测试,他第二次是跳过到这执行下一步,而不是执行第二次。
def test1():
    time.sleep(3)
    print('in the test1')

@timer
def test2():
    time.sleep(3)
    print('in the test2')

# print(timer(test1)) 返回得到deco内存地址
# <function timer.<locals>.deco at 0x000002301A283318>
# @timer 等同于 test1 = timer(test1) = deco
test1()  # 实际上执行(调用)的是deco()
test2()
# 从而没有改变test代码,也没有改变test调用方式


结果:
in the test1
the func run time is 3.000699996948242
in the test2
the func run time is 3.0007083415985107

 
中级版本:有参数
通用装饰器:无论被装饰的函数有几个参数都OK

import time

def timer(func):  # timer(test1)  func=test1
    def deco(*args,**kwargs):
        start_time = time.time()
        func(*args,**kwargs)
        stop_time = time.time()
        print("the func run time is %s" % (stop_time-start_time))
    return deco  

@timer 
def test1():
    time.sleep(3)
    print('in the test1')

@timer
# @timer 等同于 test1 = timer(test1) = deco 
# test2() = deco()    test2(name) = deco(name)
# 传多个参数呢,因为要装饰的函数可能是各种参数
# test1不需要参数,test2需要参数,这怎么整
def test2(name,age):
    time.sleep(3)
    print('test2:',name,age)

# print(timer(test1)) 返回得到deco内存地址
# <function timer.<locals>.deco at 0x000002301A283318>
test1()  # 实际上执行的是deco
test2("大B",22)

结果:
in the test1
the func run time is 3.000938653945923
test2: 大B 22
the func run time is 3.0001702308654785

 
高级版本:装饰器本身带参数的

user,passwd ='abc','abc123'
def auth(auth_type):
    print("auth func:",auth_type)
    def outer_wrapper(func):
        def wrapper(*args, **kwargs):
            # print("wrapper func args:", *args, **kwargs)
            if auth_type == 'local':
                username = input("Username:").strip()
                password = input("Password:").strip()
                if user == username and passwd == password:
                    print("\033[32;1m用户通过验证\033[0m")
                    res = func(*args, **kwargs)
                    print("-----本地验证成功------")
                    return res
                else:
                    exit("\033[32;1m验证失败\033[0m")
            elif auth_type == 'ldap':
                print("省略不写了,输入用户和密码,然后判断远程认证")
        return wrapper
    return outer_wrapper
def index():
    print("welcome to index page")

# home = wrapper  现在有括号 ,等于 wrapper()
@auth(auth_type="local")  # 本地认证
def home():
    print("welcome to home page")
    return "welcome home"  # 如何获取home的return,是因为func的返回 return res

@auth(auth_type="ldap")  # 远程ldap
def bbs():
    print("welcome to bbs page")

index()
print(home())  # 调用home() 相当于调用 wrapper()
bbs()

结果:
auth func: local
auth func: ldap
welcome to index page
Username:abc
Password:abc123
用户通过验证
welcome to home page
-----本地验证成功------
welcome home
省略不写了,输入用户和密码,然后判断远程认证

 

3.Json & pickle 数据序列化

放在day 5 的模块学习里详解
dump 两次就写两次,load只能一次

json
Java认识json

json主要用于不同语言进行数据交互
函数对json太复杂了,只支持 列表,数组,字符串等

import json
# json序列化

info = {
    'name':'Bin',
    'age':22
}
f = open("test.txt","w")
# f.write(str(info))
print(info)
f.write(json.dumps(info))
print(json.dumps(info))
f.close()
# 字典是不能直接写到文件的
# eval是取 可以将字典 转为字符串存到文件, 这里用的是json

# 反序列化
f1 = open("test.txt","r")
data1 = json.loads(f1.read())
print(data1)
print(data1["age"])
f1.close()

pickle 二进制
pickle只有在本语言上用

import pickle

def sayhi(name):
    print("hello,",name)
info = {
    'name':'Bin',
    'age':22,
    'func':sayhi  # 返回内存地址
}
f = open("test2.txt","wb")
# f.write(str(info))
print(info)
# f.write(pickle.dumps(info))
# 等同于
pickle.dump(info,f)
print(pickle.dumps(info))
f.close()
# eval是取 将字典 转为 字符串

# 反序列化
f1 = open("test2.txt","rb")
# data1 = pickle.loads(f1.read())
# 等同于
data1 = pickle.load(f1)
print(data1)
print(data1["age"])
f1.close()

 

4.软件目录结构规范

软件目录结构规范的作用:
可读性高: 不熟悉这个项目的代码的人,一眼就能看懂目录结构,知道程序启动脚本是哪个,测试目录在哪儿,配置文件在哪儿等等。从而非常快速的了解这个项目。
可维护性高: 定义好组织规则后,维护者就能很明确地知道,新增的哪个文件和代码应该放在什么目录之下。这个好处是,随着时间的推移,代码/配置的规模增加,项目结构不会混乱,仍然能够组织良好。

假设你的项目名为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的内容
这个我觉得是每个项目都应该有的一个文件,目的是能简要描述该项目的信息,让读者快速了解这个项目。

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

软件定位,软件的基本功能。
运行代码的方法: 安装环境、启动命令等。
简要的使用说明。
代码目录结构说明,更详细点可以说明软件的基本原理。
常见问题说明。

我觉得有以上几点是比较好的一个README。在软件开发初期,由于开发过程中以上内容可能不明确或者发生变化,并不是一定要在一开始就将所有信息都补全。但是在项目完结的时候,是需要撰写这样的一个文档的。

setup.py
https://www.cnblogs.com/maociping/p/6633948.html
https://jingyan.baidu.com/article/e6c8503cb35b79e54f1a18db.html

一般来说,用setup.py来管理代码的打包、安装、部署问题。业界标准的写法是用Python流行的打包工具setuptools来管理这些事情。这种方式普遍应用于开源项目中。不过这里的核心思想不是用标准化的工具来解决这些问题,而是说,一个项目一定要有一个安装部署工具,能快速便捷的在一台新机器上将环境装好、代码部署好和将程序运行起来。

setup.py可以将这些事情自动化起来,提高效率、减少出错的概率。"复杂的东西自动化,能自动化的东西一定要自动化。"是一个非常好的习惯。

requirements.txt
这个文件存在的目的是:
方便开发者维护软件的包依赖。将开发过程中新增的包添加进这个列表中,避免在setup.py安装依赖时漏掉软件包。
方便读者明确项目使用了哪些Python包。

 

5.作业:ATM项目开发

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

1.额度 自定义
2.实现购物商城,买东西加入 购物车,调用信用卡接口结账
3.可以提现,手续费5%
4.打印指定日期间购物清单
5.支持多账户
6.支持账户间转账
7.记录每月日常消费流水
8.提供还款接口
9.ATM记录操作所有日志
10.提供管理接口,包括查询账户、添加账户、注销账户,冻结解冻账户等。。。
11.用户认证用装饰器

示例代码 https://github.com/triaquae/py3_training/tree/master/atm

简易流程图:https://www.processon.com/view/link/589eb841e4b0999184934329

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值