Python实践
类别: Python 实践 标签: PYTHONPATH exec uuid目录
语言
变量
- 变量的赋值,只是表示让变量指向了某个对象,并不表示拷贝对象给变量;而一个对象,可以被多个变量所指向。
- 可变对象(列表,字典,集合等等)的改变,会影响所有指向该对象的变量。
- 对于不可变对象(字符串、整型、元组等等),所有指向该对象的变量的值总是一样的,也不会改变。但是通过某些操作(+= 等等)更新不可变对象的值时,会返回一个新的对象。
- 变量可以被删除,但是对象无法被删除。
函数参数传递
Python 里所有的数据类型都是对象,所以参数传递时,只是让新变量与原变量指向相同的对象而已,并不存在值传递或是引用传递一说。
- 不可变参数
>>> def func(x): ... x=2 ... >>> n=1 >>> func(n) >>> n 1
- 可变参数
>>> def func(x): ... x.append(2) ... >>> l=[1] >>> func(l) >>> l [1, 2]
- 最佳实践(不管参数是可变还是不可变,明确地返回值。)
>>> def func(x): ... pass ... return x ... >>> n=1 >>> n = func(n) >>> n 1
== 和 is
==
比较值;is
比较对象的 ID。通过函数 id(name) 可以获得对象的标识。
出于对性能优化的考虑,Python 内部会对 -5 到 256 的整型维持一个数组,起到一个缓存的作用。这样,每次试图创建一个 -5 到 256 范围内的整型数字时,Python 都会从这个数组中返回相对应的引用,而不是重新开辟一块新的内存空间。如果整型数字超出了这个范围,Python 则会每次使用都使用新的内存区域。
>>> n=10
>>> m=10
>>> n == m
True
>>> n is m
True
>>> n=257
>>> m=257
>>> n == m
True
>>> n is m
False
比较操作符 is
的速度效率,通常要优于 ==
。因为 is
操作符不能被重载,这样,Python 就不需要去寻找,程序中是否有其他地方重载了比较操作符,并去调用。执行比较操作符 is
,就仅仅是比较两个变量的 ID 而已。但是 ==
操作符却不同,执行 a == b 相当于是去执行 a.__eq__(b)
,而 Python 大部分的数据类型都会去重载 __eq__
这个函数,其内部的处理通常会复杂一些。比如,对于列表,__eq__
函数会去遍历列表中的元素,比较它们的顺序和值是否相等。
对于不可变(immutable)的变量,如果我们之前用 ==
或者 is
比较过,结果是不是就一直不变了呢?不是的。这里使用元组(tuple)类型看一下,元组类型是不可变的,但是里面的元素列表对象是可变的。
>>> t1 = (1, 2, [3, 4])
>>> t2 = (1, 2, [3, 4])
>>> t1 == t2
True
>>> t1[-1].append(5)
>>> t1
(1, 2, [3, 4, 5])
>>> t1 == t2
False
浅拷贝与深拷贝
- 浅拷贝:重新分配内存,生成新的对象,里面的元素是原对象中子对象的引用,对于不可变(immutable)的对象不是对象的引用。
- 深拷贝:重新分配内存,生成新的对象,将原对象中的元素以递归的方式全部拷贝。深拷贝中会维持一个字典,记录已经拷贝的对象以及对象的ID,防止出现无限递归。
装饰器
函数装饰器
- 实现函数装饰器
def func_decorator(func): def wrapper(): print('wrapper of decorator') func() return wrapper def hello(): print('Hello ') hello = func_decorator(hello) hello()
wrapper of decorator Hello
- 使用
@
语法替代手动赋值def func_decorator(func): def wrapper(): print('wrapper of decorator') func() return wrapper @func_decorator def hello(): print('Hello ') hello()
wrapper of decorator Hello
- 带参数的函数
def func_decorator(func): def wrapper(name): print('wrapper of decorator') func(name) return wrapper @func_decorator def hello(name): print('Hello {}'.format(name)) hello('World')
wrapper of decorator Hello World
- 带任意参数的函数
def func_decorator(func): def wrapper(*args, **kwargs): print('wrapper of decorator') func(*args, **kwargs) return wrapper @func_decorator def hello(name): print('Hello {}'.format(name)) @func_decorator def my_sum(*args, **kwargs): total = 0 if args: for arg in args: if type(arg) is list: total += sum(arg) else: total += arg elif kwargs: for _, v in kwargs.items(): total += v print('Sum = {}'.format(total)) hello('World') my_sum(1,2,3,4) my_sum([1,2,3,4]) my_sum(x1=1, x2=2, x3=3, x4=4) my_sum(**{'x1':1, 'x2':2, 'x3':3, 'x4':4})
wrapper of decorator Hello World wrapper of decorator Sum = 10 wrapper of decorator Sum = 10 wrapper of decorator Sum = 10 wrapper of decorator Sum = 10
-
如何保障装饰后还是原函数
经装饰后的函数,就不是原函数了,这可能并不是我们想看到的。
def func_decorator(func): def wrapper(*args, **kwargs): print('wrapper of decorator') func(*args, **kwargs) return wrapper @func_decorator def hello(name): print('Hello {}'.format(name)) print(hello.__name__) print(help(hello))
wrapper Help on function wrapper in module __main__: wrapper()
使用内置的装饰器 @functools.wrap,它会将原函数的元信息,拷贝到对应的装饰器函数里。
import functools def func_decorator(func): @functools.wraps(func) def wrapper(*args, **kwargs): print('wrapper of decorator') func(*args, **kwargs) return wrapper @func_decorator def hello(name): print('Hello {}'.format(name)) print(hello.__name__) print(help(hello))
hello Help on function hello in module __main__: hello()
类装饰器
class Count:
def __init__(self, func):
self.func = func
self.call_num = 0
def __call__(self, *args, **kwargs):
self.call_num += 1
print('Count call number: {}'.format(self.call_num))
self.func(*args, **kwargs)
@Count
def hello():
print('Hello World')
hello()
hello()
Count call number: 1
Hello World
Count call number: 2
Hello World
装饰器的嵌套
import functools
def func_decorator1(func):
@functools.wraps(func)
def wrapper(*args, **kwargs):
print('wrapper of decorator1')
func(*args, **kwargs)
return wrapper
def func_decorator2(func):
@functools.wraps(func)
def wrapper(*args, **kwargs):
print('wrapper of decorator2')
func(*args, **kwargs)
return wrapper
@func_decorator1
@func_decorator2
def hello(name):
print('Hello {}'.format(name))
hello('World')
wrapper of decorator1
wrapper of decorator2
Hello World
装饰器用法
- 身份认证
- 日志记录
- 输入合理性检查
- 缓存
下面是记录函数使用时间的装饰器
import time
import functools
def use_time(func):
@functools.wraps(func)
def wrapper(*args, **kwargs):
start = time.perf_counter()
res = func(*args, **kwargs)
end = time.perf_counter()
print('function: {} use time {} ms'.format(func.__name__, (end - start) * 1000))
return res
return wrapper
@use_time
def fib(n):
a,b = 1,1
for _ in range(n-1):
a,b = b,a+b
return a
fib(100)
function: fib use time 0.01016499999999948 ms
metaclass
任何类型的基类都是 type。
迭代器
迭代器(iterator)提供了一个 next 的方法。调用这个方法后,获得容器的下一个对象,如果没有对象了会抛出 StopIteration 异常。
it = iter([1, 2])
print(it)
print(next(it))
print(next(it))
try:
print(next(it))
except StopIteration:
print('StopIteration')
<list_iterator object at 0x1056542b0>
1
2
StopIteration
生成器
用小括号 ()
括起来
nums = (n for n in range(1, 3))
print(nums)
print(list(nums))
next(nums)
<generator object <genexpr> at 0x10447d930>
[1, 2]
Traceback (most recent call last):
File "/examples.py", line 11, in <module>
next(nums)
StopIteration
yield
调用 next() 函数,yield 会返回值,然后挂起等待,当下一次调用 next() 函数,接着 yield 下面的语句继续执行。
def generator():
n = 1
while True:
yield n
n += 1
nums = generator()
for _ in range(3):
n = next(nums)
print(n)
例子
给定两个序列,判定第一个是不是第二个的子序列。序列就是列表,子序列则指的是,一个列表的元素在第二个列表中都按顺序出现,但是并不必挨在一起。举个例子,[1, 3, 5] 是 [1, 2, 3, 4, 5] 的子序列,[1, 4, 3] 则不是。
def is_subsequence(a, b):
b = iter(b)
return all(i in b for i in a)
print(is_subsequence([1, 3, 5], [1, 2, 3, 4, 5]))
print(is_subsequence([1, 4, 3], [1, 2, 3, 4, 5]))
True
False
模块
import
解释器导入模块时,会通过特定的路径列表来查找,通过 sys.path 可以看到这个路径列表。
>>> import sys
>>> sys.path
['', '/usr/lib/python38.zip', '/usr/lib/python3.8', '/usr/lib/python3.8/lib-dynload', '/home/lnsoft/.local/lib/python3.8/site-packages', '/usr/local/lib/python3.8/dist-packages', '/usr/lib/python3/dist-packages']
在运行程序前,可以通过配置环境变量 PYTHONPATH
,在运行程序后自动加入路径列表中。
export PYTHONPATH=/InferenceServing/app:$PYTHONPATH
python3
>>> import sys
>>> sys.path
['', '/InferenceServing/app', '/usr/lib/python38.zip', '/usr/lib/python3.8', '/usr/lib/python3.8/lib-dynload', '/home/lnsoft/.local/lib/python3.8/site-packages', '/usr/local/lib/python3.8/dist-packages', '/usr/lib/python3/dist-packages']
__init__.py
在 Python 2 的规范中,需要在模块所在的文件夹新建一个 __init__.py
,内容可以为空,也可以用来表述包对外暴露的模块接口。在 Python 3 规范中,__init__.py
并不是必须的。
协程
- import asyncio 实现协程所需工具
- async 声明异步函数。调用异步函数,便可得到一个协程对象(coroutine object)。
- asyncio.run(main()) 作为主程序的入口函数,在程序运行周期内,只调用一次 asyncio.run。
- asyncio.create_task 创建任务,进入事件循环等待运行。
- await asyncio.gather(*tasks) 等待所有任务完成。
import asyncio
import time
async def sleep(id, second):
print(f'id: {id} sleep {second}.')
await asyncio.sleep(second)
print(f'id: {id} end.')
async def main(seconds):
tasks = [asyncio.create_task(sleep(id, second)) for id, second in enumerate(seconds)]
await asyncio.gather(*tasks)
begin = time.time()
asyncio.run(main([1, 2, 3, 4]))
use_time = time.time()-begin
print(f'Use time: {use_time:.4f}!')
id: 0 sleep 1.
id: 1 sleep 2.
id: 2 sleep 3.
id: 3 sleep 4.
id: 0 end.
id: 1 end.
id: 2 end.
id: 3 end.
Use time: 4.0030!
通过执行下面的代码来了解相关的异步函数的执行流程。
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())
await asyncio.sleep(3)
print('before await')
await task1
print('awaited worker_1')
await task2
print('awaited worker_2')
asyncio.run(main())
* worker_1 start
- worker_2 start
* worker_1 done
- worker_2 done
before await
awaited worker_1
awaited worker_2
return_exceptions=True,如果不设置这个参数,就会抛出(throw)异常,从而需要捕捉(try except),这就意味着其它还没被执行的任务会被全部取消掉。
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())
await asyncio.sleep(0.5)
task2.cancel()
res = await asyncio.gather(task1, task2, return_exceptions=True)
print(res)
asyncio.run(main())
* worker_1 start
- worker_2 start
* worker_1 done
[None, CancelledError()]
库
时间
strftime
from datetime import datetime
now = datetime.now()
str = now.strftime("%Y-%m-%d %H:%M:%S")
print(str)
2021-08-18 07:39:22
fromtimestamp
from datetime import datetime
timestamp = 1669085501
date = datetime.fromtimestamp(timestamp)
formatted_date = date.strftime("%Y-%m-%d %H:%M:%S")
print(formatted_date)
2022-11-22 10:51:41
UUID
生成随机UUID
import uuid
myuuid = uuid.uuid4()
print(str(myuuid))
f9e5d7c7-a708-4f2a-9a8b-5a42ca5e2e83
UUID字符串转成UUID对象
import uuid
uuid.UUID('f9e5d7c7-a708-4f2a-9a8b-5a42ca5e2e83')
工程
配置项目的根路径
在一个 Virtual Environment 里,在 activate 文件的末尾配置 PYTHONHOME。
export PYTHONPATH="您的工程路径"
每次当您通过 activate 激活这个运行时环境的时候,它就会自动将项目的根路径添加到搜索路径中去。
if __name__ == '__main__'
运行 Python 脚本的第一个脚本的 __name__
值为 __main__
,在脚本中使用 import 语句时,__name__
就会被赋值为该模块的名字了,这样导入进来的模块语句将不会执行。
其它
脚本内查看 Python 的版本信息
platform 模块
import platform
print(platform.python_version())
3.9.7
sys 模块
import sys
print(sys.version)
print(sys.version_info)
3.9.7 (v3.9.7:1016ef3790, Aug 30 2021, 16:39:15)
sys.version_info(major=3, minor=9, micro=7, releaselevel='final', serial=0)```
通过 Python 指定要执行的命令
单语句
python -c "print('Hello World')"
多语句(使用;分割)
python -c "import time;time.sleep(1)"
带缩行的语句
python -c "exec('import time\\ntry: time.sleep(1)\\nexcept: pass\\n')"
脚本文件
可以把语句直接写到 test.py 文件。
import time
try:
time.sleep(1)
except:
pass
通过读取文件来执行源代码
python -c "exec(open('test.py').read())"