Python基础

一、Python解释器种类以及特点

解释器概念

Python是一门解释器语言,代码想运行,必须通过解释器执行,Python存在多种解释器,分别基于不同语言开发,每个解释器有不同的特点,但都能正常运行Python代码;

当我们从Python官方网站下载并安装好Python 3.x后,我们就直接获得了一个官方版本的解释器:CPython。这个解释器是用C语言开发的,所以叫CPython。在命令行下运行python就是启动CPython解释器。

种类及特点

CPython

  • 当 从Python官方网站下载并安装好Python2.7后,就直接获得了一个官方版本的解释器:Cpython,这个解释器是用C语言开发的,所以叫 CPython,在命名行下运行python,就是启动CPython解释器,CPython是使用最广的Python解释器。

IPython

  • IPython是基于CPython之上的一个交互式解释器,也就是说,IPython只是在交互方式上有所增强,但是执行Python代码的功能和CPython是完全一样的,好比很多国产浏览器虽然外观不同,但内核其实是调用了IE。

PyPy

  • PyPy是另一个Python解释器,它的目标是执行速度,PyPy采用JIT技术,对Python代码进行动态编译,所以可以显著提高Python代码的执行速度。

Jython

  • Jython是运行在Java平台上的Python解释器,可以直接把Python代码编译成Java字节码执行。

IronPython

  • IronPython和Jython类似,只不过IronPython是运行在微软.Net平台上的Python解释器,可以直接把Python代码编译成.Net的字节码。

在Python的解释器中,使用广泛的是CPython,对于Python的编译,除了可以采用以上解释器进行编译外,技术高超的开发者还可以按照自己的需求自行编写Python解释器来执行Python代码,十分的方便!

二、PEP8 编码规范, 及开发中的一些惯例和建议

编码规范概念

  1. 美观,优雅,因人而异。
  2. 可读性
  3. 可维护性
  4. 健壮性

团队内最好的代码状态: 所有人写出的代码像一个人写出来的;

规范

代码编排

  1. 缩进。4个空格的缩进(编辑器都可以完成此功能),不使用Tap,更不能混合使用Tap和空格。
  2. 每行最大长度79,换行可以使用反斜杠,最好使用圆括号。换行点要在操作符的后边敲回车。
  3. 类和top-level函数定义之间空两行;类中的方法定义之间空一行;函数内逻辑无关段落之间空一行;其他地方尽量不要再空行。

文档编排

  1. 模块内容的顺序:模块说明和docstring—import—globals&constants—其他定义。其中import部分,又按标准、三方和自己编写顺序依次排放,之间空一行
  2. 不要在一句import中多个库,比如import os, sys不推荐
  3. 如果采用from XX import XX引用库,可以省略‘module.’,都是可能出现命名冲突,这时就要采用import XX

空格的使用

  1. 各种右括号前不要加空格。
  2. 逗号、冒号、分号前不要加空格。
  3. 函数的左括号前不要加空格。如Func(1)
  4. 序列的左括号前不要加空格。如list[2]
  5. 操作符左右各加一个空格,不要为了对齐增加空格
  6. 函数默认参数使用的赋值符左右省略空格
  7. 不要将多句语句写在同一行,尽管使用‘;’允许
  8. if/for/while语句中,即使执行语句只有一句,也必须另起一行

注释

**总体原则,错误的注释不如没有注释。所以当一段代码发生变化时,第一件事就是要修改注释!注释必须使用英文,最好是完整的句子,首字母大写,句后要有结束符,结束符后跟两个空格,开始下一句。如果是短语,可以省略结束符。**
  1. 块注释,在一段代码前增加的注释。在‘#’后加一空格。段落之间以只有‘#’的行间隔。比如:
# Description : Module config.
# 
# Input : None
#
# Output : None
  1. 行注释,在一句代码后加注释。比如:x = x + 1 # Increment x但是这种方式尽量少使用。

  2. 避免无谓的注释。

文档描述

  1. 为所有的共有模块、函数、类、方法写docstrings;
  2. 非共有的没有必要,但是可以写注释(在def的下一行),单行注释参考如下:
def kos_root():
   """Return the pathname of the KOS root directory."""
   global _kos_root
   if _kos_root: return _kos_root
   ...

命名规则

**总体原则,新编代码必须按下面命名风格进行,现有库的编码尽量保持风格。绝不要单独使用例如大写的"i"和大写的"o"**
  1. 模块命名尽量短小,使用全部小写的方式,可以使用下划线。
  2. 包命名尽量短小,使用全部小写的方式,不可以使用下划线。
  3. 类的命名使用CapWords的方式,模块内部使用的类采用_CapWords的方式。
  4. 异常命名使用CapWords+Error后缀的方式。
  5. 全局变量尽量只在模块内有效,类似C语言中的static。实现方法有两种,一是all机制;二是前缀一个下划线。
  6. 函数命名使用全部小写的方式,可以使用下划线。
  7. 常量命名使用全部大写的方式,可以使用下划线。
  8. 类的属性(方法和变量)命名使用全部小写的方式,可以使用下划线。
  9. 类的属性有3种作用域public、non-public和subclass API,可以理解成C++中的public、private、protected,non-public属性前,前缀一条下划线。
  10. 类的属性若与关键字名字冲突,后缀一下划线,尽量不要使用缩略等其他方式。
  11. 为避免与子类属性命名冲突,在类的一些属性前,前缀两条下划线。比如:类Foo中声明__a,访问时,只能通过Foo._Foo_a,避免歧义。如果子类也叫Foo,那就无能为力了。
  12. 类的方法第一个参数必须是self,而静态方法第一个参数必须是cls。

编程建议

  1. 编码中考虑到其他python实现的效率等问题,比如运算符‘+’在CPython(Python)中效率很高,都是Jython中却非常低,所以应该采用.join()的方式。

  2. 尽可能使用‘is’‘is not’取代‘==’,比如if x is not None 要优于if x

  3. 使用基于类的异常,每个模块或包都有自己的异常类,此异常类继承自Exception。

  4. 常中不要使用裸露的except,except后跟具体的exceptions。例如

try:
    ...
except Exception as ex:
    print ex
  1. 异常中try的代码尽可能少。

  2. 使用startswith() and endswith()代替切片进行序列前缀或后缀的检查。

foo = 'abc000xyz'

if foo.startswith('abc') and foo.endswith('xyz'):
    print 'yes'
else:
    print 'no'

#yes

#而如下的方式不提倡
if foo[:3]=='abc' and foo[-3:]=='xyz':
    print 'yes'
else:
    print 'no'
  1. 使用isinstance()比较对象的类型。比如:
foo = 'abc000xyz'

# 提倡
print isinstance(foo,int) # false

# 不提倡
print type(foo) == type('1') #true
  1. 判断序列空或不空,有如下规则:
foo = 'abc000xyz'

if foo:
    print "not empty"
else:
    print "empty"

#不提倡使用如下
if len(foo):
    print "not empty"
else:
    print "empty"
  1. 二进制数据判断使用 if boolvalue的方式。

三、python3与python2的区别

区别简介

  1. Python 的 3.0 版本,常被称为 Python 3000,或简称 Py3k。相对于 Python 的早期版本,这是一个较大的升级。
  2. 为了不带入过多的累赘,Python 3.0 在设计的时候没有考虑向下相容。
  3. 许多针对早期 Python 版本设计的程式都无法在 Python 3.0 上正常执行。
  4. 为了照顾现有程式,Python 2.6 作为一个过渡版本,基本使用了 Python 2.x 的语法和库,同时考虑了向 Python 3.0 的迁移,允许使用部分 Python 3.0 的语法与函数。
  5. 新的 Python 程式建议使用 Python 3.0 版本的语法。
  6. 除非执行环境无法安装 Python 3.0 或者程式本身使用了不支援 Python 3.0 的第三方库。目前不支持 Python 3.0 的第三方库有 Twisted, py2exe, PIL等。
  7. 大多数第三方库都正在努力地相容 Python 3.0 版本。即使无法立即使用 Python 3.0,也建议编写相容 Python 3.0 版本的程式,然后使用 Python 2.6, Python 2.7 来执行。

主要区别

1.print 函数

print 语句没有了,取而代之的是 print() 函数。 Python 2.6 与 Python 2.7 部分地支持这种形式的 print 语法。在 Python 2.6 与Python 2.7 里面,以下三种形式是等价的:

print "fish"
print ("fish") # 注意print后面有个空格
print("fish") # print()不能带有任何其它参数

然而,Python 2.6 实际已经支持新的 print() 语法,实例如下:

from __future__ import print_function
print("fish", "panda", sep=', ')

如果 Python2.x 版本想使用使用 Python3.x 的 print 函数,可以导入 future 包,该包禁用 Python2.x 的 print 语句,采用 Python3.x 的 print 函数:

list =["a", "b", "c"]
print list    # python2.x 的 print 语句
['a', 'b', 'c']
from __future__ import print_function  # 导入 __future__ 包
print list     # Python2.x 的 print 语句被禁用,使用报错
 File "<stdin", line 1
   print list
            ^
SyntaxError: invalid syntax
print (list)   # 使用 Python3.x 的 print 函数
['a', 'b', 'c']

Python3.x 与 Python2.x 的许多兼容性设计的功能可以通过 future 这个包来导入。

2.Unicode

  • Python 2 有 ASCII str() 类型,unicode() 是单独的,不是 byte 类型。

  • 现在, 在 Python 3,我们最终有了 Unicode (utf-8) 字符串,以及一个字节类:byte 和 bytearrays。

  • 由于 Python3.x 源码文件默认使用 utf-8 编码,所以使用中文就更加方便了:

 中国 = 'china' 
print(中国) 
china
  • Python 2.x
 str = "我爱北京天安门"
 str
'\xe6\x88\x91\xe7\x88\xb1\xe5\x8c\x97\xe4\xba\xac\xe5\xa4\xa9\xe5\xae\x89\xe9\x97\xa8'
 str = u"我爱北京天安门"
 str
u'\u6211\u7231\u5317\u4eac\u5929\u5b89\u95e8'
  • python 3.x
 str = "我爱北京天安门"
 str
'我爱北京天安门'

3.除法运算

Python 中的除法较其它语言显得非常高端,有套很复杂的规则。Python 中的除法有两个运算符,/ 和 //

首先来说 / 除法:

在 Python 2.x 中 / 除法就跟我们熟悉的大多数语言,比如 Java 和 C ,整数相除的结果是一个整数,把小数部分完全忽略掉,浮点数除法会保留小数点的部分得到一个浮点数的结果。

在 Python 3.x 中 / 除法不再这么做了,对于整数之间的相除,结果也会是浮点数。

而对于 // 除法,这种除法叫做 floor 除法,会对除法的结果自动进行一个 floor 操作,在 Python 2.x 和 Python 3.x 中是一致的。

注意的是并不是舍弃小数部分,而是执行 floor 操作,如果要截取整数部分,那么需要使用 math 模块的 trunc 函数

4.异常

在 Python 3 中处理异常也轻微的改变了,在 Python 3 中我们现在使用 as 作为关键词。

捕获异常的语法由 except exc, var 改为 except exc as var。

使用语法except (exc1, exc2) as var 可以同时捕获多种类别的异常。 Python 2.6 已经支持这两种语法。

  • 在 2.x 时代,所有类型的对象都是可以被直接抛出的,在 3.x 时代,只有继承自BaseException的对象才可以被抛出。

  • 2.x raise 语句使用逗号将抛出对象类型和参数分开,3.x 取消了这种奇葩的写法,直接调用构造函数抛出对象即可。

在 2.x 时代,异常在代码中除了表示程序错误,还经常做一些普通控制结构应该做的事情,在 3.x 中可以看出,设计者让异常变的更加专一,只有在错误发生的情况才能去用异常捕获语句来处理。

5.xrange

在 Python 2 中 xrange() 创建迭代对象的用法是非常流行的。比如: for 循环或者是列表/集合/字典推导式。

这个表现十分像生成器(比如。“惰性求值”)。但是这个 xrange-iterable 是无穷的,意味着你可以无限遍历。

由于它的惰性求值,如果你不得仅仅不遍历它一次,xrange() 函数 比 range() 更快(比如 for 循环)。尽管如此,对比迭代一次,不建议你重复迭代多次,因为生成器每次都从头开始。

在 Python 3 中,range() 是像 xrange() 那样实现以至于一个专门的 xrange() 函数都不再存在(在 Python 3 中 xrange() 会抛出命名异常)

6.八进制字面量表示

八进制数必须写成0o777,原来的形式0777不能用了;二进制必须写成0b111。

新增了一个bin()函数用于将一个整数转换成二进制字串。 Python 2.6已经支持这两种语法。

在Python 3.x中,表示八进制字面量的方式只有一种,就是0o1000。

7.不等运算符

Python 2.x中不等于有两种写法 != 和 <

Python 3.x中去掉了<, 只有!=一种写法,还好,我从来没有使用<的习惯

8. 去掉了repr表达式``

Python 2.x 中反引号``相当于repr函数的作用

Python 3.x 中去掉了``这种写法,只允许使用repr函数,这样做的目的是为了使代码看上去更清晰么?不过我感觉用repr的机会很少,一般只在debug的时候才用,多数时候还是用str函数来用字符串描述对象。

9.多个模块被改名(根据PEP8)

旧的名字新的名字
_winregwinreg
ConfigParserconfigparser
copy_regcopyreg
Queuequeue
SocketServersocketserver
reprreprlib

StringIO模块现在被合并到新的io模组内。 new, md5, gopherlib等模块被删除。 Python 2.6已经支援新的io模组。

httplib, BaseHTTPServer, CGIHTTPServer, SimpleHTTPServer, Cookie, cookielib被合并到http包内。

取消了exec语句,只剩下exec()函数。 Python 2.6已经支援exec()函数。

10.数据类型

Py3.X去除了long类型,现在只有一种整型——int,但它的行为就像2.X版本的long

新增了bytes类型,对应于2.X版本的八位串,定义一个bytes字面量的方法如下:

b = b'china' 
type(b) 
<type 'bytes'

str 对象和 bytes 对象可以使用 .encode() (str - bytes) 或 .decode() (bytes - str)方法相互转化。

s = b.decode() 
s 
'china' 
b1 = s.encode() 
b1 
b'china'

dict的.keys()、.items 和.values()方法返回迭代器,而之前的iterkeys()等函数都被废弃。同时去掉的还有 dict.has_key(),用 in替代它吧 。

11.file()与raw_input()已去除

python 2.x中得file()已去除在python 3.x中只保留了open函数用来操作文件资源

python 2.x中得键盘录入raw_input(“”)已去除在python 3.x 中只保留了input(“”)

12.map、filter 和 reduce的变更

这三个函数号称是函数式编程的代表。在 Python3.x 和 Python2.x 中也有了很大的差异。

首先我们先简单的在 Python2.x 的交互下输入 map 和 filter,看到它们两者的类型是 built-in function(内置函数)

map
<built-in function map
filter
<built-in function filter

但是在Python 3.x中它们却不是这个样子了:

map
<class 'map'
map(print,[1,2,3])
<map object at 0x10d8bd400
filter
<class 'filter'
filter(lambda x:x % 2 == 0, range(10))
<filter object at 0x10d8bd3c8

首先它们从函数变成了类,其次,它们的返回结果也从当初的列表成了一个可迭代的对象, 我们尝试用 next 函数来进行手工迭代:

f =filter(lambda x:x %2 ==0, range(10))
next(f)
0
next(f)
2
next(f)
4
next(f)
6

对于比较高端的 reduce 函数,它在 Python 3.x 中已经不属于 built-in 了,被挪到 functools 模块当中。

核心类差异

  1. Python3 对 Unicode 字符的原生支持。

Python2 中使用 ASCII 码作为默认编码方式导致 string 有两种类型 str 和 unicode,Python3 只支持 unicode 的 string。Python2 和 Python3 字节和字符对应关系为:

  1. Python3 采用的是绝对路径的方式进行 import

Python2 中相对路径的 import 会导致标准库导入变得困难(想象一下,同一目录下有 file.py,如何同时导入这个文件和标准库 file)。Python3 中这一点将被修改,如果还需要导入同一目录的文件必须使用绝对路径,否则只能使用相关导入的方式来进行导入。

  1. Python2 中存在老式类和新式类的区别,Python3 统一采用新式类。新式类声明要求继承 object,必须用新式类应用多重继承。
  2. Python3 使用更加严格的缩进。Python2 的缩进机制中,1 个 tab 和 8 个 space 是等价的,所以在缩进中可以同时允许 tab 和 space 在代码中共存。这种等价机制会导致部分 IDE 使用存在问题。Python3 中 1 个 tab 只能找另外一个 tab 替代,因此 tab 和 space 共存会导致报错:TabError:inconsistent use of tabs and spaces in indentation.

废弃类差异

  1. print 语句被 Python3 废弃,统一使用 print 函数

  2. exec 语句被 python3 废弃,统一使用 exec 函数

  3. execfile 语句被 Python3 废弃,推荐使用 exec(open(“./filename”).read())

  4. 不相等操作符"<“被 Python3 废弃,统一使用”!="

  5. long 整数类型被 Python3 废弃,统一使用 int

  6. xrange 函数被 Python3 废弃,统一使用 range,Python3 中 range 的机制也进行修改并提高了大数据集生成效率

  7. Python3 中这些方法再不再返回 list 对象:dictionary 关联的 keys()、values()、items(),zip(),map(),filter(),但是可以通过 list 强行转换:

mydict={"a":1,"b":2,"c":3}
mydict.keys() #<built-in method keys of dict object at 0x000000000040B4C8
list(mydict.keys()) #['a', 'c', 'b']
  1. 迭代器 iterator 的 next()函数被 Python3 废弃,统一使用 next(iterator)

  2. raw_input 函数被 Python3 废弃,统一使用 input 函数

  3. 字典变量的 has_key 函数被 Python 废弃,统一使用 in 关键词

  4. file 函数被 Python3 废弃,统一使用 open 来处理文件,可以通过 io.IOBase 检查文件类型

  5. apply 函数被 Python3 废弃

  6. 异常 StandardError 被 Python3 废弃,统一使用 Exception

修改类差异

  1. 浮点数除法操作符“/”和“//”的区别

    • “ / ”:

      • Python2:若为两个整形数进行运算,结果为整形,但若两个数中有一个为浮点数,则结果为浮点数;
      • Python3:为真除法,运算结果不再根据参加运算的数的类型。
    • “//”:

      • Python2:返回小于除法运算结果的最大整数;从类型上讲,与"/"运算符返回类型逻辑一致。
      • Python3:和 Python2 运算结果一样。
  2. 异常抛出和捕捉机制区别

    • Python2

      • raise IOError, “file error” #抛出异常
      • except NameError, err: #捕捉异常
    • Python3

      • raise IOError(“file error”) #抛出异常
      • except NameError as err: #捕捉异常
  3. for 循环中变量值区别

    • Python2,for 循环会修改外部相同名称变量的值
i = 1
print'comprehension: ', [i for i in range(5)]print'after: i =', i ) #i=4

Python3,for 循环不会修改外部相同名称变量的值

i = 1
print'comprehension: ', [i for i in range(5)]print'after: i =', i ) #i=1
  1. round 函数返回值区别

    • Python2,round 函数返回 float 类型值
isinstance(round(15.5),int) #True 

Python3,round 函数返回 int 类型值

isinstance(round(15.5),float) #True
  1. 比较操作符区别

    • Python2 中任意两个对象都可以比较
11 < 'test' #True

Python3 中只有同一数据类型的对象可以比较

11 < 'test' # TypeError: unorderable types: int() < str()

四、进程 线程 协程 GIL(全局解释器锁)

进程

进程概念

进程是程序执行的过程,包括了动态创建、调度和消亡的整个过程,进程是系统对程序进行资源分配和管理的最小基本单位;

进程管理的资源包括:CPU(寄存器)、IO、内存、网络资源等;

当创建单个进程时,操作系统会为该进程分配大小为4GB的虚拟进程地址空间,操作系统采用虚拟内存虚拟技术,把该虚拟地址空间分成用户空间和内核空间,每个进程的用户空间都是独立的,也就是进程之间默认是不能共享全局变量的,但内核空间是每个进程都共享的,所以进程间通信必须通过内核完成;

用户空间按照访问属性一致的地址空间存放在一起的原则,划分成 5个不同的内存区域,总计 3G 的容量:

内存区域内存内容
局部变量,函数参数,函数的返回值
动态分配的内存
BSS段未初始化或初值为0的全局变量和静态局部变量
数据段已初始化且初值非0的全局变量和静态局部变量
代码段可执行文件的操作指令,可执行程序在内存中的镜像(只读)

在 x86 32 位系统里,Linux 内核地址空间是指虚拟地址从 0xC0000000 开始到 0xFFFFFFFF 为止的高端内存地址空间,总计 1G 的容量, 包括了内核镜像、物理页面表、驱动程序等运行在内核空间 。

进程间通信

  • 当一个进程创建时可以共享程序代码的页,但它们各自有独立的数据拷贝(堆和栈),因此子进程对内存单元的修改对父进程是不可见的。
  • 进程间的通信方式包括管道消息队列共享内存信号量信号Socket

管道

  • 所谓的管道,就是内核里面的一串缓存。从管道的一端写入的数据,实际上是缓存在内核中的,另一端读取,也就是从内核中读取这段数据。另外,管道传输的数据是无格式的流且大小受限。匿名管道的通信的方式是单向的,如果要双向通信,需要创建两个管道。管道分为匿名管道命名管道
  • 匿名管道:没有名字标识,是只存在于内存的特殊文件,不存在于文件系统中。匿名管道只能用于存在父子关系的进程间通信。(创建的子进程会复制父进程的文件描述符,这样就做到了两个进程各有两个「 fd[0] 与 fd[1]」,两个进程就可以通过各自的 fd 写入和读取同一个管道文件实现跨进程通信了)它的生命周期随着进程创建而建立,随着进程终止而消失。shell 命令中的 “|”
  • 命名管道:需要在文件系统创建一个类型为 p 的设备文件,不相干的进程从而可以通过这个设备文件进行通信。
  • 管道这种通信方式效率低,不适合进程间频繁地交换数据

消息队列

  • 消息队列是保存在内核中的消息链表,在发送数据时,会分成一个一个独立的数据单元,也就是消息体(数据块),消息体是用户自定义的数据类型,消息的发送方和接收方要约定好消息体的数据类型,所以每个消息体都是固定大小的存储块,不像管道是无格式的字节流数据。如果进程从消息队列中读取了消息体,内核就会把这个消息体删除。
  • 消息队列不适合比较大数据的传输,因为在内核中每个消息体都有一个最大长度的限制,同时所有队列所包含的全部消息体的总长度也是有上限。
  • 消息队列通信过程中,存在用户态与内核态之间的数据拷贝开销。因为进程写入数据到内核中的消息队列时,会发生从用户态拷贝数据到内核态的过程,同理另一进程读取内核中的消息数据时,会发生从内核态拷贝数据到用户态的过程。

共享内存

  • 现代操作系统,对于内存管理,采用的是虚拟内存技术,也就是每个进程都有自己独立的虚拟内存空间,不同进程的虚拟内存映射到不同的物理内存中。所以,即使进程 A 和 进程 B 的虚拟地址是一样的,其实访问的是不同的物理内存地址,对于数据的增删查改互不影响。
  • 共享内存的机制,就是拿出一块虚拟地址空间来,映射到相同的物理内存中。这样这个进程写入的东西,另外一个进程马上就能看到了,都不需要拷贝来拷贝去,传来传去,大大提高了进程间通信的速度。

信号量

  • 用了共享内存通信方式,带来新的问题,那就是如果多个进程同时修改同一个共享内存,很有可能就冲突了。为了防止多进程竞争共享资源,而造成的数据错乱,信号量使得共享的资源,在任意时刻只能被一个进程访问。
  • 信号量是一个整型的计数器,表示的是资源个数,其值可以通过两个原子操作来控制,分别是 P 操作和 V 操作。主要用于实现进程间的互斥(初始化信号量为1)与同步(初始化信号量为0),而不是用于缓存进程间通信的数据。

信号

  • 在 Linux 操作系统中, 为了响应各种各样的事件,提供了几十种信号,分别代表不同的意义。我们可以通过 kill -l 命令,查看所有的信号。

  • 运行在 shell 终端的进程,我们可以通过键盘输入某些组合键的时候,给进程发送信号。例如:

Ctrl+C 产生 SIGINT 信号,表示终止该进程;
Ctrl+Z 产生 SIGTSTP 信号,表示停止该进程,但还未结束;
kill -9 1050 ,表示给 PID 为 1050 的进程发送 SIGKILL 信号,用来立即结束该进程

Socket

  • 前面提到的管道、消息队列、共享内存、信号量和信号都是在同一台主机上进行进程间通信,那要想跨网络与不同主机上的进程之间通信,就需要 Socket 通信了。

进程实现

multiprocessing 创建

  1. 使用multiprocessing模块的Process类创建子进程对象

  2. 通过target参数=要创建的对象方法名来指定要创建的子进程任务对象

  3. 通过args=元祖的方式来传递该进程对象方法需要的参数,其中元祖的值必须与该对象的形参保持一致或者通过kwargs以字典的方式进行参数的传递,注意字典的key需要与该对象的形参名保持一致,但不用在意传参的顺序;

  4. 通过进程对象设置daemon属性=True或False实现进程守护(主进程一结束,子进程立即销毁),通过进程对象.start()方法实现开机一个进程。

  5. join所完成的工作就是阻塞当前进程,直到调用join方法的那个进程执行完,再继续执行当前进程,通过进程对象.join()方法实现主进程等待子进程完成后;

from multiprocessing import Process
import os, time

# 子进程要执行的代码
def run_proc(name):
  time.sleep(2)
  print('Run child process %s (%s)...' % (name, os.getpid()))

if __name__=='__main__':
  print('Parent process %s.' % os.getpid())
  p = Process(target=run_proc, args=('test',))
  print('Child process will start.')
  p.start()
  p.join()
  print('Child process end.')

multiprocessing.Pool 进程池

  1. 使用multiprocessing模块中得Pool类创建进程池对象并指定进程池的大小

  2. 使用进程池对象.apply_async()方法实现创建并添加进程到进程池中,如果池还没有满,那么就会创建一个新的进程用来执行该请求,如果池已经满了那么该请求就会等待,直到池中有进程结束,才会创建新的进程。

  3. 最后通过调用进程池对象的close()进行关闭,使用进程池.join()实现主进程等待子进程完成;

from multiprocessing import Pool
import os, time, random

def long_time_task(name):
  print('Run task %s (%s)...' % (name, os.getpid()))
  start = time.time()
  time.sleep(2)
  end = time.time()
  print('Task %s runs %0.2f seconds.' % (name, (end - start)))

if __name__=='__main__':
  print('Parent process %s.' % os.getpid())
  p = Pool(2)
  for i in range(3):
      p.apply_async(long_time_task, args=(i,))
  print('Waiting for all subprocesses done...')
  p.close()
  p.join()
  print('All subprocesses done.')

进程间通信

  1. 基于共享内存:进程之间默认是不能共享全局变量的(子进程不能改变主进程中全局变量的值)。如果要共享全局变量需要用(multiprocessing.Value(“d”,10.0),数值)(multiprocessing.Array(“i”,[1,2,3,4,5]),数组)(multiprocessing.Manager().dict(),字典)(multiprocessing.Manager().list(),列表)。

  2. 基于管道:包括 multiprocessing.Pipe(),multiprocessing.Queue()。

  • 一个子进程向Queue中写数据,另外一个进程从Queue中取数据,当一个Queue为空的用get取数据会进程会被阻塞。
from multiprocessing import Process, Queue
import os, time

# 写数据进程执行的代码:
def write(q):
  print('Process to write: %s' % os.getpid())
  for value in ['A', 'B', 'C']:
      print('Put %s to queue.' % value)
      q.put(value)
      time.sleep(2)

# 读数据进程执行的代码:
def read(q):
  print('Process to read: %s' % os.getpid())
  while True:
      value = q.get(True)
      print('Get %s from queue.' % value)

if __name__=='__main__':
  # 父进程创建Queue,并传给各个子进程:
  q = Queue()
  pw = Process(target=write, args=(q,))
  pr = Process(target=read, args=(q,))
  # 启动子进程pw,写入:
  pw.start()
  # 启动子进程pr,读取:
  pr.start()
  # 等待pw结束:
  pw.join()
  # pr进程里是死循环,无法等待其结束,只能强行终止:
  pr.terminate()

线程

线程概念

线程是操作系统能够进行运算调度的最小单位。线程被包含在进程之中,是进程中的实际运作单位,一个进程内可以包含多个线程,线程是资源调度的最小单位。

线程的内存模型

每个线程独立的线程上下文:一个唯一的整数线程ID,栈和栈指针,程序计数器,通用目的寄存器和条件码

和其他线程共享的进程上下文的剩余部分:整个用户虚拟地址空间,包括只读代码段,读/写数据段,堆以及所有的共享库代码和数据区域,也共享所有打开文件的集合

线程的状态

可运行 (runnable):线程被创建之后,调用Start()函数就到了这个状态。

运行 (running):start()函数之后,CPU切换到了这个线程开始执行里面的Run方法就称为运行状态。

阻塞 (blocked):阻塞状态是指线程因为某种原因放弃了cpu执行权,暂时停止运行。直到线程进入可运行(runnable)状态,才有机会再次获得cpu 执行权 转到运行(running)状态。

根据程序分别处于 Running 以及 IO Blocked 两种状态的时间占比,可分为两种:

  1. 计算密集型 ,程序执行时大部分时间处于 Running 状态;
  2. IO密集型 ,程序执行时大部分时间处于 IO Blocked 状态;

线程的调度

线程分类:

  1. 核级线程:

a. 内核线程建立和销毁都是由操作系统负责、通过系统调用完成,内核维护进程及线程的上下文信息以及线程切换。

b. 程序一般不会直接使用内核线程,而是使用内核线程的一种高级接口-轻量级进程(Light-weight Process简称LWP ,是一种由内核支持的用户线程,每一个轻量级进程都与一个特定的内核线程关联。)。

c. 优势:内核级线级能参与全局的多核处理器资源分配,充分利用多核 CPU 优势。

e. 局限性:需要系统调度,系统调度代价大,需要在用户态和内核态来回切换;内核进程数量有限。

  1. 用户级线程

a. 用户线程的创建、调度、同步和销毁全由用户空间的库函数完成,不需要内核的参与。内核的调度对象是进程本身,内核并不知道用户线程的存在。

b. 优势:性能极强。用户线程的建立,同步,销毁和调度完全在用户态中完成,操作非常快,低消耗,无需切换内核态。

c. 局限性:所有操作都需要自己实现,逻辑极其复杂。 用户线级线程只能参与竞争该进程的处理器资源,不能参与全局处理器资源的竞争。

线程的调度最常用的两种是:

  1. 分时调度

所有线程轮流使用 CPU 的使用权,平均分配每个线程占用 CPU 的时间。

  1. 抢占式调度

优先让优先级高的线程使用 CPU,如果线程的优先级相同,那么会随机选择一个(线程随机性)。

  • Python 线程调度实现方式参考了分时调度的思路,只不过将时间片换成字节码 。当一个线程取得 GIL 全局锁并开始执行字节码时,对已执行字节码进行计数。当执行字节码达到一定数量,或者线程主动让出控制(time.sleep(), wait(), blocked IO等)时,线程主动释放 GIL 全局锁。

线程实现

多线程和多进程最大的不同在于,多进程中,同一个变量,各自有一份拷贝存在于每个进程中,互不影响。而多线程中,静态变量,实例变量被线程共享,局部变量不被线程共享

  1. 使用threading模块中的Thread类实现线程的创建
  2. 通过指定target参数等于要创建的线程对象方法名指定创建线程
  3. 通过args以元祖的方式进行参数的传递或kwargs以字典的方式进行参数的传递
  4. 通过设置线程daemon=Ture或False设置线程守护(主线程结束子线程立即销毁)或者使用线程对象.SetDaemon属性=True或False来实现
  5. 通过线程对象的start()方法实现线程的调度,通过设置join()方法实现主线程阻塞并等待子线程完成

线程基本使用

函数方法
threading.currentThread(): 返回当前的线程变量。
threading.enumerate(): 返回一个包含正在运行的线程的list。正在运行指线程启动后、结束前,不包括启动前和终止后的线程。
threading.activeCount(): 返回正在运行的线程数量,与len(threading.enumerate())有相同的结果。
Thread类方法
run(): 用以表示线程活动的方法。
start():启动线程活动。
join([time]): 等待至线程中止。这阻塞调用线程直至线程的join() 方法被调用中止-正常退出或者抛出未处理的异常-或者是可选的超时发生。
isAlive(): 返回线程是否活动的。
getName(): 返回线程名。
setName(): 设置线程名。
from threading import Thread

num = 0

def run_thread(name):
   global num
   num = num + 1
   print('{} num: {}'.format(name, num))

if __name__=='__main__':
   p1 = Thread(target=run_thread, args=('thread1',))
   p2 = Thread(target=run_thread, args=('thread2',))
   p1.start()
   p2.start()
   p1.join()
   p2.join()
   
# thread1 num: 1
# thread2 num: 2

注意事项

线程之间共享数据最大的危险在于多个线程同时改一个变量,由于线程之间是进行随机调度的,如果有多个线程同时操作一个对象,如果没有很好地保护该对象,会造成程序结果的不可预期,我们因此也称为“线程不安全”。

可以使用互斥锁(Lock),同一时刻只允许一个线程执行操作

  • 在主线程中使用threading模块的Lock类创建互斥锁对象
  • 并在子线程中修改全局变量的方法前使用创建的互斥锁对象.acquire()方法获取锁
  • 当子线程修改共享变量使用完毕后使用互斥锁对象.release()方法释放锁,实现其他子线程可获取到锁;
from threading import Thread, Lock

num = 0

def change_num(name, lock):
   global num
   lock.acquire()
   num = num + 1
   lock.release()

def run_thread(name, lock):
   for i in range(1000000):
       change_num(name, lock)

if __name__=='__main__':
   lock = Lock()
   p1 = Thread(target=run_thread, args=('thread1', lock))
   p2 = Thread(target=run_thread, args=('thread2', lock))
   p1.start()
   p2.start()
   p1.join()
   p2.join()
   print(num)
   
# 2000000

GIL全局解释器锁

  • 简而言之GIL 就是Global Interpreter Lock 全局解释器锁 也叫互斥锁,是一个互斥信号量,保证仅有一个线程能持有Python解释器的控制权。

  • 意味着任何时刻都只有一个线程可以处于执行状态。GIL不会对执行单线程应用的开发者带来影响,但可能会成为计算密集型(CPU bound)和多线程型应用的性能瓶颈。

  • 即使在拥有多CPU内核的多线程架构中,GIL一次也只允许执行一个线程,被认为是Python臭名昭著的特性。

    • Python使用引用计数(reference counting)来进行内存管理。这意味着Python里创建对象会持有一个引用计数变量,用于追溯指向该对象的引用的数量。当引用计数的值减为零时,该对象占有的内存会被释放。
    • 我们可以通过sys模块的getrefcount()方法获取到python对象被引用的次数
    • 问题在于引用计数变量需要被保护,以防在竞争情况下的两个线程同时增加或减少它的值。如果这种情况发生了,可能会引起从未释放的内存泄露,更坏的可能是在仍存在对象的引用时错误地释放内存。这些可能导致Python程序的崩溃或其他不知所以然的bug。
    • 为跨线程共享的所有数据结构加锁,可以保证引用计数变量的安全,防止不一致的修改。
    • 但为一个或者一组对象加锁,意味着多个锁将同时存在,可能会引起死锁(Deadlocks,只在拥有多个锁时才可能发生)。另一副作用是重复地获取和释放锁会带来性能下降。
    • GIL是作用于解释器自身的唯一的锁,并新定一条规则:执行任何Python字节码都需要获取解释器锁。由此防止死锁(现在就只有一个锁啦),也不会引入过多额外的性能开销。但是它强制所有计算密集型的Python应用成为了单线程应用。
  • GIL对I/O密集型的多线程影响并不大,因为线程在I/O等待时共享了这个锁,但会造成当线程是I/O型和CPU型共存应用的I/O线程饥饿,因为这类线程不能从CPU型线程中获取到GIL。

    • 因为Python内置了一种机制,以强制线程在一段固定的连续使用后释放GIL,如果没有其他线程获取到GIL,则该线程继续持有GIL锁。
    • 这种机制的问题在于,多数情况下,CPU型线程会在其他线程可以获取GIL之前重新获取到GIL。
    • 可以这样理解:线程会在一个时间段后释放GIL,但在释放的时间点,I/O仍可能处于等待中而不去获取GIL,于是CPU线程则会继续持有该GIL,如此导致IO线程饥饿。
  • I/O密集型应用是指将大量时间花费等待在I/O读写的应用,I/O可以来自于用户、文件、数据库、网络等。I/O源在准备I/O数据前可能会进行其他操作,导致I/O密集型应用有时在获取对应数据前会等待大量时间。比如,用户可能会思考很久到底输入什么,数据库也可能正在执行其他的查询操作。

    • 因此解决这类问题最常用的方式是使用多进程而非多线程。每个Python进程都有自己的解释器和内存空间,所以GIL就不成问题了。

协程

基本概念

  • 协程,又称微线程。拥有极高的执行率。协程的作用,是在执行函数A时,可以随时中断,去执行函数B,然后中断继续执行函数A(可以自由切换)。但这一过程并不是函数调用(没有调用语句),这一整个过程看似像多线程,然而协程只有一个线程执行。
  • 协程的调度完全由用户控制,协程拥有自己的寄存器上下文和栈,协程调度切换时,将寄存器上下文和栈保存到其他地方,在切回来的时候,恢复先前保存的寄存器上下文和栈,直接操作用户空间栈,完全没有内核切换的开销。
  • gevent是一个广泛使用的协程框架,它的一个特点是可以用同步的方法写异步应用,不需要写回调函数

协程实现

  • 通过gevent模块导入monkey类调用patch_all()为协程打上猴子补丁;

  • 通过gevent模块的joinall()方法以列表的方式通过gevent.spawn()进行协程的创建并spawn()第一个参数为要创建的对象方法名其他参数为协程对象需要传递的参数;

  • 基本使用

from gevent import monkey; monkey.patch_all()
import gevent
import time

def f(num):
   print('start: ' + str(num))
   time.sleep(3)
   print('end: ' + str(num))

gevent.joinall([
       gevent.spawn(f, 1),
       gevent.spawn(f, 2),
  gevent.spawn(f, 3),
])
  • python 3.5 之后还引入async/await
import asyncio

async def hw_async():
   print('Hello world!')

   loop = asyncio.get_event_loop()
   loop.run_until_complete(hw_async())
   loop.close()
  • async 是明确将函数声明为协程的关键字。这样的函数执行时会返回一个协程对象。

  • 通过asyncio.get_event_loop(),启动默认的协程调度并执行异步任务。

  • loop.run_until_complete()这个函数是阻塞执行的, 直到所有的异步函数执行完毕。

  • 最后使用创建的asyncio对象调用close()方法对协程进行关闭;

import asyncio
import datetime
from threading import currentThread

async def hw_async(num):
   print(f"Hello world {num} begin at: {datetime.datetime.now().strftime('%S')},thread:{currentThread().name}")
  await asyncio.sleep(3)
   print(f"Hello world {num} end at: {datetime.datetime.now().strftime('%S')}")

   loop = asyncio.get_event_loop()
   
   tasks = [hw_async(1), hw_async(2),hw_async(3)]
  loop.run_until_complete(asyncio.wait(tasks))
   loop.close()
  • await语法只能出现在通过async修饰的函数中,否则会报SyntaxError错误。且await后面的对象需要是一个awaitable,有三种主要类型: 协程, 任务 和 Future。

  • await io操作,此时,当前协程就会被挂起,时间循环转而执行其他协程。但并不是说所有协程里的await都会导致当前协程的挂起,如果跟的是我们定义的协程,则会执行这个协程,如果是asyncio模块制作者定义的固有协程,比如模拟io操作的asyncio.sleep,以及io操作,比如网络io:asyncio.open_connection这些,才会挂起当前协程。

  • loop.run_until_complete(asyncio.wait(tasks))运行时,会首先将tasks列表里的Coroutines先转换为future

关系对比

线程是依附在进程里面的,没有进程就没有线程

一个进程默认提供一条线程这个线程叫主线程,进程可以创建多个线程

协程看似像线程,然而协程有且仅有一个线程执行。

区别对比

创建进程的资源开销要比创建线程的资源开销要大,而协程拥有自己的寄存器上下文和栈;

进程是操作系统资源了分配的基本单位,线程是由CPU调度的基本单位,协程拥有极高的执行效率

线程不能独立执行,必须依存在进程中,而协程需要用户自己调度运行

优缺点对比

进程优缺点

  • 可以使用多核
  • 缺点资源开销很大

线程优缺点

  • 资源开销小
  • 不能使用多核,只能使用并发的方式依次使用到单核,多线程操作共享变量还需要严谨的使用GIL 互斥锁;

协程的优缺点

  • 极高的执行率,拥有自己的寄存器上下文和栈,可以快速高效的自由切换,解除GIL锁的烦恼
  • 需要用户(开发人员自己调度运行)

项目合理选型

I/O密集型:适合协程

CPU密集型:适合多进程 (但进程数量存在限制)

I/O密集型 + CPU密集型:多进程 + 协程

五、Python 面向对象

面向对象技术简介

  • 类(Class): 用来描述具有相同的属性和方法的对象的集合。它定义了该集合中每个对象所共有的属性和方法。对象是类的实例。

  • 类变量:类变量在整个实例化的对象中是公用的。类变量定义在类中且在函数体之外。类变量通常不作为实例变量使用。

  • 数据成员:类变量或者实例变量, 用于处理类及其实例对象的相关的数据。

  • 方法重写:如果从父类继承的方法不能满足子类的需求,可以对其进行改写,这个过程叫方法的覆盖(override),也称为方法的重写。

  • 局部变量:定义在方法中的变量,只作用于当前实例的类。

  • 实例变量:在类的声明中,属性是用变量来表示的。这种变量就称为实例变量,是在类声明的内部但是在类的其他成员方法之外声明的。

  • 继承:即一个派生类(derived class)继承基类(base class)的字段和方法。继承也允许把一个派生类的对象作为一个基类对象对待。例如,有这样一个设计:一个Dog类型的对象派生自Animal类,这是模拟"是一个(is-a)"关系(例图,Dog是一个Animal)。

  • 实例化:创建一个类的实例,类的具体对象。

  • 方法:类中定义的函数。

  • 对象:通过类定义的数据结构实例。对象包括两个数据成员(类变量和实例变量)和方法。

创建类

  • 使用 class 语句来创建一个新类,class 之后为类的名称并以冒号结尾:
class ClassName:
   '类的帮助信息'   #类文档字符串
   pass

类的帮助信息可以通过ClassName.doc查看。

构建实例方法

class Employee:
   '所有员工的基类'
   empCount = 0
 
   def __init__(self, name, salary):
      self.name = name
      self.salary = salary
      Employee.empCount += 1
   
   def displayCount(self):
     print "Total Employee %d" % Employee.empCount
 
   def displayEmployee(self):
      print "Name : ", self.name,  ", Salary: ", self.salary

empCount 变量是一个类变量,它的值将在这个类的所有实例之间共享。你可以在内部类或外部类使用 Employee.empCount 访问。

第一种方法init()方法是一种特殊的方法,被称为类的构造函数或初始化方法,当创建了这个类的实例时就会调用该方法

self 代表类的实例,self 在定义类的方法时是必须有的,虽然在调用时不必传入相应的参数。

  • self代表类的实例,而非类

    • 类的方法与普通的函数只有一个特别的区别——它们必须有一个额外的第一个参数名称, 按照惯例它的名称是 self
    • 通过self.class 可以查看到其指向为实例对象,当然我们也可以更换成其他关键字;

创建实例对象

实例化类其他编程语言中一般用关键字 new,但是在 Python 中并没有这个关键字,类的实例化类似函数调用方式。

以下使用类的名称 Employee 来实例化,并通过 init 方法接收参数。

"创建 Employee 类的第一个对象"
emp1 = Employee("Zara", 2000)
"创建 Employee 类的第二个对象"
emp2 = Employee("Manni", 5000)

访问属性

您可以使用点号 . 来访问对象的属性。使用如下类的名称访问类变量:

emp1.displayEmployee()
emp2.displayEmployee()
print "Total Employee %d" % Employee.empCount

还可以动态添加,删除,修改类的属性,如下所示:

emp1.age = 7  # 添加一个 'age' 属性
emp1.age = 8  # 修改 'age' 属性
del emp1.age  # 删除 'age' 属性

你也可以使用以下函数的方式来访问属性:

  • getattr(obj, name[, default]) : 访问对象的属性。
  • hasattr(obj,name) : 检查是否存在一个属性。
  • setattr(obj,name,value) : 设置一个属性。如果属性不存在,会创建一个新属性。
  • delattr(obj, name) : 删除属性。
hasattr(emp1, 'age')    # 如果存在 'age' 属性返回 True。
getattr(emp1, 'age')    # 返回 'age' 属性的值
setattr(emp1, 'age', 8) # 添加属性 'age' 值为 8
delattr(emp1, 'age')    # 删除属性 'age'

Python内置类属性

dict : 类的属性(包含一个字典,由类的数据属性组成)

doc :类的文档字符串

name: 类名

module: 类定义所在的模块(类的全名是’main.className’,如果类位于一个导入模块mymod中,那么className.module 等于 mymod)

bases : 类的所有父类构成元素(包含了一个由所有父类组成的元组)

class Employee:
   '所有员工的基类'
   empCount = 0
 
   def __init__(self, name, salary):
      self.name = name
      self.salary = salary
      Employee.empCount += 1
   
   def displayCount(self):
     print "Total Employee %d" % Employee.empCount
 
   def displayEmployee(self):
      print "Name : ", self.name,  ", Salary: ", self.salary
 
print "Employee.__doc__:", Employee.__doc__
print "Employee.__name__:", Employee.__name__
print "Employee.__module__:", Employee.__module__
print "Employee.__bases__:", Employee.__bases__
print "Employee.__dict__:", Employee.__dict__
Employee.__doc__: 所有员工的基类
Employee.__name__: Employee
Employee.__module__: __main__
Employee.__bases__: ()
Employee.__dict__: {'__module__': '__main__', 'displayCount': <function displayCount at 0x10a939c80, 'empCount': 0, 'displayEmployee': <function displayEmployee at 0x10a93caa0, '__doc__': '\xe6\x89\x80\xe6\x9c\x89\xe5\x91\x98\xe5\xb7\xa5\xe7\x9a\x84\xe5\x9f\xba\xe7\xb1\xbb', '__init__': <function __init__ at 0x10a939578}

python对象销毁(垃圾回收)

Python 使用了引用计数这一简单技术来跟踪和回收垃圾。

在 Python 内部记录着所有使用中的对象各有多少引用。

一个内部跟踪变量,称为一个引用计数器。

当对象被创建时, 就创建了一个引用计数, 当这个对象不再需要时, 也就是说, 这个对象的引用计数变为0 时, 它被垃圾回收。但是回收不是"立即"的, 由解释器在适当的时机,将垃圾对象占用的内存空间回收。

a = 40      # 创建对象  <40
b = a       # 增加引用, <40 的计数
c = [b]     # 增加引用.  <40 的计数

del a       # 减少引用 <40 的计数
b = 100     # 减少引用 <40 的计数
c[0] = -1   # 减少引用 <40 的计数

垃圾回收机制不仅针对引用计数为0的对象,同样也可以处理循环引用的情况。循环引用指的是,两个对象相互引用,但是没有其他变量引用他们。这种情况下,仅使用引用计数是不够的。Python 的垃圾收集器实际上是一个引用计数器和一个循环垃圾收集器。作为引用计数的补充, 垃圾收集器也会留心被分配的总量很大(即未通过引用计数销毁的那些)的对象。 在这种情况下, 解释器会暂停下来, 试图清理所有未引用的循环。

  • 析构函数 del ,__del__在对象销毁的时候被调用,当对象不再被使用时,del方法运行:
class Point:
   def __init__( self, x=0, y=0):
      self.x = x
      self.y = y
   def __del__(self):
      class_name = self.__class__.__name__
      print class_name, "销毁"
 
pt1 = Point()
pt2 = pt1
pt3 = pt1
print id(pt1), id(pt2), id(pt3) # 打印对象的id
del pt1
del pt2
del pt3
3083401324 3083401324 3083401324
Point 销毁

类的继承

面向对象的编程带来的主要好处之一是代码的重用,实现这种重用的方法之一是通过继承机制。

通过继承创建的新类称为子类或派生类,被继承的类称为基类、父类或超类。

继承语法

class 派生类名(基类名)
    ...

在python中继承中的一些特点:

如果在子类中需要父类的构造方法就需要显式的调用父类的构造方法,或者不重写父类的构造方法。详细说明可查看: python 子类继承父类构造函数说明。

在调用基类的方法时,需要加上基类的类名前缀,且需要带上 self 参数变量。区别在于类中调用普通函数时并不需要带上 self 参数

Python 总是首先查找对应类型的方法,如果它不能在派生类中找到对应的方法,它才开始到基类中逐个查找。(先在本类中查找调用的方法,找不到才去基类中找)。

  • 如果在继承元组中列了一个以上的类,那么它就被称作"多重继承" 。
class SubClassName (ParentClass1[, ParentClass2, ...]):
    ...

可以使用issubclass()或者isinstance()方法来检测。

  • issubclass() - 布尔函数判断一个类是另一个类的子类或者子孙类,语法:issubclass(sub,sup)
  • isinstance(obj, Class) 布尔函数如果obj是Class类的实例对象或者是一个Class子类的实例对象则返回true。

方法重写

  • 如果你的父类方法的功能不能满足你的需求,你可以在子类重写你父类的方法:
class Parent:        # 定义父类
   def myMethod(self):
      print '调用父类方法'
 
class Child(Parent): # 定义子类
   def myMethod(self):
      print '调用子类方法'
 
c = Child()          # 子类实例
c.myMethod()         # 子类调用重写方法

基础重载方法

序号方法, 描述 & 简单的调用
1init ( self [,args…] ) 构造函数 简单的调用方法: obj = className(args)
2del( self ) 析构方法, 删除一个对象 简单的调用方法 : del obj
3repr( self ) 转化为供解释器读取的形式 简单的调用方法 : repr(obj)
4str( self ) 用于将值转化为适于人阅读的形式 简单的调用方法 : str(obj)
5cmp ( self, x ) 对象比较 简单的调用方法 : cmp(obj, x)

运算符重载

  • Python同样支持运算符重载,实例如下:
class Vector:
   def __init__(self, a, b):
      self.a = a
      self.b = b
 
   def __str__(self):
      return 'Vector (%d, %d)' % (self.a, self.b)
   
   def __add__(self,other):
      return Vector(self.a + other.a, self.b + other.b)
 
v1 = Vector(2,10)
v2 = Vector(5,-2)
print v1 + v2

# 输出结果
 Vector(7,8)

类属性与方法

  • 类的私有属性

    • __private_attrs:两个下划线开头,声明该属性为私有,不能在类的外部被使用或直接访问。在类内部的方法中使用时 self.__private_attrs。
  • 类的方法

    • 在类的内部,使用 def 关键字可以为类定义一个方法,与一般函数定义不同,类方法必须包含参数 self,且为第一个参数
  • 类的私有方法

    • __private_method:两个下划线开头,声明该方法为私有方法,不能在类的外部调用。在类的内部调用 self.__private_methods
class JustCounter:
    __secretCount = 0  # 私有变量
    publicCount = 0    # 公开变量
 
    def count(self):
        self.__secretCount += 1
        self.publicCount += 1
        print self.__secretCount
 
counter = JustCounter()
counter.count()
counter.count()
print counter.publicCount
print counter.__secretCount  # 报错,实例不能访问私有变量
  • Python 通过改变名称来包含类名:
1
2
2
Traceback (most recent call last):
  File "test.py", line 17, in <module
    print counter.__secretCount  # 报错,实例不能访问私有变量
AttributeError: JustCounter instance has no attribute '__secretCount'
  • Python不允许实例化的类访问私有数据,但你可以使用 object._className_attrName( 对象名._类名__私有属性名 )访问属性,参考以下实例:
class Runoob:
    __site = "www.runoob.com"

runoob = Runoob()
print runoob._Runoob__site
www.runoob.com

单下划线、双下划线、头尾双下划线说明:

foo: 定义的是特殊方法,一般是系统定义名字 ,类似 init() 之类的。

_foo: 以单下划线开头的表示的是 protected 类型的变量,即保护类型只能允许其本身与子类进行访问,不能用于 from module import *

__foo: 双下划线的表示的是私有类型(private)的变量, 只能是允许这个类本身进行访问了。

六、Python网络编程

网络变成概念

Python 提供了两个级别访问的网络服务:

  • 低级别的网络服务支持基本的 Socket,它提供了标准的 BSD Sockets API,可以访问底层操作系统 Socket 接口的全部方法。
  • 高级别的网络服务模块 SocketServer, 它提供了服务器中心类,可以简化网络服务器的开发。

什么是 Socket?

  • Socket又称"套接字",应用程序通常通过"套接字"向网络发出请求或者应答网络请求,使主机间或者一台计算机上的进程间可以通讯。

socket()函数

  • Python 中,我们用 socket()函数来创建套接字,语法格式如下:
socket.socket([family[, type[, proto]]])

参数

family: 套接字家族可以使 AF_UNIX 或者 AF_INET。

type: 套接字类型可以根据是面向连接的还是非连接分为 SOCK_STREAM 或 SOCK_DGRAM。

protocol: 一般不填默认为 0。

Socket 对象(内建)方法

函数描述
服务器端套接字
s.bind()绑定地址(host,port)到套接字, 在 AF_INET下,以元组(host,port)的形式表示地址。
s.listen()开始 TCP 监听。backlog 指定在拒绝连接之前,操作系统可以挂起的最大连接数量。该值至少为 1,大部分应用程序设为 5 就可以了。
s.accept()被动接受TCP客户端连接,(阻塞式)等待连接的到来
客户端套接字
s.connect()主动初始化TCP服务器连接,。一般address的格式为元组(hostname,port),如果连接出错,返回socket.error错误。
s.connect_ex()connect()函数的扩展版本,出错时返回出错码,而不是抛出异常
公共用途的套接字函数
s.recv()接收 TCP 数据,数据以字符串形式返回,bufsize 指定要接收的最大数据量。flag 提供有关消息的其他信息,通常可以忽略。
s.send()发送 TCP 数据,将 string 中的数据发送到连接的套接字。返回值是要发送的字节数量,该数量可能小于 string 的字节大小。
s.sendall()完整发送 TCP 数据。将 string 中的数据发送到连接的套接字,但在返回之前会尝试发送所有数据。成功返回 None,失败则抛出异常。
s.recvfrom()接收 UDP 数据,与 recv() 类似,但返回值是(data,address)。其中 data 是包含接收数据的字符串,address 是发送数据的套接字地址。
s.sendto()发送 UDP 数据,将数据发送到套接字,address 是形式为(ipaddr,port)的元组,指定远程地址。返回值是发送的字节数。
s.close()关闭套接字
s.getpeername()返回连接套接字的远程地址。返回值通常是元组(ipaddr,port)。
s.getsockname()返回套接字自己的地址。通常是一个元组(ipaddr,port)
s.setsockopt(level,optname,value)设置给定套接字选项的值。
s.getsockopt(level,optname[.buflen])返回套接字选项的值。
s.settimeout(timeout)设置套接字操作的超时期,timeout是一个浮点数,单位是秒。值为None表示没有超时期。一般,超时期应该在刚创建套接字时设置,因为它们可能用于连接的操作(如connect())
s.gettimeout()返回当前超时期的值,单位是秒,如果没有设置超时期,则返回None。
s.fileno()返回套接字的文件描述符。
s.setblocking(flag)如果flag为0,则将套接字设为非阻塞模式,否则将套接字设为阻塞模式(默认值)。非阻塞模式下,如果调用recv()没有发现任何数据,或send()调用无法立即发送数据,那么将引起socket.error异常。
s.makefile()创建一个与该套接字相关连的文件

简单实例

服务端

我们使用 socket 模块的 socket 函数来创建一个 socket 对象。socket 对象可以通过调用其他函数来设置一个 socket 服务。

现在我们可以通过调用 bind(hostname, port) 函数来指定服务的 port(端口)。

接着,我们调用 socket 对象的 accept 方法。该方法等待客户端的连接,并返回 connection 对象,表示已连接到客户端。

完整代码如下:

import socket
# 建立一个服务端
server = socket.socket(socket.AF_INET,socket.SOCK_STREAM)
server.bind(('localhost',6999)) #绑定要监听的端口
server.listen(5) #开始监听 表示可以使用五个链接排队
while True:# conn就是客户端链接过来而在服务端为期生成的一个链接实例
    conn,addr = server.accept() #等待链接,多个链接的时候就会出现问题,其实返回了两个值
    print(conn,addr)
    while True:
        try:
            data = conn.recv(1024)  #接收数据
            print('recive:',data.decode()) #打印接收到的数据
            conn.send(data.upper()) #然后再发送数据
        except ConnectionResetError as e:
            print('关闭了正在占线的链接!')
            break
    conn.close()

客户端

接下来我们写一个简单的客户端实例连接到以上创建的服务。端口号为 12345。

socket.connect(hosname, port ) 方法打开一个 TCP 连接到主机为 hostname 端口为 port 的服务商。连接后我们就可以从服务端获取数据,记住,操作完成后需要关闭连接。

完整代码如下:

import socket# 客户端 发送一个数据,再接收一个数据
client = socket.socket(socket.AF_INET,socket.SOCK_STREAM) #声明socket类型,同时生成链接对象
client.connect(('localhost',6999)) #建立一个链接,连接到本地的6969端口
while True:
    # addr = client.accept()
    # print '连接地址:', addr
    msg = '欢迎访问菜鸟教程!'  #strip默认取出字符串的头尾空格
    client.send(msg.encode('utf-8'))  #发送一条信息 python3 只接收btye流
    data = client.recv(1024) #接收一个信息,并指定接收的大小 为1024字节
    print('recv:',data.decode()) #输出我接收的信息
client.close() #关闭这个链接

现在我们打开两个终端,第一个终端执行 server.py 文件:

$ python server.py

第二个终端执行 client.py 文件:

$ python client.py 
欢迎访问菜鸟教程!

这时我们再打开第一个终端,就会看到有以下信息输出:

连接地址: ('192.168.0.118', 62461)

Python Internet 模块

协议功能用处端口号Python 模块
HTTP网页访问80httplib, urllib, xmlrpclib
NNTP阅读和张贴新闻文章,俗称为"帖子"119nntplib
FTP文件传输20ftplib, urllib
SMTP发送邮件25smtplib
POP3接收邮件110poplib
IMAP4获取邮件143imaplib
Telnet命令行23telnetlib
Gopher信息查找70gopherlib, urllib

七、Python设计模式

A. OPP七大设计原则

  1. 单一职责原则(Single Responsiblity Principle, SRP)
一个类负责一项职责。
  1. 开闭原则(Open Closed Principle, OCP)

    一个软件实体如类、模块和函数应该对扩展开放,对修改关闭。

  2. 里氏替换原则(Liskov Substitution Principle, LSP)

    继承与派生的规则(子类可替换父类)。

  3. 依赖倒转原则(Dependency Inversion Principle, DIP)

    高层模块不应该依赖低层模块,二者都应该依赖其抽象;抽象不应该依赖细节;细节应该依赖抽象。即针对接口编程,不要针对实现编程。

  4. 接口隔离原则(Interface Segregation Principle, ISP)

    建立单一接口,不要建立庞大臃肿的接口,尽量细化接口,接口中的方法尽量少。

  5. 组合/聚合复用原则(Composite/Aggregate Reuse Principle, CARP)

    尽量使用组合和聚合少使用继承的关系来达到复用的原则。

  6. 最小知识原则(迪米特法则)(Principle of Least Knowledge, PLK)

    高内聚,低耦合(high cohesion low coupling)。

B.创建型模式

1.单例模式(Singleton pattern)

	确保一个类只有一个实例, 并提供全局访问点。

示例:

class S:
   instance = None

   def __new__(cls, *args, **kwargs):
       if S.instance is None:
           S.instance = super().__new__(cls)
   
   def __init__(self):
       pass

s1 = S()
print(id(s1))
s2 = S()
print(id(s2))

2.多例模式(Multition pattern)

	在一个解决方案中结合两个或多个模式, 以解决一般或重复发生的问题.。

3.工厂模式(factory method pattern)

	定义了一个创建对象的接口, 但由子类决定要实例化的类是哪一个。工厂方法让类把实例化推迟到子类。

4.原型模式(prototype pattern)

	当创建给定类的实例过程很昂贵或很复杂时, 就使用原形模式。

5.抽象工厂模式(Abstract factory pattern)

	提供一个接口, 用于创建相关或依赖对象的家族, 而不需要指定具体类。

6.生成器模式(Builder pattern)

	使用生成器模式封装一个产品的构造过程, 并允许按步骤构造。将一个复杂对象的构建与它的表示分离, 使得同样的构建过程可以创建不同的表示。

C.结构型模式

7.适配器模式(Adapter pattern)

将一个类的接口, 转换成客户期望的另一个接口。 适配器让原本接口不兼容的类可以合作无间. 对象适配器使用组合, 类适配器使用多重继承。

8.桥接模式(Bridge pattern)

使用桥接模式通过将实现和抽象放在两个不同的类层次中而使它们可以独立改变。

9.组合模式(composite pattern)

允许你将对象组合成树形结构来表现”整体/部分”层次结构。组合能让客户以一致的方式处理个别对象以及对象组合。

10.装饰者(修饰器)模式(decorator pattern)

动态地将责任附加到对象上, 若要扩展功能, 装饰者提供了比继承更有弹性的替代方案。

11.外观模式(facade pattern)

提供了一个统一的接口, 用来访问子系统中的一群接口。外观定义了一个高层接口, 让子系统更容易使用。

12.享元模式(Flyweight Pattern)

如想让某个类的一个实例能用来提供许多”虚拟实例”, 就使用蝇量模式。

13.代理模式(Proxy pattern)

为另一个对象提供一个替身或占位符以控制对这个对象的访问。

D.行为型模式

14.观察者模式(Observer pattern)

在对象之间定义一对多的依赖, 这样一来, 当一个对象改变状态, 依赖它的对象都会收到通知, 并自动更新。

15.责任链模式(Chain of responsibility pattern)

通过责任链模式, 你可以为某个请求创建一个对象链.。每个对象依序检查此请求并对其进行处理或者将它传给链中的下一个对象.

16.命令模式(Command pattern)

将”请求”封闭成对象, 以便使用不同的请求,队列或者日志来参数化其他对象. 命令模式也支持可撤销的操作。

17.解释器模式(Interpreter pattern)

使用解释器模式为语言创建解释器。

18.迭代器模式(Iterator pattern)

提供一种方法顺序访问一个聚合对象中的各个元素, 而又不暴露其内部的表示。

19.中介者模式(Mediator pattern)

使用中介者模式来集中相关对象之间复杂的沟通和控制方式。

20.备忘录模式(Memento pattern)

当你需要让对象返回之前的状态时(例如, 你的用户请求”撤销”), 你使用备忘录模式。

21.状态模式(State pattern)

允许对象在内部状态改变时改变它的行为, 对象看起来好象改了它的类。

22.策略模式(Strategy pattern)

定义了算法族, 分别封闭起来, 让它们之间可以互相替换, 此模式让算法的变化独立于使用算法的客户。

23.模板方法模式(Template pattern)

在一个方法中定义一个算法的骨架, 而将一些步骤延迟到子类中。模板方法使得子类可以在不改变算法结构的情况下, 重新定义算法中的某些步骤。

24.访问者模式(Visitor pattern)

当你想要为一个对象的组合增加新的能力, 且封装并不重要时, 就使用访问者模式。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值