python易学易用,但也有很多容易错用的特性,或者有一些高级特性可以大大提升开发的性能或效率,这里记录一下。
赋值、浅拷贝和深拷贝(Shallow and deep copy)
赋值只是赋予一个对象新的名字,相当于原对象的引用,类似于C/C++的指针,他们的ID(所指向的内存)都是相同的 ,因此对任何一个操作,对另一个也会生效。
浅拷贝 copy.copy() 会生成一个新的对象,ID和原对象不同,但这个对象并非是原对象的引用,而是原对象内第一层元素对象的引用。
Deep Copy相当于完全复制了一个全新的对象,对对象中所有元素以递归的形式复制到新的对象中,因此新、旧对象完全独立,彼此的操作互不影响:
% python
>>> import copy
>>> a = [[1,2],['a','b']]
>>> a2 = a
>>> a3=copy.copy(a)
>>> a4=copy.deepcopy(a)
>>> a.append('xxx')
>>> a[0].append(3)
>>> a[1] = ['c','d']
>>> a
[[1, 2, 3], ['c', 'd'], 'xxx']
>>> a2
[[1, 2, 3], ['c', 'd'], 'xxx']
>>> a3
[[1, 2, 3], ['a', 'b']]
>>> a4
[[1, 2], ['a', 'b']]
需要注意的是,Python的变量分为可变(mutable)变量和不可变(immutable)变量,对于不可变变量(数字、字符串、元组)来说,赋值和Shallow copy都类似深拷贝,即传值而不是引用。可变变量(dict、list、set、object)才有深浅拷贝的区分。
Class Variable and Class Method
类变量和类方法类似于C++中的静态方法,和对象的变量和方法是不同的,对类变量的修改需要使用类名做前缀,任何对象作为前缀的修改都是修改对象的变量而不是类变量:
class A:
a = 1
def __init__(self):
self.a = 2
@classmethod
def f(cls):
print(cls.a, end=' ')
def g(self):
print(self.a, end=' ')
if __name__ == '__main__':
a1 = A()
a2 = A()
A.f()
a1.f()
a2.f()
a1.g()
a2.g()
A.a = 3
a1.a = 4
A.f()
a1.f()
a2.f()
a1.g()
a2.g()
print(a1.a, a2.a)
# 执行结果
# 1 1 1 2 2 3 3 3 4 2 4 2
coroutine 和async await
从3.5版本开始支持新的语法(PEP 492 ),从3.7开始async和await成为保留关键字,3.6、3.7、3.8、3.9还在不断的完善这个功能之中。
import time
import requests
import asyncio
async def test2(i):
r = await other_test(i)
print(f'{i} - Done, {r.status_code}')
async def other_test(i):
r = requests.get(i)
await asyncio.sleep(4)
print(f'{i} - {(time.time() - start):.3f}')
return r
url = ["https://www.baidu.com/s?wd=python",
"https://www.baidu.com/s?wd=asyncio",
"https://www.baidu.com/s?wd=await"]
loop = asyncio.get_event_loop()
task = [asyncio.ensure_future(test2(i)) for i in url]
start = time.time()
loop.run_until_complete(asyncio.wait(task))
endtime = time.time() - start
print(f"All used: {endtime:.3f}")
loop.close()
除了async def之外,async还支持for和with语句,但只能在协程函数中使用,用于实现对异步iter的遍历:
import time
import requests
import asyncio
url = ["https://www.baidu.com/s?wd=python",
"https://www.baidu.com/s?wd=asyncio",
"https://www.baidu.com/s?wd=await"]
async def get_url():
for i in url:
await asyncio.sleep(3)
yield requests.get(i)
async def get_res():
start = time.time()
async for r in get_url():
print(f'{r.url} - {(time.time() - start):.3f}')
else:
print('Done')
asyncio.run(get_res())
#for r in get_url():
# print('这个for语句会提示语法错误')
Type Hinting
从3.5版本引入Type Hinting和typing 模块,支持类型声明,但仅仅用于声明,并不作任何实际的检查。声明的信息会保存在__annotations__中。3.6,3.7,3.8,3.9一直在完善该功能。
from __future__ import annotations
import typing
class A:
# 3.5+ :支持type hinting
def fn1(self, s: str) -> str:
print(self.fn1.__annotations__)
#实际上python不做任何检车,return None不会报错
return None
# 3.7+ : 支持postponed evaluation of type annotation
# 尚未默认支持(预计3.10开启),需要 from __future__ import annotations
def f(obj: B) -> str:
return ''
class B:
pass
a = A()
a.fn1('hehe')
# 3.6+ : 支持变量annotations
s : str
l : list = [1,2]
print(l)
# 3.7+ : 支持annotation 范型
# from 3.9 所有内置模块都支持annotation范型
l : list[int] = [1,2,3]
print(l)
def fp(l: typing.List[str], f: typing.Callable[str, str]) -> None:
for i in l:
print(f(i, 'c'))
fp(['a','b'], lambda x, y: x + '-' + y)
3.9 新语法
-
dict Union
注意, a | b 和 b | a不同,后面的Value优先。
| 只支持dict类型,但 |= 支持元组的list。
>>> a = {'a': 1, 'b': 2} >>> b = {'a': 10, 'c': 20} >>> c = {'d': 9} >>> a | b {'a': 10, 'b': 2, 'c': 20} >>> b | a {'a': 1, 'c': 20, 'b': 2} >>> c |= a >>> c {'d': 9, 'a': 1, 'b': 2} >>> c | [('e', 6)] Traceback (most recent call last): File "<stdin>", line 1, in <module> TypeError: unsupported operand type(s) for |: 'dict' and 'list' >>> c |= [('e', 6)] >>> c {'d': 9, 'a': 1, 'b': 2, 'e': 6} >>>
3.8 新语法
-
Assignment Expressions
赋值操作符 := ,其优先级非常低,需要注意。
>>> if (d := 123) > 0: print(d) ... 123 >>> if d := 123 > 0: print(d) ... True
-
Postional-only Paramters
参数列表中,/之前的参数,不能使用key=value形式,*之前的参数可以使用位置来标识,但*之后的参数必须使用key形式。
>>> def f(a, /, b, *, c): ... print(a, b, c) ... >>> f(1,2,3) Traceback (most recent call last): File "<stdin>", line 1, in <module> TypeError: f() takes 2 positional arguments but 3 were given >>> f(1,2,c=3) 1 2 3 >>> f(1,b=2,c=3) 1 2 3 >>> f(a=1,b=2,c=3) Traceback (most recent call last): File "<stdin>", line 1, in <module> TypeError: f() got some positional-only arguments passed as keyword arguments: 'a'
3.6 新语法
-
f string
前缀字符串增加f前缀,可以嵌套变量,3.8增加 “=” 的特性。
>>> s = 'WW' >>> f = 7.159 >>> a = {'h': 7} >>> f'3.6 support {s} {f:.1f} {a} {a["h"]} {{a}}' "3.6 support WW 7.2 {'h': 7} 7 {a}" >>> f'3.8 support: {f=} {f:.1f} {f=:.2f}' '3.8 support: f=7.159 7.2 f=7.16' >>> f'{math.cos(i)=:.2f} {[x for x in range(3)]} {type(a) is list}' 'math.cos(i)=0.99 [0, 1, 2] False'
-
数字类型支持下划线
>>> i = 100_000_000_00 >>> i 10000000000 >>> '{:_x}'.format(i) '2_540b_e400' >>> '{:_X}'.format(i) '2_540B_E400' >>> '{:_}'.format(i) '10_000_000_000'
3.5 新语法
-
@ 运算符
3.5引入了新的运算符 @ ,用于矩阵乘积运算,但目前内置的所有数据类型都不支持这个运算符,可以通过___matmul__(), __rmatmul__(), __imatmul__()来重载 regular/reflected/in-place 矩阵运算。原来的numpy.dot函数可以直接用 @ 运算符来代替:
>>> import numpy as np >>> x = np.ones(3) >>> m = np.eye(3) >>> x @ m array([1., 1., 1.]) >>> np.dot(x, m) array([1., 1., 1.])
class Pos: def __init__(self, x: int, y: int): self._x = x self._y = y def __str__(self): return '[' + str(self._x) + ', ' + str(self._y) + ']' def __matmul__(self, r: int): print('M') return Pos(self._x ** r, self._y ** r) def __rmatmul__(self, r: int): print('RM') return Pos(self._x ** r, self._y ** r) def __imatmul__(self, r: int): print('IM') self._x = self._x ** r self._y = self._y ** r return self p = Pos(2, 3) print(p @ 3) # call __matmul__ print(4 @ p) # call __rmatmul__ p @= 5 # call __imatmul__ print(p)
-
unpacking扩展语法支持
>>> a = [1,2,3] >>> b = {'a':3, 'b':4} >>> print(*a,*b) 1 2 3 a b >>> def fn(a,b): ... print(a,b) ... >>> fn(**b) 3 4 >>> *range(3),3 (0, 1, 2, 3) >>> *range(3), (0, 1, 2) >>> [*range(4),4] [0, 1, 2, 3, 4] >>> [*range(4),4, *(4,5)] [0, 1, 2, 3, 4, 4, 5] >>> {'x':1, **{'y':2}} {'x': 1, 'y': 2}