0%

Python小技巧

语法检查

pylint 是一个能够检查Python编码质量、编码规范的工具。它分析 Python 代码中的错误,查找不符合代码风格标准(Pylint 默认使用的代码风格是 PEP 8)和有潜在问题的代码。

个人认为正确性比风格更加重要,不妨大材小用,执行脚本之前,都使用pylint进行语法检查一下。

1
2
pip install pylint
pylint test.py

导入自定义模块

全局导入

自己新建了一个模块,怎样让它可以被全局引用?答:导入自定义模块。
具体操作方法:
1、进入 xxx/python3/lib/python3.6/site-packages 目录
2、新建 yyy.pth 文件,写入自定义模块的路径

1
/home/voidking/scripts/vktools/

详情参考python之使用.pth文件导入自定义模块

指定文件导入

自己新建了一个模块a,路径不同的情况下,怎样让它被模块b引用?答:使用 sys.path.append 函数。

1
2
# sys.path.remove('/path/to/a')
sys.path.append('/path/to/a')

获取脚本路径

已知路径:

1
2
3
4
5
path
└── module_a
├── getpath.py
└── module_b
└── getpath.py

getpath.py脚本内容为:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
import os
import sys
from module_b import getpath

def print_path():
print(f'os.getcwd() is {os.getcwd()}')
print(f'sys.path[0] is {sys.path[0]}')
print(f'sys.argv[0] is {sys.argv[0]}')
print(f'__file__ is {__file__}')
print(f'os.path.dirname(__file__) is {os.path.dirname(__file__)}')
print(f'os.path.abspath(__file__) is {os.path.abspath(__file__)}')
print(f'os.path.realpath(__file__) is {os.path.realpath(__file__)}')
print(f'os.path.split(os.path.realpath(__file__))[0] is {os.path.split(os.path.realpath(__file__))[0]}')

print('------ module_a/getpath.py ------')
print_path()
print('---------------------------------')
print('------ module_a/module_b/getpath.py ------')
getpath.print_path()
print('------------------------------------------')

在path目录中执行脚本python module_a/getpath.py ,得到结果为:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
------ module_a/getpath.py ------
os.getcwd() is /Users/vk/tmp/path
sys.path[0] is /Users/vk/tmp/path/module_a
sys.argv[0] is module_a/getpath.py
__file__ is module_a/getpath.py
os.path.dirname(__file__) is module_a
os.path.abspath(__file__) is /Users/vk/tmp/path/module_a/getpath.py
os.path.realpath(__file__) is /Users/vk/tmp/path/module_a/getpath.py
os.path.split(os.path.realpath(__file__))[0] is /Users/vk/tmp/path/module_a
---------------------------------
------ module_a/module_b/getpath.py ------
os.getcwd() is /Users/vk/tmp/path
sys.path[0] is /Users/vk/tmp/path/module_a
sys.argv[0] is module_a/getpath.py
__file__ is /Users/vk/tmp/path/module_a/module_b/getpath.py
os.path.dirname(__file__) is /Users/vk/tmp/path/module_a/module_b
os.path.abspath(__file__) is /Users/vk/tmp/path/module_a/module_b/getpath.py
os.path.realpath(__file__) is /Users/vk/tmp/path/module_a/module_b/getpath.py
os.path.split(os.path.realpath(__file__))[0] is /Users/vk/tmp/path/module_a/module_b
------------------------------------------

由实验结果,可以得出如下结论。

获取执行命令的绝对路径

1
os.getcwd()

获取入口脚本的父绝对路径

1
sys.path[0]

获取执行脚本的绝对路劲

1
2
os.path.abspath(__file__)
os.path.realpath(__file__)

获取执行脚本的父绝对路径

1
os.path.split(os.path.realpath(__file__))[0]

获取文件/目录的父路径

1
os.path.dirname(__file__)

查找文件

在指定目录中,根据文件前缀查找文件。

1
2
3
4
5
6
7
8
import glob

def find_files_by_prefix(prefix, dir_path):
file_list = list()
file_pattern = os.path.join(dir_path, '{}*'.format(prefix))
for f in glob.glob(file_pattern):
file_list.append(f)
return file_list

删除目录和文件

封装几个简单函数,删除目录和文件。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
import os
import shutil

# 删除空目录
def remove_dir(dir_path):
if os.path.exists(dir_path):
os.removedirs(dir_path)

# 删除文件
def remove_file(file_path):
if os.path.exists(file_path):
os.remove(file_path)

# 删除目录和文件
def remove_dir_and_file(dir_path):
if os.path.exists(dir_path):
shutil.rmtree(dir_path)

单元测试

unittest是一个Python单元测试框架。它受到 JUnit 的启发,与其他语言中的主流单元测试框架有着相似的风格。其支持测试自动化,配置共享和关机代码测试。支持将测试样例聚合到测试集中,并将测试与报告框架独立。

脚本编写

新建一个 tests/test_demo.py 脚本,内容如下:

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

class TestStringMethods(unittest.TestCase):

def test_upper(self):
self.assertEqual('foo'.upper(), 'FOO')

def test_isupper(self):
self.assertTrue('FOO'.isupper())
self.assertFalse('Foo'.isupper())

def test_split(self):
s = 'hello world'
self.assertEqual(s.split(), ['hello', 'world'])
# check that s.split fails when the separator is not a string
with self.assertRaises(TypeError):
s.split(2)

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

继承 unittest.TestCase 就创建了一个测试样例。上述三个独立的测试是三个类的方法,这些方法的命名都以 test 开头。 这个命名约定告诉测试运行者类的哪些方法表示测试。

每个测试的关键是:调用 assertEqual() 来检查预期的输出; 调用 assertTrue() 或 assertFalse() 来验证一个条件;调用 assertRaises() 来验证抛出了一个特定的异常。

通过 setUp() 和 tearDown() 方法,可以设置测试开始前与完成后需要执行的指令。

最后的代码块中,演示了运行测试的一个简单的方法。 unittest.main() 提供了一个测试脚本的命令行接口。

执行测试

方法一:脚本级别调用(直接运行脚本)

1
2
3
python tests/test_demo.py
python tests/test_demo.py -v
python -m unittest tests/test_demo.py

方法二:包、模块、类和方法级别调用

1
2
3
4
5
6
7
8
# 包级别调用(进入tests目录查找所有test_*.py文件并运行)
python -m unittest discover -s tests -p "test_*.py"
# 模块级别调用
python -m unittest tests.test_demo
# 类级别调用
python -m unittest tests.test_demo.TestStringMethods
# 方法级别调用
python -m unittest tests.test_demo.TestStringMethods.test_upper

单元测试之mock

单元测试时,如果涉及网络请求,建议使用mock模块来模拟。

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
32
33
34
35
36
37
38
39
40
41
42
43
44
45
import json
import unittest
from unittest import mock
import requests

def request_system(method: str, url: str, data={}):
headers = {
'Content-Type': 'application/json'
}
response = requests.request(method=method, url=url, headers=headers, data=json.dumps(data))
return response

def login(username: str, password: str):
# 以下接口暂时访问不通
LOGIN_URL='http://127.0.0.1/login'
data = {
'username': username,
'password': password
}
response = request_system('POST', LOGIN_URL)
if response.status == 200:
return json.loads(response.text)
else:
return None

class TestMockMethods(unittest.TestCase):
def test_request_system(self):
mock_response = mock.Mock()
mock_response.status_code = 200
mock_response.text = json.dumps({"code": 0, "msg":"success"})
request_system = mock.Mock(return_value=mock_response)

response = request_system(method='POST',url='http://127.0.0.1/')
print(response.status_code)
print(response.text)
self.assertEqual(200, response.status_code)

def test_login(self):
login = mock.Mock(return_value={"code": 0, "msg":"success"})
data = login(username='voidking',password='voidking')
print(data)
self.assertEqual(0, data.get('code'))

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

这种模拟测试方式很巧妙,适合测试访问第三方接口,但是并不会真正发出请求。
另一个问题来了,Python怎么测试自己的接口?不知道。
想到以前使用Beego框架进行开发,它的单元测试就很巧妙,先在测试数据库插入数据,然后通过beego.BeeApp.Handlers.ServeHTTP(w, r)把自己临时启动起来,最后自己的单元测试调用自己的接口。
其中的关键在于把自己启动起来,理论上Python也能做到。

url特殊字符转义

1
2
3
from urllib import parse
labelselector = parse.quote('app_id=voidking,env in (test,online)')
url = f'https://www.voidking.com?labelselector={labelselector}'

特殊字符对应url编码表:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
空格 : %20
" : %22
# : %23
% : %25
& : %26
( : %28
) : %29
+ : %2B
, : %2C
/ : %2F
: : %3A
; : %3B
< : %3C
= : %3D
> : %3E
? : %3F
@ : %40
\ : %5C
| : %7C

函数重试

函数重试,我们可以通过循环多次调用实现,也可以使用retrying包。

任意异常重试

1
2
3
4
5
6
7
8
9
from retrying import retry

@retry(stop_max_attempt_number=3, wait_random_min=1000, wait_random_max=3000)
def run():
print('run')
raise ValueError

if __name__ == '__main__':
run()

当函数run抛出任意异常时,则进行重试。

  • stop_max_attempt_number:最大重试次数
  • wait_random_min:最小重试间隔
  • wait_random_max:最大重试间隔

注意函数内不要使用try except,否则异常就被函数自己捕获处理了,不会抛给retry。

指定异常重试

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
from retrying import retry
import random

def if_value_error(exception):
return isinstance(exception, ValueError)

@retry(retry_on_exception=if_value_error, stop_max_attempt_number=3, wait_random_min=1000, wait_random_max=3000)
def run():
value = random.randint(0, 10)
msg = f'value is {value}'
print(msg)
if value <= 5:
raise ValueError('value is too small!')
else:
raise Exception('value is too large!')

if __name__ == '__main__':
run()

当函数run抛出ValueError异常时,则进行重试;抛出Exception异常时,不会重试。

指定返回值重试

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
from retrying import retry
import random

def if_return_small(value):
return value <= 5

@retry(retry_on_result=if_return_small, stop_max_attempt_number=3, wait_random_min=1000, wait_random_max=3000)
def run():
value = random.randint(0, 10)
msg = f'value is {value}'
print(msg)
return value

if __name__ == '__main__':
run()

当函数run返回值小于5时,进行重试。

多次重试后容忍异常

1
2
3
4
5
6
7
8
9
10
11
12
13
from retrying import retry

@retry(stop_max_attempt_number=3, wait_random_min=1000, wait_random_max=3000)
def run():
print('run')
raise ValueError

def run2():
print('run2')

if __name__ == '__main__':
run()
run2()

当函数run抛出任意异常时,则进行重试。重试指定次数后,程序就异常结束了。这时,如果我们想要容忍异常,让run2可以正常运行,该怎么办呢?很简单,给run函数包裹一个try except即可。

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

@retry(wrap_exception=True,stop_max_attempt_number=3, wait_random_min=1000, wait_random_max=3000)
def run():
print('run')
raise ValueError

def wrap_run():
try:
run()
except:
pass

def run2():
print('run2')

if __name__ == '__main__':
wrap_run()
run2()

操作mysql数据库

python操作mysql数据库,是经常遇到的需求,下面整理一下具体操作方法。

数据库准备

1
2
3
4
5
6
7
8
9
10
create database vkphp default character set utf8 collate utf8_general_ci;

use vkphp;

CREATE TABLE IF NOT EXISTS `user` (
`id` int(8) NOT NULL AUTO_INCREMENT,
`name` varchar(32) NOT NULL,
`password` varchar(32) NOT NULL DEFAULT '',
PRIMARY KEY (id)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

更多内容参考《使用Docker安装配置Mysql》《MySQL常用命令》

安装依赖

pip3 install mysqlclient==1.3.13

插入数据

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
#!/usr/bin/python3
# -*- coding: UTF-8 -*-

import MySQLdb

# 打开数据库连接
db = MySQLdb.connect(host="localhost",
port="3306",
user="root",
password="voidking",
database="vkphp",
charset='utf8')

# 使用cursor()方法获取操作游标
cursor = db.cursor()

# SQL 插入语句
sql = "insert into `user` (`name`,`password`) values('haojin','voidking');"
try:
# 执行sql语句
cursor.execute(sql)
# 提交到数据库执行
db.commit()
except:
# 发生错误时回滚
db.rollback()

# 关闭数据库连接
db.close()

其他操作参考Python 操作 MySQL 数据库

安装pip

一些情况下,在安装python时没有默认安装pip,这时就需要手动安装。

1
2
3
4
5
6
# 方法一:
python -m ensurepip

# 方法二:
wget https://bootstrap.pypa.io/get-pip.py
python get-pip.py -i http://pypi.douban.com/simple/ --trusted-host pypi.douban.com

参考文档

  • 本文作者: 好好学习的郝
  • 本文链接: https://www.voidking.com/dev-python-tricks/
  • 版权声明: 本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!源站会及时更新知识点及修正错误,阅读体验也更好。欢迎分享,欢迎收藏~