python进阶

1,学习路径

python进阶_第1张图片

 

2,安装Jupyter Notebook

3,列表和元组基础

在绝大多数编程语言中,集合的数据类型必须一致。不过,对于 Python 的列表和元组来说,并无此要求

 

l = [1, 2, 'hello', 'world'] # 列表中同时含有int和string类型的元素

l

[1, 2, 'hello', 'world']

tup = ('jason', 22) # 元组中同时含有int和string类型的元素

tup

('jason', 22)

 

列表是动态的,长度大小不固定,可以随意地增加、删减或者改变元素(mutable)。而元组是静态的,长度大小固定,无法增加删减或者改变(immutable)。

 

 

改变元祖只能重新开辟一块内存,创建新的元组

tup = (1, 2, 3, 4)

new_tup = tup + (5, ) # 创建新的元组new_tup,并依次填充原元组的值

new _tup

(1, 2, 3, 4, 5)

 

l = [1, 2, 3, 4]

l.append(5) # 添加元素5到原列表的末尾

l

[1, 2, 3, 4, 5]

 

 

Python 中的列表和元组都支持负数索引

列表和元组都支持切片操作

 

l = [1, 2, 3, 4]

l[1:3] # 返回列表中索引从1到2的子列表

[2, 3]

 

tup = (1, 2, 3, 4)

tup[1:3] # 返回元组中索引从1到2的子元组

(2, 3)

 

 

列表和元组都可以随意嵌套

 

l = [[1, 2, 3], [4, 5]] # 列表的每一个元素也是一个列表

tup = ((1, 2, 3), (4, 5, 6)) # 元组的每一个元素也是一个元组

 

可以通过 list() 和 tuple() 函数相互转换

list((1, 2, 3))

[1, 2, 3]

 

tuple([1, 2, 3])

(1, 2, 3)

 

列表和元组常用的内置函数

 

l = [3, 2, 3, 7, 8, 1]

l.count(3)

2

l.index(7)

3

l.reverse()

l

[1, 8, 7, 3, 2, 3]

l.sort()

l

[1, 2, 3, 3, 7, 8]

 

tup = (3, 2, 3, 7, 8, 1)

tup.count(3)

2

tup.index(7)

3

list(reversed(tup))

[1, 8, 7, 3, 2, 3]

sorted(tup)

[1, 2, 3, 3, 7, 8]

 

 

列表和元组存储方式的差异

列表和元组最重要的区别就是,列表是动态的、可变的,而元组是静态的、不可变的。这样的差异,势必会影响两者存储方式

 

l = [1, 2, 3]

l.__sizeof__()

64

 

tup = (1, 2, 3)

tup.__sizeof__()

48

 

由于列表是动态的,所以它需要存储指针,来指向对应的元素(上述例子中,对于 int 型,8 字节)。另外,由于列表可变,所以需要额外存储已经分配的长度大小(8 字节),这样才可以实时追踪列表空间的使用情况,当空间不足时,及时分配额外空间

 

列表空间分配的过程。我们可以看到,为了减小每次增加 / 删减操作时空间分配的开销,Python 每次分配空间时都会额外多分配一些,这样的机制(over-allocating)保证了其操作的高效性:增加 / 删除的时间复杂度均为 O(1)。

 

元组长度大小固定,元素不可变,所以存储空间固定

 

列表和元组的性能

元组要比列表更加轻量级一些,所以总体上来说,元组的性能速度要略优于列表

 

如果你想要增加、删减或者改变元素,那么列表显然更优

 

列表和元组的使用场景

 

如果存储的数据和数量不变,比如你有一个函数,需要返回的是一个地点的经纬度,然后直接传给前端渲染,那么肯定选用元组更合适。

 

def get_location():

    .....

return (longitude, latitude)

 

如果存储的数据或数量是可变的,比如社交平台上的一个日志功能,是统计一个用户在一周之内看了哪些用户的帖子,那么则用列表更合适。

 

viewer_owner_id_list = [] # 里面的每个元素记录了这个viewer一周内看过的所有owner的id

records = queryDB(viewer_id) # 索引数据库,拿到某个viewer一周内的日志

for record in records:

viewer_owner_id_list.append(record.id)

4,字典、集合

字典是一系列由键(key)和值(value)配对组成的元素的集合

在 Python3.7+,字典被确定为有序(注意:在 3.6 中,字典有序是一个 implementation detail,在 3.7 才正式成为语言特性,因此 3.6 中无法 100% 确保其有序性),而 3.6 之前是无序的,其长度大小可变,元素可以任意地删减和改变

 

Python 中字典和集合,无论是键还是值,都可以是混合类型

 

d1 = {'name': 'jason', 'age': 20, 'gender': 'male'}

d2 = dict({'name': 'jason', 'age': 20, 'gender': 'male'})

d3 = dict([('name', 'jason'), ('age', 20), ('gender', 'male')])

d4 = dict(name='jason', age=20, gender='male')

d1 == d2 == d3 ==d4

True

 

s1 = {1, 2, 3}

s2 = set([1, 2, 3])

s1 == s2

True

典访问可以直接索引键,如果不存在,就会抛出异常

d = {'name': 'jason', 'age': 20}

d['name']

'jason'

d['location']

Traceback (most recent call last):

  File "", line 1, in

KeyError: 'location'

 

也可以使用 get(key, default) 函数来进行索引。如果键不存在,调用 get() 函数可以返回一个默认值

 

d = {'name': 'jason', 'age': 20}

d.get('name')

'jason'

d.get('location', 'null')

'null'

 

集合并不支持索引操作,因为集合本质上是一个哈希表,和列表不一样

 

s = {1, 2, 3}

s[0]

Traceback (most recent call last):

  File "", line 1, in

TypeError: 'set' object does not support indexing

 

想要判断一个元素在不在字典或集合内,我们可以用 value in dict/set 来判断

 

s = {1, 2, 3}

1 in s

True

10 in s

False

 

d = {'name': 'jason', 'age': 20}

'name' in d

True

'location' in d

False

 

字典和集合也同样支持增加、删除、更新等操作

 

d = {'name': 'jason', 'age': 20}

d['gender'] = 'male' # 增加元素对'gender': 'male'

d['dob'] = '1999-02-01' # 增加元素对'dob': '1999-02-01'

d

{'name': 'jason', 'age': 20, 'gender': 'male', 'dob': '1999-02-01'}

d['dob'] = '1998-01-01' # 更新键'dob'对应的值

d.pop('dob') # 删除键为'dob'的元素对

'1998-01-01'

d

{'name': 'jason', 'age': 20, 'gender': 'male'}

 

s = {1, 2, 3}

s.add(4) # 增加元素4到集合

s

{1, 2, 3, 4}

s.remove(4) # 从集合中删除元素4

s

{1, 2, 3}

 

注意: 集合的 pop() 操作是删除集合中最后一个元素,可是集合本身是无序的,你无法知道会删除哪个元素,因此这个操作得谨慎使用

 

对于字典,通常会根据键或值,进行升序或降序排序

 

d = {'b': 1, 'a': 2, 'c': 10}

d_sorted_by_key = sorted(d.items(), key=lambda x: x[0]) # 根据字典键的升序排序

d_sorted_by_value = sorted(d.items(), key=lambda x: x[1]) # 根据字典值的升序排序

d_sorted_by_key

[('a', 2), ('b', 1), ('c', 10)]

d_sorted_by_value

[('b', 1), ('a', 2), ('c', 10)]

 

集合,其排序和前面讲过的列表、元组很类似,直接调用 sorted(set) 即可

s = {3, 4, 2, 1}

sorted(s) # 对集合的元素进行升序排序

[1, 2, 3, 4]

 

字典和集合是进行过性能高度优化的数据结构,特别是对于查找、添加和删除操作

为了提高存储空间的利用率,现在的哈希表除了字典本身的结构,会把索引和哈希值、键、值单独分开

 

插入位置计算

每次向字典或集合插入一个元素时,Python 会首先计算键的哈希值(hash(key)),再和 mask = PyDicMinSize - 1 做与操作,计算这个元素应该插入哈希表的位置 index = hash(key) & mask。如果哈希表中此位置是空的,那么这个元素就会被插入其中。而如果此位置已被占用,Python 便会比较两个元素的哈希值和键是否相等。若两者都相等,则表明这个元素已经存在,如果值不同,则更新值。若两者中有一个不相等,这种情况我们通常称为哈希冲突(hash collision),意思是两个元素的键不相等,但是哈希值相等。这种情况下,Python 便会继续寻找表中空余的位置,直到找到位置为止。

 

元素查询

和前面的插入操作类似,Python 会根据哈希值,找到其应该处于的位置;然后,比较哈希表这个位置中元素的哈希值和键,与需要查找的元素是否相等。如果相等,则直接返回;如果不等,则继续查找,直到找到空位或者抛出异常为止。

 

删除元素

对于删除操作,Python 会暂时对这个位置的元素,赋于一个特殊的值,等到重新调整哈希表的大小时,再将其删除。不难理解,哈希冲突的发生,往往会降低字典和集合操作的速度。因此,为了保证其高效性,字典和集合内的哈希表,通常会保证其至少留有 1/3 的剩余空间。随着元素的不停插入,当剩余空间小于 1/3 时,Python 会重新获取更大的内存空间,扩充哈希表。不过,这种情况下,表内所有的元素位置都会被重新排放。虽然哈希冲突和哈希表大小的调整,都会导致速度减缓,但是这种情况发生的次数极少。所以,平均情况下,这仍能保证插入、查找和删除的时间复杂度为 O(1)。

5,字符串

字符串是由独立字符组成的一个序列,通常包含在单引号('')双引号("")或者三引号之中(''' '''或""" """,两者一样)

 

s1 = 'hello'

s2 = "hello"

s3 = """hello"""

s1 == s2 == s3

True

 

"I'm a student"

Python 的三引号字符串,则主要应用于多行字符串的情境,比如函数的注释等等

 

 

 

Python 也支持转义字符

 

 

字符串的常用操作

Python 的字符串同样支持索引,切片和遍历等等操作

Python 的字符串是不可变的(immutable)

 

s = 'hello'

s[0] = 'H'

Traceback (most recent call last):

  File "", line 1, in

TypeError: 'str' object does not support item assignment

 

Python 中字符串的改变,通常只能通过创建新的字符串来完成

 

s = 'H' + s[1:]

s = s.replace('h', 'H')

 

自从 Python2.5 开始,每次处理字符串的拼接操作时(str1 += str2),Python 首先会检测 str1 还有没有其他的引用。如果没有的话,就会尝试原地扩充字符串 buffer 的大小,而不是重新分配一块内存来创建新的字符串并拷贝。这样的话,上述例子中的时间复杂度就仅为 O(n)

 

使用字符串内置的 join 函数。string.join(iterable),表示把每个元素都按照指定的格式连接起来

 

string.split(separator) 表示把字符串按照 separator 分割成子字符串

string.strip(str),表示去掉首尾的 str 字符串;

string.lstrip(str),表示只去掉开头的 str 字符串;

string.rstrip(str),表示只去掉尾部的 str 字符串

string.find(sub, start, end),表示从 start 到 end 查找字符串中子字符串 sub 的位置

 

string.format(),就是所谓的格式化函数

print('no data available for person with id: {}, name: {}'.format(id, name))

 

在 Python 之前版本中,字符串格式化通常用 % 来表示

print('no data available for person with id: %s, name: %s' % (id, name))

 

拼接性能比较

s = ''

for n in range(0, 100000):

    s += str(n)

 

 

l = []

for n in range(0, 100000):

    l.append(str(n))

    

s = ' '.join(l)

 

如果字符串拼接的次数较少,比如range(100),那么方法一更优,因为时间复杂度精确的来说第一种是O(n),第二种是O(2n),如果拼接的次数较多,比如range(1000000),方法二稍快一些,虽然方法二会遍历两次,但是join的速度其实很快,列表append和join的开销要比字符串+=小一些

6,输入与输出

name = input('your name:')

gender = input('you are a boy?(y/n)')

 

###### 输入 ######

your name:Jack

you are a boy?

 

welcome_str = 'Welcome to the matrix {prefix} {name}.'

welcome_dic = {

    'prefix': 'Mr.' if gender == 'y' else 'Mrs',

    'name': name

}

 

print('authorizing...')

print(welcome_str.format(**welcome_dic))

 

########## 输出 ##########

authorizing...

Welcome to the matrix Mr. Jack.

 

 

a = input()

1

b = input()

2

 

print('a + b = {}'.format(a + b))

########## 输出 ##############

a + b = 12

print('type of a is {}, type of b is {}'.format(type(a), type(b)))

########## 输出 ##############

type of a is , type of b is

print('a + b = {}'.format(int(a) + int(b)))

########## 输出 ##############

a + b = 3

 

把 str 强制转换为 int 请用 int(),转为浮点数请用 float()。而在生产环境中使用强制转换时,请记得加上 try except

 

文件输入输出

 

with open('in.txt', 'r') as fin:    

text = fin.read()

word_and_freq = parse(text)

with open('out.txt', 'w') as fout:    

for word, freq in word_and_freq:        

fout.write('{} {}\n'.format(word, freq))

 

代码 text = fin.read() ,即表示把文件所有内容读取到内存中,并赋值给变量 text。

这么做自然也是有利有弊:

优点是方便,接下来我们可以很方便地调用 parse 函数进行分析;

缺点是如果文件过大,一次性读取可能造成内存崩溃。

这时,我们可以给 read 指定参数 size ,用来表示读取的最大长度。

还可以通过 readline() 函数,每次读取一行

 

在 with 的语境下任务执行完毕后,close() 函数会被自动调用

 

注意: 所有 I/O 都应该进行错误处理

 

 

 

JSON 序列化

 

import json

 

params = {

    'symbol': '123456',

    'type': 'limit',

    'price': 123.4,

    'amount': 23

}

 

params_str = json.dumps(params)

 

print('after json serialization')

print('type of params_str = {}, params_str = {}'.format(type(params_str), params))

 

original_params = json.loads(params_str)

 

print('after json deserialization')

print('type of original_params = {}, original_params = {}'.format(type(original_params), original_params))

 

########## 输出 ##########

 

after json serialization

type of params_str = , params_str = {'symbol': '123456', 'type': 'limit', 'price': 123.4, 'amount': 23}

after json deserialization

type of original_params = , original_params = {'symbol': '123456', 'type': 'limit', 'price': 123.4, 'amount': 23}

7,条件和循环

条件

if condition_1:

    statement_1

elif condition_2:

    statement_2

...

elif condition_i:

    statement_i

else:

statement_n

循环

 

 

 

l = [1, 2, 3, 4]

for item in l:

    print(item)

1

2

3

4

 

Python 中的数据结构只要是可迭代的(iterable),比如列表、集合等等,那么都可以通过下面这种方式遍历

 

for item in :

...

 

字典本身只有键是可迭代的,如果我们要遍历它的值或者是键值对,就需要通过其内置的函数 values() 或者 items() 实现。其中,values() 返回字典的值的集合,items() 返回键值对的集合

 

d = {'name': 'jason', 'dob': '2000-01-01', 'gender': 'male'}

for k in d: # 遍历字典的键

    print(k)

name

dob

gender

 

for v in d.values(): # 遍历字典的值

    print(v)

jason

2000-01-01

male    

 

for k, v in d.items(): # 遍历字典的键值对

    print('key: {}, value: {}'.format(k, v))

key: name, value: jason

key: dob, value: 2000-01-01

key: gender, value: male

 

 

 

 

使用索引遍历元素

 

l = [1, 2, 3, 4, 5, 6, 7]

for index in range(0, len(l)):

    if index < 5:

        print(l[index])        

        

1

2

3

4

5

 

通过 Python 内置的函数 enumerate()。用它来遍历集合,不仅返回每个元素,并且还返回其对应的索引

 

l = [1, 2, 3, 4, 5, 6, 7]

for index, item in enumerate(l):

    if index < 5:

        print(item)  

              

1

2

3

4

5

 

continue break

 

# name_price: 产品名称(str)到价格(int)的映射字典

# name_color: 产品名字(str)到颜色(list of str)的映射字典

for name, price in name_price.items():

    if price >= 1000:

        continue

    if name not in name_color:

        print('name: {}, color: {}'.format(name, 'None'))

        continue

    for color in name_color[name]:

        if color == 'red':

            continue

        print('name: {}, color: {}'.format(name, color))

 

 

 

通常来说,如果你只是遍历一个已知的集合,找出满足条件的元素,并进行相应的操作,那么使用 for 循环更加简洁。但如果你需要在满足某个条件前,不停地重复某些操作,并且没有特定的集合需要去遍历,那么一般则会使用 while 循环。

 

条件与循环的复用

expression1 if condition else expression2 for item in iterable

 

如果没有 else 语句

expression for item in iterable if condition

 

多个循环

[(xx, yy) for xx in x for yy in y if xx != yy]

等价于

 

l = []

for xx in x:

    for yy in y:

        if xx != yy:

            l.append((xx, yy))

8,异常

try:

    s = input('please enter two numbers separated by comma: ')

    num1 = int(s.split(',')[0].strip())

    num2 = int(s.split(',')[1].strip())

    ...

except ValueError as err:

    print('Value Error: {}'.format(err))

 

print('continue')

...

 

加入多种异常的类型

try:

    s = input('please enter two numbers separated by comma: ')

    num1 = int(s.split(',')[0].strip())

    num2 = int(s.split(',')[1].strip())

    ...

except (ValueError, IndexError) as err:

    print('Error: {}'.format(err))

    

print('continue')

...

 

 

try:

    s = input('please enter two numbers separated by comma: ')

    num1 = int(s.split(',')[0].strip())

    num2 = int(s.split(',')[1].strip())

    ...

except ValueError as err:

    print('Value Error: {}'.format(err))

except IndexError as err:

    print('Index Error: {}'.format(err))

 

print('continue')

...

 

我们很难保证程序覆盖所有的异常类型,所以,更通常的做法,是在最后一个 except block,声明其处理的异常类型是 Exception。Exception 是其他所有非系统异常的基类,能够匹配任意非系统异常

 

try:

    s = input('please enter two numbers separated by comma: ')

    num1 = int(s.split(',')[0].strip())

    num2 = int(s.split(',')[1].strip())

    ...

except ValueError as err:

    print('Value Error: {}'.format(err))

except IndexError as err:

    print('Index Error: {}'.format(err))

except Exception as err:

    print('Other error: {}'.format(err))

 

print('continue')

...

 

可以在 except 后面省略异常类型,这表示与任意异常相匹配(包括系统异常等)

 

 

try:

    s = input('please enter two numbers separated by comma: ')

    num1 = int(s.split(',')[0].strip())

    num2 = int(s.split(',')[1].strip())

    ...

except ValueError as err:

    print('Value Error: {}'.format(err))

except IndexError as err:

    print('Index Error: {}'.format(err))

except:

    print('Other error')

 

print('continue')

...

 

需要注意,当程序中存在多个 except block 时,最多只有一个 except block 会被执行。换句话说,如果多个 except 声明的异常类型都与实际相匹配,那么只有最前面的 except block 会被执行,其他则被忽略

 

异常处理中,还有一个很常见的用法是 finally,经常和 try、except 放在一起来用。无论发生什么情况,finally block 中的语句都会被执行,哪怕前面的 try 和 excep block 中使用了 return 语句

 

import sys

try:

    f = open('file.txt', 'r')

    .... # some data processing

except OSError as err:

    print('OS error: {}'.format(err))

except:

    print('Unexpected error:', sys.exc_info()[0])

finally:

f.close()

 

用户自定义异常

 

class MyInputError(Exception):

    """Exception raised when there're errors in input"""

    def __init__(self, value): # 自定义异常类型的初始化

        self.value = value

    def __str__(self): # 自定义异常类型的string表达形式

        return ("{} is invalid input".format(repr(self.value)))

    

try:

    raise MyInputError(1) # 抛出MyInputError这个异常

except MyInputError as err:

print('error: {}'.format(err))

 

对于流程控制的代码逻辑,我们一般不用异常处理

9,自定义函数

函数就是为了实现某一功能的代码段,只要写好以后,就可以重复利用

 

def my_func(message):

    print('Got a message: {}'.format(message))

 

# 调用函数 my_func()

my_func('Hello World')

# 输出

Got a message: Hello World

 

主程序调用函数时,必须保证这个函数此前已经定义过

 

my_func('hello world')

def my_func(message):

    print('Got a message: {}'.format(message))

    

# 输出

NameError: name 'my_func' is not defined

 

但是,如果我们在函数内部调用其他函数,函数间哪个声明在前、哪个在后就无所谓,因为 def 是可执行语句,函数在调用之前都不存在,我们只需保证调用时,所需的函数都已经声明定义

 

def my_func(message):

    my_sub_func(message) # 调用my_sub_func()在其声明之前不影响程序执行

    

def my_sub_func(message):

    print('Got a message: {}'.format(message))

 

my_func('hello world')

 

# 输出

Got a message: hello world

 

Python 函数的参数可以设定默认值

 

def func(param = 0):

...

 

参数类型是动态识别,不需要指定,但必要时请你在开头加上数据的类型检查

 

支持函数嵌套

1,函数的嵌套能够保证内部函数的隐私。内部函数只能被外部函数所调用和访问,不会暴露在全局作用域,因此,如果你的函数内部有一些隐私数据(比如数据库的用户、密码等),不想暴露在外,那你就可以使用函数的的嵌套,将其封装在内部函数中,只通过外部函数来访问

2,合理的使用函数嵌套,能够提高程序的运行效率

 

def factorial(input):

    # validation check

    if not isinstance(input, int):

        raise Exception('input must be an integer.')

    if input < 0:

        raise Exception('input must be greater or equal to 0' )

    ...

 

    def inner_factorial(input):

        if input <= 1:

            return 1

        return input * inner_factorial(input-1)

    return inner_factorial(input)

 

 

print(factorial(5))

 

函数变量作用域

如果变量是在函数内部定义的,就称为局部变量,只在函数内部有效

 

全局变量则是定义在整个文件层次上的

不能在函数内部随意改变全局变量的值

如果我们一定要在函数内部改变全局变量的值,就必须加上 global 这个声明

 

MIN_VALUE = 1

MAX_VALUE = 10

def validation_check(value):

    global MIN_VALUE

    ...

    MIN_VALUE += 1

    ...

validation_check(5)

 

 

 

对于嵌套函数来说,内部函数可以访问外部函数定义的变量,但是无法修改,若要修改,必须加上 nonlocal 这个关键字

 

def outer():

    x = "local"

    def inner():

        nonlocal x # nonlocal表示这里的x就是外部函数outer定义的变量x

        x = 'nonlocal'

        print("inner:", x)

    inner()

    print("outer:", x)

outer()

# 输出

inner: nonlocal

outer: nonlocal

 

如果不加上 nonlocal 这个关键字,而内部函数的变量又和外部函数变量同名,那么同样的,内部函数变量会覆盖外部函数的变量。

 

def outer():

    x = "local"

    def inner():

        x = 'nonlocal' # 这里的x是inner这个函数的局部变量

        print("inner:", x)

    inner()

    print("outer:", x)

outer()

# 输出

inner: nonlocal

outer: local

 

闭包

 

def nth_power(exponent):

    def exponent_of(base):

        return base ** exponent

    return exponent_of # 返回值是exponent_of函数

 

square = nth_power(2) # 计算一个数的平方

cube = nth_power(3) # 计算一个数的立方

square

# 输出

.exponent(base)>

 

cube

# 输出

.exponent(base)>

 

print(square(2))  # 计算2的平方

print(cube(2)) # 计算2的立方

# 输出

4 # 2^2

8 # 2^3

闭包常常和装饰器(decorator)一起使用

10,匿名函数

square = lambda x: x**2

square(3)

 

9

 

lambda 是一个表达式(expression),并不是一个语句(statement)

lambda 可以用在列表内部,而常规函数却不能

lambda 可以被用作某些函数的参数,而常规函数 def 也不能

lambda 的主体是只有一行的简单表达式,并不能扩展成一个多行的代码块

 

函数式编程

所谓函数式编程,是指代码中每一块都是不可变的(immutable),都由纯函数(pure function)的形式组成。这里的纯函数,是指函数本身相互独立、互不影响,对于相同的输入,总会有相同的输出,没有任何副作用

重新创建一个新的列表并返回

 

Python 主要提供了这么几个函数:map()、filter() 和 reduce(),通常结合匿名函数 lambda 一起使用

 

map

对 iterable 中的每个元素,都运用 function 这个函数,最后返回一个新的可遍历的集合

 

reduce

用来对一个集合做一些累积操作

 

 

 

11,面向对象

 

class Document():

    def __init__(self, title, author, context):

        print('init function called')

        self.title = title

        self.author = author

        self.__context = context # __开头的属性是私有属性

 

    def get_context_length(self):

        return len(self.__context)

 

    def intercept_context(self, length):

        self.__context = self.__context[:length]

 

harry_potter_book = Document('Harry Potter', 'J. K. Rowling', '... Forever Do not believe any thing is capable of thinking independently ...')

 

print(harry_potter_book.title)

print(harry_potter_book.author)

print(harry_potter_book.get_context_length())

 

harry_potter_book.intercept_context(10)

 

print(harry_potter_book.get_context_length())

 

print(harry_potter_book.__context)

 

########## 输出 ##########

 

init function called

Harry Potter

J. K. Rowling

77

10

 

---------------------------------------------------------------------------

AttributeError                            Traceback (most recent call last)

in ()

     22 print(harry_potter_book.get_context_length())

     23

---> 24 print(harry_potter_book.__context)

 

AttributeError: 'Document' object has no attribute '__context'

 

类:一群有着相似性的事物的集合,这里对应 Python 的 class。

对象:集合中的一个事物,这里对应由 class 生成的某一个 object,比如代码中的 harry_potter_book。

属性:对象的某个静态特征,比如上述代码中的 title、author 和 __context。

函数:对象的某个动态能力,比如上述代码中的 intercept_context () 函数

 

 

class Document():

    

    WELCOME_STR = 'Welcome! The context for this book is {}.'

    

    def __init__(self, title, author, context):

        print('init function called')

        self.title = title

        self.author = author

        self.__context = context

    

    # 类函数

    @classmethod

    def create_empty_book(cls, title, author):

        return cls(title=title, author=author, context='nothing')

    

    # 成员函数

    def get_context_length(self):

        return len(self.__context)

    

    # 静态函数

    @staticmethod

    def get_welcome(context):

        return Document.WELCOME_STR.format(context)

 

 

empty_book = Document.create_empty_book('What Every Man Thinks About Apart from Sex', 'Professor Sheridan Simove')

 

 

print(empty_book.get_context_length())

print(empty_book.get_welcome('indeed nothing'))

 

########## 输出 ##########

 

init function called

7

Welcome! The context for this book is indeed nothing.

 

类函数、成员函数和静态函数三个概念

前两者产生的影响是动态的,能够访问或者修改对象的属性;而静态函数则与类没有什么关联,最明显的特征便是,静态函数的第一个参数没有任何特殊性

 

继承

 

class Entity():

    def __init__(self, object_type):

        print('parent class init called')

        self.object_type = object_type

    

    def get_context_length(self):

        raise Exception('get_context_length not implemented')

    

    def print_title(self):

        print(self.title)

 

class Document(Entity):

    def __init__(self, title, author, context):

        print('Document class init called')

        Entity.__init__(self, 'document')

        self.title = title

        self.author = author

        self.__context = context

    

    def get_context_length(self):

        return len(self.__context)

    

class Video(Entity):

    def __init__(self, title, author, video_length):

        print('Video class init called')

        Entity.__init__(self, 'video')

        self.title = title

        self.author = author

        self.__video_length = video_length

    

    def get_context_length(self):

        return self.__video_length

 

harry_potter_book = Document('Harry Potter(Book)', 'J. K. Rowling', '... Forever Do not believe any thing is capable of thinking independently ...')

harry_potter_movie = Video('Harry Potter(Movie)', 'J. K. Rowling', 120)

 

print(harry_potter_book.object_type)

print(harry_potter_movie.object_type)

 

harry_potter_book.print_title()

harry_potter_movie.print_title()

 

print(harry_potter_book.get_context_length())

print(harry_potter_movie.get_context_length())

 

########## 输出 ##########

 

Document class init called

parent class init called

Video class init called

parent class init called

document

video

Harry Potter(Book)

Harry Potter(Movie)

77

120

 

每个类都有构造函数,继承类在生成对象的时候,是不会自动调用父类的构造函数的,因此你必须在 init() 函数中显式调用父类的构造函数。它们的执行顺序是 子类的构造函数 -> 父类的构造函数

 

抽象函数和抽象类

 

from abc import ABCMeta, abstractmethod

 

class Entity(metaclass=ABCMeta):

    @abstractmethod

    def get_title(self):

        pass

 

    @abstractmethod

    def set_title(self, title):

        pass

 

class Document(Entity):

    def get_title(self):

        return self.title

    

    def set_title(self, title):

        self.title = title

 

document = Document()

document.set_title('Harry Potter')

print(document.get_title())

 

entity = Entity()

 

########## 输出 ##########

 

Harry Potter

 

---------------------------------------------------------------------------

TypeError                                 Traceback (most recent call last)

in ()

     21 print(document.get_title())

     22

---> 23 entity = Entity()

     24 entity.set_title('Test')

 

TypeError: Can't instantiate abstract class Entity with abstract methods get_title, set_title

 

菱形继承

 

class A():
    def __init__(self):
        print('A class called')

class B(A):
    def __init__(self):
        print('B class called')
        A.__init__(self)
class C(A):
    def __init__(self):
        print('C class called')
        A.__init__(self)
class D(B,C):
    def __init__(self):
        print('D class called')
        B.__init__(self)
        C.__init__(self)
d = D()
####输出
D class called
B class called
A class called
C class called
A class called

 

使用 super 来召唤父类的构造函数,而且 python 使用一种叫做方法解析顺序的算法(具体实现算法叫做 C3),来保证一个类只会被初始化一次

 

class A():
    def __init__(self):
        print('enter A')
        print('leave A')

class B(A):
    def __init__(self):
        print('enter B')
        super().__init__()
        print('leave B')

class C(A):
    def __init__(self):
        print('enter C')
        super().__init__()
        print('leave C')

class D(B, C):
    def __init__(self):
        print('enter D')
        super().__init__()
        print('leave D')

D()

enter D
enter B
enter C
enter A
leave A
leave C
leave B
leave D

12,对象比较,拷贝

比较

'=='操作符比较对象之间的值是否相等

'is'操作符比较的是对象的身份标识是否相等,即它们是否是同一个对象,是否指向同一个内存地址

Id(aaa)

对于整型数字来说,以上a is b为 True 的结论,只适用于 -5 到 256 范围内的数字

比较一个变量与一个单例(singleton)时,通常会使用'is'

比较操作符'is'的速度效率,通常要优于'=='

执行a == b相当于是去执行a.__eq__(b),大部分的数据类型都会去重载__eq__这个函数

 

常见的浅拷贝的方法,是使用数据类型本身的构造器

 

l1 = [1, 2, 3]

l2 = list(l1)

 

l2

[1, 2, 3]

 

l1 == l2

True

 

l1 is l2

False

 

s1 = set([1, 2, 3])

s2 = set(s1)

 

s2

{1, 2, 3}

 

s1 == s2

True

 

s1 is s2

False

 

对于可变的序列,我们还可以通过切片操作符':'完成浅拷贝

 

l1 = [1, 2, 3]

l2 = l1[:]

 

l1 == l2

True

 

l1 is l2

False

 

Python 中也提供了相对应的函数 copy.copy(),适用于任何数据类型

 

import copy

l1 = [1, 2, 3]

l2 = copy.copy(l1)

 

对于元组,使用 tuple() 或者切片操作符':'不会创建一份浅拷贝,相反,它会返回一个指向相同元组的引用

 

t1 = (1, 2, 3)

t2 = tuple(t1)

t1 == t2

Truet

1 is t2

True

 

浅拷贝,是指重新分配一块内存,创建一个新的对象,里面的元素是原对象中子对象的引用。因此,如果原对象中的元素不可变,那倒无所谓;但如果元素可变,浅拷贝通常会带来一些副作用,尤其需要注意

 

 

l1 = [[1, 2], (30, 40)]

l2 = list(l1)

l1.append(100)

l1[0].append(3)

 

l1

[[1, 2, 3], (30, 40), 100]

 

l2

[[1, 2, 3], (30, 40)]

 

l1[1] += (50, 60)

l1

[[1, 2, 3], (30, 40, 50, 60), 100]

 

l2

[[1, 2, 3], (30, 40)]

 

Python 中以 copy.deepcopy() 来实现对象的深度拷贝

 

 

import copy

 

l1 = [[1, 2], (30, 40)]

l2 = copy.deepcopy(l1)

l1.append(100)

l1[0].append(3)

 

l1

[[1, 2, 3], (30, 40), 100]

 

l2

[[1, 2], (30, 40)]

 

如果被拷贝对象中存在指向自身的引用,那么程序很容易陷入无限循环

 

 

import copy

x = [1]

x.append(x)

 

x

[1, [...]]

 

y = copy.deepcopy(x)

y

[1, [...]]

 

上面这个例子,列表 x 中有指向自身的引用,因此 x 是一个无限嵌套的列表。

但是我们发现深度拷贝 x 到 y 后,程序并没有出现 stack overflow 的现象。这是为什么呢?其实,

这是因为深度拷贝函数 deepcopy 中会维护一个字典,记录已经拷贝的对象与其 ID。

拷贝过程中,如果字典里已经存储了将要拷贝的对象,则会从字典直接返回

相对应的源码

 

 

def deepcopy(x, memo=None, _nil=[]):

    """Deep copy operation on arbitrary Python objects.

 

  See the module's __doc__ string for more info.

  """

 

    if memo is None:

        memo = {}

    d = id(x) # 查询被拷贝对象x的id

y = memo.get(d, _nil) # 查询字典里是否已经存储了该对象

if y is not _nil:

    return y # 如果字典里已经存储了将要拷贝的对象,则直接返回

    ...

13,参数传递

Python 的参数传递是赋值传递 (pass by assignment),或者叫作对象的引用传递(pass by object reference)。Python 里所有的数据类型都是对象,所以参数传递时,只是让新变量与原变量指向相同的对象而已,并不存在值传递或是引用传递一说

要改变值在函数里面返回即可

14,装饰器

 

def my_decorator(func):

    def wrapper():

        print('wrapper of decorator')

        func()

    return wrapper

 

@my_decorator

def greet():

    print('hello world')

 

greet()

 

*args和**kwargs,表示接受任意数量和类型的参数

def my_decorator(func):

    def wrapper(*args, **kwargs):

        print('wrapper of decorator')

        func(*args, **kwargs)

return wrapper

 

 

 

 

 

自定义参数的装饰器

 

def repeat(num):

    def my_decorator(func):

        def wrapper(*args, **kwargs):

            for i in range(num):

                print('wrapper of decorator')

                func(*args, **kwargs)

        return wrapper

    return my_decorator

 

 

@repeat(4)

def greet(message):

    print(message)

 

greet('hello world')

 

# 输出:

wrapper of decorator

hello world

wrapper of decorator

hello world

wrapper of decorator

hello world

wrapper of decorator

hello world

 

使用内置的装饰器@functools.wrap(func),它会帮助保留原函数的元信息(也就是将原函数的元信息,拷贝到对应的装饰器函数里)

 

类装饰器主要依赖于函数__call__(),每当你调用一个类的示例时,函数__call__()就会被执行一次

 

 

class Count:

    def __init__(self, func):

        self.func = func

        self.num_calls = 0

 

    def __call__(self, *args, **kwargs):

        self.num_calls += 1

        print('num of calls is: {}'.format(self.num_calls))

        return self.func(*args, **kwargs)

 

@Count

def example():

    print("hello world")

 

example()

 

# 输出

num of calls is: 1

hello world

 

example()

 

# 输出

num of calls is: 2

hello world

 

...

 

装饰器嵌套

 

@decorator1

@decorator2

@decorator3

def func():

...

等效于

decorator1(decorator2(decorator3(func)))

 

装饰器用法实例

身份认证

日志记录

输入合理性检查

缓存

15,metaclass 的超越变形特性

所有的 Python 的用户定义类,都是 type 这个类的实例

用户自定义类,只不过是 type 类的__call__运算符重载

metaclass 是 type 的子类,通过替换 type 的__call__运算符重载机制,“超越变形”正常的类

 

 

 

class Mymeta(type):
    def __init__(self, name, bases, dic):
        super().__init__(name, bases, dic)
        print('===>Mymeta.__init__')
        print(self.__name__)
        print(dic)
        print(self.yaml_tag)

    def __new__(cls, *args, **kwargs):
        print('===>Mymeta.__new__')
        print(cls.__name__)
        return type.__new__(cls, *args, **kwargs)

    def __call__(cls, *args, **kwargs):
        print('===>Mymeta.__call__')
        obj = cls.__new__(cls)
        cls.__init__(cls, *args, **kwargs)
        return obj
    
class Foo(metaclass=Mymeta):
    yaml_tag = '!Foo'

    def __init__(self, name):
        print('Foo.__init__')
        self.name = name

    def __new__(cls, *args, **kwargs):
        print('Foo.__new__')
        return object.__new__(cls)

foo = Foo('foo')
把上面的例子运行完之后就会明白很多了,正常情况下我们在父类中是不能对子类的属性进行操作,但是元类可以。换种方式理解:元类、装饰器、类装饰器都可以归为元编程(引用自 python-cook-book 中的一句话)。

 

16,迭代器和生成器

所有的容器都是可迭代的(iterable)enumerate

生成器在 Python 的写法是用小括号括起来,(i for i in range(100000000)),即初始化了一个生成器。

 

 

(i in b)

等价于

while True:

    val = next(b)

    if val == i:

        yield True

 

b = (i for i in range(5))

 

print(2 in b)

print(4 in b)

print(3 in b)

 

########## 输出 ##########

 

True

True

False

 

17,协程

 

import asyncio

 

async def crawl_page(url):

    print('crawling {}'.format(url))

    sleep_time = int(url.split('_')[-1])

    await asyncio.sleep(sleep_time)

    print('OK {}'.format(url))

 

async def main(urls):

    for url in urls:

        await crawl_page(url)

 

%time asyncio.run(main(['url_1', 'url_2', 'url_3', 'url_4']))

 

########## 输出 ##########

 

crawling url_1

OK url_1

crawling url_2

OK url_2

crawling url_3

OK url_3

crawling url_4

OK url_4

Wall time: 10 s

 

使用task才行

import asyncio

 

async def crawl_page(url):

    print('crawling {}'.format(url))

    sleep_time = int(url.split('_')[-1])

    await asyncio.sleep(sleep_time)

    print('OK {}'.format(url))

 

async def main(urls):

    tasks = [asyncio.create_task(crawl_page(url)) for url in urls]

    for task in tasks:

        await task

 

%time asyncio.run(main(['url_1', 'url_2', 'url_3', 'url_4']))

 

########## 输出 ##########

 

crawling url_1

crawling url_2

crawling url_3

crawling url_4

OK url_1

OK url_2

OK url_3

OK url_4

Wall time: 3.99 s

 

import asyncio

 

async def crawl_page(url):

    print('crawling {}'.format(url))

    sleep_time = int(url.split('_')[-1])

    await asyncio.sleep(sleep_time)

    print('OK {}'.format(url))

 

async def main(urls):

    tasks = [asyncio.create_task(crawl_page(url)) for url in urls]

    await asyncio.gather(*tasks)

 

%time asyncio.run(main(['url_1', 'url_2', 'url_3', 'url_4']))

 

########## 输出 ##########

 

crawling url_1

crawling url_2

crawling url_3

crawling url_4

OK url_1

OK url_2

OK url_3

OK url_4

Wall time: 4.01 s

 

*tasks 解包列表,将列表变成了函数的参数  

** dict 将字典变成了函数的参数

 

 

 

import asyncio

 

async def worker_1():

    print('worker_1 start')

    await asyncio.sleep(1)

    print('worker_1 done')

 

async def worker_2():

    print('worker_2 start')

    await asyncio.sleep(2)

    print('worker_2 done')

 

async def main():

    task1 = asyncio.create_task(worker_1())

    task2 = asyncio.create_task(worker_2())

    print('before await')

    await task1

    print('awaited worker_1')

    await task2

    print('awaited worker_2')

 

%time asyncio.run(main())

 

########## 输出 ##########

 

before await

worker_1 start

worker_2 start

worker_1 done

awaited worker_1

worker_2 done

awaited worker_2

Wall time: 2.01 s

 

asyncio.run(main()),程序进入 main() 函数,事件循环开启;

task1 和 task2 任务被创建,并进入事件循环等待运行;运行到 print,输出 'before await';

await task1 执行,用户选择从当前的主任务中切出,事件调度器开始调度 worker_1;

worker_1 开始运行,运行 print 输出'worker_1 start',然后运行到 await asyncio.sleep(1), 从当前任务切出,事件调度器开始调度

worker_2;worker_2 开始运行,运行 print 输出 'worker_2 start',然后运行 await asyncio.sleep(2) 从当前任务切出;

以上所有事件的运行时间,都应该在 1ms 到 10ms 之间,甚至可能更短,事件调度器从这个时候开始暂停调度;

一秒钟后,worker_1 的 sleep 完成,事件调度器将控制权重新传给 task_1,输出 'worker_1 done',task_1 完成任务,从事件循环中退出;

await task1 完成,事件调度器将控制器传给主任务,输出 'awaited worker_1',·然后在 await task2 处继续等待;

两秒钟后,worker_2 的 sleep 完成,事件调度器将控制权重新传给 task_2,输出 'worker_2 done',task_2 完成任务,从事件循环中退出;

主任务输出 'awaited worker_2',协程全任务结束,事件循环结束。

 

import asyncio

 

async def worker_1():

    await asyncio.sleep(1)

    return 1

 

async def worker_2():

    await asyncio.sleep(2)

    return 2 / 0

 

async def worker_3():

    await asyncio.sleep(3)

    return 3

 

async def main():

    task_1 = asyncio.create_task(worker_1())

    task_2 = asyncio.create_task(worker_2())

    task_3 = asyncio.create_task(worker_3())

 

    await asyncio.sleep(2)

    task_3.cancel()

 

    res = await asyncio.gather(task_1, task_2, task_3, return_exceptions=True)

    print(res)

 

%time asyncio.run(main())

 

########## 输出 ##########

 

[1, ZeroDivisionError('division by zero'), CancelledError()]

Wall time: 2 s

 

 

18,Futures

并发和并行

 

并发是同步

并行是同时

 

多线程

import concurrent.futures

import requests

import threading

import time

 

def download_one(url):

    resp = requests.get(url)

    print('Read {} from {}'.format(len(resp.content), url))

 

 

def download_all(sites):

    with concurrent.futures.ThreadPoolExecutor(max_workers=5) as executor:

        executor.map(download_one, sites)

 

def main():

    sites = [

        'https://en.wikipedia.org/wiki/Portal:Arts',

        'https://en.wikipedia.org/wiki/Portal:History',

        'https://en.wikipedia.org/wiki/Portal:Society',

        'https://en.wikipedia.org/wiki/Portal:Biography',

        'https://en.wikipedia.org/wiki/Portal:Mathematics',

        'https://en.wikipedia.org/wiki/Portal:Technology',

        'https://en.wikipedia.org/wiki/Portal:Geography',

        'https://en.wikipedia.org/wiki/Portal:Science',

        'https://en.wikipedia.org/wiki/Computer_science',

        'https://en.wikipedia.org/wiki/Python_(programming_language)',

        'https://en.wikipedia.org/wiki/Java_(programming_language)',

        'https://en.wikipedia.org/wiki/PHP',

        'https://en.wikipedia.org/wiki/Node.js',

        'https://en.wikipedia.org/wiki/The_C_Programming_Language',

        'https://en.wikipedia.org/wiki/Go_(programming_language)'

    ]

    start_time = time.perf_counter()

    download_all(sites)

    end_time = time.perf_counter()

    print('Download {} sites in {} seconds'.format(len(sites), end_time - start_time))

 

if __name__ == '__main__':

    main()

 

## 输出

Read 151021 from https://en.wikipedia.org/wiki/Portal:Mathematics

Read 129886 from https://en.wikipedia.org/wiki/Portal:Arts

Read 107637 from https://en.wikipedia.org/wiki/Portal:Biography

Read 224118 from https://en.wikipedia.org/wiki/Portal:Society

Read 184343 from https://en.wikipedia.org/wiki/Portal:History

Read 167923 from https://en.wikipedia.org/wiki/Portal:Geography

Read 157811 from https://en.wikipedia.org/wiki/Portal:Technology

Read 91533 from https://en.wikipedia.org/wiki/Portal:Science

Read 321352 from https://en.wikipedia.org/wiki/Computer_science

Read 391905 from https://en.wikipedia.org/wiki/Python_(programming_language)

Read 180298 from https://en.wikipedia.org/wiki/Node.js

Read 56765 from https://en.wikipedia.org/wiki/The_C_Programming_Language

Read 468461 from https://en.wikipedia.org/wiki/PHP

Read 321417 from https://en.wikipedia.org/wiki/Java_(programming_language)

Read 324039 from https://en.wikipedia.org/wiki/Go_(programming_language)

Download 15 sites in 0.19936635800002023 seconds

 

 

并行的方式一般用在 CPU heavy 的场景中,因为对于 I/O heavy 的操作,多数时间都会用于等待,相比于多线程,使用多进程并不会提升效率。反而很多时候,因为 CPU 数量的限制,会导致其执行效率不如多线程版本

 

Futures

import concurrent.futures

import requests

import time

 

def download_one(url):

    resp = requests.get(url)

    print('Read {} from {}'.format(len(resp.content), url))

 

def download_all(sites):

    with concurrent.futures.ThreadPoolExecutor(max_workers=5) as executor:

        to_do = []

        for site in sites:

            future = executor.submit(download_one, site)

            to_do.append(future)

            

        for future in concurrent.futures.as_completed(to_do):

            future.result()

def main():

    sites = [

        'https://en.wikipedia.org/wiki/Portal:Arts',

        'https://en.wikipedia.org/wiki/Portal:History',

        'https://en.wikipedia.org/wiki/Portal:Society',

        'https://en.wikipedia.org/wiki/Portal:Biography',

        'https://en.wikipedia.org/wiki/Portal:Mathematics',

        'https://en.wikipedia.org/wiki/Portal:Technology',

        'https://en.wikipedia.org/wiki/Portal:Geography',

        'https://en.wikipedia.org/wiki/Portal:Science',

        'https://en.wikipedia.org/wiki/Computer_science',

        'https://en.wikipedia.org/wiki/Python_(programming_language)',

        'https://en.wikipedia.org/wiki/Java_(programming_language)',

        'https://en.wikipedia.org/wiki/PHP',

        'https://en.wikipedia.org/wiki/Node.js',

        'https://en.wikipedia.org/wiki/The_C_Programming_Language',

        'https://en.wikipedia.org/wiki/Go_(programming_language)'

    ]

    start_time = time.perf_counter()

    download_all(sites)

    end_time = time.perf_counter()

    print('Download {} sites in {} seconds'.format(len(sites), end_time - start_time))

 

if __name__ == '__main__':

    main()

 

# 输出

Read 129886 from https://en.wikipedia.org/wiki/Portal:Arts

Read 107634 from https://en.wikipedia.org/wiki/Portal:Biography

Read 224118 from https://en.wikipedia.org/wiki/Portal:Society

Read 158984 from https://en.wikipedia.org/wiki/Portal:Mathematics

Read 184343 from https://en.wikipedia.org/wiki/Portal:History

Read 157949 from https://en.wikipedia.org/wiki/Portal:Technology

Read 167923 from https://en.wikipedia.org/wiki/Portal:Geography

Read 94228 from https://en.wikipedia.org/wiki/Portal:Science

Read 391905 from https://en.wikipedia.org/wiki/Python_(programming_language)

Read 321352 from https://en.wikipedia.org/wiki/Computer_science

Read 180298 from https://en.wikipedia.org/wiki/Node.js

Read 321417 from https://en.wikipedia.org/wiki/Java_(programming_language)

Read 468421 from https://en.wikipedia.org/wiki/PHP

Read 56765 from https://en.wikipedia.org/wiki/The_C_Programming_Language

Read 324039 from https://en.wikipedia.org/wiki/Go_(programming_language)

Download 15 sites in 0.21698231499976828 seconds

 

Python 的解释器并不是线程安全的,为了解决由此带来的 race condition 等问题,Python 便引入了全局解释器锁,也就是同一时刻,只允许一个线程执行。当然,在执行 I/O 操作时,如果一个线程被 block 了,全局解释器锁便会被释放,从而让另一个线程能够继续执行

 

19,asyncio

io密集型,使用异步处理

 

import asyncio

import aiohttp

import time

 

async def download_one(url):

    async with aiohttp.ClientSession() as session:

        async with session.get(url) as resp:

            print('Read {} from {}'.format(resp.content_length, url))

 

async def download_all(sites):

    tasks = [asyncio.create_task(download_one(site)) for site in sites]

    await asyncio.gather(*tasks)

 

def main():

    sites = [

        'https://en.wikipedia.org/wiki/Portal:Arts',

        'https://en.wikipedia.org/wiki/Portal:History',

        'https://en.wikipedia.org/wiki/Portal:Society',

        'https://en.wikipedia.org/wiki/Portal:Biography',

        'https://en.wikipedia.org/wiki/Portal:Mathematics',

        'https://en.wikipedia.org/wiki/Portal:Technology',

        'https://en.wikipedia.org/wiki/Portal:Geography',

        'https://en.wikipedia.org/wiki/Portal:Science',

        'https://en.wikipedia.org/wiki/Computer_science',

        'https://en.wikipedia.org/wiki/Python_(programming_language)',

        'https://en.wikipedia.org/wiki/Java_(programming_language)',

        'https://en.wikipedia.org/wiki/PHP',

        'https://en.wikipedia.org/wiki/Node.js',

        'https://en.wikipedia.org/wiki/The_C_Programming_Language',

        'https://en.wikipedia.org/wiki/Go_(programming_language)'

    ]

    start_time = time.perf_counter()

    asyncio.run(download_all(sites))

    end_time = time.perf_counter()

    print('Download {} sites in {} seconds'.format(len(sites), end_time - start_time))

    

if __name__ == '__main__':

    main()

 

## 输出

Read 63153 from https://en.wikipedia.org/wiki/Java_(programming_language)

Read 31461 from https://en.wikipedia.org/wiki/Portal:Society

Read 23965 from https://en.wikipedia.org/wiki/Portal:Biography

Read 36312 from https://en.wikipedia.org/wiki/Portal:History

Read 25203 from https://en.wikipedia.org/wiki/Portal:Arts

Read 15160 from https://en.wikipedia.org/wiki/The_C_Programming_Language

Read 28749 from https://en.wikipedia.org/wiki/Portal:Mathematics

Read 29587 from https://en.wikipedia.org/wiki/Portal:Technology

Read 79318 from https://en.wikipedia.org/wiki/PHP

Read 30298 from https://en.wikipedia.org/wiki/Portal:Geography

Read 73914 from https://en.wikipedia.org/wiki/Python_(programming_language)

Read 62218 from https://en.wikipedia.org/wiki/Go_(programming_language)

Read 22318 from https://en.wikipedia.org/wiki/Portal:Science

Read 36800 from https://en.wikipedia.org/wiki/Node.js

Read 67028 from https://en.wikipedia.org/wiki/Computer_science

Download 15 sites in 0.062144195078872144 seconds

3.7版本之前

asyncio.run()

等价于

loop = asyncio.get_event_loop()

try:

    loop.run_until_complete(coro)

finally:

loop.close()

 

asyncio.create_task(coro)

等价于

asyncio.ensure_future(coro)

 

requests 库并不兼容 Asyncio,但是 aiohttp 库兼容

 

如果你需要 await 一系列的操作,就得使用 asyncio.gather();如果只是单个的 future,或许只用 asyncio.wait()

 

场景

如果是 I/O bound,并且 I/O 操作很慢,需要很多任务 / 线程协同实现,那么使用 Asyncio 更合适。

如果是 I/O bound,但是 I/O 操作很快,只需要有限数量的任务 / 线程,那么使用多线程就可以了。

如果是 CPU bound,则需要使用多进程来提高程序运行效率。

 

20,GIL全局解释锁

CPython 使用引用计数来管理内存,所有 Python 脚本中创建的实例,都会有一个引用计数,来记录有多少个指针指向它

如果有两个 Python 线程同时引用了 a,就会造成引用计数的 race condition,引用计数可能最终只增加 1,这样就会造成内存被污染。因为第一个线程结束时,会把引用计数减少 1,这时可能达到条件释放内存,当第二个线程再试图访问 a 时,就找不到有效的内存了

 

Thread 1、2、3 轮流执行,每一个线程在开始执行时,都会锁住 GIL,以阻止别的线程执行;同样的,每一个线程执行完一段后,会释放 GIL,以允许别的线程开始利用资源

 

CPython 中还有另一个机制,叫做 check_interval,意思是 CPython 解释器会去轮询检查线程 GIL 的锁住情况。每隔一段时间,Python 解释器就会强制当前线程去释放 GIL,这样别的线程才能有执行的机会

早期的 Python 是 100 个 ticks,而 Python 3 以后,interval 是 15 毫秒

 

绕过 GIL

绕过 CPython,使用 JPython(Java 实现的 Python 解释器)等别的实现;把关键性能代码,放到别的语言(一般是 C++)中实现。

 

21,gc

计数引用

循环引用

分代收集算法,则是另一个优化手段

Python 将所有对象分为三代。刚刚创立的对象是第 0 代;经过一次垃圾回收后,依然存在的对象,便会依次从上一代挪到下一代。而每一代启动自动垃圾回收的阈值,则是可以单独指定的。当垃圾回收器中新增对象减去删除对象达到相应的阈值时,就会对这一代对象启动垃圾回收。事实上,分代收集基于的思想是,新生的对象更有可能被垃圾回收,而存活更久的对象也有更高的概率继续存活。因此,通过这种做法,可以节约不少计算量,从而提高 Python 的性能。

 

调试内存泄漏 objgraph ---> show_backrefs()

 

22,其他

GIL 的存在与 Python 支持多线程并不矛盾。前面我们讲过,GIL 是指同一时刻,程序只能有一个线程运行;而 Python 中的多线程,是指多个线程交替执行,造成一个“伪并行”的结果,但是具体到某一时刻,仍然只有 1 个线程在运行,并不是真正的多线程并行

你可能感兴趣的:(PYTHON,python)