Python程序软件目录规范化

空心菜 发表了文章 0 个评论 2752 次浏览 2016-09-21 15:18 来自相关话题

为什么要设计好目录结构? "设计项目目录结构",就和"代码编码风格"一样,属于个人风格问题。对于这种风格上的规范,一直都存在两种态度: 一类同学认为,这种个人风格问题"无关紧要"。理由是能让程序work就好,风格问题根本不 ...查看全部


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


"设计项目目录结构",就和"代码编码风格"一样,属于个人风格问题。对于这种风格上的规范,一直都存在两种态度:
  1. 一类同学认为,这种个人风格问题"无关紧要"。理由是能让程序work就好,风格问题根本不是问题;
  2. 另一类同学认为,规范化能更好的控制程序结构,让程序具有更高的可读性。

 
我是比较偏向于后者的,因为我是前一类同学思想行为下的直接受害者。我曾经维护过一个非常不好读的项目,其实现的逻辑并不复杂,但是却耗费了我非常长的时间去理解它想表达的意思。从此我个人对于提高项目可读性、可维护性的要求就很高了。"项目目录结构"其实也是属于"可读性和可维护性"的范畴,我们设计一个层次清晰的目录结构,就是为了达到以下两点:
  1. 可读性高: 不熟悉这个项目的代码的人,一眼就能看懂目录结构,知道程序启动脚本是哪个,测试目录在哪儿,配置文件在哪儿等等。从而非常快速的了解这个项目。
  2. 可维护性高: 定义好组织规则后,维护者就能很明确地知道,新增的哪个文件和代码应该放在什么目录之下。这个好处是,随着时间的推移,代码/配置的规模增加,项目结构不会混乱,仍然能够组织良好。

 
所以,保持一个层次清晰的目录结构是有必要的。更何况组织一个良好的工程目录,其实是一件很简单的事儿。
 


目录组织方式


关于如何组织一个较好的Python工程目录结构,已经有一些得到了共识的目录结构。在Stackoverflow关于这个问题 ,可以看到很多赞同对Python目录结构规范的情况。

假设你的项目名为foo, 我比较建议的最方便快捷目录结构这样就足够了:
Foo/
|-- bin/
| |-- foo
|
|-- foo/
| |-- tests/
| | |-- __init__.py
| | |-- test_main.py
| |
| |-- __init__.py
| |-- main.py
|
|-- docs/
| |-- conf.py
| |-- abc.rst
|
|-- setup.py
|-- requirements.txt
|-- README
简要解释一下:
  1. bin/: 存放项目的一些可执行文件,当然你可以起名script/之类的也行。
  2. foo/: 存放项目的所有源代码。(1) 源代码中的所有模块、包都应该放在此目录。不要置于顶层目录。(2) 其子目录tests/存放单元测试代码; (3) 程序的入口最好命名为main.py。
  3. docs/: 存放一些文档。
  4. setup.py: 安装、部署、打包的脚本。
  5. requirements.txt: 存放软件依赖的外部Python包列表。
  6. README: 项目说明文件。

 
除此之外,有一些方案给出了更加多的内容。比如LICENSE.txt,ChangeLog.txt文件等,我没有列在这里,因为这些东西主要是项目开源的时候需要用到。如果你想写一个开源软件,目录该如何组织,可以参考开源Python项目的正确方法 。


关于README的内容 


这个我觉得是每个项目都应该有的一个文件,目的是能简要描述该项目的信息,让读者快速了解这个项目。
 
它需要说明以下几个事项:
  1. 软件定位,软件的基本功能。
  2. 运行代码的方法: 安装环境、启动命令等。
  3. 简要的使用说明。
  4. 代码目录结构说明,更详细点可以说明软件的基本原理。
  5. 常见问题说明。

我觉得有以上几点是比较好的一个README。在软件开发初期,由于开发过程中以上内容可能不明确或者发生变化,并不是一定要在一开始就将所有信息都补全。但是在项目完结的时候,是需要撰写这样的一个文档的。
 
可以参考Redis源码中Readme的写法,这里面简洁清晰的描述了Redis功能和源码结构。


关于requirements.txt和setup.py


setup.py
 
一般来说,用setup.py来管理代码的打包、安装、部署问题。业界标准的写法是用Python流行的打包工具setuptools 来管理这些事情。这种方式普遍应用于开源项目中。不过这里的核心思想不是用标准化的工具来解决这些问题,而是说,一个项目一定要有一个安装部署工具,能快速便捷的在一台新机器上将环境装好、代码部署好和将程序运行起来。
 
我想大多数人是踩过坑的,刚开始接触Python写项目的时候,安装环境、部署代码、运行程序这个过程全是手动完成,遇到过以下问题:
  1. 安装环境时经常忘了最近又添加了一个新的Python包,结果一到线上运行,程序就出错了。
  2. Python包的版本依赖问题,有时候我们程序中使用的是一个版本的Python包,但是官方的已经是最新的包了,通过手动安装就可能装错了。
  3. 如果依赖的包很多的话,一个一个安装这些依赖是很费时的事情。
  4. 新同学开始写项目的时候,将程序跑起来非常麻烦,因为可能经常忘了要怎么安装各种依赖。

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

setuptools的文档比较庞大,刚接触的话,可能不太好找到切入点。学习技术的方式就是看他人是怎么用的,可以参考一下Python的一个Web框架,flask是如何写的:setup.py 。
 
当然,简单点自己写个安装脚本(deploy.sh)替代setup.py也未尝不可。


requirements.txt


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

这个文件的格式是每一行包含一个包依赖的说明,通常是flask>=0.10这种格式,要求是这个格式能被pip识别,这样就可以简单的通过 pip install -r requirements.txt来把所有Python包依赖都装好了。具体格式说明参考 。
 
关于配置文件的使用方法
注意,在上面的目录结构中,没有将conf.py放在源码目录下,而是放在docs/目录下。
 
很多项目对配置文件的使用做法是:
  1. 配置文件写在一个或多个python文件中,比如此处的conf.py。
  2. 项目中哪个模块用到这个配置文件就直接通过import conf这种形式来在代码中使用配置。

 
这种做法我不太赞同:
  1. 这让单元测试变得困难(因为模块内部依赖了外部配置)
  2. 另一方面配置文件作为用户控制程序的接口,应当可以由用户自由指定该文件的路径。
  3. 程序组件可复用性太差,因为这种贯穿所有模块的代码硬编码方式,使得大部分模块都依赖conf.py这个文件。

 
所以,我认为配置的使用,更好的方式:
  1. 模块的配置都是可以灵活配置的,不受外部配置文件的影响。
  2. 程序的配置也是可以灵活控制的。

 
能够佐证这个思想的是,用过nginx和mysql的同学都知道,nginx、mysql这些程序都可以自由的指定用户配置。

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

Python编码解析

空心菜 发表了文章 0 个评论 3930 次浏览 2016-09-09 17:38 来自相关话题

一般我们在Python2.7的环境进行Python的编程的时候,一般头部会加#-*- coding:utf-8 -*- ​来声明编码类型为utf-8的编码,那为什么要声明,一定要声明吗? 针对如上问题我们先来讨论另外一个问题,为什么我 ...查看全部
一般我们在Python2.7的环境进行Python的编程的时候,一般头部会加#-*- coding:utf-8 -*- ​来声明编码类型为utf-8的编码,那为什么要声明,一定要声明吗?

针对如上问题我们先来讨论另外一个问题,为什么我们可以在显示器上能看到这些文字、数字、图片、字符、等等信息呢?大家都知道计算机本身只能识别 0  1 的组合,他们是怎么展示这些内容的呢?我们怎么和计算机去沟通呢?

如果我们使用0 1 的组合和计算机沟通你还能看到这些内容吗?还有一个问题就是01的组合我相信对于常人类都是没有办法看懂的。

那怎么办?如何让计算机理解我们的语言,并且我们能理解计算机的语言呢?

举个比较形象的例子,中英文词典对照表,这样我们就可以把中英文进行互相的翻译了呢?对不对!同理计算机也是这样的他需要一个标准的对照关系,那么这个标准最早叫什么呢?ASCII表
ascii.gif

表格内容大致如下:
有特殊符号、大写字母、小写字母、数字(这里注意下0~9的数字是字符),在这些字符左边都有一个10进制的数字。但是对于10进制来说计算机他也是不能理解的,因为他只能理解0 1 ,但是10进制和2进制的转换就非常容易了!

举例来说:如果我在键盘上按一个A字母的时候那么实际是给计算机传输了一个数字65,通过这样的机制和计算机沟通,有了这个ASCII码表就可以和任何计算机进行沟通了。

这里在看个知识点:计算机中最小的单位是什么?bit   bit就咱们常说一位二进制,一位二进制要么是0 要么是 1

但是bit这个单位太小了,我们用字节(byte)来表示。他们是有换算的规则的(看下面的规则我想大家都不是很陌生对吧):
8b = 1B  #小b=bit ; 大B=byte
1024B = 1KB
1024KB = 1M
1024M = 1G
1024G = 1T
在存储英文的时候我们至少需要1个字节(一个字母),就是8位(bit),看下ASCII表中1个字节就可以表示所有的英文所需要的字符,是不非常高效!

为什么呢?早期的计算机的空间是非常宝贵的!

那你会发现1个字节8位,他能存储的最大数据是2的8次方-1 = 255,一个字节最多能表示255个字符 那西方国家他们使用了127个字符,那么剩下字符是做什么的呢?就是用来做扩展的,西方人考虑到还有其他国家。所以留下了扩展位。

但是呢有问题,计算机是西方人发明的,如果仅仅支持英文的话,这127个字符完全就可以表示所有英文中能用的的内容了。但是他没有考虑咱们大中国啊!ASCII到了中国之后发现:咱们中国最常用的中文都有6000多个完全不够用啊!

但是怎们办?中国人非常聪明:就在原有的扩展位中,扩展出自己的gbk、gb2312、gb2318字符编码。 

他是怎么扩展的呢?比如说在ASCII码中的128这个位置,这个位置又指定一张单独表,聪明吧! 其他国家也是这样设计的!

中国东亚大国是吧,我们国家比较NB,我要兼容其他国家的常用的编码!比如韩国日本,因为韩国和日本人家都有自己的编码,人家根本就不鸟你,举个例子来说,比如韩国的游戏,在中国下载安装之后会出现乱码的情况?什么鬼?

这种乱码的出现基本上就两种情况:
1、字符编码没有
2、字符编码冲突了,人家在写这个程序的时候指定的字符集和咱们使用的字符集的位置不对。 

你想想不光是亚洲国家这样,欧洲国家,非洲国家都会存在这个问题,基于这个乱象国际互联网组织就说你们各个国家都别搞了,我们给你们搞一个统一的,这个统一的是什么呢Unicode“万国编码”

Unicode(统一码、万国码、单一码)是一种在计算机上使用的字符编码。Unicode 是为了解决传统的字符编码方案的局限而产生的,它为每种语言中的每个字符设定了统一并且唯一的二进制编码,规定虽有的字符和符号最少由 16 位来表示(2个字节),即:2 **16 = 65536, 注:此处说的的是最少2个字节,可能更多。

这里还有个问题:使用的字节增加了,那么造成的直接影响就是使用的空间就直接翻倍了!举例还说:同样是ABCD这些字符存储一篇相同的文章,使用ASCII码如果是1M的话,那么Unicode存储至少2M可能还会更多。

为了解决个问题就出现了:UTF-8编码

UTF-8编码:是对Unicode编码的压缩和优化,他不再使用最少使用2个字节,而是将所有的字符和符号进行分类:ascii码中的内容用1个字节保存、欧洲的字符用2个字节保存,东亚的字符用3个字节保存...

通过这种可扩展的方式来存储。

OK 上面了解了:
1、什么ASCII编码
2、什么Unicode编码
3、什么UTF-8编码

回顾下乱码的出现原因:1、没有字符集 2、字符集冲突

回过头来看下为什么需要在第二行加上指定编码呢?在2.x版本的Python中Pyton在解释.py文件的时候,默认是给他一个编码的就是ASCII码,so如果在2.7版本中如果你不指定编码并且在.py文件中写了一个ASCII码中没有的字符就会显示乱码 。

不过这个问题在Python3中就不存在了,因为在Python3中默认就是Unicode编码。


Python编码转换


有一个问题,既然有统一的Unicode编码了,为毛还需要编码转换?大家都统一一个编码不就可以了吗?

不要问我为什么,我问你们个问题,如果世界上出了一种世界语言,你会放弃中文吗?去使用这个世界通用语言吗?这就是个坑,是个遗留问题。

但是虽然以后可能世界语言会慢慢替代咱们常用的语言,大家以后沟通就使用世界语言就不会有沟通障碍了对吧。(就是举个例子)

还有一个情况是什么呢?韩国的游戏到中国来之后,是乱码?结合上一个回答咱们可以猜出:编写这个游戏的人在编写游戏的时候可能根本就没有考虑出口其他国家。那如果没有这个Unicode编码的话,到咱们这里来显示肯定是乱码是吧。

那就得需要通过转码把他们编码集,转换为Unicode(utf-8)编码集。这样他们就可以正常显示韩文了!(这里只是转编码集并不是翻译成中文不要弄混了~~!)

Python3中的编码转换
#在Python3中默认就是unicode编码
#!/usr/bin/env python3
# _*_coding:utf-8_*_
# Author: Lucky.chen

tim = '华仔'
#转为UTF-8编码
print(tim.encode('UTF-8'))

#转为GBK编码
print(tim.encode('GBK'))

#转为ASCII编码(报错为什么?因为ASCII码表中没有‘华仔’这个字符集~~)
print(tim.encode('ASCII'))
EncodeError.png

 
二、Python2.X中的编码转换
#因为在python2.X中默认是ASCII编码,你在文件中指定编码为UTF-8,但是UTF-8如果你想转GBK的话是不能直接转,需要Unicode做一个中间人转换角色。
decode.png

#!/usr/bin/env python2
# _*_coding:utf-8_*_
# Author: Lucky.chen

import sys
print(sys.getdefaultencoding())


msg = "我爱北京天安门"
msg_gb2312 = msg.decode("utf-8").encode("gb2312")
gb2312_to_gbk = msg_gb2312.decode("gbk").encode("gbk")

print(msg)
print(msg_gb2312)
print(gb2312_to_gbk)

Python3中异常处理常用的三种方法

小白菜 发表了文章 0 个评论 9332 次浏览 2016-08-22 23:13 来自相关话题

一般我们我们想捕获python的异常写入到log,做处理的话,一般用try语句来做处理,大概语法如下:try: 语句1 语句2 . . 语句N ex ...查看全部
一般我们我们想捕获python的异常写入到log,做处理的话,一般用try语句来做处理,大概语法如下:
try:
语句1
语句2
.
.
语句N
except ........ :
do something .......
但是你并不知道"语句1至语句N"哪个会出现什么样的错误,但你还要做异常处理,且想把出现的异常打印出来,并不停止程序的运行,所以在"except ......"语句就起作用了。
 
方法一:捕获所有异常
Python2如下:
try:
语句1
语句2
.....
语句N
except Exception,e:
print Exception,":",e

Python3如下:
try:
语句1
语句2
.....
语句N
except Exception as e:
print(Exception, ":", e)
例子如下:
try:
a = 1
b = a
c = w
except Exception as e:
print(Exception, ":", e)

结果为:
: name 'w' is not defined

 方法二:采用sys模块回溯最后的异常
sys.exc_info() 会返回一个3值元表,其中包含调用该命令时捕获的异常。
这个元表的内容为 (type, value, traceback) ,其中:
  • type 从获取到的异常中得到类型名称,它是BaseException 的子类;
  • value 是捕获到的异常实例;
  • traceback 是一个 traceback 对象,下面会详述。
import systry:    w = abs(-1)    list.append(w)except:    info = sys.exc_info()    print(info[0], ":", info[1])    # 结果如下: : descriptor 'append' requires a 'list' object but received a 'int'
sys.last_traceback 包含的内容与 sys.exc_info() 相同,但它主要用于调试,并不总是被定义。 三、采用traceback模块查看异常trackback 模块用来精确模仿 python3 解析器的 stack trace 行为。在程序中应该尽量使用这个模块。traceback.print_exc() 可以直接打印当前的异常。
import tracebacktry:    raiseexcept:    traceback.print_exc()
traceback.print_tb() 用来打印上面提到的 trackback 对象。
import sys,tracebacktry:    raiseexcept:    t,v,tb = sys.exc_info()    traceback.print_tb(tb)
traceback.print_exception() 可以直接打印 sys.exc_info()提供的元表。
import sys,tracebacktry:    raiseexcept:    traceback.print_exception(*sys.exc_info())
其实,下面两句是等价的:
  • traceback.print_exc()
  • traceback.print_exception(*sys.exc_info())

 
traceback 提供的参数可以将 print 的内容写入到文件中
import traceback
try:
a=b
b=c
except:
f=open("log.txt",'a')
traceback.print_exc(file=f)
f.flush()
f.close()
参考:
https://docs.python.org/3/library/traceback.html?highlight=print_tb#traceback.print_exc
https://docs.python.org/3/tutorial/errors.html

Python的生成器和迭代器

空心菜 发表了文章 0 个评论 2343 次浏览 2016-08-19 18:39 来自相关话题

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


生成器


通过列表生成式,我们可以直接创建一个列表。但是,受到内存限制,列表容量肯定是有限的。而且,创建一个包含100万个元素的列表,不仅占用很大的存储空间,如果我们仅仅需要访问前面几个元素,那后面绝大多数元素占用的空间都白白浪费了。
 
 
所以,如果列表元素可以按照某种算法推算出来,那我们是否可以在循环的过程中不断推算出后续的元素呢?这样就不必创建完整的list,从而节省大量的空间。在Python中,这种一边循环一边计算的机制,称为生成器:generator。
 
 
要创建一个generator,有很多种方法。第一种方法很简单,只要把一个列表生成式的[]改成(),就创建了一个generator:
>>> L = [x * x for x in range(5)]
>>> L
[0, 1, 4, 9, 16]
>>> g = (x * x for x in range(5))
>>> g
at 0x10fe179e8>
创建L和g的区别仅在于最外层的[]和(),L是一个list,而g是一个generator。

我们可以直接打印出list的每一个元素,但我们怎么打印出generator的每一个元素呢?
>>> for i in g:
... print(i)
...
0
1
4
9
16
>>> for i in L:
... print(i)
...
0
1
4
9
16

如果要一个一个打印出来,可以通过next()函数获得generator的下一个返回值:
>>> g = (x * x for x in range(5))
>>> next(g)
0
>>> next(g)
1
>>> next(g)
4
>>> next(g)
9
>>> next(g)
16
>>> next(g)
Traceback (most recent call last):
File "", line 1, in
StopIteration

# 取完之后就会报错,因为取完了,就没有,异常关键字:StopIteration
generator保存的是算法,每次调用next(g),就计算出g的下一个元素的值,直到计算到最后一个元素,没有更多的元素时,抛出StopIteration的错误。

generator也是可迭代对象,所以,我们创建了一个generator后,基本上永远不会调用next(),而是通过for循环来迭代它,并且不需要关心StopIteration的错误。
 
generator非常强大。如果推算的算法比较复杂,用类似列表生成式的for循环无法实现的时候,还可以用函数来实现。

比如,著名的斐波拉契数列(Fibonacci),除第一个和第二个数外,任意一个数都可由前两个数相加得到:
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]
但不必显式写出临时变量t就可以赋值。

上面的函数可以输出斐波那契数列的前N个数:
>>> fib(10)
1
1
2
3
5
8
13
21
34
55
done
仔细观察,可以看出,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)
>>> f
这里,最难理解的就是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__())

#输出

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

同样的,把函数改成generator后,我们基本上从来不会用next()来获取下一个返回值,而是直接使用for循环来迭代:
 
>>> for n in fib(6):
... print(n)
...
1
1
2
3
5
8
但是用for循环调用generator时,发现拿不到generator的return语句的返回值。如果想要拿到返回值,必须捕获StopIteration错误,返回值包含在StopIteration的value中:
>>> 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
关于如何捕获错误,后面的错误处理还会详细讲解。

还可通过yield实现在单线程的情况下实现并发运算的效果
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)
c2.send(i)

producer("lucky")


迭代器


我们已经知道,可以直接作用于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是永远不可能存储全体自然数的。
 
小结
凡是可作用于for循环的对象都是Iterable类型;

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

集合数据类型如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

Python3内置函数介绍

空心菜 发表了文章 0 个评论 2846 次浏览 2016-08-19 17:28 来自相关话题

详情参考:https://docs.python.org/3/library/functions.html​    1、abs(x)(返回一个数的绝对值) >>> abs(-12) 12 >>> ab ...查看全部
intfunc.png

详情参考:https://docs.python.org/3/library/functions.html​ 
 
1、abs(x)(返回一个数的绝对值)
>>> abs(-12)
12
>>> abs(-12.89)
12.89
>>> abs(-0.1)
0.1
 
2、all(iterable) 
all会循环括号内的每一个元素,如果括号内的所有元素都是真的,则返回True,如果有一个为假的那么就返回False
>>> all([0, 3])
False
>>> all([1, 3])
True
>>> all([1, ""])
False
一假则假,假的参数有:False、0、None、""、[]、()、{}等。
查看一个元素是否为假可以使用bool进行查看:
>>> bool("")
False
>>> bool(())
False
>>> bool(0)
False
>>> bool(1)
True

3、any(iterable)
循环元素,如果有一个元素为真,那么就返回真,一真则真。
>>> any([0, 1, 2, 3])
True
>>> any([{}, (), 0])
False

4、ascii(object)
在对象的类中寻找__repr__方法,获取返回值
 
 
>>> class Foo:
... def __repr_(self):
... return "Result"
...
>>> obj = Foo()
>>> r = ascii(obj)
>>> print(r)
<__main__.Foo object at 0x1045075c0>

5、bin(x)
将整数x转换为二进制字符串,如果x不为Python中int类型,x必须包含方法index()并且返回值为integer
 
# 返回一个整数的二进制
>>> bin(999)
'0b1111100111'
# 非整型的情况,必须包含__index__()方法切返回值为integer的类型
>>> class myType:
... def __index__(self):
... return 35
...
>>> myvar = myType()
>>> bin(myvar)
'0b100011'

6、bool([x])
查看一个元素的布尔值,非真即假
>>> bool([])
False
>>> bool(0)
False
>>> bool(1)
True

7、bytearray([source [, encoding [, errors]]])
bytearray([source [, encoding [, errors]]])返回一个byte数组。Bytearray类型是一个可变的序列,并且序列中的元素的取值范围为 [0 ,255]。
 
source参数:
  • 如果source为整数,则返回一个长度为source的初始化数组;
  • 如果source为字符串,则按照指定的encoding将字符串转换为字节序列;
  • 如果source为可迭代类型,则元素必须为[0 ,255]中的整数;
  • 如果source为与buffer接口一致的对象,则此对象也可以被用于初始化bytearray。

 
>>> bytearray(3)
bytearray(b'\x00\x00\x00')

8、bytes([source[, encoding[, errors]]])
>>> bytes("asdasd",encoding="utf-8")
b'asdasd'
返回一个bytes类型。
 
9、callable(object)
返回一个对象是否可以被执行
 
>>> def func():
... return 123
...
>>> callable(func)
True
>>> func = 123
>>> callable(func)
False

10、chr(i)
返回一个数字在ASCII编码中对应的字符,取值范围256个
>>> chr(66)
'B'
>>> chr(5)
'\x05'
>>> chr(55)
'7'
>>> chr(255)
'\xff'
>>> chr(25)
'\x19'
>>> chr(65)
'A'

11、compile(source, filename, mode, flags=0, dont_inherit=False, optimize=-1)
把字符串编译成python可执行的代码
 
>>> str = "for i in range(0,6): print(i)"
>>> c = compile(str, '', 'exec')
>>> exec(c)
0
1
2
3
4
5

 
12、complex([real[, imag]])
创建一个值为real + imag * j的复数或者转化一个字符串或数为复数。如果第一个参数为字符串,则不需要指定第二个参数
>>> complex(1, 2)
(1+2j)
# 数字
>>> complex(1)
(1+0j)
# 当做字符串处理
>>> complex("1")
(1+0j)
# 注意:这个地方在“+”号两边不能有空格,也就是不能写成"1 + 2j",应该是"1+2j",否则会报错
>>> complex("1+2j")
(1+2j)

13、dict(**kwarg)
创建一个数据类型为字典
 
>>> dic = dict({"k1":"123","k2":"456"})
>>> dic
{'k1': '123', 'k2': '456'}

14、dir([object])
返回一个类中的所有方法
 
>>> a = [1, 2, 3, 4]
>>> dir(a)
['__add__', '__class__', '__contains__', '__delattr__', '__delitem__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', '__getitem__', '__gt__', '__hash__', '__iadd__', '__imul__', '__init__', '__iter__', '__le__', '__len__', '__lt__', '__mul__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__reversed__', '__rmul__', '__setattr__', '__setitem__', '__sizeof__', '__str__', '__subclasshook__', 'append', 'clear', 'copy', 'count', 'extend', 'index', 'insert', 'pop', 'remove', 'reverse', 'sort']

15、divmod(a, b)
divmod(a,b)方法返回的是a//b(除法取整)以及a对b的余数,返回结果类型为tuple
 
 
>>> divmod(10, 3)
(3, 1)

16、enumerate(iterable, start=0)
遍历可迭代的数据类型,为元素生成下标
>>> a = [0, 1, 2, 3]
>>> for n, k in enumerate(a):
... print(n, k)
...
0 0
1 1
2 2
3 3

17、eval(expression, globals=None, locals=None)
把一个字符串当作一个表达式去执行
>>> string = "1 + 3"
>>> string
'1 + 3'
>>> eval(string)
4

 
18、exec(object[, globals[, locals]])
把字符串当作python代码执行
>>> exec("for n in range(5): print(n)")
0
1
2
3
4

19、filter(function, iterable)
筛选过滤,循环可迭代的对象,把迭代的对象当作函数的参数,如果符合条件就返回True,否则就返回False
>>> def func(x):
... if x == 11 or x == 22:
... return True
...
>>> ret = filter(func,[11,22,33,44])
>>> for n in ret:
... print(n)
...
11
22

20、float([x])
将整数和字符串转换成浮点数
>>> float("124")
124.0
>>> float("123.45")
123.45
>>> float("-123.34")
-123.34

 
21、format(value[, format_spec]) 
字符串格式化
>>> a = "My name is {0}, age is {1}".format('lucky', 18)
>>> print(a)
My name is lucky, age is 18

22、frozenset([iterable])
frozenset是冻结的集合,它是不可变的,存在哈希值,好处是它可以作为字典的key,也可以作为其它集合的元素。缺点是一旦创建便不能更改,没有add,remove方法。
 
23、globals()
获取当前scripts文件内的所有全局变量
 
>>> a = 3
>>> bsd = "54asd"
>>> globals()
{'__doc__': None, 'bsd': '54asd', '__package__': None, 'a': 3, '__spec__': None, '__builtins__': , '__name__': '__main__', '__loader__': }

24、hash(object)
返回一个对象的hash值
 
>>> a = "asdadasdwqeq234sdfdf"
>>> hash(a)
5390438057823015497

25、help([object])
查看一个类的所有详细方法
 
>>> help(list)
Help on class list in module __builtin__:

class list(object)
| list() -> new empty list
| list(iterable) -> new list initialized from iterable's items
|
| Methods defined here:
|
| __add__(...)
| x.__add__(y) <==> x+y
|
| __contains__(...)
| x.__contains__(y) <==> y in x
|
| __delitem__(...)
| x.__delitem__(y) <==> del x[y]
|
| __delslice__(...)
| x.__delslice__(i, j) <==> del x[i:j]
|
| Use of negative indices is not supported.
..........

26、hex(x)
获取一个数的十六进制
 
>>> hex(13)
'0xd'

27、id(object)
返回一个对象的内存地址
>>> a = 123
>>> id(a)
1835400816

28、input([prompt])
交互式输入
 
 
29、int(x, base=10)
获取一个数的十进制
>>> int("31")
31

30、isinstance(object, classinfo)
判断对象是否是这个类创建的
 
>>> li = [11,22,33]
>>> isinstance(li,list)
True

31、issubclass(class, classinfo)
查看一个对象是否为子类
 
32、iter(object[, sentinel])
创建一个可迭代的对象
>>> obj = iter([11,22,33,44])
>>> obj

>>> for n in obj:
... print(n)
...
11
22
33
44

 
33、len(s)
查看一个对象的长度
 
>>> name = 'lucky'
>>> len(name)
5

34、list([iterable])
创建一个数据类型为列表
>>> li = list([11,22,33,44])
>>> li
[11, 22, 33, 44]

35、locals()
返回当前scripts的局部变量,返回结果为字典格式
>>> def func():
... name="lucky"
... print(locals())
...
>>> func()
{'name': 'lucky'}

 
 
36、map(function, iterable, …)
把可迭代的对象作为函数的值
 
>>> ret = map(lambda x: x + 100, [1, 2, 3, 4, 5])
>>> for n in ret:
... print(n)
...
101
102
103
104
105

37、max(iterable, *[, key, default]) max(arg1, arg2, *args[, key])
获取一个对象中的最大值
 
>>> li = [1, 3, 5, 9 ,3]
>>> max(li)
9

38、min(iterable, *[, key, default])  min(arg1, arg2, *args[, key])
获取一个对象中的最小值
>>> li = list([11,22,33,44])
>>> li = [11,22,33,44]
>>> min(li)
11

39、next(iterator[, default])
 
每次只拿取可迭代对象的一个元素
 
>>> obj = iter([11,22,33,44])
>>> next(obj)
11
>>> next(obj)
22
>>> next(obj)
33
>>> next(obj)
44
>>> next(obj)
# 如果没有可迭代的元素了就会报错
Traceback (most recent call last):
File "", line 1, in
StopIteration

40、oct(x)
获取一个字符串的八进制
>>> oct(13)
'0o15'

41、open(file, mode=’r’, buffering=-1, encoding=None, errors=None, newline=None, closefd=True, opener=None)
文件操作的函数,用来做文件操作的
 
 
 
42、ord(c)
把一个字母转换为ASCII对对应表中的数字
 
>>> ord("a")
97
>>> ord("t")
116

43、pow(x, y[, z])
返回一个数的N次方
>>> pow(2, 10)
1024
>>> pow(2, 20)
1048576

 
44、print(*objects, sep=’ ‘, end=’\n’, file=sys.stdout, flush=False)
打印输出
 
 
45、range(start, stop[, step])
生成一个可迭代序列
 
>>> range(10)
range(0, 10)
>>> for n in range(5):
... print(n)
...
0
1
2
3
4

46、reversed(seq)
对一个对象的元素进行反转
>>> li = [1, 2, 3, 4]
>>> reversed(li)

>>> for n in reversed(li):
... print(n)
...
4
3
2
1

47、round(number[, ndigits])
四舍五入
>>> round(3.3)
3
>>> round(3.7)
4

48、set([iterable])
创建一个数据类型为集合
>>> varss = set([11,222,333])
>>> type(varss)

49、slice(start, stop[, step])
元素的切片操作都是调用的这个方法
 
50、sorted(iterable[, key][, reverse])
为一个对象的元素进行排序
 
>>> li = [ 2, 3, 4, 9, 1, 100]
>>> sorted(li)
[1, 2, 3, 4, 9, 100]

51、sum(iterable[, start])
求和
>>> sum([11,22,33])
66

52、type(object)
查看一个对象的数据类型
 
>>> a = 1
>>> type(a)

>>> a = "str"
>>> type(a)

53、vars([object])
查看一个对象里面有多少个变量
 
54、zip(*iterables)
联合迭代
>>> li1 = ["k1","k2","k3"]
>>> li2 = ["a","b","c"]
>>> zip(li1,li2)

>>> dic = zip(li1,li2)
>>> for n in dic:
... print(n)
...
('k1', 'a')
('k2', 'b')
('k3', 'c')

55、import(name, globals=None, locals=None, fromlist=(), level=0)
导入模块,把导入的模块作为一个别名。
生成一个六位的随机验证码,且包含数字,数字的位置随机:
# 导入random模块
import random
temp = ""
for i in range(6):
num = random.randrange(0,4)
if num == 3 or num == 1:
rad2 = random.randrange(0,10)
temp = temp + str(rad2)
else:
rad1 = random.randrange(65,91)
c1 = chr(rad1)
temp = temp + c1
print(temp)

Python装饰器详解

空心菜 发表了文章 0 个评论 3236 次浏览 2016-08-18 23:11 来自相关话题

装饰器 定义:本质就是函数,(装饰其他函数)就是为其他函数添加额外的功能; 遵循的原则:1、不能修改其被装饰函数的源代码    2、不能修改其被装饰函数的调用方式   实现装饰器需要用到的知识点: ...查看全部


装饰器


定义:本质就是函数,(装饰其他函数)就是为其他函数添加额外的功能;
遵循的原则:1、不能修改其被装饰函数的源代码    2、不能修改其被装饰函数的调用方式
 
实现装饰器需要用到的知识点:
  1. 函数即"变量"
  2. 高阶函数
  3. 嵌套函数

如果你对装饰器实现的如上三个知识点理解了的话,那你就完全可以理解装饰器了,图示如下:
Decorator.png

下面我们就把装饰器的知识分解一个一个来理解。
 
1、函数即"变量"
让我们来看一下几个例子,来说明函数即变量
函数的错误调用案例,跟变量一样,还没有定义就调用:
# 错误调用方式一:
def foo():
print('in the foo!')
bar()

foo()
# 输出
NameError: name 'bar' is not defined (因为在调用foo函数执行的时候函数体内又调用了bar函数,但是bar函数并没有定义)

# 错误调用方式二:
def foo():
print('in the foo!')
bar()

foo()

def bar():
print('in the bar!')

# 输出
NameError: name 'bar' is not defined (同上面,不同的是虽然你在下面定义了bar函数,但是在调用之后才定义的.)
从上面的例子可以看出,函数的定义和调用,其实是跟变量是一样的,变量在没有定义之前你对变量进行操作也是会报变量没有被定义的错误。
 
正确的调用方式如下:
# 错误调用方式二:
def foo():
print('in the foo!')
bar()

def bar():
print('in the bar!')

foo()

# 输出
in the foo!
in the bar!
具体说明我们可以结合定义变量图示说明,我们把整个内存看作是一个大楼,把变量看做是门牌号,而变量真正存在的值看做是房间里面的东西,房间就相当于是大内存中的一小块内存块,图示如下:
funcvar.png

如图所示我们可以看出函数def下定义的函数名就相当于变量的变量名,而函数体就相当于变量的值;所以函数的执行跟调用变量是一样的,只有在你执行之前所有的函数已经声明,占用了内存地址才可以执行,否则在你执行之前未定义,而会报错。
 
函数和变量一样,如果你已经定义好了,就会从内存中划分出一块内存空间存储进行存储,那什么时候回收这个内存空间呢?1、当你整个程序执行完成后,你所定义的变量或者函数占用的内存空间就会被释放  2、就是你利用del()内置函数的删除方法,把变量名和函数名删除,然后Python的内存回收机制发现这块内存空间没有被引用了,就把这块没有被引用的内存空间回收了。
 
2、高阶函数
高阶函数定义:
  1. 把一个函数名当做实参传给另外一个函数当做形参
  2. 返回值(return)中包含函数名

只要满足以上其中一个条件就称为高阶函数。
 
首先我们看一个高阶函数的例子:
def foo(func):
print('in the foo!')
print(func)
func()


def bar():
print('in the bar!')

foo(bar)

结果:
in the foo!
# 打印了函数bar的内存地址
in the bar!
如上是一个符号传递函数名的一个高阶函数的例子。
 
下面我们利用高阶函数来做一个不改变被装饰函数代码,为被装饰函数添加一个新功能,我们这里添加一个程序运行时间的功能。
 
把一个函数名当做实参传给另外一个函数(没有改变函数的代码,添加了一个功能,但是运行方法改变了)
import time
def foo(func):
print('in the foo!')
start_time = time.time()
func()
stop_time = time.time()
print("func run time is %s" % (stop_time - start_time))


def bar():
time.sleep(2)
print('in the bar!')

foo(bar)

# 结果如下:
in the foo!
in the bar!
func run time is 2.002309799194336
如上代码把bar函数当做一个实参,传给了foo函数当做形参,然后在里面运行了bar函数。
 
返回值中包含函数(虽然没有改变函数运行方式,但是好像功能没有加上)
import time
def foo(func):
print(func)
return func


def bar():
time.sleep(2)
print('in the bar!')

bar = foo(bar)
bar()

# 结果为:

in the bar!
好像都并没有什么卵用,但是你应该发现了,我可以做到不改变函数调用方式和增加功能,但是单独利用高阶函数,这两个条件同事满足是做不到的,因为还需要结合嵌套函数才行。
 
3、嵌套函数
定义:在一个函数体内创建另外一个函数,这种函数就叫内嵌函数(基于python支持静态嵌套域)
 
函数嵌套示范:
def foo():
print('in the foo')
def bar():
print('in the bar')

bar()

foo()
这就是一个最简单的一个嵌套函数,一个函数下,再定义另外一个函数。
 
局部作用域和全局作用域的访问顺序:
x = 0
def grandpa():
x = 1
def dad():
x = 2
def son():
x = 3
print(x) # 打印出来为3
son()
dad()
grandpa()

print(x) # 这个还是0
结果是 3, 0,过程如图所示:
sorted.png

 
闭包:
如果在一个内部函数里,对在外部作用域(但不是在全局作用域)的变量进行引用,那么内部函数就被认为是 closure
def counter(start_num=0):
count=[start_num]
def incr():
count[0]+=1
return count[0]
return incr

print(counter())
print(counter()())
print(counter()())

c=counter()
print(c())
print(c())
没看懂,先记录一下。
 
4、装饰器
讲了装饰器 = 高阶函数 + 嵌套函数,还是添加一个打印程序执行时间功能,例子如下:
import time
def timer(func):
print("in the timer")
def foo():
start_time = time.time()
func()
stop_time = time.time()
print("fun run time is %s" % (stop_time - start_time))
return foo


def bar():
time.sleep(2)
print('in the bar')

bar = timer(bar)
bar()

# 结果

in the timer
in the bar
fun run time is 2.004901885986328
如上代码所示,我们为了给bar函数增加一个计算他运行时间的功能,我们既没有改变他的运行方式bar(),也没有改变他的内部代码,功能,也实现了,这就是利用高阶函数 + 嵌套函数完成的一个简易版的装饰器,在python中为了简化方法,装饰函数可以直接利用@符号把装饰函数应用到被装饰函数的上,如下所示:
import time
def timer(func):
print("in the timer")
def foo():
start_time = time.time()
func()
stop_time = time.time()
print("fun run time is %s" % (stop_time - start_time))
return foo

@timer # @timer 相当于 bar = timer(bar)
def bar():
time.sleep(2)
print('in the bar')

# bar = timer(bar)
bar()

# 结果如下:
in the timer
in the bar
fun run time is 2.000408887863159
如上可以看出来,结果是一样的,简易版的装饰器我们可以做出来了,那下面我们在深入一下,做一个高级版本的装饰器,那我们在熟悉如下几个知识点。
 
1、函数参数固定
def decor(func):
def wrapper(n):
print('starting')
func(n)
print('stopping')
return wrapper


def test(n):
print('in the test arg is %s' % n)

decor(test)('lucky')
分解过程:
argsfun.png

其实就是 decor(test)('lucky')   的过程你可以理解为  a = decor(test) = wrapper  然后在调用  a('lucky') = wrapper('lucky') 。
 
2、函数参数不固定
def decor(func):
def wrapper(*args, **kwargs):
print('starting')
func(*args, **kwargs)
print('stopping')
return wrapper


def test(n, x=1):
print('in the test n is %s, x is %s' % (n, x))

decor(test)('alex', x=2)

# 结果如下:
starting
in the test n is alex, x is 2
stopping

3、无参装饰器
import time
def decorator(func):
def wrapper(*args, **kwargs):
start = time.time()
func(*args, **kwargs)
stop = time.time()
print('run time is %s ' % (stop - start))
print('timeout')
return wrapper

@decorator
def test(list_test):
for i in list_test:
time.sleep(0.1)
print('-' * 20, i)

#decorator(test)(range(10))
test(range(10))

4、带参数的装饰器
import time
def timer(timeout=0):
def decorator(func):
def wrapper(*args, **kwargs):
start=time.time()
func(*args, **kwargs)
stop=time.time()
print('run time is %s ' %(stop-start))
print(timeout)
return wrapper
return decorator

@timer(2)
def test(list_test):
for i in list_test:
time.sleep(0.1)
print('-'*20, i)

# timer(timeout=2)(test)(range(10)) timer(timeout=2) = decorator timer(timeout=2)(test) = decorator(test) = wrapper
# timer(timeout=2)(test)(range(10)) = decorator(test)(range(10)) = wrapper(range(10))
test(range(10))

高级版装饰器:
user, passwd = 'lucky', '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[32mUser has passwed authentication\033[0m")
res = func(*args, **kwargs) # from home
print("---after authenticaion")
return res
else:
exit("\033[31mInvalid username or passwd\033[0m")
elif auth_type == "ldap":
print("\033[33m不会ldap,搞毛线啊!\033[0m")
return wrapper
return outer_wrapper

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

@auth(auth_type="local") # home = wrapper()
def home():
print("welcome to home page")
return "from home"

bbs()
home()

装饰器的应用:
装饰器功能:函数超时则终止
# -*- coding: utf-8 -*-  
from threading import Thread
import time

class TimeoutException(Exception):
pass

ThreadStop = Thread._Thread__stop#获取私有函数

def timelimited(timeout):
def decorator(function):
def decorator2(*args,**kwargs):
class TimeLimited(Thread):
def __init__(self,_error= None,):
Thread.__init__(self)
self._error = _error

def run(self):
try:
self.result = function(*args,**kwargs)
except Exception,e:
self._error =e

def _stop(self):
if self.isAlive():
ThreadStop(self)

t = TimeLimited()
t.start()
t.join(timeout)

if isinstance(t._error,TimeoutException):
t._stop()
raise TimeoutException('timeout for %s' % (repr(function)))

if t.isAlive():
t._stop()
raise TimeoutException('timeout for %s' % (repr(function)))

if t._error is None:
return t.result

return decorator2
return decorator

@timelimited(2)
def fn_1(secs):
time.sleep(secs)
return 'Finished'

if __name__ == "__main__":
print fn_1(4)

Python匿名函数和高阶函数

空心菜 发表了文章 0 个评论 2199 次浏览 2016-08-17 23:23 来自相关话题

匿名函数 Lambda(Lambda expressions)表达式是用lambda关键字创建的匿名函数,Lambda函数可以用于任何需要函数对象的地方,在语法上,它们被局限于只能有一个单独的表达式,但是他可以跟其他内置函数map,r ...查看全部


匿名函数


Lambda(Lambda expressions)表达式是用lambda关键字创建的匿名函数,Lambda函数可以用于任何需要函数对象的地方,在语法上,它们被局限于只能有一个单独的表达式,但是他可以跟其他内置函数map,reduce,filter等联合使用,更加便利和快捷的一些方法。
 
lambda 和if  else的三元运算一样,是为了简化函数定义,但是:
  1. 只能做简单的操作
  2. 自动return

例子如下:
# 这段代码
def f(x, y):
return x + y
print(f(3, 4))

# 换成匿名函数
f = lambda x, y: x + y

print(f(3, 4))

效果是相同的结果都是7,lambda创建的函数和def创建的函数对应关系如图所示:
lambda.png

 
这看起来并没有什么卵用,但是它和其他内置函数搭配起来,具体联合使用请参考:http://openskill.cn/article/272 


高阶函数


变量可以指向函数,函数的参数能接收变量,那么一个函数就可以接收另一个函数作为参数,这种函数就称之为高阶函数。
 
满足下列条件之一就可成函数为高阶函数:
  1. 某一函数当做实参传入另一个函数中
  2. 函数的返回值包含n个函数,n>0

 
先来看一个简单的例子:
def bar():
print 'in the bar'


def foo(func):
res=func()
return res

foo(bar)
首先定义好函数bar和foo,然后执行foo的时候把bar函数当实参传递进去。

Python三元计算和深浅拷贝

空心菜 发表了文章 0 个评论 2773 次浏览 2016-08-17 21:54 来自相关话题

三元计算 三元计算也称为三目计算,如名字表示的三元运算符需要三个操作数,语法为: 条件表达式?表达式1:表达式2。 or var = 值1 if 条件 else 值2 说明:问号前面的位置是判 ...查看全部


三元计算


三元计算也称为三目计算,如名字表示的三元运算符需要三个操作数,语法为:
条件表达式?表达式1:表达式2。  or  
var = 值1 if 条件 else 值2
说明:问号前面的位置是判断的条件,判断结果为bool型,为true时调用表达式1,为false时调用表达式2。
其逻辑为:如果为真执行第一个,否则执行第二个。
例子:
>>> a = 'xxoo'

>>> Re = 'xx' if a == 'xxoo' else 'oo'
>>> print(Re)
xx

>>> a = 'xx'
>>> Re = 'xx' if a == 'xxoo' else 'oo'
>>> print(Re)
oo
如上结果你可以看成是如下代码的简写的例子:
a = 'xxoo'
if a == 'xxoo':
Re = 'xx'
else:
Re = 'oo'
这样我想你就知道了!
 


深浅拷贝


深浅拷贝分为两部分,一部分是数字和字符串另一部分是列表、元组、字典等其他数据类型。
 
数字和字符串:
对于数字和字符串而言,赋值、浅拷贝和深拷贝无意义,因为他们的值永远都会指向同一个内存地址,他们就是内存地址的复用。
>>> import copy      # 导入内置copy模块
>>> name = 'lucky' # 命名name变量
>>> id(name) # 查看name变量的内存地址
4426658624
>>>
>>> name1 = name # 赋值应用变量 name1
>>> id(name1) # 查看变量name1内存地址
4426658624
>>>
>>> name2 = copy.copy(name) # 浅copy name变量赋值给name2变量
>>> id(name2) # 查看name2变量内存地址
4426658624
>>>
>>> name3 = copy.deepcopy(name) # 深copy name变量复制给name2变量
>>> id(name3) # 查看name3变量的内存地址
4426658624
>>>
>>> print(name, name1, name2, name3) # 打印四个变量的值
lucky lucky lucky lucky
如上结果可以看出,不管是直接引用还是深浅copy其最后的结果变量的内存地址都没有变,只是定义的方法不一样,如下图示:
strcopy.png

 
其他数据类型:
对于字典、元祖、列表 而言,进行赋值、浅拷贝和深拷贝时,内存地址可能会发生相应的变化。
1、赋值
>>> num1 = {'n1': 1, 'n2': 2, 'n3': [3, 4]}
>>> num2 = num1
>>> print(id(num1), id(num2))
4424864840 4424864840
>>>
>>> num1['n1'] = 7
>>> print(id(num1), id(num2))
4424864840 4424864840
>>> print(num1, num2)
{'n2': 2, 'n3': [3, 4], 'n1': 7} {'n2': 2, 'n3': [3, 4], 'n1': 7}
>>>
>>> num1['n3'][0] = 9
>>> print(id(num1), id(num2))
4424864840 4424864840
>>> print(num1, num2)
{'n2': 2, 'n3': [9, 4], 'n1': 7} {'n2': 2, 'n3': [9, 4], 'n1': 7}

如上所示,不管怎么改变原始的info字典内容,哪怕是改变里面的列表元素值,而info2的内容随之变化,运维他们的引用的内存地址是一样的,图示如下:
fuzhi.png

 
2、浅拷贝
 
浅拷贝,在内存中只额外创建第一层数据,里面则是完全复制。
>>> num1 = {'n1': 1, 'n2': 2, 'n3': [3, 4]}
>>> num2 = num1.copy()
>>> print(id(num1), id(num2))
4426663432 4426602824

>>> print(id(num1['n1']), id(num2['n1']))
4423026704 4423026704
>>> print(id(num1['n3']), id(num2['n3']))
4427044168 4427044168
>>> print(id(num1['n3'][0]), id(num2['n3'][0]))
4423026768 4423026768
>>> num1['n3'][0] = 9
>>> print(id(num1), id(num2))
4426663432 4426602824
>>> print(num1, num2)
{'n2': 2, 'n3': [9, 4], 'n1': 1} {'n2': 2, 'n3': [9, 4], 'n1': 1}
>>> num1['n1'] = 19
>>> print(num1, num2)
{'n2': 2, 'n3': [9, 4], 'n1': 19} {'n2': 2, 'n3': [9, 4], 'n1': 1}
>>> print(id(num1['n3'][0]), id(num2['n3'][0]))
4423026960 4423026960

浅copy也可以利用copy模块 num2 = copy.copy(num1)
 图示如下:
ashcopy.png

 
3、深拷贝
深拷贝,在内存中将所有的数据重新创建一份(排除最后一层,即:python内部对字符串和数字的优化)
 
>>> num1 = {'n1': 1, 'n2': 2, 'n3': [3, 4]}
>>> num2 = copy.deepcopy(num1)
>>> print(id(num1), id(num2))
4424864840 4427043656
>>> print(id(num1['n1']), id(num2['n1']))
4423026704 4423026704
>>> print(id(num1['n3']), id(num2['n3']))
4427043592 4426939976
>>> num1['n1'] = 9
>>> print(id(num1['n1']), id(num2['n1']))
4423026960 4423026704
从上面来看,深copy内存地址则完全不一样,图示如下:
deepcopy.png

Python函数详解

空心菜 发表了文章 2 个评论 2395 次浏览 2016-08-15 00:43 来自相关话题

在介绍Python的函数之前我们先来回顾一下,我们平常写代码是不是需要什么功能就写什么功能,但是到后面深入你会发现在写代码的过程中,有好多代码是重叠的,就是一个功能块在不同的地方都被使用,但是每次都得重新写一遍,这样就造成了写程序的效率问题,而Python中的 ...查看全部
在介绍Python的函数之前我们先来回顾一下,我们平常写代码是不是需要什么功能就写什么功能,但是到后面深入你会发现在写代码的过程中,有好多代码是重叠的,就是一个功能块在不同的地方都被使用,但是每次都得重新写一遍,这样就造成了写程序的效率问题,而Python中的函数就可以很好的解决这么一点。例如一个登陆代码快的代码,你比如刚登陆的时候需要验证用户名密码,你执行他事件比如购物、结算等逻辑的时候也需要做验证,那如果我们把这个验证的逻辑封装成一个函数,然后用到的地方,想怎么调就怎么调用,所以减少代码的重复性是函数的特性之一。
 
同时在过去的十年间,大家广为熟知的编程方法无非两种:面向对象和面向过程,其实,无论哪种,都是一种编程的规范或者是如何编程的方法论。而如今,一种更为古老的编程方式:函数式编程,以其不保存状态,不修改变量等特性重新进入人们的视野。下面我们就来依次了解这一传统的编程理念,下面我们介绍一些Python中函数的概念。
 
面向过程:根据业务逻辑从上到下写垒代码
函数式:将某功能代码封装到函数中,日后便无需重复编写,仅调用函数即可


函数是什么?


在我们的初中书写中我们应该知道3x  = 4y 这就是一个简单的函数,而x、y就是两个变量,如果x确定为一个值,那y的值就是确定的,我们把x叫做自变量,而把y叫做应变量,y是x的函数,当然也可以反过来说。这里就是y会随着x的值变化而变化。而这个自变量x的取值范围我们就叫做这个函数的定义域。
 
函数一词虽源于数学,但编程中的「函数」概念,与数学中的函数是有很大不同的,具体区别,我们后面会讲,编程中的函数在英文中也有很多不同的叫法。在BASIC中叫做subroutine(子过程或子程序),在Pascal中叫做procedure(过程)和function,在C中只有function,在Java里面叫做method。
 
定义: 函数是指将一组语句的集合通过一个名字(函数名)封装起来,要想执行这个函数,只需调用其函数名的方法即可

函数的特性:
  • 减少重复代码
  • 使程序变的可扩展(装饰器本质是函数)
  • 使程序变得易维护
 语法定义:
def test(x):    "The function definitions"    x+=1    return x    ****************************************************************** def:定义函数的关键字test:函数名():内可定义形参"":文档描述(非必要,但是强烈建议为你的函数添加描述信息)x+=1:泛指代码块或程序处理逻辑return:定义返回值
**编程语言中函数定义:函数是逻辑结构化和过程化的一种编程方法,函数式编程就是先定义一个数学函数,然后按照这个数学模型用编程语言去实现它。 一个栗子
#!/usr/bin/env python3# _*_coding:utf-8_*_# Author: Lucky.chen# 常规码代码a, b = 5, 8c = a * bprint(c)print("我是分割线".center(30, '*'))# 函数方法def calc(x, y):    res = x * y    return res  # 返回函数执行结果c = calc(a, b)   # 结果赋值给c变量print(c)
Result:
40************我是分割线*************40
这不是功能一样吗,看起来并没有什么卵用!!!嘿嘿!

函数参数与局部变量

func.png
形参:
形式参数,不是实际存在,是虚拟变量。在定义函数和函数体的时候使用形参,目的是在函数调用时接收实参(实参个数,类型应与实参一一对应)。
实参:
实际参数,调用函数时传给函数的参数,可以是常量,变量,表达式,函数,传给形参。
区别:
形参是虚拟的,不占用内存空间,形参变量只有在被调用时才分配内存单元,实参是一个变量,占用内存空间,数据传送单向,实参传给形参,不能形参传给实参。
实参就是函数执行的时候用户调用函数实际传输的参数简称为实参,实参又分为位置参数和关键参数。 位置参数实例如下
def test(x, y):     # x, y是形参    print("x is %d, y is %d" % (x, y))    calc_age = x * y    return calc_age# 3在前,6在后 那么传给函数的形参就是按照位置 x = 3 , y = 6print(test(3, 6))print("我是分割线".center(30, '*'))# 如果调用的时候6在前,3在后,那么 x = 6, y = 3print(test(6, 3))
结果如下:
x is 3, y is 618************我是分割线*************x is 6, y is 318
关键参数实例如下:
def test(x, y, z):     # x, y是形参    print("x is %d, y is %d, z is %d" % (x, y, z))    calc_age = x * y * z    return calc_age# 指定y和z的值而x的值就是默认出入位置为第个的实参print(test(3, y=2, z=3))print("我是分割线".center(30, '*'))# 指定y和z的值,但是y和z的位置对调print(test(3, z=2, y=3))
结果如下:
x is 3, y is 2, z is 318************我是分割线*************x is 3, y is 3, z is 218
如果这样呢?
def test(x, y, z):     # x, y是形参    print("x is %d, y is %d, z is %d" % (x, y, z))    calc_age = x * y * z    return calc_age# 我指定第一个实参的数值,而不指定y和z的呢?print(test(x=3, 2, 3))
结果如下报错了:
  File "/Users/crh/PycharmProjects/app/test.py", line 12    print(test(x=3, 2, 3))                   ^SyntaxError: positional argument follows keyword argument
如上实例介绍可以得出如下结论:正常情况下,给函数传参数要按顺序,不想按顺序就可以用关键参数,只需指定参数名即可,但记住一个要求就是,关键参数必须放在位置参数之后。 默认参数实例如下:
def test(x, y=3, z):     # x, y是形参    print("x is %d, y is %d, z is %d" % (x, y, z))    calc_age = x * y * z    return calc_age# 只传两个参数,按照正常逻辑 x=1, y=3, z=3print(test(1, 3))# 但是报错了
报错如下:
  File "/Users/crh/PycharmProjects/app/test.py", line 6    def test(x, y=3, z):     # x, y是形参            ^SyntaxError: non-default argument follows default argument
def test(x, y, z=3):     # x, y是形参    print("x is %d, y is %d, z is %d" % (x, y, z))    calc_age = x * y * z    return calc_age# 只传两个参数,按照正常逻辑 x=1, y=3, z=3print(test(1, 6))
结果如下:
x is 1, y is 6, z is 318
def test(x, z, y=3):     # x, y是形参    print("x is %d, y is %d, z is %d" % (x, y, z))    calc_age = x * y * z    return calc_age# 只传两个参数,按照正常逻辑 x=1, y=3, z=3print(test(3, 2))
结果如下:
x is 3, y is 3, z is 218
如上实例可以知道,函数的形参设置默认参数的时候,默认参数应该放到非默认参数后面,否则按照位置参数传入则就会报错,除非传入实参都设置位置参数!上面实例报错分析:
args.png
 动态参数实例如下:动态参数也称为非固定参数,若你的函数在定义时不确定用户想传入多少个参数,就可以使用动态参数,使用了动态参数你就可以动态扩展,想传入多少参数都行! 1、args
def test(x, y, *args):    print("x is %d, y is %d, z is %s" % (x, y, args))test(1, 2, 3, 4, 5, 6)
结果如下:
x is 1, y is 2, z is (3, 4, 5, 6)
 
def test(x, y, *args):    print("x is %d, y is %d, z is %s" % (x, y, args))    print(type(args))test(1, 2)  # 不传入args的值test(1, 2, (3, 4, 5))  # 传入一个元组test(1, 2, [3, 4, 5])  # 传入一个列表test(1, 2, {3, 4, 5})  # 传入一个集合test(1, 2, {1: 'lucky', 2: 'jack'})  # 传入一个字典
结果如下:
x is 1, y is 2, z is ()x is 1, y is 2, z is ((3, 4, 5),)x is 1, y is 2, z is ([3, 4, 5],)x is 1, y is 2, z is ({3, 4, 5},)x is 1, y is 2, z is ({1: 'lucky', 2: 'jack'},)
总结:由上面代码可以看出,*args会把多传入的实参变成一个元组的类型;传入其他数据类型也一样,成为元组中的一个元素;另函数中有*args与其他形参的时候,*args一定要写到其他形参的后面,否则传入的实参都会被传入到*args当中打印成元组;还有如果没有多出传入的实参即*args没有值的时候,*args为空,不会报错。 2、kwargs
def test(x, y, **kwargs):    print("x is %d, y is %d, z is %s" % (x, y, kwargs))    print(type(kwargs))test(1, 2)  # 不传入kwargs的值test(1, 2, 3, 4, 5)
结果如下:
Traceback (most recent call last):x is 1, y is 2, z is {}  File "/Users/crh/PycharmProjects/app/test.py", line 11, in     test(1, 2, 3, 4, 5)TypeError: test() takes 2 positional arguments but 5 were given
def test(x, y, **kwargs):    print("x is %d, y is %d, z is %s" % (x, y, kwargs))    print(type(kwargs))test(1, 2, name='lucky', age=18, job='IT')
结果如下:
x is 1, y is 2, z is {'job': 'IT', 'name': 'lucky', 'age': 18}
**kwargs会把多出的a=b这种类型的实参打印成字典的类型(要区分开与关键参数的区别,关键参数的实参有对应的形参)
test_list = [8, 9, 10, 11]test_dict = {'name': 'lucky', 'age': '18', 'job': 'it'}def test(x, y, *args, **kwargs):    print("x is %d, y is %d, args is %s, kwargs is %s" % (x, y, args, kwargs))test(1, 2, test_list, test_dict)
结果如下:
x is 1, y is 2, args is ([8, 9, 10, 11], {'job': 'it', 'name': 'lucky', 'age': '18'}), kwargs is {}
如上所示为什么把把字典传入函数后,打印的**kwargs为空值呢?!  是这样的,传入的字典会被当成一个元素传入函数,所有被当成多余的实参传入到了*args里面,所以**kwargs的值才为空;那么有什么办法可以把字典传入到**kwargs呢?
test_list = [8, 9, 10, 11]test_dict = {'name': 'lucky', 'age': '18', 'job': 'it'}def test(x, y, *args, **kwargs):    print("x is %d, y is %d, args is %s, kwargs is %s" % (x, y, args, kwargs))test(1, 2, *test_list, **test_dict)
结果如下:
x is 1, y is 2, args is (8, 9, 10, 11), kwargs is {'name': 'lucky', 'job': 'it', 'age': '18'}
这下结果就是我想要的了,所以如果要把列表或者字典放到函数中,指定关键符号就好。 3、局部变量
def change_name(name):    print("before change:", name)    name = "采菊篱下"    print("after change", name)change_name(name)print("在外面看看name改了么?", name)
结果如下:
before change: Lucky chenafter change 采菊篱下在外面看看name改了么? Lucky chen
全局与局部变量:
  • 在子程序中定义的变量称为局部变量,在程序的一开始定义的变量称为全局变量;
  • 全局变量作用域是整个程序,局部变量作用域是定义该变量的子程序;
  • 当全局变量与局部变量同名时,在定义局部变量的子程序内,局部变量起作用;在其它地方全局变量起作用。
zuoyongyu.png

返回值

要想获取函数的执行结果,就可以用return语句把结果返回,可以返回任何你想得到的东西,多返回值返回的是一个元组。
def test(x, y, z, name):    calc_age = x * y * z    print(name)    return calc_age, nameage = test(3, 2, 3, 'lucky')print(age, type(age))
结果如下:
lucky(18, 'lucky') 
总结:
  • 函数在执行过程中只要遇到return语句,就会停止执行并返回结果,所以 return 语句代表着函数的结束,跟循环中的break一样。
  • 如果未在函数中指定return,那这个函数的返回值为None 
 

递归函数

在函数内部,可以调用其他函数。如果一个函数在内部调用自身本身,这个函数就是递归函数。就是把函数体处理的结果,再次传到本函数再次执行。递归特性:[list=1]
  • 必须有一个明确的结束条件
  • 每次进入更深一层递归时,问题规模相比上次递归都应有所减少
  • 递归效率不高,递归层次过多会导致栈溢出(在计算机中,函数调用是通过栈(stack)这种数据结构实现的,每当进入一个函数调用,栈就会加一层栈帧,每当函数返回,栈就会减一层栈帧。由于栈的大小不是无限的,所以,递归调用的次数过多,会导致栈溢出) 

  • def calc(n):
    print(n)
    if int(n/2) == 0:
    return n
    return calc(int(n/2))


    calc(10)

    Result:
    10
    5
    2
    1
    下面我们再来看一个斐波那契数列的例子:
    def func(count, n1, n2):        # 获取斐波那契数列第200个数字并返回给调用者
    if count == 200:
    return n1
    n3 = n1 + n2
    r = func(count + 1, n2, n3)
    return r
    ret = func(1, 0, 1)
    print(ret)

    Result:
    173402521172797813159685037284371942044301


    函数的作用域


    zyy.png

    再看一个例子:
    name2 = "chenronghua"
    def say():
    name = "lucky"
    print(name)
    print(name2)
    say()
    结果为:
    lucky
    chenronghua
    总结:
    函数的作用域就是在函数里定义的变量不能被外面使用!但是外部全局定义的全局变量在函数内是可以使用的。
    举个例子来说:你在房子里可以看到屋内的东西和房子外的东西,但是你在房子外面就只能看到房子外的东西不能看到房子内的东西!
    原因防止在函数调用的时候防止变量冲突!

    那问题来了,我在外面定义的全局变量在函数内可以改他吗,例子如下:
    name2 = "chenronghua"
    def say():
    name = "lucky"
    name2 = "crh"
    print(name)
    print("function inside name2 value %s" % name2)
    say()
    print("function outside name value %s" % name2)
    结果如下:
    lucky
    function inside name2 value crh
    function outside name value chenronghua
    从上面的例子可以看出函数里面把name2的值改了,但是在外部打印还是没有发生改变,这就是函数作用域的诠释。
     
    如果我就是想改,怎么办呢,可以吗?如下例子所示:
    name2 = "chenronghua"
    def say():
    global name2
    name = "lucky"
    name2 = "crh"
    print(name)
    print("function inside name2 value %s" % name2)
    say()
    print("function outside name value %s" % name2)
    结果如下:
    lucky
    function inside name2 value crh
    function outside name value crh
    可以看出,是可以做到的,你需要在函数体内利用内置函数global来声明你想作为全局变量的变量。

    Python中怎么去除字符串中间的空格?

    空心菜 回复了问题 2 人关注 1 个回复 6307 次浏览 2016-08-13 14:56 来自相关话题