python进阶

魔法方法

__init__ 和 __new__

  • __init__通常用于初始化一个新实例, 对这个实例添加一些属性, 做一些额外的工作, 属于对象方法
  • __new__通常用于控制生成一个新的实例, 属于类方法
  • __init__在实例生成后生效, __new__控制实例生成, 因此__init____new__后执行

__iter__和__next__

通常要创建一个可迭代的对象(可迭代的类对象),需要定义__iter__和__next__对象,iter获取迭代器,next获取迭代器对象中值,若对象中的元素为空时,引出StopIteration错误

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
class Counter:
def __init__(self, num):
self.num = num
self.index = 0
def __iter__(self):
return self
def __next__(self):
self.index += 1
while self.index <= self.num:
if is_prime(self.index):
return self.index
self.index += 1
raise StopIteration

def is_prime(item):
if item in (0, 1):
return False
for i in range(2, int(item ** 0.5) + 1):
if item % i == 0:
return False
return True

counter = Counter(50)
print(list(counter))
# [2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37, 41, 43, 47]

__enter__和__exit__

通常要创建一个上下文管理器对象,就需要定义__iter__和__exit__两个魔法方法,其中前者用于打开对象时执行的功能,后者用于关闭对象后执行的功能。

如果在执行 with 语句的语句体期间发生了异常,则参数会包含异常的类型、值以及回溯信息。 在其他情况下三个参数均为 None

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
import time

class TimeThis:
def __init__(self, label):
self.label = label

def __enter__(self):
self.start = time.time()
time.sleep(1)

def __exit__(self, exc_type, exc_value, traceback):
end = time.time()
print(f'{self.label}waste time {str(end-self.start)}')
return self.label

with TimeThis('波斯猫') as tt:
print('func()')

'''
func()
波斯猫waste time 1.0010571479797363
'''

__hash__和__eq__

对于两个类,是不能直接使用比较运算的;而为了达到这个目的,根据__hash__和__eq__两个魔法方法可以实现类对象之间的比较

集合类型和字典类型其底层实现原理就是根据__hash__和__eq__去判断元素是否相同或不同的元素

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
class Person:

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

def __hash__(self, other):
return hash((self.name, self.age)) == hash(other.name, other.age)

def __eq__(self, other):
return self.name == other.name and self.age == other.age

p1 = Person('王安石', 40)
p2 = Person('王安石', 40)
p3 = Person('王维'50)
p1 == p2, p1 == p1, p1 == p3

# (True, True, False)

__call__

在Python中,函数是一等的对象,可以传递到函数和方法中。如果要使一个类的对象也表现跟函数一样的特点,可以使用__call__方法去实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
class Entity:
'''表示一个实体的类,调用它的实例可以更新实体的位置'''
def __init__(self,size,x,y):
self.x, self.y = x, y
self.size = size
def __call__(self,x,y):
'''改变实体的位置'''
self.x, self.y = x, y
entity = Entity(20, 4, 5)
print(entity.x, entity.y)
entity(2, 10)
print(entity.x, entity.y)

# 4 5
# 2 10

运算

__eq__ __ne__ __lt __gt__ __le__ __ge__

单例

单例指的是对一个单一的类, 它负责创建自己的对象, 同时确保单个对象只有一个实例。

注意:

  1. 单例类只能有一个实例
  2. 单例类必须自己创建自己的唯一实例
  3. 单例类必须给所有其他对象提供这个实例

单例的实现方式很多,但是使用类的装饰器和元类构建装饰器是最容易被使用的方式

装饰器实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
from functools import wraps

def singleton(cls):
instances = {}

@wraps(cls)
def wrapper(*args, **kwargs):
if cls not in instances:
instances[cls] = cls(*args, **kwargs)
return instances[cls]

return wrapper


@singleton
class TestSingleton:

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

def __str__(self):
return self.name

a = TestSingleton('实例a')
b = TestSingleton('实例b')
c = TestSingleton.__wrapped__('实例c')
d = TestSingleton.__wrapped__('实例d')
print(a,b,c,d) # 实例a 实例a 实例c 实例d

以上的内容只能保证在单线程下的正确性,但是在多线程环境下,线程之间可能重复执行导致丢失更新,需要给数据加锁

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
from functools import wraps
from threading import RLock

def singleton(cls):
instances = {}
lock = RLock() #重入锁,可再次取到锁对象(普通锁是Lock)

@wraps(cls)
def wrapper(*args, **kwargs):
if cls not in instances: # 先判断有没有实例,若有直接返回
with lock: # 加锁,保证多线程环境
if cls not in instances: # 加锁判断实例
instances[cls] = cls(*args, **kwargs)
return instances[cls]

return wrapper

元类

对象是类的实例,而类就是元类的实例

自定义元类继承于type,要重写__new__/__init__,__call__等魔术方法

其中 __call__相当于元类造出类的构造器

Python 3中使用元类可以在定义类时通过(metaclass=元类)来使用

Python 2中可以在类中添加__metaclass__魔法属性

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
class SingletonMeta(type):
"""自定义元类,继承于type"""

def __init__(cls, *args, **kwargs):
super().__init__(*args, **kwargs)

def __call__(cls, *args, **kwargs):
if not hasattr(cls, 'instance'):
cls.instance = super().__call__(*args, **kwargs)
return cls.instance

class Person(metaclass=SingletonMeta):
# __metaclass__=SingletonMeta #python2的做法现在也支持
def __init__(self, name):
self.name = name

def __str__(self):
return self.name

def main():
p1 = Person('dawda')
p2 = Person('apple')
print(p1.name)
print(p2)

也可以直接写在__new__魔术方法中实现单例

1
2
3
4
5
class Singleton(object):
def __new__(cls):
if not hasattr(cls, 'instance'):
cls.instance = super(Singleton, cls).__new__(cls)
return cls.instance

迭代器和生产器

迭代器

拥有__iter____next__两个魔方方法的对象(字符串、列表、元祖或类),它就是一个迭代器

iter方法可以将一个对象变成迭代器对象

1
2
3
4
5
6
7
8
9
# 使用hashlib模块检查文件的哈希摘要

import hashlib

hasher = hashlib.md5()
with open(r'xadmin.zip', 'rb') as f:
for data in iter(lambda: f.read(1024), b''):
hasher.update(data)
print(hasher.hexdigest())

生成器

在Python中,使用关键字的yield的函数就是一个生成器generator

生成器返回的就是一个迭代器对象,只能用于迭代操作;每次遇到yield函数(让步操作,让出CPU)时会暂停保存当前的所有运行信息和状态,并在下一次执行next方法会从上一次运行的位置继续执行

1
2
3
4
5
6
7
8
9
def foo(num):
a = 0
b = 1
for _ in range(num):
a, b = b, a + b
yield a

b = foo(5)
print(list(b))

生成式

案例: 将字典实现反转

1
2
3
4
5
6
7
i_dict = {1: 'a', 2: 'b', 3: 'c'}
# 用dict(列表生成式), 列表中是二位元素组成的元组
im_dict = dict([(v, k) for k, v in i_dict.items()]) # {'a': 1, 'b': 2, 'c': 3}
# 使用字典生成式
im2_dict = {v:k for k, v in i_dict.items()} # {'a': 1, 'b': 2, 'c': 3}
# 使用dict(zip)
im3_dict = dict(zip(i_dict.values(), i_dict.keys())) # {'a': 1, 'b': 2, 'c': 3}

案例: 一行代码实现阶乘

1
2
3
4
5
6
7
8
from functools import reduce
from operator import mul
# 方法1 使用int.__mul__ 实现乘法
fact = reduce(int.__mul__, range(1, 10)) # 实现9的阶乘
# 方法2 使用operator.mul 实现乘法
fact = reduce(mul, range(1, 10)) # 实现9的阶乘
# 方法3 使用lambda函数, 不推荐使用
fact = reduce(lambda x, y: x * y, range(1, 10)) # 实现9的阶乘

中间件

旧写法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
class XxxMiddleware():

def process_request(self, request):
pass

def process_view(self, request):
pass

def process_template_response(self, request, response):
pass

def process_response(self, request, response):
pass

def process_exception(self, request, exception):
pass

新写法

1
2
3
4
5
6
7
8
9
def xxx_middleware(get_response):

def middleware(request, *args, **kwargs):

resp = get_response(request, *args, **kwargs)

return resp

return wrapper

上下文语法

上下文语法都需要提供__enter____exit__两个魔术方法

contextmanger上下文管理器

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
from contextlib import contextmanager
from time import time

# contextmanager 提供了__enter__方法
@contextmanager
def record_time():
start = time()
yield 'yield 返回值'
end = time()
print(f'执行时间{end - start}')

def foo(num):
if num in (0, 1):
return 1
return num * foo(num - 1)

with record_time() as msg:
print(msg)
print(foo(100))

atomic事务上下文管理器

Django提供的一种直接给函数 事务环境的方法

1
2
3
4
5
6
7
8
9
10
11
from django.db.transaction import atomic

# 使用with
def foo_view(request):
with atomic():
pass

# 使用装饰器
@atomic()
def foo2_view(request):
pass

装饰器

装饰器属于代理模式,实际上是给某个程序增加功能。而装饰器需要满足

  • 不能修改被装饰函数的源代码
  • 不能修改被装饰函数的调用方式
  • 满足上述2个条件,给函数增加功能

不带参装饰器

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
from functools import wraps

def decorate(func):

@wraps(func)
def wrapper(*args, **kwargs):
# 装饰代码
result = func(*args, **kwargs)
# 装饰代码
if isinstance(result, str):
result = result[::-1]
return result

return wrapper

@decorate
def foo():
return 'hello wraps'

print(foo(), foo.__name__)

# wraps的作用
# 1. 没有wraps时执行 foo.__name__ 会返回装饰器内返回的函数名(wrapper)
# 2. __wrapped__ 魔法方法会执行被装饰的函数(无装饰器作用)
print(foo.__wrapped__())

带参装饰器

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
from functools import wraps
from time import time, sleep

def record_time(output):

def decorate(func):

@wraps(func)
def wrapper(*args, **kwargs):
start = time()
result = func(*args, **kwargs)
end = time()
output(func.__name__, end - start)
return result

return decorate

return decorate

def output_file(name, duration):
with open(r'abc.txt', 'a') as f:
f.write(f'{name}执行时间是{duration}')

@record_time(output_file)
def foo():
sleep(2)
return 'hello foo'

if __name__ == '__main__':
a = foo()
print(a)

python高阶函数

filter过滤

使用方法: filter(function, iterable)
对可迭代对象, 根据function函数进行过滤(对序列中的每个元素过滤)
同时, python2.x中filter返回的是一个列表, python3返回的是一个迭代器对象

1
2
3
4
5
6
7
def is_odd(n):
return n % 2
newlist = filter(is_odd, range(1,10))
t_list = [i for i in newlist]
print(t_list)

# 输出: [1,3,5,7,9]

map映射

使用方法: map(function, iterable)

对可迭代对象, 根据function函数进行转换(function函数作用于序列中的每一个元素), 返回一个迭代器对象

1
2
3
4
5
6
>>> def f(x):
... return x*x
...
>>> r = map(f,[1,2,3])
>>> list(r)
[1, 4, 9]

sorted排序

内置排序函数, 对所有可迭代对象生效
使用方法: sorted(iterable[, cmp[, key[], reverse]])

  • cmp 比较的函数, 它具有两个参数, 参数的值从可迭代的对象取出(遵守的规则为大于返回1, 小于返回-1,等于返回0) –> python3.x中已取消这个参数
  • key 比较的函数, 具有一个参数, 参数的值从可迭代的对象中取出
  • reverse 排序规则, 默认为升序False, 为True降序

functools模块

reduce化简

使用方法:reduce(function, iterable[, initializer])

对可迭代对象使用, 根据function函数进行转换(function函数必须有两个参数, 并且对序列中两两之间作累积运算), 返回一个元素(值);最后一个参数是初始值参数, 可选。

例如: reduce(f, [a, b, c, d]) = f(f(f(a,b),c),d)

1
2
3
4
5
6
7
>>> from functools import reduce
>>> def f2(x, y):
... return x*10 + y
...
>>> r2 = reduce(f2, [1, 2, 3, 4])
>>> r2
1234

实现将str转换成int

1
2
3
4
5
6
7
8
9
10
11
>>> from functools import reduce
>>> def f1(x, y):
... return x*10 + y
...
>>> def char2int(key):
... num_dict = {'0': 0, '1': 1, '2': 2, '3': 3, '4': 4, '5': 5, '6': 6, '7': 7, '8': 8, '9': 9}
... return num_dict[key]
...
>>> result = reduce(f1, map(char2int, '12345'))
>>> result
12345

wraps

wraps可以方便的使用函数装饰器的功能,它能够自动的设定选择是否选用装饰器

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
>>> from functools import wraps
>>> def my_decorator(f):
... @wraps(f)
... def wrapper(*args, **kwds):
... print('Calling decorated function')
... return f(*args, **kwds)
... return wrapper
...
>>> @my_decorator
... def example():
... """Docstring"""
... print('Called example function')
...
>>> example()
Calling decorated function
Called example function
>>> example.__wrapped__()
Called example function
>>> example.__name__
'example'
>>> example.__doc__
'Docstring'

  1. 没有wraps时执行 foo.__name__会返回装饰器内函数的名字(wrapper),有wraps时,其返回被装饰函数的名字(exmaple)
  2. __wrapped__ 魔法方法会执行被装饰的函数(无装饰器作用)

partial

偏函数,先看核心源码

1
2
3
4
5
6
7
8
9
def partial(func, *args, **keywords):
def newfunc(*fargs, **fkeywords):
newkeywords = keywords.copy()
newkeywords.update(fkeywords)
return func(*args, *fargs, **newkeywords)
newfunc.func = func
newfunc.args = args
newfunc.keywords = keywords
return newfunc

其结构类似于装饰器,但是其改变被装饰函数的结构,因此又不能成为装饰器。

看上面代码,partial函数将自己的关键字参数**kwargs更新到被装饰函数中,将自己的位置参数*args放到被装饰函数的位置参数前,然后再执行被装饰函数,再举个例子

1
2
3
4
5
6
7
8
9
10
11
from functools import partial
def adds(x, *args):
print(x, *args)
return x + sum(args)

p = partial(adds, 4,5,6)
p(1,2,3)

#output(partial的位置参数放在被装饰函数的位置参数前,再执行使用被装饰函数)
4 5 6 1 2 3
21

lru_cache

最近最久未使用

itertools模块

groupby数据分组

用法: groupby(iterable, key=function)

groupby()函数扫描整个序列并且查找连续相同值,并根据key函数返回值相同的元素序列。在每次迭代的时候,它会返回一个值和一个迭代器对象,这个迭代器对象可以生成元素值全部等于上面那个值的组中所有对象。

必须注意的是,这个待操作数据是预先已排好序的。因为groupby仅仅检查连续的元素,它不会做排序操作。如果没有预先排序,得到的结果就不是预期的结果。

一个比较好的操作就是将数据分组和defaultdict(构建多值字典)结合使用 。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
from itertools import groupby
test5 = [
{'classroom': '1401', 'name': 'joy', 'age': 11},
{'classroom': '1402', 'name': 'chrome', 'age': 12},
{'classroom': '1403', 'name': 'scape', 'age': 20},
{'classroom': '1403', 'name': 'safril', 'age': 15},
{'classroom': '1401', 'name': 'ie', 'age': 12},
{'classroom': '1403', 'name': 'abc', 'age': 14}
]
test5_sort = sorted(test5, key=itemgetter('classroom', 'age'))
e = defaultdict(list)
for classroom, items in groupby(test5_sort, key=itemgetter('classroom')):
for item in items:
e[classroom].append(item)
print(e)
# defaultdict(<class 'list'>, {'1401': [{'classroom': '1401', 'name': 'joy', 'age': 11}, {'classroom': '1401', 'name': 'ie', 'age': 12}], '1402': [{'classroom': '1402', 'name': 'chrome', 'age': 12}], '1403': [{'classroom': '1403', 'name': 'abc', 'age': 14}, {'classroom': '1403', 'name': 'safril', 'age': 15}, {'classroom': '1403', 'name': 'scape', 'age': 20}]})

compress过滤

它是将一个可迭代对象序列根据另一个由布尔类型元素组成的序列进行过滤,返回一个迭代器。

使用方法: compress(iterable, bool_seq)

1
2
3
4
test6 = ['first', 'second', 'third', 'fourth', 'fifth', 'sixth']
from itertools import compress
test6_cp = compress(test6, [True, False, False, False, True, True])
print(list(test6_cp)) # ['first', 'fifth', 'sixth']

permutations排列

combinations组合

product笛卡尔积

collections模块

deque双端队列

deque, 即双端队列(double-ended queue), 它增加了从头部操作的popleft()和appendleft(),并且它同时也支持列表的一些方法(如append(),pop(),insert()等)

deque给定一个maxlen参数,指定队列的最大长度。如果队列中的元素超过最大长度,就会去除旧元素。

与列表相比, 队列在两端插入或删除的时间复杂度都是O(1),而在列表的开头插入或删除的时间复杂度是O(N),在列表的结束插入append或删除pop的时间复杂度是O(1),pop(index)是O(N)

1
2
3
4
5
6
7
8
9
10
from collections import deque
b = deque([1,2,3,4,5], maxlen=5)
b.append(6)
print(b) # deque([2, 3, 4, 5, 6], maxlen=5)
b.popleft()
print(b) # deque([3, 4, 5, 6], maxlen=5)
b.appendleft(10)
print(b) # deque([10, 3, 4, 5, 6], maxlen=5)
b.pop()
print(b) # deque([10, 3, 4, 5], maxlen=5)

Counter计数器

Counter, 作为计数器使用, 作用对象是可序列化的对象,它的底层是通过字典来实现的,因为也可以通过字典的方法增加计数器的值。

Counter对象之间能够使用数学运算

most_common(N)能返回前N个元素

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
In [62]: from collections import Counter

In [63]: test1 = ['a', 1, 2, 'a', 10, 'b', 2]

In [64]: test2 = ('x', 4, 'info', 4, 10)

In [65]: counter1 = Counter(test1)

In [66]: counter2 = Counter(test2)

In [67]: counter1
Out[67]: Counter({'a': 2, 1: 1, 2: 2, 10: 1, 'b': 1})

In [68]: counter2
Out[68]: Counter({'x': 1, 4: 2, 'info': 1, 10: 1})
# 对相同键的元素进行加法操作
In [69]: counter1 + counter2
Out[69]: Counter({'a': 2, 1: 1, 2: 2, 10: 2, 'b': 1, 'x': 1, 4: 2, 'info': 1})
# 减操作,不够减的情况删除键
In [70]: counter1 - counter2
Out[70]: Counter({'a': 2, 1: 1, 2: 2, 'b': 1})
# 返回前两个统计数据
In [71]: counter1.most_common(2)
Out[71]: [('a', 2), (2, 2)]
# 返回所有的统计数据
In [72]: counter1.most_common()
Out[72]: [('a', 2), (2, 2), (1, 1), (10, 1), ('b', 1)]
# 在返回所有的情况进行切片操作
In [73]: counter1.most_common()[:2]
Out[73]: [('a', 2), (2, 2)]

OrderedDict有序字典

OrderedDict,作为有序字典使用, 在python2.x时期, 字典是无序的, 在python3.x后字典没有被定义成有序的, 但是对于同一个字典, 使用标识连接内部的数据, 因此读取时是按存储顺序取数据的

在序列化或编码后需保证字段的顺序的场合,它很有效。但是有序字典内部维护这一个根据键插入顺序排序的双向链表,对于已经存在键的重复赋值不会改变键的顺序。这种数据结构的大小是普通字典的两倍,如果要构建大量数据的数据结构,它可能不合适。

1
2
3
4
5
6
7
8
>>> from collections import OrderedDict
>>> d = OrderedDict()
>>> d = OrderedDict([('name','lisa'),('age',11),('gender','female')])
>>> d
OrderedDict([('name', 'lisa'), ('age', 11), ('gender', 'female')])
>>> d['face']='nice'
>>> d
OrderedDict([('name', 'lisa'), ('age', 11), ('gender', 'female'), ('face', 'nice')])

defaultdict默认字典

defaultdict, 作为默认字典使用,它能够在定义时给给定一个初始的类型, 这样就能够在输出定义没有的键值对时,初始化一个值。

使用方法:

a = defaultdict(factory_function), 其中参数是工厂函数,它通常用于int,str,list,tuple,dict等,更多工厂函数点击

1
2
3
4
5
6
>>> from collections import defaultdict
>>> a = defaultdict(int)
>>> a['age']
0
>>> a
defaultdict(<class 'int'>, {'age': 0})

它的另一个使用场合就是为字典的键映射多个值

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
#普通实现也很容易
pairs = [('a',1), ('a',2), ('b',1), ('c',2), ('b',2)]
print(pairs)
d = {}
for key, value in pairs:
if key not in d:
d[key] = []
d[key].append(value)
print(d) # {'a': [1, 2], 'b': [1, 2], 'c': [2]}
# defaultdict实现
from collections import defaultdict
e = defaultdict(list)
for key, value in pairs:
e[key].append(value)
print(e) # defaultdict(<class 'list'>, {'a': [1, 2], 'b': [1, 2], 'c': [2]})

namedtuple命名元组

namedtuple函数通过一个普通的元组对象映射名称到元组中的每个元素。它返回一个Python标准元组类型子类的工厂方法。需要传入类型名和需要的字段(由[]括起来),它返回一个能够初始化的类,并为定义的字段传递值。

namedtuple与普通元组是可交换的, 它支持所有的普通元组操作, 例如切片、解压。

命名元组主要用途之一就是将你从下标操作解脱出来,你可以像使用对象的属性一样使用命名元组,但是你仍然也能使用下标。

1
2
3
4
5
6
from collections import namedtuple
Subscriber = namedtuple('Student', ['name', 'age'])
sub = Subscriber('joy', 15)
print(sub) # Student(name='joy', age=15)
print(sub.name, sub[0]) # joy joy
print(sub.age, sub[1]) # 15 15

命名元组另一个主要用途就是作为字典的替代,因为字典存储需要更多的存储空间。但是注意, 不同于字典,一个命名元组时不可更改的。

1
2
3
4
5
6
7
8
9
print(type(sub))
sub.age = 20
'''
<class '__main__.Student'>
Traceback (most recent call last):
File "D:\pycharm\pythoncode\practice\whiteboard\python_cookbook.py", line 224, in <module>
sub.age = 20
AttributeError: can't set attribute
'''

如果你非要改变命名元组, 你可以使用_replace()方法

1
2
sub = sub._replace(age=21, name='new_name')
print(sub) # Student(name='new_name', age=21)

ChainMap 合并字典

用法: ChainMap(dict, dict, …) chain意思是连锁,map意思是映射(在python中映射对应就是由键值对组成的字典类型),ChainMap就是将两个或多个字典(并没有真正的合并)从逻辑组成一个新字典。

这个字典能够使用字典的大部分方法。

也有一些需注意的地方: 如果出现重复键,总是会返回第一次出现键位置的值;对这个新字典使用更新或删除操作,它总是影响列表中的第一个字典。

1
2
3
4
5
6
7
8
9
10
fromcollections import ChainMap

a = {'name': 'joy', 'age': 21}
b = {'height': 180, 'hobby': ['sing', 'play games']}
d = {'class': 'Student', 'location': 'Sichuan'}
c = ChainMap(a, b, d)
print(c) # ChainMap({'name': 'joy', 'age': 21}, {'height': 180, 'hobby': ['sing', 'play games']}, {'class': 'Student', 'location': 'Sichuan'})
print(c['name']) # joy
print(c['hobby']) # ['sing', 'play games']
print(c['location']) # Sichuan

另外一种字典的合并方式就是使用update()方法

1
2
3
e = dict(b)
e.update(a)
print(e) # {'name': 'joy', 'height': 180, 'hobby': ['sing', 'play games'], 'age': 21}

heapq模块

heapq堆队列

heapq模块通过nsmallestnlargest可以实现查找一个集合中最小或最大的N(小于集合元素数量)个元素,它的底层实现是将集合数据进行堆排序后放入到一个列表中(heapify方法)。

所谓堆排序,就是….。堆数据结构最重要的特征就是heap[0]永远是最小的元素,并且通过heappop()可以获取下一个最小的元素,时间复杂度是O(log2N);heappush()方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
>>> import heapq
>>> nums = [1, 8, 2, 23, 7, -4, 18, 23, 42, 37, 2]
>>> part = heapq.nlargest(3, nums)
>>> print(part)
[42, 37, 23]
>>> heapq.heapify(nums)
>>> print(nums)
[-4, 2, 1, 23, 7, 2, 18, 23, 42, 37, 8]
>>> heapq.heappop(nums)
-4
>>> heapq.heappop(nums)
1
>>> print(nums)
[2, 2, 8, 23, 7, 37, 18, 23, 42]

同时nlargestnsmallest两个方法,都有key参数,可以自定义比较方式。

1
2
3
4
5
6
7
8
9
10
portfolio = [
{'name': 'IBM', 'shares': 100, 'price': 91.1},
{'name': 'AAPL', 'shares': 50, 'price': 543.22},
{'name': 'FB', 'shares': 200, 'price': 21.09},
{'name': 'HPQ', 'shares': 35, 'price': 31.75},
{'name': 'YHOO', 'shares': 45, 'price': 16.35},
{'name': 'ACME', 'shares': 75, 'price': 115.65}
]
cheap = heapq.nlargest(3, portfolio, key=lambda x: x['shares'])
print(cheap) # [{'name': 'FB', 'shares': 200, 'price': 21.09}, {'name': 'IBM', 'shares': 100, 'price': 91.1}, {'name': 'ACME', 'shares': 75, 'price': 115.65}]

min和max

sorted(seq)[:N]通过排序切片操作

operator模块

基本上每个运算都可以使用其对应的魔术方法

比较运算

cmp用于python2.x中比较两个元素大小的函数, 在python3中已经被去掉了, 可以使用operator模块中的函数进行比较

1
2
3
4
5
6
operator.lt(a, b) operator.__lt__(a, b) 
operator.le(a, b) operator.__le__(a, b)
operator.eq(a, b) operator.__eq__(a, b)
operator.ne(a, b) operator.__ne__(a, b)
operator.ge(a, b) operator.__ge__(a, b)
operator.gt(a, b) operator.__gt__(a, b)

数学运算

1
2
3
4
5
6
7
8
9
abs(obj)
add(a, b) 加 iadd(a,b) +=
sub(a, b) 减 isub(a,b) -=
mul(a,b) __mul__(a,b) 乘积 imul(a,b) *=
truediv(a,b) __truediv__(a,b) 除 itruediv(a,b) /=
floordiv(a, b) 整除 ifloordiv(a,b) //=
mod(a, b) __mod__(a,b) 求模
matmul(a,b) __matmul_(a,b) 矩阵乘积
pow(a,b) __pow__(a,b) a的b次方

逻辑运算

1
2
3
4
5
6
and_(a, b) __and__(a, b) 与运算 iand(a,b) a &= b 
or_(a, b) __or__(a, b) 或运算 ior(a,b) a |= b
xor(a, b) __xor__(a,b) 异或运算(上下相同取0,相异取1) ixor(a.b) a ^= b
inv(obj) invert(obj) __inv__(obj) __invert__(obj) 按位取反(一个有符号的二进制数的补码)
lshift(a, b) __lshift__(a, b) a数左移b位 ilshift(a,b) a <<= b
rshift(a, b) __rshift__(a, b) a数右移b位

移位运算符

1
2
3
4
5
6
8 >> 1 
4
8 << 1 # 左移相当于 8 * (2 ** 1)
16
8 >> 3 # 右移相当于 8 // 2**3
1

异或运算应用

1
2
3
4
5
6
7
8
# 对于一个有奇数个N个整数组成的数组, 其中只有一个整数的数量为1个,其余全为2个或偶数个,找出这个整数
# 用普通循环的方式肯定不可行,需要利用异或运算的性质
# n ^ 0 = n n ^ n = 0
items = [1, 2, 3, 5, 3, 2, 1]
result = 0
for item in items:
result ^= item
print(result) # 5

序列运算

itemgetter基本数据排序

itemgetter是一个函数,它作用于一个对象来获取指定位置的值

1
2
3
a = [1,2,3,4]
func = itemgetter(0,1,3)
print(func(a)) # (1, 2, 4)

处理列表、元祖数据

1
2
3
4
5
6
7
fruits = [('orange',20),('apple', 10), ('banana', 15), ('banana', 10)]
fruit2 = sorted(fruits, key=itemgetter(0))
fruit3 = sorted(fruits, key=itemgetter(1))
fruit4 = sorted(fruits, key=itemgetter(0,1)) # 作用多个属性排序
print(fruit2) # [('apple', 10), ('banana', 15), ('banana', 10), ('orange', 20)]
print(fruit3) # [('apple', 10), ('banana', 10), ('banana', 15), ('orange', 20)]
print(fruit3) # [('apple', 10), ('banana', 10), ('banana', 15), ('orange', 20)]

处理字典列表数据排序

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
from operator import itemgetter
test4 = [
{'name': 'joy', 'age': 20, 'gender': 'male'},
{'name': 'haro', 'age': 20, 'gender': 'male'},
{'name': 'zqi', 'age': 24, 'gender': 'female'},
{'name': 'rocket', 'age': 21, 'gender': 'male'},
]
# sorted方法不会改变被处理的序列
stus = sorted(test4, key=itemgetter('age', 'name'))
# sort方法会改变被处理的序列
test4.sort(key=itemgetter('age', 'name'))
print(stus)
# [{'name': 'haro', 'age': 20, 'gender': 'male'}, {'name': 'joy', 'age': 20, 'gender': 'male'}, {'name': 'rocket', 'age': 21, 'gender': 'male'}, {'name': 'zqi', 'age': 24, 'gender': 'female'}]
print(test4)
# [{'name': 'haro', 'age': 20, 'gender': 'male'}, {'name': 'joy', 'age': 20, 'gender': 'male'}, {'name': 'rocket', 'age': 21, 'gender': 'male'}, {'name': 'zqi', 'age': 24, 'gender': 'female'}]

当然,我们使用匿名函数也能实现,但是itemgetter的性能要好些

1
stus = sorted(test4, key=lambda item: (item['age'], item['name']))

还有max和min函数也是支持key参数的, 因此他们也能使用上述的方法

1
2
stus = max(test4, key=itergetter('age', 'name'))
stus = min(test4, key=lambda item: (item['age'], item['name']))

attrgetter类的对象排序

对类的实例化对象进行排序。

1
2
3
4
5
6
7
8
9
10
11
12
13
from operator import attrgetter
class User:

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

def __repr__(self):
return f'<User {self.userid} {self.name}>'

users = [User(10, 'joy'), User(5, 'google'), User(12, 'april'), User(5, 'apple')]
user_sort = sorted(users, key=attrgetter('userid', 'name'))
# [<User 5 apple>, <User 5 google>, <User 10 joy>, <User 12 april>]

匿名函数也可以实现

1
user_sort = sorted(users, key=lambda obj: (obj.userid, obj.name))

除此, 不要忘记min/max两个函数也能使用上述的函数

python内置函数

hasattr

hasattr(object, name)

1
2
3
4
class test():
name='lip'
def run(self):
return 'runing'

判断一个对象里面是否有name属性或name方法, 返回bool

1
2
3
4
5
6
7
>>> a= test()
>>> hasattr(a, 'name')
True
>>> hasattr(a, 'run')
True
>>> hasattr(a, 'age')
False

getattr

getattr(object, name[, default])

获取对象object的属性或者方法, 若存在, 就将值打印出来;若不存在, 就返回错误,也可以使用默认值

1
2
3
4
5
6
7
8
9
10
11
12
13
>>> getattr(a, 'name')
'lip'
>>> getattr(a, 'run')
<bound method test.run of <__main__.test object at 0x7f5861d50828>>
>>> getattr(a, 'run')()
'running'
>>> getattr(a, 'age')
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
AttributeError: 'test' object has no attribute 'age'
>>> getattr(a, 'age', 10)
10
>>>

setattr

setattr(object, name[, default])**

给对象的属性赋值(若属性不存在, 它会先创建再赋值)

1
2
3
4
5
>>> hasattr(a, 'hobby')
False
>>> setattr(a, 'hobby', 'sing song')
>>> a.hobby
'sing song'

综合使用:

1
2
3
4
>>> getattr(a, 'status', setattr(a, 'status', 'well'))
'well'
>>> a.status
'well'

zip

zip函数用于将两个或多个可迭代对象进行整合(成元组形式的数据结构),也可以用于二维数组到一维数组的转换。它的是实现是将同索引位置的元素进行最短序列长度的组合。更合适的使用方式是将两个等长序列组成字典的场景

注意:zip()函数返回的是一个迭代器(迭代器都只能使用一次)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
>>> a = [1,2,3]
>>> b = [4,5,6]
>>> c = [7,8,9]
>>> zip1 = zip(a,b,c)
>>> for i in zip1:
... print(i)
...
(1, 4, 7)
(2, 5, 8)
(3, 6, 9)
>>>

>>> list(zip1)
[(1,4,7),(2,5,8),(3,6,8)]
# 注: 当上面使用for...in就使用完迭代器zip1, 此时不能再使用list(zip1)

# 将两个序列组成字典
>>> keys = ['a', 'b', 'c']
>>> values = ['1', '2', '3']
>>> items = dict(zip(keys, values))
>>> items
{'a': '1', 'b': '2', 'c': '3'}
# 将两个序列组成列表
>>> items2 = list(zip(keys, values))
>>> items2
[('a', '1'), ('b', '2'), ('c', '3')]

all和any

all(iterable)/any(iterable),前者对一个序列中的所有数据进行检测是否为真,全为真就返回True;后者检查序列中是否存在真,存在就返回True

1
2
3
4
5
6
7
8
9
10
import os
# 取当前文件夹中所有文件
files = os.listdir(os.path.dirname(__file__))
print(files)
if all(name.endswith('.py') for name in files):
print('all file is python')
elif any(name.endswith('.py') for name in files):
print('exist file is python')
else:
print('no python')

上面隐藏一个知识点就是生成器表达式作为独立参数时,可以省略括号。而这个生成器也可以使用列表表达式替换。但是当数据量较大的时候,列表表达式因为会单独生成一个列表结构,浪费内存空间;而使用生成器表达式就不会有这样的问题。

1
2
3
4
5
# 生成器
all(name.endswith('.py') for name in files)
all((name.endswith('.py') for name in files))
# 列表表达式
all([name.endswith('.py') for name in files])

globals和locals

eval / exec / compile

并发编程

多进程

多线程

异步IO

aiohttp asyncio await async

协程

yield

yield from

哈希对象和可变类型

unhashable和hashable

hashable即可哈希,unhashable即不可哈希

一个对象能被称为 hashable , 它必须有个 hash 值,这个值在整个生命周期都不会变化,而且必须可以进行相等比较,所以一个对象可哈希,它必须实现__hash__() 与 __eq__() 方法.

python的某些链接库在内部需要使用hash值,例如集合中添加对象需要使用__hash__()的方法获取哈希值,并比较它是否与集合中已有对象的hash值相同,若相同就比较__eq__()方法比较是否相等,以确定能否加入集合中

另外一种集合中添加数据必须是可哈希的对象,字典中添加键值对中的键也必须是可哈希的对象

对于python的内建类型来说,建立之后的数据无法修改的类型immutable才能是hashable,如字符串,元组,数值型int、float和bytes,除此对于自定义的方法(函数)或者类也是可哈希的;可修改的数据mutable才能是不可哈希的unhashable: 列表、字典、集合,它们在改变值的时候没有改变id,无法由地址定位值的唯一性,因此是不可哈希的

总结:

  1. 可修改的数据就是不可哈希的,不可修改的数据就是可哈希的(因为它们总能有由id定位值的唯一性)
  2. 集合中的元素必须是可哈希的, 但是集合本身却不可哈希
  3. 字典的键也必须是可哈希的

immutable和mutable

immutable即不可变, mutable即可变

python里面, 传递的都是对象的引用,也可以理解成地址。

可变类型就是对数据的修改是在原内存单元中进行。对于不可变类型,是不能修改值的,例如

1
2
3
4
5
6
7
8
a = 1
print(id(a))
a = 2
print(id(a))

# 输出:
2012992560
2012992576

当使用a=2时, 原数据1的引用就已经被销毁了,此时是将2的引用给了a

可变类型有: list, dict, set

不可变类型有: int, float, complex, str, tuple

python规范

  • lambda使用与单行函数, 如果代码超过60-80个字符,还是定于常规函数;对于常见的操作符, 如乘法,应该使用operator.mul而不是lambda x, y: x * y
  • 对于单行条件语句, 建议使用条件表达式,如x = 1 if item else -1
  • 鼓励使用默认参数值, 但是不要在默认参数是可变对象时使用
  • 鼓励使用if not user 或者 if user, 但是对于None和False的区分时, 可以使用if a is None 或者 if a is not None, 对于整数如0和False的区分,使用if a == 0 而不是if not a, 因为a可能取到空串, 空列表等出现误判
-------------end-------------
0%