Python3

基本语法

输入输出

print / input

如果字符串里面有很多字符都需要转义,就需要加很多\,为了简化,Python还允许用r''表示''内部的字符串默认不转义

>>> print('\\\t\\')
\        \
>>> print(r'\\\t\\')
\\\t\\

如果字符串内部有很多换行,用\n写在一行里不好阅读,为了简化,Python允许用 '''...''' 的格式表示多行内容,注意...是提示符,不是代码的一部分

>>> print('''line1
...     line2
...     line3''')
 line1
 line2
 line3

数据类型,变量和常量

布尔值 True, False, and, or , not
空值 None,None不能理解为0,因为0是有意义的,而None是一个特殊的空值

变量名必须是大小写英文、数字和_的组合,且不能用数字开头,同一个变量可以反复赋值,而且可以是不同类型的变量

通常用全部大写的变量名表示常量

有两种除法,一种除法是 /,/ 除法计算结果是浮点数,即使是两个整数恰好整除,结果也是浮点数,还有一种除法是 //,称为地板除,两个整数的除法仍然是整数,取余 % 输出仍然是整数

字符编码和字符串

Unicode把所有语言都统一到一套编码里,这样就不会再有乱码问题了。Unicode标准也在不断发展,但最常用的是用两个字节表示一个字符(如果要用到非常偏僻的字符,就需要4个字节)。

UTF-8编码把一个Unicode字符根据不同的数字大小编码成1-6个字节,常用的英文字母被编码成1个字节,汉字通常是3个字节,只有很生僻的字符才会被编码成4-6个字节。如果你要传输的文本包含大量英文字符,用UTF-8编码就能节省空间。

在计算机内存中,统一使用Unicode编码,当需要保存到硬盘或者需要传输的时候,就转换为UTF-8编码。

举例:记事本编辑保存

ord() 函数获取字符的整数表示,chr() 函数把编码转换为对应的字符,如果知道字符的整数编码,还可以用十六进制这么写str

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

如果要在网络上传输,或者保存到磁盘上,就需要把str变为以字节为单位的bytes。Python对bytes类型的数据用带b前缀的单引号或双引号表示。要注意区分'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)

反过来,如果我们从网络或磁盘上读取了字节流,那么读到的数据就是bytes。要把bytes变为str,就需要用decode()方法,如果bytes中包含无法解码的字节,decode()方法会报错,如果bytes中只有一小部分无效的字节,可以传入errors='ignore'忽略错误的字节。

要计算str包含多少个字符,可以用len()函数,记住不是包含多少byte,如果换成bytes,len()函数就计算字节数。

由于Python源代码也是一个文本文件,所以,当你的源代码中包含中文的时候,在保存源代码时,就需要务必指定保存为UTF-8编码。当Python解释器读取源代码时,为了让它按UTF-8编码读取,我们通常在文件开头写上这两行:

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

格式化

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

常见的占位符

有些时候,字符串里面的%是一个普通字符就需要转义,用%%来表示一个%
如果不太确定应该用什么,%s永远起作用,它会把任何数据类型转换为字符串

format(),另一种格式化字符串的方法

list

list是一种有序的集合,可以随时添加和删除其中的元素,用len()函数可以获得list元素的个数。

>>> class = ['aa', 'bb', 'cc']

如果要取最后一个元素,除了计算索引位置外,还可以用-1做索引,直接获取最后一个元素。

追加元素 .append(xx),插入元素 .insert(idx, xx),删除末尾的元素 .pop(),删除指定位置元素 .pop(i)

list里面的元素的数据类型也可以不同,list元素也可以是另一个list

多维list访问可以通过 A[x][y]或A[x, y]

tuple

tuple和list非常类似,但是tuple一旦初始化就不能修改,它也没有append(),insert()这样的方法

只有1个元素的tuple定义时必须加一个逗号 , 来消除歧义

>>> class = ('aa', 'bb', 'cc')
>>> class = (1, )

tuple所谓的“不变”是说,tuple的每个元素,指向永远不变,指向一个list,就不能改成指向其他对象,但指向的这个list本身是可变的

条件判断

if xxx:

elif yyy:

else:

循环

Python的循环有两种,一种是for...in循环,依次把list或tuple中的每个元素迭代出来:

for x in y:

range()函数,可以生成一个整数序列

第二种循环是while循环,只要条件满足,就不断循环,条件不满足时退出循环:

while xxx:

break 提前结束循环,continue跳过当前循环

dict

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

这是一种key-value存储方式,如果key不存在,dict就会报错,要避免key不存在的错误,有两种办法,一是通过in判断key是否存在

>>> 'Thomas' in d
False

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

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

要删除一个key,用pop(key)方法,对应的value也会从dict中删除,请务必注意,dict内部存放的顺序和key放入的顺序是没有关系的。

和list比较,dict有以下几个特点:
        1. 查找和插入的速度极快,不会随着key的增加而变慢;
        2. 需要占用大量的内存,内存浪费多。

而list相反:
        1. 查找和插入的时间随着元素的增加而增加;
        2. 占用空间小,浪费内存很少。

所以,dict是用空间来换取时间的一种方法

set

set和dict类似,也是一组key的集合,但不存储value。由于key不能重复,所以,在set中,没有重复的key,要创建一个set,需要提供一个list作为输入集合,重复元素在set中自动被过滤:

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

通过add(key)方法可以添加元素到set中,可以重复添加,但不会有效果;通过remove(key)方法可以删除元素

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

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

函数

def my_abs(x):
    xxxx
    return (y)

如果想定义一个什么事也不做的空函数,可以用pass语句,实际上pass可以用来作为占位符,比如现在还没想好怎么写函数的代码,就可以先放一个pass,让代码能运行起来

def nop():
    pass

参数类型做检查,可以用内置函数isinstance()实现:

def my_abs(x):
    if not isinstance(x, (int, float)):
        raiseTypeError('bad operand type')
    if x >=0:
        return x
    else:
        return -x

函数可以返回多个值,但其实这只是一种假象,Python函数返回的仍然是单一值,原来返回值是一个tuple。

默认参数等同C
可变参数就是传入的参数个数是可变的

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

定义可变参数和定义一个list或tuple参数相比,仅仅在参数前面加了一个*号。在函数内部,参数numbers接收到的是一个tuple,因此,函数代码完全不变。但是,调用该函数时,可以传入任意个参数,包括0个参数。Python允许你在list或tuple前面加一个*号,把list或tuple的元素变成可变参数传进去

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

关键字参数允许传入0个或任意个含参数名的参数,这些关键字参数在函数内部自动组装为一个dict

>>> def person(name, age, **kw):

如果要限制关键字参数的名字,就可以用命名关键字参数

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

和关键字参数**kw不同,命名关键字参数需要一个特殊分隔符*,*后面的参数被视为命名关键字参数

递归函数

递归函数需要注意防止栈溢出,解决递归调用栈溢出的方法是通过尾递归优化,事实上尾递归和循环的效果是一样的,所以,把循环看成是一种特殊的尾递归函数也是可以的。尾递归调用时,如果做了优化,栈不会增长,因此,无论多少次调用也不会导致栈溢出。

deffact(n):
    return fact_iter(n,1)

deffact_iter(num, product):
    ifnum ==1:
        return product
    return fact_iter(num -1, num * product)

高级特性

切片(slice)

L[0:3]表示,从索引0开始取,直到索引3为止,但不包括索引3,如果第一个索引是0,还可以省略,同样支持倒数切片

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

>>> L = list(range(100))
>>> L
[0,1,2,3, ...,99]
>>> L[:10:2]        //前10个数,每两个取一个
[0, 2, 4, 6, 8]
>>> L[::5]            //所有数,每5个取一个:
[0, 5, 10, 15, 20, 25, 30, 35, 40, 45, 50, 55, 60, 65, 70, 75, 80, 85, 90, 95]
>>> L[:]              //甚至什么都不写,只写[:]就可以原样复制一个list

list和tuple都可以使用切片

迭代

python的for循环不仅可以用在list或tuple上,还可以作用在其他可迭代对象上。

list这种数据类型虽然有下标,但很多其他数据类型是没有下标的,但是,只要是可迭代对象,无论有无下标,都可以迭代,比如dict就可以迭代,默认情况下,dict迭代的是key。如果要迭代value,可以用for value in d.values(),如果要同时迭代key和value,可以用for k, v in d.items(),字符串也是可迭代对象。

如何判断一个对象是可迭代对象呢?方法是通过collections模块的Iterable类型判断:

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

如果要对list实现类似Java那样的下标循环怎么办?Python内置的enumerate函数可以把一个list变成索引-元素对,这样就可以在for循环中同时迭代索引和元素本身:

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

列表生成式

for循环后面还可以加上if判断,还可以使用两层循环,可以生成全排列

>>> [x * x for x in range(1,11)]
[1,4,9,16,25,36,49,64,81,100]
>>> [x * x for x in range(1,11) if x %2==0]
[4,16,36,64,100]
>>> [m + n for m in 'ABC' for n in 'XYZ']
['AX','AY','AZ','BX','BY','BZ','CX','CY','CZ']

循环其实可以同时使用两个甚至多个变量,列表生成式也可以使用两个变量来生成list

>>> d = {'x':'A','y':'B','z':'C'}
>>> for k, v in d.items()
:...         print(k,'=', v)
...
y = B
x = A
z = C
>>> d = {'x':'A','y':'B','z':'C'}
>>> [k +'='+ v for k, v in d.items()]
['y=B','x=A','z=C']
>>> L = ['Hello','World','IBM','Apple']
>>> [s.lower() for s in L]
['hello','world','ibm','apple']

生成器(generator)

通过列表生成式,我们可以直接创建一个列表。但是,受到内存限制,列表容量肯定是有限的。而且,创建一个包含100万个元素的列表,不仅占用很大的存储空间,如果我们仅仅需要访问前面几个元素,那后面绝大多数元素占用的空间都白白浪费了。所以,如果列表元素可以按照某种算法推算出来,那我们是否可以在循环的过程中不断推算出后续的元素呢?这样就不必创建完整的list,从而节省大量的空间。在Python中,这种一边循环一边计算的机制,称为生成器:generator

要创建一个generator,有很多种方法。第一种方法很简单,只要把一个列表生成式的[]改成(),就创建了一个generator,如果要一个一个打印出来,可以通过next()函数获得generator的下一个返回值,没有更多的元素时,抛出StopIteration的错误

>>> 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

正确的方法是使用for循环,因为generator也是可迭代对象

>>> g = (x * x for x in range(10))
>>> for n in g:
...         print(n)

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

>>>def odd():
...    print('step 1')
...    yield(1)
...    print('step 2')
...    yield(3)
...    print('step 3')
...    yield(5)
>>>o = odd()
>>>next(o)
step1
1
>>>next(o)
step2
3
>>>next(o)
step3
5
>>>next(o)
Traceback(most recent call last):
    File"<stdin>", line1, in <module>
StopIteration

同样的,把函数改成generator后,我们基本上从来不会用next()来获取下一个返回值,而是直接使用for循环来迭代。但是用for循环调用generator时,发现拿不到generator的return语句的返回值。如果想要拿到返回值,必须捕获StopIteration错误,返回值包含在StopIteration的value中。

迭代器

可以直接作用于for循环的数据类型有以下几种:

一类是集合数据类型,如list、tuple、dict、set、str等;
一类是generator,包括生成器和带yield的generator function。

这些可以直接作用于for循环的对象统称为可迭代对象:Iterable。可以使用isinstance()判断一个对象是否是Iterable对象。

而生成器不但可以作用于for循环,还可以被next()函数不断调用并返回下一个值,直到最后抛出StopIteration错误表示无法继续返回下一个值了。可以被next()函数调用并不断返回下一个值的对象称为迭代器:Iterator。可以使用isinstance()判断一个对象是否是Iterator对象。

生成器都是Iterator对象,但list、dict、str虽然是Iterable,却不是Iterator。把list、dict、str等Iterable变成Iterator可以使用iter()函数。

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

map/reduce

map()函数接收两个参数,一个是函数,一个是Iterable,map将传入的函数依次作用到序列的每个元素,并把结果作为新的Iterator返回.

>>> 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()把一个函数作用在一个序列[x1, x2, x3, ...]上,这个函数必须接收两个参数,reduce把结果继续和序列的下一个元素做累积计算,其效果就是:

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

filter

Python内建的filter()函数用于过滤序列。和map()类似,filter()也接收一个函数和一个序列。和map()不同的是,filter()把传入的函数依次作用于每个元素,然后根据返回值是True还是False决定保留还是丢弃该元素。

注意到filter()函数返回的是一个Iterator,也就是一个惰性序列,所以要强迫filter()完成计算结果,需要用list()函数获得所有结果并返回list。

sorted

Python内置的sorted()函数就可以对list进行排序,sorted()函数也是一个高阶函数,它还可以接收一个key函数来实现自定义的排序,要进行反向排序,不必改动key函数,可以传入第三个参数reverse=True。

>>> sorted([36,5, -12,9, -21], key=abs)
[5,9, -12, -21,36]
>>> sorted(['bob','about','Zoo','Credit'], key=str.lower)
['about','bob','Credit','Zoo']
>>> sorted(['bob','about','Zoo','Credit'], key=str.lower, reverse=True)
['Zoo','Credit','bob','about']

函数作为返回值

相关参数和变量都保存在返回的函数中,这种称为“闭包(Closure)”,麻烦,有用吗?

匿名函数

关键字lambda表示匿名函数,冒号前面的x表示函数参数。匿名函数有个限制,就是只能有一个表达式,不用写return,返回值就是该表达式的结果。

用匿名函数有个好处,因为函数没有名字,不必担心函数名冲突。此外,匿名函数也是一个函数对象,也可以把匿名函数赋值给一个变量,再利用变量来调用该函数:

装饰器

__name__,可以拿到函数的名字,__doc__,__author__ 这里略过

偏函数

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

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

当函数的参数个数太多,需要简化时,使用functools.partial可以创建一个新的函数,这个新函数可以固定住原函数的部分参数,从而在调用时更简单。

模块

在Python中,一个.py文件就称之为一个模块(Module)。使用模块还可以避免函数名和变量名冲突。相同名字的函数和变量完全可以分别存在不同的模块中,为了避免模块名冲突,Python又引入了按目录来组织模块的方法,称为包(Package)。

引入了包以后,只要顶层的包名不与别人冲突,那所有模块都不会与别人冲突。现在,abc.py模块的名字就变成了mycompany.abc,类似的,xyz.py的模块名变成了mycompany.xyz。请注意,每一个包目录下面都会有一个__init__.py的文件,这个文件是必须存在的,否则,Python就把这个目录当成普通目录,而不是一个包。__init__.py可以是空文件,也可以有Python代码,因为__init__.py本身就是一个模块,而它的模块名就是mycompany。

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

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

使用模块

#!/usr/bin/env python3
# -*- coding: utf-8 -*-
' a test module '
__author__ ='Leo Zhao'

import sys

def test():
    args = sys.argv

if __name__=='__main__':
    test()

类似__xxx__这样的变量是特殊变量,可以被直接引用,但是有特殊用途,比如上面的__author__,__name__就是特殊变量,hello模块定义的文档注释也可以用特殊变量__doc__访问,我们自己的变量一般不要用这种变量名;

类似_xxx和__xxx这样的函数或变量就是非公开的(private),不应该被直接引用,比如_abc,__abc等;

安装第三方模块

在Python中,安装第三方模块,是通过包管理工具pip完成的,一般来说,第三方库都会在Python官方的pypi.python.org网站注册。用pip一个一个安装费时费力,还需要考虑兼容性。我们推荐直接使用Anaconda,这是一个基于Python的数据处理和科学计算平台,它已经内置了许多非常有用的第三方库,我们装上Anaconda,就相当于把数十个第三方模块自动安装好了,非常简单易用。

默认情况下,Python解释器会搜索当前目录、所有已安装的内置模块和第三方模块,搜索路径存放在sys模块的path变量中,如果我们要添加自己的搜索目录,有两种方法:一是直接修改sys.path,添加要搜索的目录,第二种方法是设置环境变量PYTHONPATH,该环境变量的内容会被自动添加到模块搜索路径中。设置方式与设置Path环境变量类似。

>>> import sys
>>> sys.path.append('/Users/michael/my_py_scripts')

面向对象编程

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

class后面紧接着是类名,即Student,类名通常是大写开头的单词,紧接着是(object),表示该类是从哪个类继承下来的,如果没有合适的继承类,就使用object类,这是所有类最终都会继承的类。

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

和普通的函数相比,在类中定义的函数只有一点不同,就是第一个参数永远是实例变量self,并且,调用时,不用传递该参数。除此之外,类的方法和普通函数没有什么区别,所以,你仍然可以用默认参数、可变参数、关键字参数和命名关键字参数。

和静态语言不同,Python允许对实例变量绑定任何数据,也就是说,对于两个实例变量,虽然它们都是同一个类的不同实例,但拥有的变量名称都可能不同:

>>> bart = Student('Bart Simpson', 59)
>>> lisa = Student('Lisa Simpson', 87)
>>> bart.age = 8
>>> bart.age
8
>>> lisa.age
Traceback (most recent call last):
   File "", line 1, in <module>
AttributeError: 'Student' object has no attribute 'age'

访问限制

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

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

双下划线开头的实例变量是不是一定不能从外部访问呢?其实也不是。不能直接访问__name是因为Python解释器对外把__name变量改成了_Student__name,所以,仍然可以通过_Student__name来访问__name变量

继承和多态

同C++

静态语言 vs 动态语言

对于静态语言(例如C++)来说,如果需要传入Animal类型,则传入的对象必须是Animal类型或者它的子类,否则,将无法调用run()方法。对于Python这样的动态语言来说,则不一定需要传入Animal类型。我们只需要保证传入的对象有一个run()方法就可以了.

获取对象信息

type():

使用type()判断对象类型,判断基本数据类型可以直接写int,str等,但如果要判断一个对象是否是函数怎么办?可以使用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的类型,可以使用isinstance()函数,能用type()判断的基本类型也可以用isinstance()判断,并且还可以判断一个变量是否是某些类型中的一种,比如:

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

dir():

如果要获得一个对象的所有属性和方法,可以使用dir()函数,它返回一个包含字符串的list,这个可用于查询特定类型支持的函数操作

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

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

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

我们自己写的类,如果也想用len(myObj)的话,就自己写一个__len__()方法:

>>> class MyDog(object):
... def __len__(self):
... return 100
...
>>> dog = MyDog()
>>> len(dog)
100

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

>>> 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的错误,可以传入一个default参数,如果属性不存在,就返回默认值:

>>> getattr(obj, 'z')     # 获取属性'z'
Traceback (most recent call last):  File "", line 1, in <module>
AttributeError: 'MyObject' object has no attribute 'z'
>>> getattr(obj,'z',404)    # 获取属性'z',如果不存在,返回默认值404
404

也可以获得对象的方法:

>>> hasattr(obj,'power')    # 有属性'power'吗?
True
>>> getattr(obj,'power')    # 获取属性'power'
<bound method MyObject.power of <__main__.MyObject object at0x10077a6a0>>
>>>> fn = getattr(obj,'power')    # 获取属性'power'并赋值到变量fn
>>> fn    # fn指向obj.power
<bound method MyObject.power of <__main__.MyObject object at0x10077a6a0>>
>>> fn()    # 调用fn()与调用obj.power()是一样的
81

实例属性和类属性

由于Python是动态语言,根据类创建的实例可以任意绑定属性,在编写程序的时候,千万不要对实例属性和类属性使用相同的名字,因为相同名称的实例属性将屏蔽掉类属性,但是当你删除实例属性后,再使用相同的名称,访问到的将是类属性。实例属性属于各个实例所有,互不干扰;类属性属于类所有,所有实例共享一个属性,类似C++ static;

>>>class Student(object):
...     def __init__(self, name):
...     self.name = name
...
>>>s = Student('Bob')
>>>s.score =90    #实例属性
>>>del s.score

>>>classStudent(object):
...     name ='Student'  #类属性

实例绑定方法

给一个实例绑定的方法,对另一个实例是不起作用:

>>> 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

__slots__

使用__slots__限制实例能添加的属性:

>>> 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>", line1, in <module>
AttributeError: 'Student' object has no attribute 'score'

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

@property

Python内置的@property装饰器就是负责把一个方法变成属性调用的,这个神奇的@property,我们在对实例属性操作的时候,就知道该属性很可能不是直接暴露的,而是通过getter和setter方法来实现的,还可以定义只读属性,只定义getter方法,不定义setter方法就是一个只读属性。用于对赋值操作进行检查限制。

>>>class Student(object):
...        @property
...        def birth(self):
...            return self._birth
...
...        @birth.setter
...        def birth(self, value):
...            self._birth = value
...
...        @property
...        def age(self):
...            return 2015 - self._birth

定制类

__str__() ,__repr__() 添加打印类信息,

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

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

__getattr__ 获得属性,可以把一个类的所有属性和方法调用全部动态化处理了

__call__ 

枚举类

Enum可以把一组相关常量定义在一个class中,且class不可变,而且成员可以直接比较,@unique装饰器可以帮助我们检查保证没有重复值。

from enum import Enum, unique

@unique
class Weekday(Enum):
    Sun =0    # Sun的value被设定为0
    Mon =1
    Tue =2
    Wed =3
    Thu =4
    Fri =5
    Sat =6

元类

type()

创建一个class对象,type()函数依次传入3个参数:

1. class的名称;
2. 继承的父类集合,注意Python支持多重继承,如果只有一个父类,别忘了tuple的单元素写法;
3. class的方法名称与函数绑定,这里我们把函数fn绑定到方法名hello上。

metaclass

除了使用type()动态创建类以外,要控制类的创建行为,还可以使用metaclass,暂且略过。

错误、调试和测试

错误处理

try
...
except xxException as e:
...
else:
...
finally
...

Python内置的logging模块可以非常容易地记录错误信息:

import logging

logging.exception(e)
logging.info(xxx)
logging.basicConfig()

raise语句抛出一个错误的实例:

raise xxxError(xxx)

assert断言,同C,启动Python解释器时可以用-O参数来关闭assert,关闭后,可以把所有的assert语句当成pass来看

assert n !=0, 'n is zero!'

调试

启动Python的调试器pdb,让程序以单步方式运行,可以随时查看运行状态

$ python -m pdb err.py
>/Users/michael/Github/learn-python3/samples/debug/err.py(2)<module>()
-> s ='0'

IDE:

Visual Studio Code:https://code.visualstudio.com/,需要安装Python插件。PyCharm:http://www.jetbrains.com/pycharm/
另外,Eclipse加上pydev插件也可以调试Python程序。

单元测试

编写单元测试时,我们需要编写一个测试类,从unittest.TestCase继承

import unittest

from mydict import Dict

class TestDict(unittest.TestCase):
    def test_init(self):
        d = Dict(a=1, b='test')
        self.assertEqual(d.a,1)
        self.assertEqual(d.b,'test')
        self.assertTrue(isinstance(d, dict))

    def test_key(self):
        d = Dict()
        d['key'] ='value'
        self.assertEqual(d.key,'value')

以test开头的方法就是测试方法,不以test开头的方法不被认为是测试方法,测试的时候不会被执行。

运行单元测试

if __name__=='__main__':
    unittest.main()

文档测试

我们编写注释时,如果写上这样的注释,无疑更明确地告诉函数的调用者该函数的期望输入和输出。并且,Python内置的“文档测试”(doctest)模块可以直接提取注释中的代码并执行测试。

def abs(n):
    '''
    Function to get absolute value of number.

    Example:

    >>> abs(1)
    1
    >>> abs(-1)
    1
    >>> abs(0)
    0
    '''
    return n if n >=0 else (-n)

IO编程

文件读写

open()函数,传入文件名和标示符,如果文件不存在,open()函数就会抛出一个IOError的错误,并且给出错误码和详细的信息告诉你文件不存在。

如果文件打开成功,接下来,调用read()方法可以一次读取文件的全部内容,Python把内容读到内存,用一个str对象表示。

最后一步是调用close()方法关闭文件。文件使用完毕后必须关闭,因为文件对象会占用操作系统的资源,并且操作系统同一时间能打开的文件数量也是有限的。

由于文件读写时都有可能产生IOError,一旦出错,后面的f.close()就不会调用。所以,为了保证无论是否出错都能正确地关闭文件,我们可以使用try ... finally来实现。但是每次都这么写实在太繁琐,所以,Python引入了with语句来自动帮我们调用close()方法:

try:
    f = open('/path/to/file','r')
    print(f.read())
finally:
    if f:
        f.close()

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

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

StringIO/BytesIO

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

要把str写入StringIO,我们需要先创建一个StringIO,然后,像文件一样写入即可, getvalue()方法用于获得写入后的str。

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

操作文件和目录

信息和环境变量

>>> import os
>>> os.name    # 操作系统类型
'posix'
>>> 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')
>>> os.environ
environ({'VERSIONER_PYTHON_PREFER_32_BIT':'no','TERM_PROGRAM_VERSION':'326','LOGNAME':'michael','USER':'michael','PATH':'/usr/bin:/bin:/usr/sbin:/sbin:/usr/local/bin:/opt/X11/bin:/usr/local/mysql/bin', ...})
>>> 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.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()函数,这样可以正确处理不同操作系统的路径分隔符。同样的道理,要拆分路径时,也不要直接去拆字符串,而要通过os.path.split()函数,这样可以把一个路径拆分为两部分,后一部分总是最后级别的目录或文件名。os.path.splitext()可以直接让你得到文件扩展名,很多时候非常方便。

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

复制文件的函数居然在os模块中不存在!原因是复制文件并非由操作系统提供的系统调用。幸运的是shutil模块提供了copyfile()的函数,还可以在shutil模块中找到很多实用函数,它们可以看做是os模块的补充。

利用Python的特性来过滤文件

>>> [x for x in os.listdir('.') if os.path.isdir(x)]
['.lein', '.local', '.m2', '.npm', '.ssh', '.Trash', '.vim', 'Applications', 'Desktop', ...]
>>> [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']

序列化

我们把变量从内存中变成可存储或传输的过程称之为序列化,在Python中叫pickling,在其他语言中也被称之为serialization,marshalling,flattening等等,都是一个意思。反过来,把变量内容从序列化的对象重新读到内存里称之为反序列化,即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.'
>>> f = open('dump.txt','wb')
>>> pickle.dump(d, f)
>>> f.close()
>>> f = open('dump.txt','rb')
>>> d = pickle.load(f)
>>> f.close()
>>> d
{'age':20, 'score':88, 'name':'Bob'}

Pickle的问题和所有其他编程语言特有的序列化问题一样,就是它只能用于Python,并且可能不同版本的Python彼此都不兼容,因此,只能用Pickle保存那些不重要的数据,不能成功地反序列化也没关系。

JSON

JSON表示的对象就是标准的JavaScript语言的对象,JSON和Python内置的数据类型对应如下:

Python内置的json模块提供了非常完善的Python对象到JSON格式的转换。

如何序列化class对象,https://docs.python.org/3/library/json.html#json.dumps

进程和线程

多进程

Python的os模块封装了常见的系统调用,其中就包括fork,可以在Python程序中轻松创建子进程,由于Windows没有fork调用,在Windows上无法运行。

import os

print('Process (%s) start...'% os.getpid())
# Only works on Unix/Linux/Mac:
pid = os.fork()
if pid ==0:
      print('I am child process (%s) and my parent is %s.'% (os.getpid(), os.getppid()))
else:
      print('I (%s) just created a child process (%s).'% (os.getpid(), pid))

multiprocessing

由于Python是跨平台的,自然也应该提供一个跨平台的多进程支持。multiprocessing模块就是跨平台版本的多进程模块。multiprocessing模块提供了一个Process类来代表一个进程对象。

from multiprocessing import Process
import os

# 子进程要执行的代码
def run_proc(name):
    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.')

Pool

如果要启动大量的子进程,可以用进程池的方式批量创建子进程:

from multiprocessing importPool
import os, time, random

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

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

对Pool对象调用join()方法会等待所有子进程执行完毕,调用join()之前必须先调用close(),调用close()之后就不能继续添加新的Process了。

子进程

subprocess模块可以让我们非常方便地启动一个子进程,然后控制其输入和输出。如果子进程还需要输入,则可以通过communicate()方法输入。

import subprocess

print('$ nslookup www.python.org')
r = subprocess.call(['nslookup','www.python.org'])print('Exit code:', r)

print('$ nslookup')
p = subprocess.Popen(['nslookup'], stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
output, err = p.communicate(b'set q=mx\npython.org\nexit\n')
print(output.decode('utf-8'))
print('Exit code:', p.returncode)

进程间通信

Process之间肯定是需要通信的,操作系统提供了很多机制来实现进程间的通信。Python的multiprocessing模块包装了底层的机制,提供了Queue、Pipes等多种方式来交换数据。

from multiprocessing import Process, Queue
import os, time, random

# 写数据进程执行的代码:
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(random.random())

# 读数据进程执行的代码:
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()

多线程

Python的标准库提供了两个模块:_thread和threading,_thread是低级模块,threading是高级模块,对_thread进行了封装。绝大多数情况下,我们只需要使用threading这个高级模块。

import time, threading

# 新线程执行的代码:
def loop():
    print('thread %s is running...'% threading.current_thread().name)
    n =0
    while n <5:
        n = n +1
        print('thread %s >>> %s'% (threading.current_thread().name, n))
        time.sleep(1)
    print('thread %s ended.'% threading.current_thread().name)

print('thread %s is running...'% threading.current_thread().name)
t = threading.Thread(target=loop, name='LoopThread')
t.start()
t.join()
print('thread %s ended.'% threading.current_thread().name)

Lock

lock = threading.Lock()
lock.acquire()
lock.release()

多核cpu多线程

因为Python的线程虽然是真正的线程,但解释器执行代码时,有一个GIL锁:Global Interpreter Lock,任何Python线程执行前,必须先获得GIL锁,然后,每执行100条字节码,解释器就自动释放GIL锁,让别的线程有机会执行。这个GIL全局锁实际上把所有线程的执行代码都给上了锁,所以,多线程在Python中只能交替执行,即使100个线程跑在100核CPU上,也只能用到1个核。

GIL是Python解释器设计的历史遗留问题,通常我们用的解释器是官方实现的CPython,要真正利用多核,除非重写一个不带GIL的解释器。

所以,在Python中,可以使用多线程,但不要指望能有效利用多核。如果一定要通过多线程利用多核,那只能通过C扩展来实现,不过这样就失去了Python简单易用的特点。

不过,也不用过于担心,Python虽然不能利用多线程实现多核任务,但可以通过多进程实现多核任务。多个Python进程有各自独立的GIL锁,互不影响。

ThreadLocal

一个ThreadLocal变量虽然是全局变量,但每个线程都只能读写自己线程的独立副本,互不干扰。ThreadLocal解决了参数在一个线程中各个函数之间互相传递的问题。

import threading

# 创建全局ThreadLocal对象:
local_school = threading.local()

def process_student():
    # 获取当前线程关联的student:
    std = local_school.student
    print('Hello, %s (in %s)'% (std, threading.current_thread().name))

def process_thread(name):
    # 绑定ThreadLocal的student:
    local_school.student = name
    process_student()

t1 = threading.Thread(target= process_thread, args=('Alice',), name='Thread-A')
t2 = threading.Thread(target= process_thread, args=('Bob',), name='Thread-B')
t1.start()
t2.start()
t1.join()
t2.join()

分布式进程

Python的multiprocessing模块不但支持多进程,其中managers子模块还支持把多进程分布到多台机器上。一个服务进程可以作为调度者,将任务分布到其他多个进程中,依靠网络通信。由于managers模块封装很好,不必了解网络通信的细节,就可以很容易地编写分布式多进程程序。

示意图

正则表达式

1. 在正则表达式中,如果直接给出字符,就是精确匹配。
2. \d 可以匹配一个数字;
3. \w 可以匹配一个字母或数字;
4. . 可以匹配任意字符;
5. 要匹配变长的字符,在正则表达式中,用*表示任意个字符(包括0个);
6. +表示至少一个字符;
7. ?表示0个或1个字符;
8. {n}表示n个字符,用{n,m}表示n-m个字符。
9. \s可以匹配一个空格(也包括Tab等空白符)

来看一个复杂的例子:\d{3}\s+\d{3,8},我们来从左到右解读一下:
    \d{3}表示匹配3个数字,例如'010';
    \s可以匹配一个空格(也包括Tab等空白符),所以\s+表示至少有一个空格,例如匹配'  ','   '等;
    \d{3,8}表示3-8个数字,例如'1234567'。

10. 用[]表示范围,比如:

        [0-9a-zA-Z\_]可以匹配一个数字、字母或者下划线;
        [0-9a-zA-Z\_]+可以匹配至少由一个数字、字母或者下划线组成的字符串,比如'a100','0_Z','Py3000'等等;
        [a-zA-Z\_][0-9a-zA-Z\_]*可以匹配由字母或下划线开头,后接任意个由一个数字、字母或者下划线组成的字符串,也就是Python合法的变量;
        [a-zA-Z\_][0-9a-zA-Z\_]{0, 19}更精确地限制了变量的长度是1-20个字符(前面1个字符+后面最多19个字符)。

11. A|B可以匹配A或B,所以(P|p)ython可以匹配'Python'或者'python'。
12. ^表示行的开头,^\d表示必须以数字开头。
13. $表示行的结束,\d$表示必须以数字结束。

re模块

>>> import re
>>> re.match(r'^\d{3}\-\d{3,8}$', '010-12345')
<_sre.SRE_Match object; span=(0,9), match='010-12345'>
>>> re.match(r'^\d{3}\-\d{3,8}$', '010 12345')
>>>

match()方法判断是否匹配,如果匹配成功,返回一个Match对象,否则返回None。

切分字符串

用正则表达式切分字符串比用固定的字符更灵活

>>> 'a b c'.split(' ')
['a','b','','','c']
>>> re.split(r'\s+','a b c')
['a','b','c']
>>> re.split(r'[\s\,]+','a,b, c d')
['a','b','c','d']
>>> re.split(r'[\s\,\;]+','a,b;; c d')
['a','b','c','d']

分组

除了简单地判断是否匹配之外,正则表达式还有提取子串的强大功能。用()表示的就是要提取的分组(Group)。

>>> m = re.match(r'^(\d{3})-(\d{3,8})$', '010-12345')
>>> m
<_sre.SRE_Match object; span=(0,9), match='010-12345'>
>>> m.group(0)
'010-12345'
>>> m.group(1)
'010'
>>> m.group(2)
'12345'

贪婪匹配

正则匹配默认是贪婪匹配,也就是匹配尽可能多的字符。

>>> re.match(r'^(\d+)(0*)$','102300').groups()
('102300', '')
>>> re.match(r'^(\d+?)(0*)$','102300').groups()
('1023', '00')

由于\d+采用贪婪匹配,直接把后面的0全部匹配了,结果0*只能匹配空字符串了。必须让\d+采用非贪婪匹配(也就是尽可能少匹配),才能把后面的0匹配出来,加个?就可以让\d+采用非贪婪匹配。

编译

如果一个正则表达式要重复使用几千次,出于效率的考虑,我们可以预编译该正则表达式,接下来重复使用时就不需要编译这个步骤了,直接匹配

>>> import re
# 编译:
>>> re_telephone = re.compile(r'^(\d{3})-(\d{3,8})$')
# 使用:
>>> re_telephone.match('010-12345').groups()
('010', '12345')
>>> re_telephone.match('010-8086').groups()
('010', '8086')

编译后生成Regular Expression对象,由于该对象自己包含了正则表达式,所以调用对应的方法时不用给出正则字符串。

常见模块

内建模块

datetime

collections: namedtuple, deque, defaultdict, OrderedDict, ChainMap, Counter

base64

struct

harshlib

hmac

itertools

contextlib

urllib

xml

HTMLParser

第三方模块

Pillow:图像库

request:网络库

chardet:检测编码

psutil:系统监控

其他

virtualenv

virtualenv就是用来为一个应用创建一套“隔离”的Python运行环境

IDE

PyCharm

Komodo

引用

https://www.liaoxuefeng.com/wiki/0014316089557264a6b348958f449949df42a6d3a2e542c000
http://www.runoob.com/python3/python3-tutorial.html

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

推荐阅读更多精彩内容

  • Python文件 Python程序保存为文件以.py结尾,一个简单的例子: .py文件能不能像.exe文件那样直接...
    胡奚冰阅读 2,625评论 0 1
  • 内置函数Python解释器内置了许多功能和类型,总是可用的。他们是按字母顺序列在这里。 abs(x)返回一个数的绝...
    uangianlap阅读 1,211评论 0 0
  • 写在前面的话 代码中的# > 表示的是输出结果 输入 使用input()函数 用法 注意input函数输出的均是字...
    FlyingLittlePG阅读 2,712评论 0 8
  • 一、当局者迷 春游踏青,放空自己,放开心境,放飞自我。 想出去春游踏青,放空自己,但是找不到状态。 即使出去了,还...
    啊里_f9d6阅读 415评论 0 0
  • 刘清欢阅读 228评论 0 0