输入输出的设备主要有键盘、屏幕、网卡、文件等。
7.1 键盘和屏幕
- input
- 接收键盘的输入,得到的是字符串,
name = input('your name:')
,要想的到数字,需要进行类型转换
- 输出到屏幕,print(“Hello world”)
7.2 文件路径
对文件系统的访问,大多通过Python的os模块实现。该模块是Python访问文件系统功能的主要接口。
通过下面代码可以查看os模块的全部功能。
>>> import os
>>> dir(os)
例如,获取当前工作目录:
import os
print(os.getcwd())
设置和读取环境变量:
import os
os.environ['WORKON_HOME'] = "value" # 设置环境变量
print(os.getenv('WORKON_HOME')) # 读取环境变量
创建目录,或者多层目录
import os
path = "/Users/chunming.liu/learn"
if not os.path.exists(path):
os.mkdir(path, 777) # 如果是创建多层目录,则使用os.mkdirs
else:
print("Already exists")
文件路径的操作是工程中经常遇到的。对文件路径的操作是通过os.path模块实现的。可以通过help(os.path)查看所有方法的使用方法。
>>> path = '/Users/chunming.liu/learn/learn_python_by_coding/learn_string/learn_str.py'
>>> os.path.basename(path) # 获取文件名
'learn_str.py'
>>> os.path.dirname(path) # 获取目录名
'/Users/chunming.liu/learn/learn_python_by_coding/learn_string'
>>> os.path.split(path) # 获取目录名和文件名
('/Users/chunming.liu/learn/learn_python_by_coding/learn_string', 'learn_str.py')
>>> os.path.splitext(path) #获取扩展名
('/Users/chunming.liu/learn/learn_python_by_coding/learn_string/learn_str', '.py')
>>> os.path.join("/Users/chunming.liu","learn/learn_python_by_coding/","learn_str.py") # 拼接
'/Users/chunming.liu/learn/learn_python_by_coding/learn_str.py'
通常实际工程中,会遇到求当前py文件所在的路径,这时path是__file__
。
import os
print(os.path.realpath(__file__)) # 如果是符号链接,则返回符号链接指向的文件绝对路径
print(os.path.abspath(__file__)) # 文件绝对路径
print(os.path.basename(os.path.abspath(__file__))) #当前py文件名
print(os.path.dirname(os.path.dirname(__file__))) # 当前文件名的上上级目录
案例应用,查找某个目录下特定扩展名的所有文件名,返回列表。
import os
def find_ext(work_dir, extension):
lst = []
for f in os.listdir(work_dir):
ext = os.path.splitext(f)[1]
if ext == extension:
lst.append(os.path.basename(f))
return lst
if __name__ == '__main__':
print(find_ext(os.path.dirname(__file__), '.txt'))
7.3 文件读写
open函数用于打开一个文件。
7.3.1 读取整个文件
import os
def read_file(filename):
content = None
if os.path.exists(filename): # 判断文件存在
with open(filename) as f:
content = f.read() # 一次读取整个文件
return content
if __name__ == '__main__':
print(read_file(__file__)) # 读取当前py文件内容^.^
7.3.2 一行一行读取
read 函数一次读取整个文件,readlines 函数按行一次读取整个文件。读入文件小时,使用它们没有问题。
但是,如果读入文件大,read 或 readlines 一次读取整个文件,内存就会面临重大挑战。
使用 readline 一次读取文件一行,读入一行处理一行,能解决大文件读取内存溢出问题。
假设文件 python.txt 内容如下,请你统计单词出现频次,按照频次从大到小降序:
Python 3.8.3 documentation
Welcome! This is the documentation for Python 3.8.3.
Parts of the documentation:
What's new in Python 3.8?
or all "What's new" documents since 2.0
思路及步骤:
- 每次读入一行。
- 选择正则 split 分词,注意观察 a.txt,单词间有的一个空格,有的多个。这些情况,实际工作中确实也会遇到。
- 使用 defaultdict 统计单词出现频次。
- 按照频次从大到小降序。
import re
from collections import defaultdict
word_count = defaultdict(int) # 声明一个int值型的默认字典
with open('python.txt') as f:
for line in f: # 哇哈,f可以迭代,一次取一行处理
clean_line = line.strip()
if clean_line:
sp = re.compile(r'\s+') # 多个空格
words = sp.split(clean_line) # 正则分词
for w in words:
word_count[w] += 1 # 默认字典的好处
sorted(word_count.items(), key=lambda x: x[1], reverse=True) # 排序,哇哦
print(word_count) # defaultdict(<class 'int'>, {'Python': 3, '3.8.3': 1, 'documentation': 2, 'Welcome!': 1, ......
print(word_count['Python']) # 输出3
7.3.3 写入文件
代码思路:
- 路径不存在,创建路径
- 写文件
- 读取同一文件
- 验证写入到文件的内容是否正确
import os
def write_to_file(file_path, file_name):
if not os.path.exists(file_path):
os.mkdir(file_path)
whole_path_filename = os.path.join(file_path, file_name)
to_write_content = '''
什么是 Java?
Java 是一项用于开发应用程序的技术,可以让 Web 变得更有意思和更实用。
Java 与 javascript 并不相同,后者是一种用于创建 Web 页的简单技术,只能在浏览器中运行。
'''
with open(whole_path_filename, mode="w", encoding='utf-8') as f: # 以utf-8编码方式写入文件
f.write(to_write_content)
with open(whole_path_filename, encoding='utf-8') as f:
content = f.read()
print(content)
if __name__ == '__main__':
write_to_file(os.path.dirname(os.path.abspath(__file__)), "java.txt") # 写入到当前py文件所在目录的java.txt中
7.3.4 文件的打开模式
open函数第一个参数是一个文件名称,可以使绝对路径也可以是相对当前py文件的相对路径。第二个参数是打开文件的模式,r表示以只读方式打开文件,w表示以覆盖写的方式打开文件,每次写操作都会覆盖之前的内容,x表示文件不存在的时候,先创建再写入。更多的模式可以通过通过help(open)可以查看帮助文档,很多模式可以组合使用。
========= ===============================================================
Character Meaning
--------- ---------------------------------------------------------------
'r' open for reading (default)
'w' open for writing, truncating the file first
'x' create a new file and open it for writing
'a' open for writing, appending to the end of the file if it exists
'b' binary mode
't' text mode (default)
'+' open a disk file for updating (reading and writing)
'U' universal newline mode (deprecated)
========= ===============================================================
7.3.5 with妙用
open() 函数对应 close() 函数,用了with,就不需要手动调用close()函数了,Python会自动帮我们close文件。
7.4 序列化与反序列化
写程序我们经常是处理字典、列表、集合以及各种对象,这些对象都是在内存中的。如果我们想将处理完成后的对象保存到一个文件中或者是传输到网络中?该怎么办呢?这就要用到序列化。
我们知道,在文件中或者网络中,数据都是一个个字节序列,所以必须把对象转成字节序列,接着就可以写到文件或者传输到网络中了,将对象转成字节序列的过程就是序列化。反之,从文件读取字节序列或者从网络接收字节序列,将其转成对象的过程就是反序列化。
进行序列化的主要目的是:
- 持久化保存数据;
- 跨平台跨系统的数据交互;
7.4.1 pickle模块
pickle模块是python专用的持久化模块,可以持久化包括自定义类在内的各种数据;只能在python程序之间进行数据交换。
看下pickle的官方文档,在Pycharm中,按两下shift,输入pickle则可跳转到源码文件。源码文件的开头有这样一段说明:
"""Create portable serialized representations of Python objects.
See module copyreg for a mechanism for registering custom picklers.
See module pickletools source for extensive comments.
Classes:
Pickler
Unpickler
Functions:
dump(object, file)
dumps(object) -> string
load(file) -> object
loads(string) -> object
可见pickle模块提供了四个主要的函数:
- dump——对象序列化到文件对象,就是存入文件;
- dumps——将对象序列化成string;
- load——对象反序列化,从文件读取数据;
- loads——从bytes对象反序列化;
怎么记忆到底哪个函数时序列化,哪个函数是反序列化呢?一个办法是,站在内存的角度想,从内存出去就是序列化,进入到内存中就是反序列化。下面看一个使用pickle进行序列化和反序列化的用法:
import pickle
class Person:
def __init__(self, name, age):
self.name = name
self.age = age
def __str__(self):
return "Name is {}, age is {}".format(self.name, self.age)
__repr__ = __str__
if __name__ == '__main__':
p = Person("Mike", 19)
with open("person.txt", 'wb') as wf: # 序列化,以wb模式打开文件
pickle.dump(p, wf)
with open("person.txt", 'rb') as wf: # 反序列化,以rb模式打开文件
p_obj = pickle.load(wf)
print("反序列化回来", p_obj)
7.4.2 Json模块
最常用的Json序列化,Json是一种轻量级的数据交换格式,跨平台语言,只能对python的基本数据类型进行序列化和反序列化。但是基本能满足大部分应用场景了。
在Pycharm中,按两下shift,输入json则可跳转到源码文件。源码文件中有很多例子,告诉我们如何使用。
- 序列化:Python字典对象转成json字符串,json.dumps
import json
params = { 'symbol': '123456', 'type': 'limit', 'price': 123.4, 'amount': 23}
params_str = json.dumps(params)
- 反序列化:json字符串转成Python字典,json.loads
original_params = json.loads(params_str)
- 将Python字典对象写入到文件中,json.dump
import json
params = { 'symbol': '123456', 'type': 'limit', 'price': 123.4, 'amount': 23}
with open('params.json', 'w') as fout:
params_str = json.dump(params, fout)
- 从json文件中读取字符串,转成Python字典,json.load
with open('params.json', 'r') as fin:
original_params = json.load(fin)
7.5 典型文件读写
7.5.1 ini文件
pytest.ini是Pytest提供的配置文件,用来设定pytest在运行中的行为。我的自动化测试项目中的配置如下:
[pytest]
disable_test_id_escaping_and_forfeit_all_rights_to_community_support = True
addopts = -rsxX -v --alluredir ./allure-results
markers =
slow: marks tests as slow (deselect with '-m "not slow"')
console_output_style = count
log_file_level = DEBUG
log_file = test_result.log
log_file_date_format = %Y-%m-%d %H:%M:%S
log_file_format = %(asctime)s [%(filename)s:%(lineno)d] %(levelname)s: %(message)s
log_cli = 1
log_cli_level = DEBUG
log_cli_date_format=%Y-%m-%d %H:%M:%S
log_cli_format = %(asctime)s [%(filename)s:%(lineno)d] %(levelname)s: %(message)s
- addopts用来给pytest命令自动添加命令行的选项和参数。–rsxX表示pytest报告所有测试用例被跳过、预计失败、预计失败但实际通过的原因。这几个字母表示的含义可以通过
$ pytest --help
查看reporting部分。· --alluredir是我安装的allure-pytest插件提供的参数。 - testpaths用来告诉pytest在哪里寻找测试用例。
- markers表示注册的标记,这个marker用来对测试函数进行标记,比如@pytest.mark.smoke。如果addopts中设置了–strict选项,则必须在这里注册的marker才能在测试函数中使用。
- console_output_style 用来在测试执行过程中,console上显示执行进度的方式, count表示显示执行到第几个,percentage表示执行的百分比。
- log开头的那些用来表示日志的配置。上面对日志文件和日志的console输出进行了配置。设置了日志配置,就可以在测试函数中通过下面的方法记录日志了:
import loggging
def test_the_unknown(self):
logging.info("跳过不执行,因为被测逻辑还没有被实现")
assert 0
使用pytest --help
指令可以查看pytest.ini的更多的选项:
[pytest] ini-options in the first pytest.ini|tox.ini|setup.cfg file found:
markers (linelist): markers for test functions
empty_parameter_set_mark (string):
default marker for empty parametersets
norecursedirs (args): directory patterns to avoid for recursion
testpaths (args): directories to search for tests when no files or directories
are given in the command line.
usefixtures (args): list of default fixtures to be used with this project
python_files (args): glob-style file patterns for Python test module discovery
python_classes (args):
prefixes or glob names for Python test class discovery
python_functions (args):
prefixes or glob names for Python test function and method
discovery
disable_test_id_escaping_and_forfeit_all_rights_to_community_support (bool):
disable string escape non-ascii characters, might cause
unwanted side effects(use at your own risk)
console_output_style (string):
console output: "classic", or with additional progress
information ("progress" (percentage) | "count").
xfail_strict (bool): default for the strict parameter of xfail markers when not
given explicitly (default: False)
enable_assertion_pass_hook (bool):
Enables the pytest_assertion_pass hook.Make sure to delete
any previously generated pyc cache files.
junit_suite_name (string):
Test suite name for JUnit report
junit_logging (string):
Write captured log messages to JUnit report: one of
no|log|system-out|system-err|out-err|all
junit_log_passing_tests (bool):
Capture log information for passing tests to JUnit report:
junit_duration_report (string):
Duration time to report: one of total|call
junit_family (string):
Emit XML for schema: one of legacy|xunit1|xunit2
doctest_optionflags (args):
option flags for doctests
doctest_encoding (string):
encoding used for doctest files
cache_dir (string): cache directory path.
filterwarnings (linelist):
Each line specifies a pattern for warnings.filterwarnings.
Processed after -W/--pythonwarnings.
log_print (bool): default value for --no-print-logs
log_level (string): default value for --log-level
log_format (string): default value for --log-format
log_date_format (string):
default value for --log-date-format
log_cli (bool): enable log display during test run (also known as "live
logging").
log_cli_level (string):
default value for --log-cli-level
log_cli_format (string):
default value for --log-cli-format
log_cli_date_format (string):
default value for --log-cli-date-format
log_file (string): default value for --log-file
log_file_level (string):
default value for --log-file-level
log_file_format (string):
default value for --log-file-format
log_file_date_format (string):
default value for --log-file-date-format
log_auto_indent (string):
default value for --log-auto-indent
faulthandler_timeout (string):
Dump the traceback of all threads if a test takes more than
TIMEOUT seconds to finish. Not available on Windows.
addopts (args): extra command line options
minversion (string): minimally required pytest version
讲了这么多,应该知道如何配置pytest.ini来控制pytest的执行过程了。现在回归到本节的主题,如何对ini文件进行读写。Python内置了configparser模块来支持ini文件的读写。使用起来非常简单,先来看下读取ini的方法:
import configparser
config = configparser.ConfigParser()
config.read('../pytest.ini')
print(config)
section_list = config.sections()
print(section_list)
print(config['pytest']['addopts'])
再来看一下写ini的方法,很简单,就是给section赋值1个字典:
import os
import configparser
config = configparser.RawConfigParser() # 因为pytest.ini文件中有%,所有这里要用RawConfigParser() 类
file = 'pytest.ini'
config['pytest'] = {'disable_test_id_escaping_and_forfeit_all_rights_to_community_support': True,
'addopts': '-rsxX -l --tb=short --strict -v --alluredir ./allure-results',
'markers': "\nslow: marks tests as slow (deselect with '-m \"not slow\"')",
'console_output_style': 'count',
'log_file_level': 'DEBUG',
'log_file': 'test_result.log',
'log_file_date_format': '%Y-%m-%d %H:%M:%S',
'log_file_format': '%(asctime)s [%(filename)s:%(lineno)d] %(levelname)s: %(message)s',
'log_cli': 1,
'log_cli_level': 'DEBUG',
'log_cli_date_format': "%Y-%m-%d %H:%M:%S",
'log_cli_format': '%(asctime)s [%(filename)s:%(lineno)d] %(levelname)s: %(message)s'
}
with open(file, 'a') as configfile:
config.write(configfile)
7.5.2 yaml文件
YAML文件是软件领域常用的配置文件,YAML文件后缀为 .yml。使用空格缩进表示层级关系,空格多少没关系,只要同一层级左侧对齐即可。对大小写敏感,相比ini文件,我觉得更加友好的一点是,可以用#添加注释。
YAML文件中可以存放列表、字符串、数值、对象等数据类型。目前火热的容器集群管理系统Kubernetes中,各种API的配置文件都是使用YAML格式,下面以Kubernetes中Pod对象的YAML的为例,介绍一下YAML文件的格式。
apiVersion: v1 #必选,版本号,例如v1
kind: Pod # 必选,表明这个yaml是个Pod对象
metadata:
name: nginx # 声明这个pod的名字是nginx
spec: #必选,Pod中容器的详细定义
containers: #必选,Pod中容器列表
- name: nginx #必选,容器1的名称, -开头表示列表元素
image: nginx #必选,容器的镜像名称
- name: shell #必选,容器2的名称
image: busybox
stdin: true
tty: true
这个 YAML 文件定义了两个容器:一个是 nginx 容器,一个是开启了 tty 和 stdin 的 shell 容器。
Python中处理YAML文件需要安装PyYaml包。读取上面的YAML文件,方法很简单,通过yaml.load()
将YAML文件内容转成一个字典。
import yaml
with open("pod.yml") as yml:
pod = yaml.load(yml) # pod是一个字典
print(pod)
print(type(pod)) # <class 'dict'>
上面的YAML文件转成字典是这样的:
{
'apiVersion': 'v1',
'kind': 'Pod',
'metadata': {
'name': 'nginx'
},
'spec': {
'containers': [
{
'name': 'nginx',
'image': 'nginx'
},
{
'name': 'shell',
'image': 'busybox',
'stdin': True,
'tty': True
}]
}
}
接下来处理这个字典就很方便了。在自动化测试中,测试数据用YAML格式文件来存储既方便阅读,又方便使用。例如在接口自动化测试中,HTTP的请求对象和预期结果可以放到YAML文件中:
---
tests:
- case: 验证响应中start和count与请求中的参数一致 # 这是第一条case
http: # 这是请求对象,包括请求方法method、请求头headers、请求参数params
method: GET
path: /v2/movie/in_theaters
headers:
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_5) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/80.0.3987.149 Safari/537.36
params:
apikey: 0df993c66c0c636e29ecbb5344252a4a
start: 0
count: 10
expected: # 这是预期结果,response表示接口的响应
response:
title: 正在上映的电影-上海
count: 10
start: 0
- case: 验证响应中title"正在上映的电影-北京" # 这是第二条case
http:
method: GET
path: /v2/movie/in_theaters
headers:
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_5) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/80.0.3987.149 Safari/537.36
params:
apikey: 0df993c66c0c636e29ecbb5344252a4a
start: 1
count: 5
expected:
response:
title: 正在上映的电影-北京
count: 5
start: 1