Python学习笔记

python简介

python的优点:

1、Python为我们提供了非常完善的基础代码库,覆盖了网络、文件、GUI、数据库、文本等大量内容。
2、用Python开发,许多功能不必从零编写,直接使用现成的即可。
3、Python有大量的第三方库。

python的缺点:

缺点就是运行速度慢,和C程序相比非常慢,因为Python是解释型语言,你的代码在执行时会一行一行地翻译成CPU能理解的机器码,这个翻译过程非常耗时,所以很慢。

python适合开发的应用:

1、网络应用,包括网站、后台服务等等;
2、许多日常需要的小工具,包括系统管理员需要的脚本任务等等;
3、把其他语言开发的程序再包装起来,方便使用。

Python基础

#print absolute value of an integer:
a=100
if a >= 0:
       print(a)
else:
       print(-a)

以#开头的语句是注释。
当语句以冒号 : 结尾时,缩进的语句视为代码块。
按照约定俗成的管理,应该始终坚持使用4个空格的缩进。
Python是大小写敏感的。

数据类型和变量

数据类型

在Python中,能够直接处理的数据类型有以下几种:
1.整数:
十六进制:0xff00,0xa5b4c3d2
2.浮点数:
(1)数学写法:1.23,-1.23
(2)科学计数法:1.23x109 : 1.23e912.3e8 0.000012 : 1.2e-5
3.字符串:
字符串是以单引号'或双引号"括起来的任意文本,例:'abc',''xyz
既包含'又包含"'I\'m \"OK\"!' 在' "前加
\n表示换行
如果字符串内部有很多换行,用\n写在一行里不好阅读,为了简化,Python允许用 '''...'''的格式表示多行内容。
4.布尔值:TrueFalse
布尔值可以用andornot运算。
5.空值:None
None不能理解为0,因为0是有意义的,而None是一个特殊的空值。
6.变量:
变量名必须是大小写英文、数字和_的组合,且不能用数字开头。
7.常量:
通常用全部大写的变量名表示常量:
PI = 3.14159265359
在Python中,有两种除法,一种除法是/
>>> 10 / 3
3.3333333333333335
/除法计算结果是浮点数,即使是两个整数恰好整除,结果也是浮点数:
>>> 9 / 3
3.0
还有一种除法是//,称为地板除,两个整数的除法仍然是整数:
>>> 10 // 3
3

字符编码

Python的字符串

对于单个字符的编码,Python提供了ord()函数获取字符的整数表示,chr()函数把编码转换为对应的字符:

>>> ord('A')
65
>>> ord('中')
20013
>>> chr(66)
'B'
>>> chr(25991)
'文'

如果知道字符的整数编码,还可以用十六进制这么写str

>>> '\u4e2d\u6587'
'中文'

Python的字符串类型是str,在内存中以Unicode表示,一个字符对应若干个字节。如果要在网络上传输,或者保存到磁盘上,就需要把str变为以字节为单位的bytes
Python对bytes类型的数据用带b前缀的单引号或双引号表示:
x = b'ABC'
要注意区分'ABC'b'ABC',前者是str,后者虽然内容显示得和前者一样,但bytes的每个字符都只占用一个字节。
以Unicode表示的str通过encode()方法可以编码为指定的bytes,例如:

>>> 'ABC'.encode('ascii')
b'ABC'
>>> '中文'.encode('utf-8')
b'\xe4\xb8\xad\xe6\x96\x87'
>>> '中文'.encode('ascii')
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
UnicodeEncodeError: 'ascii' codec can't encode characters in position 0-1: ordinal not in range(128)

格式化:

我们经常会输出类似'亲爱的xxx你好!你xx月的话费是xx,余额是xx'之类的字符串。
在Python中,采用的格式化方式和C语言是一致的,用%实现,例:

>>> 'Hello, %s' % 'world'
'Hello, world'
>>> 'Hi, %s, you have $%d.' % ('Michael', 1000000)
'Hi, Michael, you have $1000000.'

%运算符就是用来格式化字符串的。在字符串内部,有几个%?占位符,后面就跟几个变量或者值,顺序要对应好。
如果只有一个%?,括号可以省略。

占位符 替换内容
-%d 整数
-%f 浮点数
-%s 字符串
-%x 十六进制整数

format()
另一种格式化字符串的方法是使用字符串的format()方法,它会用传入的参数依次替换字符串内的占位符{0}{1}……,不过这种方式写起来比%要麻烦得多:

>>> 'Hello, {0}, 成绩提升了 {1:.1f}%'.format('小明', 17.125)
'Hello, 小明, 成绩提升了 17.1%'

使用list和tuple

list

操作小结:

list[i]访问(正向0,反向-1) 后=可修改值
len(list)获取list长度
.append()追加元素至末尾
.insert(1,'Jack)插入元素至指定位置
.pop()删除末尾元素
.pop(i)删除指定位置元素
Python内置的一种数据类型是列表:list。
list是一种有序的集合,可添加和删除其中的元素。

>>> classmates = ['Michael', 'Bob', 'Tracy']
>>> classmates
['Michael', 'Bob', 'Tracy']

变量classmates就是一个list。len()函数获得list元素个数:

>>> len(classmates)
3

用索引访问list中的元素,索引从0开始:

>>> classmates[0]
'Michael'
>>> classmates[1]
'Bob'
>>> classmates[2]
'Tracy'
>>> classmates[3]
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
IndexError: list index out of range

当索引超出了范围时,Python会报一个IndexError错误。
可以用-1做索引,直接获取最后一个元素:

>>> classmates[-1]
'Tracy'

以此类推,可以获取倒数第2个、倒数第3个。
list是一个可变的有序表,所以,可以往list中追加元素到末尾:

>>> classmates.append('Adam')
>>> classmates
['Michael', 'Bob', 'Tracy', 'Adam']

把元素插入到指定的位置,比如索引号为1的位置:

>>> classmates.insert(1, 'Jack')
>>> classmates
['Michael', 'Jack', 'Bob', 'Tracy', 'Adam']

删除list末尾的元素,用pop()方法:

>>> classmates.pop()
'Adam'
>>> classmates
['Michael', 'Jack', 'Bob', 'Tracy']

要删除指定位置的元素,用pop(i)方法,其中i是索引位置:

>>> classmates.pop()
'Adam'
>>> classmates
['Michael', 'Jack', 'Bob', 'Tracy']

把某个元素替换成别的元素,可以直接赋值给对应的索引位置:

>>> classmates[1] = 'Sarah'
>>> classmates
['Michael', 'Sarah', 'Tracy']

list里面的元素的数据类型也可以不同,比如:

>>> L = ['Apple', 123, True]

ist元素也可以是另一个list,比如:

>>> s = ['python', 'java', ['asp', 'php'], 'scheme']
>>> len(s)
4

要注意s只有4个元素,其中s[2]又是一个list,拆开写容易理解:

>>> p = ['asp', 'php']
>>> s = ['python', 'java', p, 'scheme']

要拿到'php'可以写p[1]或者s[2][1],因此s可以看成是一个二维数组,类似的还有三维、四维……数组,不过很少用到。

tuple

另一种有序列表叫元组:tuple。
tuple和list非常类似,但是tuple一旦初始化就不能修改。
比如同样是列出同学的名字:

>>> classmates = ('Michael', 'Bob', 'Tracy')

你可使用classmates[0]classmates[-1],不能赋值。
因为tuple不可变,所以代码更安全。

tuple的陷阱:

定义一个tuple,在定义时,tuple的元素必须被确定,例:

>>> t = (1, 2)
>>> t
(1, 2)

如果要定义一个空的tuple,可以写成()

>>> t = ()
>>> t
()

但是,要定义一个只有1个元素的tuple,如果你这么定义:

>>> t = (1)
>>> t
1

定义的不是tuple,是1这个数!这是因为括号()既可以表示tuple,又可以表示数学公式中的小括号,这就产生了歧义,
Python规定,这种情况按小括号进行计算,计算结果是1
所以,只有1个元素的tuple定义时必须加一个逗号,,来消除歧义:

>>> t = (1,)
>>> t
(1,)

最后看一个“可变的”tuple:

>>> t = ('a', 'b', ['A', 'B'])
>>> t[2][0] = 'X'
>>> t[2][1] = 'Y'
>>> t
('a', 'b', ['X', 'Y'])

这个tuple定义的时候有3个元素,分别是·'a'·,·'b'·和一个list。

image.png

当我们把list的元素'A''B'修改为'X''Y'后,tuple变为:
image.png

归纳:tuple的不变,指的是tuple中指向的元素没有变,比如指向'a''b',同理,tuple指向的list,这个指向也没有变,变化的list中的内容。

条件判断

if语句

例:

if <条件判断1>:
    <执行1>
elif <条件判断2>:
    <执行2>
elif <条件判断3>:
    <执行3>
else:
    <执行4>

if判断条件还可以简写,比如写:

if x:
    print('True')

只要x是非零数值、非空字符串、非空list等,就判断为True,否则为False

input

例:

birth = input('birth: ')
if birth < 2000:
    print('00前')
else:
    print('00后')

输入1982,结果报错:

Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: unorderable types: str() > int()

因为input()返回的数据类型是strstr不能直接和整数比较,必须先把str转换成整数。
Python提供了int()函数来完成这件事情:

s = input('birth: ')
birth = int(s)
if birth < 2000:
    print('00前')
else:
    print('00后')

int()函数发现一个字符串不是合法的数字时就会报错,程序退出。

循环

for...in

循环,依次把list或tuple中的每个元素迭代出来,例:

names = ['Michael', 'Bob', 'Tracy']
for name in names:
    print(name)

依次打印names的每一个元素:

Michael
Bob
Tracy

for x in ...循环就是把每个元素代入变量x,然后执行缩进块的语句。
计算1-100的整数之和:
range()函数生成一个整数序列,再通过list()函数可以转换为list。
range(101)生成0-100的整数序列:

sum = 0
for x in range(101):
    sum = sum + x
print(sum)

while循环:

例:计算100以内所有奇数之和

sum = 0
n = 99
while n > 0
      sum = sum + n
      n = n - 2
print(sum)

break

提前退出循环

n = 1
while n <= 100:
    if n > 10: # 当n = 11时,条件满足,执行break语句
        break # break语句会结束当前循环
    print(n)
    n = n + 1
print('END')

continue

continue的作用是提前结束本轮循环,并直接开始下一轮循环。

n = 0
while n < 10:
    n = n + 1
    if n % 2 == 0: # 如果n是偶数,执行continue语句
        continue # continue语句会直接继续下一轮循环,后续的print()语句不会执行
    print(n)

dict和set

总结:

dict:
1.初始化:d = {'Michael': 95, 'Bob': 75, 'Tracy': 85}
2.通过key放入:d['Adam'] = 67
3.一个key对应一个value
4.判断key是否存在:

'Thomas' in d  
False

5.返回指定key的值/返回指定位置的key:

>>> d.get('Thomas')
>>> d.get('Thomas', -1)
-1

6.删除一个key:pop(key) 对应的value也会被删除
set:
1.初始化:

 s = set([1, 1, 2, 2, 3, 3])
>>> s
{1, 2, 3}

2.添加元素到set中:.add(key)
3.删除元素:.remove(key)

dict

Python内置了字典:dict的支持,dict全称dictionary,在其他语言中也称为map,使用键-值(key-value)存储,具有极快的查找速度。
例:根据同学的名字查找对应的成绩:
用dict实现------“名字”-“成绩”对照表,直接根据名字查找成绩,无论这个表有多大,查找速度都不会变慢。
用Python写一个dict如下:

>>> d = {'Michael': 95, 'Bob': 75, 'Tracy': 85}
>>> d['Michael']
95

给定一个名字,'Michael',dict在内部就可以直接计算出Michael对应的存放成绩的“页码”,也就是95这个数字存放的内存地址,直接取出来。
key-value存储方式::前是key,:后是value
把数据放入dict,还可以通过key放入:

>>> d['Adam'] = 67
>>> d['Adam']
67

一个key只对应一个value,多次对一个key放入value,后值会把前值冲掉:

>>> d['Jack'] = 90
>>> d['Jack']
90
>>> d['Jack'] = 88
>>> d['Jack']
88

如果key不存在,dict就会报错:

>>> d['Thomas']
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
KeyError: 'Thomas'

避免key不存在的错误:
1.通过in判断key是否存在:

>>> 'Thomas' in d
False

2.通过dict提供的get()方法,如果key不存在,可以返回None,或者自己指定的value

>>> d.get('Thomas')
>>> d.get('Thomas', -1)
-1

删除一个key,用pop(key),对应的value也会从dict中删除:

>>> d.pop('Bob')
75
>>> d
{'Michael': 95, 'Tracy': 85}

dict内部存放的顺序和key放入的顺序是没有关系的。

和list比较,dict有以下几个特点:
1.查找和插入的速度极快,不会随着key的增加而变慢;
2.需要占用大量的内存,内存浪费多。
而list相反:
1.查找和插入的时间随着元素的增加而增加;
2.占用空间小,浪费内存很少。
所以,dict是用空间来换取时间的一种方法。

dict的key必须是不可变对象。

因为dict根据key来计算value的存储位置,若每次计算相同的key得出的结果不同,dict内部就完全混乱了。这个通过key计算位置的算法称为哈希算法(Hash)

要保证hash的正确性,作为key的对象就不能变。在Python中,字符串、整数等都是不可变的,因此,可以放心地作为key。而list是可变的,就不能作为key:

>>> key = [1, 2, 3]
>>> d[key] = 'a list'
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: unhashable type: 'list'

set

也是一组key的集合,但不存储value。由于key不能重复,所以,在set中,没有重复的key。
要创建一个set,需要提供一个list作为输入集合:

>>> s = set([1, 2, 3])
>>> s
{1, 2, 3}

注意,传入的参数[1, 2, 3]是一个list,而显示的{1, 2, 3}只是告诉你这个set内部有1,2,3这3个元素,显示的顺序不表示set是有序的。
重复元素在set中自动被过滤:

>>> s = set([1, 1, 2, 2, 3, 3])
>>> s
{1, 2, 3}

.add(key)添加元素到set中,可以重复添加,但不会有效果:

>>> s.add(4)
>>> s
{1, 2, 3, 4}
>>> s.add(4)
>>> s
{1, 2, 3, 4}

.remove(key)删除元素:

>>> s.remove(4)
>>> s
{1, 2, 3}

set可以看成数学意义上无序无重复元素的集合,因此,两个set可以做数学意义上的交集、并集等操作:

>>> s1 = set([1, 2, 3])
>>> s2 = set([2, 3, 4])
>>> s1 & s2
{2, 3}
>>> s1 | s2
{1, 2, 3, 4}

再议不可变对象

对于可变对象,比如list,对list进行操作,list内部的内容是会变化的,比如:

>>> a = ['c', 'b', 'a']
>>> a.sort()
>>> a
['a', 'b', 'c']

而对于不可变对象,比如str,对str进行操作呢:

>>> a = 'abc'
>>> a.replace('a', 'A')
'Abc'
>>> a
'abc'

虽然字符串有个replace()方法,也确实变出了'Abc',但变量a最后仍是'abc',应该怎么理解呢?

我们先把代码改成下面这样:

>>> a = 'abc'
>>> b = a.replace('a', 'A')
>>> b
'Abc'
>>> a
'abc'

a是变量,'abc'才是字符串对象!对象a的内容是'abc',指a本身是变量,指向对象的内容是'abc'
当调用 a.replace('a', 'A'),实际上replace是作用在字符串对象'abc'上,replace没有改变字符串'abc'的内容。
相反,replace创建一个新字符串'Abc'并返回,如果我们用变量b指向该新字符串,就容易理解了,变量a仍指向原有的字符串'abc',变量b指向新字符串'Abc'

函数

调用函数

求绝对值的函数:abs( )
求max函数:max()可传入任意多参数

数据类型转换

>>> int('123')
123
>>> int(12.34)
12
>>> float('12.34')
12.34
>>> str(1.23)
'1.23'
>>> str(100)
'100'
>>> bool(1)
True
>>> bool('')
False

可以把函数名赋给一个变量,相当于给函数起一个“别名”:

>>> a = abs # 变量a指向abs函数
>>> a(-1) # 所以也可以通过a调用abs函数
1

定义函数

定义一个函数要使用def语句,依次写出函数名、括号、括号中的参数和冒号:,在缩进块中编写函数体,函数返回值用return语句返回。
例:

def my_abs(x):
    if x >= 0:
        return x
    else:
        return -x

空函数

定义一个什么也不做的空函数,可以用pass语句:

def nop():
    pass

返回多个值

从一个点移动到另一个点,给出坐标、位移和角度,就可以计算出新的坐标:

import math

def move(x, y, step, angle=0):
    nx = x + step * math.cos(angle)
    ny = y - step * math.sin(angle)
    return nx, ny

import math语句表示导入math包,并允许后续代码引用math包里的sincos等函数。

>>> x, y = move(100, 100, 60, math.pi / 6)
>>> print(x, y)
151.96152422706632 70.0

但其实这只是一种假象,Python函数返回的仍然是单一值:

>>> r = move(100, 100, 60, math.pi / 6)
>>> print(r)
(151.96152422706632, 70.0)

返回值是一个tuple

函数的参数

位置参数

def power(x):
    return x * x

对于power(x)函数,参数x就是一个位置参数。

默认参数

经常计算x2,所以,可以把第二个参数n的默认值设定为2:

def power(x, n=2):
    s = 1
    while n > 0:
        n = n - 1
        s = s * x
    return s

调用power(5) = 调用power(5, 2)
必选参数在前,默认参数在后。
默认参数必须指向不变对象。
一个大坑:
先定义一个函数,传入一个list,添加一个END再返回:

def add_end(L=[]):
    L.append('END')
    return L

正常调用时,结果似乎不错:

>>> add_end([1, 2, 3])
[1, 2, 3, 'END']
>>> add_end(['x', 'y', 'z'])
['x', 'y', 'z', 'END']

当你使用默认参数调用时,一开始结果也是对的:

>>> add_end()
['END']

但是,再次调用add_end()时,结果就不对了:

>>> add_end()
['END', 'END']
>>> add_end()
['END', 'END', 'END']

原因:Python函数在定义的时候,默认参数L的值就被计算出来了,即[],因为默认参数L也是一个变量,它指向对象[ ],每次调用该函数,如果改变了L的内容,下次调用时,默认参数的内容就变了,不再是函数定义时的[ ]了。
可以用None这个不变对象来实现:

def add_end(L=None):
    if L is None:
        L = []
    L.append('END')
    return L

创建不变对象,如Nonestr,对象内部的数据就不能修改,此外由于对象不变,多任务环境下同时读取对象不需要加锁,同时读一点问题都没有。

可变参数

例:给定一组数字a,b,c……,请计算a2 + b2 + c2 + ……
由于参数个数不确定,我们首先想到可以把a,b,c……作为一个list或tuple传进来,函数定义如下:

def calc(numbers):
    sum = 0
    for n in numbers:
        sum = sum + n * n
    return sum

但是调用的时候,需要先组装出一个list或tuple:

>>> calc([1, 2, 3])
14
>>> calc((1, 3, 5, 7))
84

如果利用可变参数,调用函数的方式可以简化成这样:

>>> calc(1, 2, 3)
14
>>> calc(1, 3, 5, 7)
84

所以,把函数的参数改为可变参数:

def calc(*numbers):
    sum = 0
    for n in numbers:
        sum = sum + n * n
    return sum

如果已经有一个list或者tuple,要调用一个可变参数怎么办:

>>> nums = [1, 2, 3]
>>> calc(nums[0], nums[1], nums[2])
14

太繁琐,所以Python允许你在list或tuple前面加一个*号,把list或tuple的元素变成可变参数传进去:

>>> nums = [1, 2, 3]
>>> calc(*nums)
14

关键字参数

可变参数允许你传入0个或任意个参数,这些可变参数在函数调用时自动组装为一个tuple。而关键字参数允许你传入0个或任意个含参数名的参数,这些关键字参数在函数内部自动组装为一个dict。请看示例:

def person(name, age, **kw):
    print('name:', name, 'age:', age, 'other:', kw)

函数person除了必选参数name和age外,还接受关键字参数kw。在调用该函数时,可以只传入必选参数:

>>> person('Michael', 30)
name: Michael age: 30 other: {}

也可以传入任意个数的关键字参数:

>>> person('Bob', 35, city='Beijing')
name: Bob age: 35 other: {'city': 'Beijing'}
>>> person('Adam', 45, gender='M', job='Engineer')
name: Adam age: 45 other: {'gender': 'M', 'job': 'Engineer'}

它可以扩展函数的功能。比如,在person函数里,我们保证能接收到nameage这两个参数,但是,如果调用者愿意提供更多的参数,我们也能收到。
和可变参数类似,也可以先组装出一个dict,然后,把该dict转换为关键字参数传进去:

>>> extra = {'city': 'Beijing', 'job': 'Engineer'}
>>> person('Jack', 24, city=extra['city'], job=extra['job'])
name: Jack age: 24 other: {'city': 'Beijing', 'job': 'Engineer'}

可以用简化的写法:

>>> extra = {'city': 'Beijing', 'job': 'Engineer'}
>>> person('Jack', 24, **extra)
name: Jack age: 24 other: {'city': 'Beijing', 'job': 'Engineer'}

**extra表示把extra这个dict的所有key-value用关键字参数传入到函数的**kw参数,kw将获得一个dict,注意kw获得的dict是extra的一份拷贝,对kw的改动不会影响到函数外的extra

命名关键字参数

对于关键字参数,函数的调用者可以传入任意不受限制的关键字参数。至于到底传入了哪些,就需要在函数内部通过kw检查。
person()函数为例,我们希望检查是否有cityjob参数:

def person(name, age, **kw):
    if 'city' in kw:
        # 有city参数
        pass
    if 'job' in kw:
        # 有job参数
        pass
    print('name:', name, 'age:', age, 'other:', kw)

如果要限制关键字参数的名字,就可以用命名关键字参数,例如,只接收cityjob作为关键字参数。这种方式定义的函数如下:

def person(name, age, *, city, job):
    print(name, age, city, job)

命名关键字参数需要一个特殊分隔符**后面的参数被视为命名关键字参数。
调用方式如下:

>>> person('Jack', 24, city='Beijing', job='Engineer')
Jack 24 Beijing Engineer

命名关键字参数可以有缺省值,从而简化调用:

def person(name, age, *, city='Beijing', job):
    print(name, age, city, job)

由于命名关键字参数city具有默认值,调用时,可不传入city
数:

>>> person('Jack', 24, job='Engineer')
Jack 24 Beijing Engineer

使用命名关键字参数时,要特别注意,如果没有可变参数,就必须加一个作为特殊分隔符。如果缺少,Python解释器将无法识别位置参数和命名关键字参数:

def person(name, age, city, job):
    # 缺少 *,city和job被视为位置参数
    pass

个人理解:一个函数,含有必选参数和关键字参数,在调用函数时,必选参数必须输入,关键字参数可以传入任意个,但当你使用命名关键字参数时,传入参数的数量不得少于命名关键字参数的个数。

参数组合

在Python中定义函数,可以用必选参数、默认参数、可变参数、关键字参数和命名关键字参数。
顺序:必选参数、默认参数、可变参数、命名关键字参数和关键字参数。
例:

def f1(a, b, c=0, *args, **kw):
    print('a =', a, 'b =', b, 'c =', c, 'args =', args, 'kw =', kw)

def f2(a, b, c=0, *, d, **kw):
    print('a =', a, 'b =', b, 'c =', c, 'd =', d, 'kw =', kw)

在函数调用的时候,Python解释器自动按照参数位置和参数名把对应的参数传进去。

>>> f1(1, 2)
a = 1 b = 2 c = 0 args = () kw = {}
>>> f1(1, 2, c=3)
a = 1 b = 2 c = 3 args = () kw = {}
>>> f1(1, 2, 3, 'a', 'b')
a = 1 b = 2 c = 3 args = ('a', 'b') kw = {}
>>> f1(1, 2, 3, 'a', 'b', x=99)
a = 1 b = 2 c = 3 args = ('a', 'b') kw = {'x': 99}
>>> f2(1, 2, d=99, ext=None)
a = 1 b = 2 c = 0 d = 99 kw = {'ext': None}

通过一个tuple和dict,你也可以调用上述函数:


>>> args = (1, 2, 3, 4)
>>> kw = {'d': 99, 'x': '#'}
>>> f1(*args, **kw)
a = 1 b = 2 c = 3 args = (4,) kw = {'d': 99, 'x': '#'}
>>> args = (1, 2, 3)
>>> kw = {'d': 88, 'x': '#'}
>>> f2(*args, **kw)
a = 1 b = 2 c = 3 d = 88 kw = {'x': '#'}

对于任意函数,都可以通过类似func(*args, **kw)的形式调用它,无论它的参数是如何定义的。
小结
要注意定义可变参数和关键字参数的语法:
*args是可变参数,args接收的是一个tuple;
**kw是关键字参数,kw接收的是一个dict。
以及调用函数时如何传入可变参数和关键字参数的语法:
可变参数既可以直接传入:func(1, 2, 3),又可以先组装list或tuple,再通过*args传入:func(*(1, 2, 3))
关键字参数既可以直接传入:func(a=1, b=2),又可以先组装dict,再通过**kw传入:func(**{'a': 1, 'b': 2})

递归函数

例:n! = 1 x 2 x 3 x ... x n

def fact(n):
      if n==1:
           return 1
      return n * fact(n-1)

高级特性

切片

切片小结:
[ x : y : z ] : x 起始位置,y 终止位置(不包含),z 每z个取值
x为负则是倒数x个
x=0表示是第一个
x=-1表示是倒数第一个
例:
一个list如下:>>>L = ['Michael', 'Sarah', 'Tracy', 'Bob', 'Jack']
取前3个元素:

>>> [L[0], L[1], L[2]]
['Michael', 'Sarah', 'Tracy']

循环:

>>>r = []
>>>n=3
>>>for i in range(n):
           r.append(L[i])  #append函数向尾部添加元素

>>>r
['Michael', 'Sarah', 'Tracy']

切片(Slice)操作符

>>>L[0:3]
['Michael', 'Sarah', 'Tracy']

也可以从索引1开始,取出2个元素出来:

>>> L[1:3]
['Sarah', 'Tracy']

支持L[-1]取倒数第一个元素,例:

>>> L[-2:]
['Bob', 'Jack']
>>> L[-2:-1]
['Bob']

倒数第一个元素的索引是-1
切片操作十分有用。我们先创建一个0-99的数列:

>>> L = list(range(100))
>>> L
[0, 1, 2, 3, ..., 99]

可以通过切片轻松取出某一段数列。比如前10个数:

>>> L[:10]
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]

后10个数:

>>> L[-10:]
[90, 91, 92, 93, 94, 95, 96, 97, 98, 99]

前11-20个数:

>>> L[10:20]
[10, 11, 12, 13, 14, 15, 16, 17, 18, 19]

前10个数,每两个取一个:

>>> L[:10:2]
[0, 2, 4, 6, 8]

所有数,每5个取一个:

>>> L[::5]
[0, 5, 10, 15, 20, 25, 30, 35, 40, 45, 50, 55, 60, 65, 70, 75, 80, 85, 90, 95]

只写[:]就可以原样复制一个list:

>>> L[:]
[0, 1, 2, 3, ..., 99]

tuple也可以用切片操作,只是操作的结果仍是tuple:

>>> (0, 1, 2, 3, 4, 5)[:3]
(0, 1, 2)

字符串'xxx'也可以看成是一种list,每个元素就是一个字符。因此,字符串也可以用切片操作,只是操作结果仍是字符串:

>>> 'ABCDEFG'[:3]
'ABC'
>>> 'ABCDEFG'[::2]
'ACEG'

迭代

给定一个list或tuple,通过for循环来遍历这个list或tuple,这种遍历称为迭代(Iteration)。
默认情况dict迭代key。如果要迭代value,用for value in d.values(),同时迭代key和value,用for k, v in d.items()
判断一个对象是可迭代对象:

>>> from collections import Iterable
>>> isinstance('abc', Iterable) # str是否可迭代
True
>>> isinstance([1,2,3], Iterable) # list是否可迭代
True
>>> isinstance(123, Iterable) # 整数是否可迭代
False

对list实现下标循环:enumerate函数

>>> for i, value in enumerate(['A', 'B', 'C']):
...     print(i, value)
...
0 A
1 B
2 C

上面的for循环里,同时引用了两个变量,在Python里是很常见的,比如下面的代码:

>>> for x, y in [(1, 1), (2, 4), (3, 9)]:
...     print(x, y)
...
1 1
2 4
3 9

列表生成式

例:生成list [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]

>>> list(range(1, 11))
[1, 2, 3, 4, 5, 6, 7, 8, 9, 10]

生成[1x1, 2x2, 3x3, ..., 10x10]

>>>[x * x for x in range(1,11)]
[1, 4, 9, 16, 25, 36, 49, 64, 81, 100]

使用两层循环,可以生成全排列:

>>> [m + n for m in 'ABC' for n in 'XYZ']
['AX', 'AY', 'AZ', 'BX', 'BY', 'BZ', 'CX', 'CY', 'CZ']

for循环可同时使用多个变量,如dictitems()可以同时迭代key和value:

>>> d = {'x': 'A', 'y': 'B', 'z': 'C' }
>>> for k, v in d.items():
...     print(k, '=', v)
...
y = B
x = A
z = C

列表生成式使用两个变量来生成list:

>>> d = {'x': 'A', 'y': 'B', 'z': 'C' }
>>> [k + '=' + v for k, v in d.items()]
['y=B', 'x=A', 'z=C']

生成器

如果列表元素可以按照某种算法推算出来,可以在循环的过程中不断推算出后续的元素。
在Python中,这种一边循环一边计算的机制,称为生成器:generator。
创建一个generator:
1.把一个列表生成式的[]改成()

>>> 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>
>>> next(g)
0
>>> next(g)
1
>>> next(g)
4
>>> next(g)
9
>>> next(g)
16
>>> next(g)
25
>>> next(g)
36
>>> next(g)
49
>>> next(g)
64
>>> next(g)
81
>>> next(g)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
StopIteration

generator保存的是算法,每次调用next(g),就计算出g的下一个元素的值,直到计算到最后一个元素,没有更多的元素时,抛出StopIteration的错误。
正确的方法是使用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

例:斐波那契数列

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

这种逻辑其实非常类似generator。
要把fib函数变成generator,只需要把print(b)改为yield b就可以了:

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

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

>>> f = fib(6)
>>> f
<generator object fib at 0x104feaaa0>

函数是顺序执行,遇到return语句或者最后一行函数语句就返回。变成generator的函数,在每次调用next()的时候执行,遇到yield语句返回,再次执行时从上次返回的yield语句处继续执行。

迭代器

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

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

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

函数式编程

函数式编程——Functional Programming,虽然也可以归结到面向过程的程序设计,但其思想更接近数学计算。

高阶函数

map/reduce

map:
map()函数接收两个参数,一个是函数,一个是Iterablemap将传入的函数依次作用到序列的每个元素,并把结果作为新的Iterator返回。
例:f(x)=x2

>>> def f(x):
...     return x * x
...
>>> r = map(f, [1, 2, 3, 4, 5, 6, 7, 8, 9])
>>> list(r)
[1, 4, 9, 16, 25, 36, 49, 64, 81]

它把运算规则抽象了。
reduce:
reduce把一个函数作用在一个序列[x1, x2, x3, ...]上,这个函数必须接收两个参数,reduce把结果继续和序列的下一个元素做累积计算:

reduce(f, [x1, x2, x3, x4]) = f(f(f(x1, x2), x3), x4)

例:序列求和

>>> from functools import reduce
>>> def add(x, y):
...     return x + y
...
>>> reduce(add, [1, 3, 5, 7, 9])
25

例:把序列[1, 3, 5, 7, 9]变换成整数13579

>>> from functools import reduce
>>> def fn(x, y):
...     return x * 10 + y
...
>>> reduce(fn, [1, 3, 5, 7, 9])
13579

filter:筛选

filter()也接收一个函数和一个序列。filter()把传入的函数依次作用于每个元素,然后根据返回值是True还是False决定保留还是丢弃该元素。
例:在一个list中,删掉偶数,只保留奇数

def is_odd(n):
    return n % 2 == 1

list(filter(is_odd, [1, 2, 4, 5, 6, 9, 10, 15]))
# 结果: [1, 5, 9, 15]

sorted

Python内置的sorted()函数就可以对list进行排序:

>>> sorted([36, 5, -12, 9, -21])
[-21, -12, 5, 9, 36]

sorted()函数也是一个高阶函数,它还可以接收一个key函数来实现自定义的排序,例如按绝对值大小排序:

>>> sorted([36, 5, -12, 9, -21], key=abs)
[5, 9, -12, -21, 36]

返回函数

函数作为返回值

例:返回求和函数

def lazy_sum(*args):
    def sum():
        ax = 0
        for n in args:
            ax = ax + n
        return ax
    return sum

闭包

匿名函数

不显式地定义函数,直接传入匿名函数更方便。
例:f(x)=x2

>>> list(map(lambda x: x * x, [1, 2, 3, 4, 5, 6, 7, 8, 9]))
[1, 4, 9, 16, 25, 36, 49, 64, 81]

关键字lambda表示匿名函数,冒号前面的x表示函数参数。
不用写return,返回值就是该表达式的结果。
匿名函数也是一个函数对象,也可以把匿名函数赋值给一个变量,再利用变量来调用该函数:

>>> f = lambda x: x * x
>>> f
<function <lambda> at 0x101c6ef28>
>>> f(5)
25

也可以把匿名函数作为返回值返回,比如:

def build(x, y):
    return lambda: x * x + y * y

装饰器

函数是一个对象,函数对象可以被赋值给变量,通过变量也能调用该函数。

>>> def now():
...     print('2015-3-25')
...
>>> f = now
>>> f()
2015-3-25

__name__属性,可以拿到函数的名字:

>>> now.__name__
'now'
>>> f.__name__
'now'

假设我们要增强now()函数的功能,比如,在函数调用前后自动打印日志,但又不希望修改now()函数的定义,这种在代码运行期间动态增加功能的方式,称之为“装饰器”(Decorator)。
例:定义一个能打印日志的decorator

def log(func):
    def wrapper(*args, **kw):
        print('call %s():' % func.__name__)
        return func(*args, **kw)
    return wrapper

助Python的@语法,把decorator置于函数的定义处:

@log
def now():
   print('2015-3-25')

调用now()函数,运行now()函数本身,还会在运行now()函数前打印一行日志:

>>> now()
call now():
2015-3-25

@log放到now()函数的定义处,相当于执行了语句:

now = log(now)

偏函数

Python的functools模块提供了很多有用的功能,其中一个就是偏函数(Partial function)。
例:
int()函数可以把字符串转换为整数,当仅传入字符串时,int()函数默认按十进制转换:

>>> int('12345')
12345

int()函数还提供额外的base参数,默认值为10。如果传入base参数,就可以做N进制的转换:

>>> int('12345', base=8)
5349
>>> int('12345', 16)
74565

假设要转换大量的二进制字符串,每次都传入int(x, base=2)非常麻烦,所以可以定义一个int2()的函数,默认把base=2传进去:

def int2(x, base=2):
    return int(x, base)

functools.partial帮助我们创建一个偏函数,不需要自己定义int2(),可以直接使用下面的代码创建一个新的函数int2:

>>> import functools
>>> int2 = functools.partial(int, base=2)
>>> int2('1000000')
64
>>> int2('1010101')
85

functools.partial:把一个函数的某些参数给固定住(也就是设置默认值),返回一个新的函数,调用这个新函数会更简单。

模块

为了编写可维护的代码,我们把很多函数分组,分别放到不同的文件里。在Python中,一个.py文件就称之为一个模块(Module)。

模块是一组Python代码的集合,可以使用其他模块,也可以被其他模块使用。

创建自己的模块时,要注意:

1.模块名要遵循Python变量命名规范,不要使用中文、特殊字符;
2.模块名不要和系统模块名冲突,最好先查看系统是否已存在该模块,检查方法是在Python交互环境执行import abc,若成功则说明系统存在此模块。

使用模块

以内建的sys模块为例,编写一个hello的模块:

#!/usr/bin/env python3
# -*- coding: utf-8 -*-

' a test module '

__author__ = 'Michael Liao'

import sys

def test():
    args = sys.argv
    if len(args)==1:
        print('Hello, world!')
    elif len(args)==2:
        print('Hello, %s!' % args[1])
    else:
        print('Too many arguments!')

if __name__=='__main__':
    test()

第1行注释可以让这个hello.py文件直接在Unix/Linux/Mac上运行;
第2行注释表示.py文件本身使用标准UTF-8编码;
第4行是一个字符串,表示模块的文档注释,任何模块代码的第一个字符串都被视为模块的文档注释;
第6行使用__author__变量把作者写进去;

以上就是Python模块的标准文件模板,当然也可以全部删掉不写。

使用sys模块的第一步,就是导入该模块:

import sys

导入sys模块后,变量sys指向该模块,利用sys,可以访问sys模块的所有功能。
sys模块有一个argv变量,用list存储了命令行的所有参数。argv至少有一个元素,因为第一个参数永远是该.py文件的名称,例如:
运行python3 hello.py获得的sys.argv就是['hello.py']
运行python3 hello.py Michael获得的sys.argv就是['hello.py', 'Michael']
最后,注意到这两行代码:

if __name__=='__main__':
    test()

面向对象编程

类和实例

定义类:

class Student(object):
    pass

class后面紧接着是类名,即Student(object)表示该类是从哪个类继承下来的,通常,如果没有合适的继承类,就使用object类。
可以根据Student类创建出Student的实例,创建实例:

>>> bart = Student()
>>> bart
<__main__.Student object at 0x10a67a590>
>>> Student
<class '__main__.Student'>

可以自由地给一个实例变量绑定属性,比如,给实例bart绑定一个name属性:

>>> bart.name = 'Bart Simpson'
>>> bart.name
'Bart Simpson'

由于类可以起到模板的作用,因此,可以在创建实例的时候,把一些我们认为必须绑定的属性强制填写进去。通过定义一个特殊的__init__方法,在创建实例的时候,就把namescore等属性绑上去:

class Student(object):

    def __init__(self, name, score):
        self.name = name
        self.score = score

注意到__init__方法的第一个参数永远是self,表示创建的实例本身,因此,在__init__方法内部,就可以把各种属性绑定到self,因为self就指向创建的实例本身。
有了__init__方法,在创建实例的时候,就不能传入空的参数了,必须传入与__init__方法匹配的参数,但self不需要传,Python解释器自己会把实例变量传进去:

>>> bart = Student('Bart Simpson', 59)
>>> bart.name
'Bart Simpson'
>>> bart.score
59

数据封装

在上面的Student类中,每个实例就拥有各自的namescore这些数据。我们可以通过函数来访问这些数据,比如打印一个学生的成绩:

>>> def print_score(std):
...     print('%s: %s' % (std.name, std.score))
...
>>> print_score(bart)
Bart Simpson: 59

可以直接在Student类的内部定义访问数据的函数——把“数据”给封装起来。这些封装数据的函数是和Student类本身是关联起来的,我们称之为类的方法:

class Student(object):

    def __init__(self, name, score):
        self.name = name
        self.score = score

    def print_score(self):
        print('%s: %s' % (self.name, self.score))

感觉和c++的类差不多

访问限制

如果要让内部属性不被外部访问,可以把属性的名称前加上两个下划线__,实例的变量名如果以__开头,就变成了一个私有变量,只有内部可以访问,外部不能访问:

class Student(object):

    def __init__(self, name, score):
        self.__name = name
        self.__score = score

    def print_score(self):
        print('%s: %s' % (self.__name, self.__score))

继承和多态

比如,我们已经编写了一个名为Animal的class,有一个run()方法可以直接打印:

class Animal(object):
    def run(self):
        print('Animal is running...')

当我们需要编写DogCat类时,就可以直接从Animal类继承:

class Dog(Animal):
    pass
class Cat(Animal):
    pass

Animial实现了run()方法,DogCat作为它的子类自动拥有run()方法:

dog = Dog()
dog.run()

cat = Cat()
cat.run()

运行结果:

Animal is running...
Animal is running...

可以对子类增加一些方法,比如Dog类:

class Dog(Animal):

    def run(self):
        print('Dog is running...')

class Cat(Animal):

    def run(self):
        print('Cat is running...')

运行结果:

Dog is running...
Cat is running...

子类的run()覆盖了父类的run(),在代码运行的时候,总是会调用子类的run()。这样,我们就获得了继承的另一个好处:多态。

判断一个变量是否是某个类型可以用isinstance()判断:

>>> isinstance(a, list)
True
>>> isinstance(b, Animal)
True
>>> isinstance(c, Dog)
True

要理解多态的好处,我们还需要再编写一个函数,这个函数接受一个Animal类型的变量:

def run_twice(animal):
    animal.run()
    animal.run()

当我们传入Animal的实例时,run_twice()就打印出:

>>> run_twice(Animal())
Animal is running...
Animal is running...

当我们传入Dog的实例时,run_twice()就打印出:

>>> run_twice(Dog())
Dog is running...
Dog is running...

当我们传入Cat的实例时,run_twice()就打印出:

>>> run_twice(Cat())
Cat is running...
Cat is running...

看上去没啥意思,但是仔细想想,现在,如果我们再定义一个Tortoise类型,也从Animal派生:

class Tortoise(Animal):
    def run(self):
        print('Tortoise is running slowly...')

当我们调用run_twice()时,传入Tortoise的实例:

>>> run_twice(Tortoise())
Tortoise is running slowly...
Tortoise is running slowly...

任何依赖Animal作为参数的函数或者方法都可以不加修改地正常运行,原因就在于多态。

获取对象信息

判断对象类型,使用type()函数:

>>> type(123)
<class 'int'>
>>> type('str')
<class 'str'>
>>> type(None)
<type(None) 'NoneType'>

如果一个变量指向函数或者类,也可以用type()判断:

>>> type(abs)
<class 'builtin_function_or_method'>
>>> type(a)
<class '__main__.Animal'>

type()函数返回对应的Class类型。
判断一个对象是否是函数怎么办:使用types模块中定义的常量

>>> import types
>>> def fn():
...     pass
...
>>> type(fn)==types.FunctionType
True
>>> type(abs)==types.BuiltinFunctionType
True
>>> type(lambda x: x)==types.LambdaType
True
>>> type((x for x in range(10)))==types.GeneratorType
True

使用isinstance()

对于class的继承关系来说,使用type()就很不方便。可以使用isinstance()函数。
如:object -> Animal -> Dog -> Husky

>>> isinstance(h, Husky)
True

isinstance()判断的是一个对象是否是该类型本身,或者位于该类型的父继承链上。
能用type()判断的基本类型也可以用isinstance()判断:

>>> isinstance('a', str)
True
>>> isinstance(123, int)
True
>>> isinstance(b'a', bytes)
True

判断一个变量是否是某些类型中的一种,比如下面的代码就可以判断是否是list或者tuple:

>>> isinstance([1, 2, 3], (list, tuple))
True
>>> isinstance((1, 2, 3), (list, tuple))
True

dir()

获得一个对象的所有属性和方法:

>>> dir('ABC')
['__add__', '__class__',..., '__subclasshook__', 'capitalize', 'casefold',..., 'zfill']

类似__xxx__的属性和方法在Python中都是有特殊用途的,比如__len__方法返回长度。在Python中,如果你调用len()函数试图获取一个对象的长度,实际上,在len()函数内部,它自动去调用该对象的__len__()方法,所以,下面的代码是等价的:

>>> len('ABC')
3
>>> 'ABC'.__len__()
3

配合getattr()setattr()以及hasattr(),我们可以直接操作一个对象的状态:

>>> class MyObject(object):
...     def __init__(self):
...         self.x = 9
...     def power(self):
...         return self.x * self.x
...
>>> obj = MyObject()

测试该对象的属性:

>>> hasattr(obj, 'x') # 有属性'x'吗?
True
>>> obj.x
9
>>> hasattr(obj, 'y') # 有属性'y'吗?
False
>>> setattr(obj, 'y', 19) # 设置一个属性'y'
>>> hasattr(obj, 'y') # 有属性'y'吗?
True
>>> getattr(obj, 'y') # 获取属性'y'
19
>>> obj.y # 获取属性'y'
19

如果试图获取不存在的属性,会抛出AttributeError的错误:

>>> getattr(obj, 'z') # 获取属性'z'
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
AttributeError: 'MyObject' object has no attribute 'z'

可以传入一个default参数,如果属性不存在,就返回默认值:

>>> getattr(obj, 'z', 404) # 获取属性'z',如果不存在,返回默认值404
404

实例属性和类属性

Python是动态语言,根据类创建的实例可以任意绑定属性。
给实例绑定属性的方法是通过实例变量,或者通过self变量:

class Student(object):
    def __init__(self, name):
        self.name = name

s = Student('Bob')
s.score = 90

类属性,归Student类所有:

class Student(object):
    name = 'Student'

面向对象高级编程

使用slots

先定义class:

class Student(object):
    pass

给实例绑定一个属性:

>>> s = Student()
>>> s.name = 'Michael' # 动态给实例绑定一个属性
>>> print(s.name)
Michael

给实例绑定一个方法:

>>> def set_age(self, age): # 定义一个函数作为实例方法
...     self.age = age
...
>>> from types import MethodType
>>> s.set_age = MethodType(set_age, s) # 给实例绑定一个方法
>>> s.set_age(25) # 调用实例方法
>>> s.age # 测试结果
25

但是,给一个实例绑定的方法,对另一个实例是不起作用的。
为了给所有实例都绑定方法,可以给class绑定方法:

>>> def set_score(self, score):
...     self.score = score
...
>>> Student.set_score = set_score

如果我们想要限制实例的属性怎么办?比如,只允许对Student实例添加nameage属性。
定义一个特殊的__slots__变量,来限制该class实例能添加的属性:

class Student(object):
    __slots__ = ('name', 'age') # 用tuple定义允许绑定的属性名称

>>> s = Student() # 创建新的实例
>>> s.name = 'Michael' # 绑定属性'name'
>>> s.age = 25 # 绑定属性'age'
>>> s.score = 99 # 绑定属性'score'
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
AttributeError: 'Student' object has no attribute 'score'

由于score没有被放到__slots__中,所以不能绑定score属性,试图绑定score将得到AttributeError的错误。

使用__slots__要注意,__slots__定义的属性仅对当前类实例起作用,对继承的子类是不起作用的:

>>> class GraduateStudent(Student):
...     pass
...
>>> g = GraduateStudent()
>>> g.score = 9999

使用@property

多重继承

于需要Runnable功能的动物,就多继承一个Runnable,例如Dog

class Dog(Mammal, Runnable):
    pass

对于需要Flyable功能的动物,就多继承一个Flyable,例如Bat

class Bat(Mammal, Flyable):
    pass

通过多重继承,一个子类就可以同时获得多个父类的所有功能。

定制类

___str___
定义一个Student类,打印一个实例:

>>> class Student(object):
...     def __init__(self, name):
...         self.name = name
...
>>> print(Student('Michael'))
<__main__.Student object at 0x109afb190>

<__main__.Student object at 0x109afb190>并不好看。
定义好__str__()方法,返回一个好看的字符串就可以了:

>>> class Student(object):
...     def __init__(self, name):
...         self.name = name
...     def __str__(self):
...         return 'Student object (name: %s)' % self.name
...
>>> print(Student('Michael'))
Student object (name: Michael)

直接敲变量不用print,打印出来的实例还是不好看:

>>> s = Student('Michael')
>>> s
<__main__.Student object at 0x109afb310>

因为直接显示变量调用的不是__str__(),而是__repr__(),两者的区别是__str__()返回用户看到的字符串,而__repr__()返回程序开发者看到的字符串。
解决办法是再定义一个__repr__()。简便方法:

class Student(object):
    def __init__(self, name):
        self.name = name
    def __str__(self):
        return 'Student object (name=%s)' % self.name
    __repr__ = __str__

__iter__
一个类被用于for ... in循环,类似list或tuple那样,就必须实现一个__iter__()方法。
Python的for循环就会不断调用该迭代对象的__next__()方法拿到循环的下一个值,直到遇到StopIteration错误时退出循环。

class Fib(object):
    def __init__(self):
        self.a, self.b = 0, 1 # 初始化两个计数器a,b

    def __iter__(self):
        return self # 实例本身就是迭代对象,故返回自己

    def __next__(self):
        self.a, self.b = self.b, self.a + self.b # 计算下一个值
        if self.a > 100000: # 退出循环的条件
            raise StopIteration()
        return self.a # 返回下一个值

__getitem__
要表现得像list那样按照下标取出元素,需要实现__getitem__()方法:

class Fib(object):
    def __getitem__(self, n):
        a, b = 1, 1
        for x in range(n):
            a, b = b, a + b
        return a

__getattr__
要避免这个错误,除了可以加上一个score属性外,Python还有另一个机制,那就是写一个__getattr__()方法,动态返回一个属性。修改如下:

class Student(object):

    def __init__(self):
        self.name = 'Michael'

    def __getattr__(self, attr):
        if attr=='score':
            return 99

当调用不存在的属性时,比如score,Python解释器会试图调用__getattr__(self, 'score')来尝试获得属性,这样,我们就有机会返回score的值:

>>> s = Student()
>>> s.name
'Michael'
>>> s.score
99

返回函数也是完全可以的:

class Student(object):

    def __getattr__(self, attr):
        if attr=='age':
            return lambda: 25

只是调用方式要变为:

>>> s.age()
25

注意,只有在没有找到属性的情况下,才调用__getattr__,已有的属性,比如name,不会在__getattr__中查找。

__call__
定义一个__call__()方法,可以直接对实例进行调用:

class Student(object):
    def __init__(self, name):
        self.name = name

    def __call__(self):
        print('My name is %s.' % self.name)

调用方式:

>>> s = Student('Michael')
>>> s() # self参数不要传入
My name is Michael.

使用枚举类

为这样的枚举类型定义一个class类型,然后,每个常量都是class的一个唯一实例。Python提供了Enum类来实现这个功能:

from enum import Enum

Month = Enum('Month', ('Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'))

使用元类

错误、调试和测试(异常处理)

try/except语句。

try/except语句用来检测try语句块中的错误,从而让except语句捕获异常信息并处理:

try:
<语句>        #运行别的代码
except <名字>:
<语句>        #如果在try部份引发了'name'异常
except <名字>,<数据>:
<语句>        #如果引发了'name'异常,获得附加的数据
else:
<语句>        #如果没有异常发生

实例:

#!/usr/bin/python
# -*- coding: UTF-8 -*-

try:
    fh = open("testfile", "w")
    fh.write("这是一个测试文件,用于测试异常!!")
except IOError:
    print "Error: 没有找到文件或读取文件失败"
else:
    print "内容写入文件成功"
    fh.close()

结果:

$ python test.py 
内容写入文件成功
$ cat testfile       # 查看写入的内容
这是一个测试文件,用于测试异常!!

你可以不带任何异常类型使用except:

try:
    正常的操作
   ......................
except:
    发生异常,执行这块代码
   ......................
else:
    如果没有异常执行这块代码

使用except而带多种异常类型:

try:
    正常的操作
   ......................
except(Exception1[, Exception2[,...ExceptionN]]]):
   发生以上多个异常中的一个,执行这块代码
   ......................
else:
    如果没有异常执行这块代码

try-finally 语句

try-finally 语句无论是否发生异常都将执行最后的代码。

try:
<语句>
finally:
<语句>    #退出try时总会执行
raise

实例:

#!/usr/bin/python
# -*- coding: UTF-8 -*-

try:
   fh = open("testfile", "w")
   fh.write("这是一个测试文件,用于测试异常!!")
finally:
   print "Error: 没有找到文件或读取文件失败"

如果打开的文件没有可写权限,输出如下所示:

$ python test.py 
Error: 没有找到文件或读取文件失败

当在try块中抛出一个异常,立即执行finally块代码。

IO编程

文件读写

读文件

以读文件的模式打开一个文件对象,使用open()函数,传入文件名和标示符:

f = open('/Users/michael/test.txt', 'r')

标示符'r'表示读。
如果文件不存在,open()函数就会抛出一个IOError的错误,并且给出错误码和详细的信息告诉你文件不存在。
如果文件打开成功,调用read()方法可以一次读取文件的全部内容,Python把内容读到内存,用一个str对象表示:

>>> f.read()
'Hello, world!'

最后一步是调用close()方法关闭文件:

>>> f.close()

由于文件读写时都有可能产生IOError,一旦出错,后面的f.close()就不会调用。所以,为了保证无论是否出错都能正确地关闭文件,我们可以使用try ... finally来实现:

#try-finally 语句无论是否发生异常都将执行最后的代码。
try:
    f = open('/path/to/file', 'r')
    print(f.read())
finally:
    if f:
        f.close()

Python引入了with语句来自动帮我们调用close()方法:

with open('/path/to/file', 'r') as f:
    print(f.read())

调用read()会一次性读取文件的全部内容。
反复调用read(size),每次最多读取size个字节的内容。
调用readline()可以每次读取一行内容。
调用readlines()一次读取所有内容并按行返回list。
如果是配置文件,调用readlines()最方便:

for line in f.readlines():
    print(line.strip()) # 把末尾的'\n'删掉

file-like Object

一个概念:
open()函数返回的这种有个read()方法的对象,在Python中统称为file-like Object。除了file外,还可以是内存的字节流,网络流,自定义流等等。file-like Object不要求从特定类继承,只要写个read()方法就行。

StringIO就是在内存中创建的file-like Object,常用作临时缓冲。

二进制文件

要读取二进制文件,比如图片、视频等等,用'rb'模式打开文件:

>>> f = open('/Users/michael/test.jpg', 'rb')
>>> f.read()
b'\xff\xd8\xff\xe1\x00\x18Exif\x00\x00...' # 十六进制表示的字节

字符编码

读取非UTF-8编码的文本文件,需要给open()函数传入encoding参数,例如,读取GBK编码的文件:

>>> f = open('/Users/michael/gbk.txt', 'r', encoding='gbk')
>>> f.read()
'测试'

遇到有些编码不规范的文件,你可能会遇到UnicodeDecodeError,因为在文本文件中可能夹杂了一些非法编码的字符。遇到这种情况,open()函数还接收一个errors参数,表示如果遇到编码错误后如何处理。最简单的方式是直接忽略:

>>> f = open('/Users/michael/gbk.txt', 'r', encoding='gbk', errors='ignore')

写文件

调用open()函数时,传入标识符'w'或者'wb'表示写文本文件或写二进制文件:

>>> f = open('/Users/michael/test.txt', 'w')
>>> f.write('Hello, world!')
>>> f.close()

可以反复调用write()来写入文件,但要调用f.close()来关闭文件。
写文件时,操作系统往往不会立刻把数据写入磁盘,而是放到内存缓存起来,空闲时再写入。调用close()方法时,操作系统才保证把没有写入的数据全部写入磁盘。
如果记调用close(),数据可能只写了一部分到磁盘,剩下的丢失了。

with open('/Users/michael/test.txt', 'w') as f:
    f.write('Hello, world!')
#with:不管with中的代码出现什么错误,都会进行对当前对象进行清理工作。

要写入特定编码的文本文件,给open()函数传入encoding参数,将字符串自动转换成指定编码。
'w'模式写入文件时,如果文件已存在,会直接覆盖。
可以传入'a'以追加(append)模式写入追加到文件末尾。

StringIO和BytesIO

StringIO是在内存中读写str。

要把str写入StringIO,我们需要先创建一个StringIO,然后,像文件一样写入即可:

>>> from io import StringIO
>>> f = StringIO()
>>> f.write('hello')
5
>>> f.write(' ')
1
>>> f.write('world!')
6
>>> print(f.getvalue())
hello world!

getvalue()用于获得写入后的str。
要读取StringIO,可以用一个str初始化StringIO,像读文件一样读取:

>>> from io import StringIO
>>> f = StringIO('Hello!\nHi!\nGoodbye!')
>>> while True:
...     s = f.readline()
...     if s == '':
...         break
...     print(s.strip())
...
Hello!
Hi!
Goodbye!

操作二进制数据使用BytesIO。

BytesIO实现了在内存中读写bytes,创建一个BytesIO,然后写入一些bytes:

>>> from io import BytesIO
>>> f = BytesIO()
>>> f.write('中文'.encode('utf-8'))
6
>>> print(f.getvalue())
b'\xe4\xb8\xad\xe6\x96\x87'

请注意,写入的不是str,而是经过UTF-8编码的bytes。
和StringIO类似,可以用一个bytes初始化BytesIO,再像读文件一样读取:

>>> from io import BytesIO
>>> f = BytesIO(b'\xe4\xb8\xad\xe6\x96\x87')
>>> f.read()
b'\xe4\xb8\xad\xe6\x96\x87'

操作文件和目录

Python内置的os模块可以直接调用操作系统提供的接口函数。
打开Python交互式命令行,使用os模块的基本功能:

>>> import os
>>> os.name # 操作系统类型
'posix'

如果是posix,说明系统是Linux、Unix或Mac OS X,如果是nt,就是Windows系统。
获取详细的系统信息调用uname()

>>> os.uname()
posix.uname_result(sysname='Darwin', nodename='MichaelMacPro.local', release='14.3.0', version='Darwin Kernel Version 14.3.0: Mon Mar 23 11:59:05 PDT 2015; root:xnu-2782.20.48~5/RELEASE_X86_64', machine='x86_64')

uname()函数在Windows上不提供!!!(不早说)
所有说,os模块的某些函数是跟操作系统相关的。

环境变量

在操作系统中定义的环境变量,全部保存在os.environ这个变量中,可以直接查看:

>>> os.environ

要获取某个环境变量的值,可以调用os.environ.get('key')

>>> os.environ.get('PATH')
'/usr/bin:/bin:/usr/sbin:/sbin:/usr/local/bin:/opt/X11/bin:/usr/local/mysql/bin'
>>> os.environ.get('x', 'default')
'default'

操作文件和目录

操作文件和目录的函数一部分放在os模块中,一部分放在os.path模块中。
查看、创建和删除目录可以这么调用:

# 查看当前目录的绝对路径:
>>> os.path.abspath('.')
'/Users/michael'
# 在某个目录下创建一个新目录,首先把新目录的完整路径表示出来:
>>> os.path.join('/Users/michael', 'testdir')
'/Users/michael/testdir'
# 然后创建一个目录:
>>> os.mkdir('/Users/michael/testdir')
# 删掉一个目录:
>>> os.rmdir('/Users/michael/testdir')

把两个路径合成一个要通过os.path.join(),这样可以正确处理不同操作系统的路径分隔符。
在Linux/Unix/Mac下,os.path.join()返回这样的字符串:

part-1/part-2

在Windows下返回这样的字符串:

part-1\part-2

拆分路径要通过os.path.split(),这样可以把一个路径拆分为两部分,后一部分总是最后级别的目录或文件名:

>>> os.path.split('/Users/michael/testdir/file.txt')
('/Users/michael/testdir', 'file.txt')

os.path.splitext()可以直接让你得到文件扩展名:

>>> os.path.splitext('/path/to/file.txt')
('/path/to/file', '.txt')

文件操作使用下面的函数。假定当前目录下有一个test.txt文件:

# 对文件重命名:
>>> os.rename('test.txt', 'test.py')
# 删掉文件:
>>> os.remove('test.py')

复制文件的函数在os模块中不存在!因为复制文件并非由操作系统提供的系统调用。
理论上讲,通过读写文件可以完成文件复制,不过要多写很多代码。
shutil模块提供了copyfile()的函数。(以看做是os模块的补充。)

利用Python的特性来过滤文件。

比如我们要列出当前目录下的所有目录:

>>> [x for x in os.listdir('.') if os.path.isdir(x)]
['.lein', '.local', '.m2', '.npm', '.ssh', '.Trash', '.vim', 'Applications', 'Desktop', ...]

要列出所有的.py文件:

>>> [x for x in os.listdir('.') if os.path.isfile(x) and os.path.splitext(x)[1]=='.py']
['apis.py', 'config.py', 'models.py', 'pymonitor.py', 'test_db.py', 'urls.py', 'wsgiapp.py']

未解决的问题:啥是dircp

序列化

在程序运行的过程中,所有的变量都是在内存中的。如定义一个dict:

d = dict(name='Bob', age=20, score=88)

name改成'Bill',但程序结束,变量所占用的内存就被操作系统全部回收。如果没有把修改后的'Bill'存储到磁盘上,下次重新运行程序,变量又被初始化为'Bob'
我们把变量从内存中变成可存储或传输的过程称之为序列化,在Python中叫pickling。
序列化之后,就可以把序列化后的内容写入磁盘,或者通过网络传输到别的机器上。
反过来,把变量内容从序列化的对象重新读到内存里称之为反序列化,即unpickling。
Python提供了pickle模块来实现序列化。
首先,把一个对象序列化并写入文件:

>>> import pickle
>>> d = dict(name='Bob', age=20, score=88)
>>> pickle.dumps(d)
b'\x80\x03}q\x00(X\x03\x00\x00\x00ageq\x01K\x14X\x05\x00\x00\x00scoreq\x02KXX\x04\x00\x00\x00nameq\x03X\x03\x00\x00\x00Bobq\x04u.'

pickle.dumps()方法把任意对象序列化成一个bytes,然后,就可以把这个bytes写入文件。
或者用pickle.dump()直接把对象序列化后写入一个file-like Object:

>>> f = open('dump.txt', 'wb')
>>> pickle.dump(d, f)
>>> f.close()

当要把对象从磁盘读到内存时,可以先把内容读到一个bytes,然后用pickle.loads()反序列化出对象,也可以直接用pickle.load()方法从一个file-like Object中直接反序列化出对象。
打开另一个Python命令行来反序列化刚才保存的对象:

>>> f = open('dump.txt', 'rb')
>>> d = pickle.load(f)
>>> f.close()
>>> d
{'age': 20, 'score': 88, 'name': 'Bob'}

JSON

如果我们要在不同的编程语言之间传递对象,就必须把对象序列化为标准格式:序列化为JSON,因为JSON表示出来就是一个字符串,可以被所有语言读取,也可以方便地存储到磁盘或者通过网络传输。JSON是标准格式,可以直接在Web页面中读取。

image.png

Python内置的json模块提供了Python对象到JSON格式的转换:

>>> import json
>>> d = dict(name='Bob', age=20, score=88)
>>> json.dumps(d)
'{"age": 20, "score": 88, "name": "Bob"}'

dumps()方法返回一个str,内容就是标准的JSON。类似的,dump()方法可以直接把JSON写入一个file-like Object

进程和线程

现代操作系统比如Mac OS X,UNIX,Linux,Windows等,都是支持“多任务”的操作系统。
多任务:简单地说,就是操作系统可以同时运行多个任务。
单核CPU执行多任务:操作系统轮流让各个任务交替执行,任务1执行0.01秒,切换到任务2,任务2执行0.01秒,再切换到任务3,执行0.01秒……
对于操作系统来说,一个任务就是一个进程(Process)。
进程内的这些“子任务”为线程(Thread)。
多任务的实现有3种方式:
· 多进程模式
· 多线程模式
· 多进程+多线程模式

多进程

访问数据库

数据库类别:
付费的商用数据库:
· Oracle,典型的高富帅;
· SQL Server,微软自家产品,Windows定制专款;
· DB2,IBM的产品,听起来挺高端;
· Sybase,曾经跟微软是好基友,后来关系破裂,现在家境惨淡。
这些数据库都是不开源而且付费的。
免费的开源数据库:
· MySQL,常用
· PostgreSQL,学术气息有点重
· sqlite,嵌入式数据库,适合桌面和移动应用。

web开发

HTTP协议简介

在Web应用中,服务器把网页传给浏览器,实际上就是把网页的HTML代码发送给浏览器,让浏览器显示出来。
览器和服务器之间的传输协议是HTTP:
· TML是一种用来定义网页的文本,会HTML,就可以编写网页;
· HTTP是在网络上传输HTML的协议,用于浏览器和服务器的通信。
F12打开开发者工具。
Elements显示网页的结构,Network显示浏览器和服务器的通信。
HTTP请求流程:
步骤1:浏览器首先向服务器发送HTTP请求,请求包括:
方法:GET还是POSTGET仅请求资源,POST会附带用户数据;
路径:/full/url/path
域名:由Host头指定:Host: www.sina.com.cn
以及其他相关的Header;
如果是POST,那么请求还包括一个Body,包含用户数据。

步骤2:服务器向浏览器返回HTTP响应,响应包括:

响应代码:200表示成功,3xx表示重定向,4xx表示客户端发送的请求有错误,5xx表示服务器端处理时发生了错误;

响应类型:由Content-Type指定,例如:Content-Type: text/html;charset=utf-8表示响应类型是HTML文本,并且编码是UTF-8Content-Type: image/jpeg表示响应类型是JPEG格式的图片;

以及其他相关的Header;

通常服务器的HTTP响应会携带内容,也就是有一个Body,包含响应的内容,网页的HTML源码就在Body中。

步骤3:如果浏览器还需要继续向服务器请求其他资源,比如图片,就再次发出HTTP请求,重复步骤1、2。

一个HTTP请求只处理一个资源。
HTTP协议同时具备极强的扩展性,在HTML中可以链入其他服务器的资源,从而将请求压力分散到各个服务器上,并且,一个站点可以链接到其他站点,无数个站点互相链接起来,形成World Wide WebWWW)。

HTTP格式:
一个HTTP包含Header和Body两部分,其中Body是可选的。
HTTP GET请求的格式:

GET /path HTTP/1.1
Header1: Value1
Header2: Value2
Header3: Value3

每个Header一行一个,换行符是\r\n

HTTP POST请求的格式:

POST /path HTTP/1.1
Header1: Value1
Header2: Value2
Header3: Value3

body data goes here...

当遇到连续两个\r\n时,Header部分结束,后面的数据全部是Body。

HTTP响应的格式:

200 OK
Header1: Value1
Header2: Value2
Header3: Value3

body data goes here...

**HTML简介
HTML文档就是一系列的Tag组成,最外层的Tag是<html>。规范的HTML也包含<head>...</head><body>...</body>),HTML是富文档模型,还有一系列的Tag用来表示链接、图片、表格、表单等等。

CSS简介
CSS用来控制HTML里的所有元素如何展现。

JavaScript简介
为了让HTML具有交互性而作为脚本语言添加的,JavaScript既可以内嵌到HTML中,也可以从外部链接到HTML中。

**WSGI接口
Web应用的本质就是:
1.浏览器发送一个HTTP请求;
2.服务器收到请求,生成一个HTML文档;
3.服务器把HTML文档作为HTTP响应的Body发送给浏览器;
4.浏览器收到HTTP响应,从HTTP Body取出HTML文档并显示。

动态生成HTML,底层代码由专门的服务器软件实现,用Python专注于生成HTML文档。因为我们不希望接触到TCP连接、HTTP原始请求和响应格式,所以,需要一个统一的接口,让我们专心用Python编写Web业务。

这个接口就是WSGI:Web Server Gateway Interface。

WSGI接口定义非常简单,它只要求Web开发者实现一个函数,就可以响应HTTP请求。我们来看一个最简单的Web版本的“Hello, web!”:

def application(environ, start_response):
    start_response('200 OK', [('Content-Type', 'text/html')])
    return [b'<h1>Hello, web!</h1>']

无论多么复杂的Web应用程序,入口都是一个WSGI处理函数。HTTP请求的所有输入信息都可以通过environ获得,HTTP响应的输出都可以通过start_response()加上函数返回值作为Body。

使用Web框架

有了Web框架,我们在编写Web应用时,注意力就从WSGI处理函数转移到URL+对应的处理函数。

在编写URL处理函数时,除了配置URL外,从HTTP请求拿到用户数据也是非常重要的。Web框架都提供了自己的API来实现这些功能。Flask通过request.form['name']来获取表单的内容。

异步IO

协程

协程,又称微线程,纤程。英文名Coroutine。
子程序,或者称为函数,在所有语言中都是层级调用,比如A调用B,B在执行过程中又调用了C,C执行完毕返回,B执行完毕返回,最后是A执行完毕。
子程序调用是通过栈实现的,一个线程就是执行一个子程序。
子程序调用总是一个入口,一次返回,调用顺序是明确的。

协程看上去也是子程序,但执行过程中,在子程序内部可中断,然后转而执行别的子程序,在适当的时候再返回来接着执行。
比如子程序A、B:

def A():
    print('1')
    print('2')
    print('3')

def B():
    print('x')
    print('y')
    print('z')

假设由协程执行,在执行A的过程中,可以随时中断,去执行B,B也可能在执行过程中中断再去执行A,结果可能是:

1
2
x
y
3
z

协程有何优势?
最大的优势就是协程极高的执行效率。没有线程切换的开销,和多线程比,线程数量越多,协程的性能优势就越明显。
第二大优势就是不需要多线程的锁机制,因为只有一个线程,也不存在同时写变量冲突,在协程中控制共享资源不加锁,只需要判断状态就行。

Python对协程的支持是通过generator实现的。
在generator中:
1.通过for循环来迭代
2.不断调用next()函数获取由yield语句返回的下一个值。

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 206,214评论 6 481
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 88,307评论 2 382
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 152,543评论 0 341
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 55,221评论 1 279
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 64,224评论 5 371
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,007评论 1 284
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,313评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,956评论 0 259
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,441评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,925评论 2 323
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,018评论 1 333
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,685评论 4 322
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,234评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,240评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,464评论 1 261
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,467评论 2 352
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,762评论 2 345

推荐阅读更多精彩内容