测试开发之Python核心笔记(2):字符串

2.1 字符串表示

表示一个字符串就是用引号将一连串字符包裹起来,或者使用str()构造器。

  • 单引号’’
  • 双引号 “”
  • 三引号之中(’’’ ‘’'或""" “”",两者一样)
  • 构造器str()

打开Pycharm集成开发环境,输入下面的代码:

a = 'This is a string'  # 用单引号
b = "Selenium automates browsers. That's it!"  # 如果字符串内容包含单引号,那么最好用双引号将其括起来
c = """  # 用三个引号,定义多行字符串
This is a multiple lines string
This is the second line
"""
d = str('hah')  # str构造器
e = '*'*10  # 连续10个星号
print(a)
print(b)
print(c)
print(d)
print(e)

在Pycharm的编辑器窗口上,点击右键,点击Run,可以看到下面的输出:

This is a string
This is another string

This is a multiple lines string
This is the second line

hah
**********

下面这几个字符串来自string.py源码:

whitespace = ' \t\n\r\v\f'
ascii_lowercase = 'abcdefghijklmnopqrstuvwxyz'
ascii_uppercase = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ'
ascii_letters = ascii_lowercase + ascii_uppercase
digits = '0123456789'
hexdigits = digits + 'abcdef' + 'ABCDEF'
octdigits = '01234567'
punctuation = r"""!"#$%&'()*+,-./:;<=>?@[\]^_`{|}~"""
printable = digits + ascii_letters + punctuation + whitespace

想使用这些字符串,可以通过下面的方式:

import string
print(string.printable)

2.2 字符串操作

查询一下Python内置的字符串操作方法都有哪些。在Python交互式界面上,输入下面的代码:

>>> dir('')

按回车键,你会看到下面的这样的输出信息:

['__add__', '__class__', '__contains__', '__delattr__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', '__getitem__', '__getnewargs__', '__gt__', '__hash__', '__init__', '__init_subclass__', '__iter__', '__le__', '__len__', '__lt__', '__mod__', '__mul__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__rmod__', '__rmul__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__', 'capitalize', 'casefold', 'center', 'count', 'encode', 'endswith', 'expandtabs', 'find', 'format', 'format_map', 'index', 'isalnum', 'isalpha', 'isascii', 'isdecimal', 'isdigit', 'isidentifier', 'islower', 'isnumeric', 'isprintable', 'isspace', 'istitle', 'isupper', 'join', 'ljust', 'lower', 'lstrip', 'maketrans', 'partition', 'replace', 'rfind', 'rindex', 'rjust', 'rpartition', 'rsplit', 'rstrip', 'split', 'splitlines', 'startswith', 'strip', 'swapcase', 'title', 'translate', 'upper', 'zfill']

这些都是Python为字符串类型的数据提供的内置方法。这些方法的命名让我们非常直观的猜测的其用途,我们自己命名变量和函数时,也最好起有意义的名字。

从上面的输出中看到lower这个函数,猜测可能用来将一个字符串中的大写字母转成小写字母用的。为了验证这一点,可以输入下面的命令具体查看一下:

>>> help(''.lower)

按回车键,将看到这个函数的用法信息:

Help on built-in function lower:

lower() method of builtins.str instance
    Return a copy of the string converted to lowercase.

从描述中看到,lower函数可以将一个字符串转换成小写字母。实际编写一段代码验证一下:
从描述中看到,lower函数可以将一个字符串转换成小写字母。实际编写一段代码验证一下:

>>> 'HADFADFA'.lower()
'hadfadfa'

哈哈,符合我们的猜测。接下来,介绍几种在工作中对字符串的几种常见操作。

2.2.1 读取字符或子串

通过索引和切片的方式读取字符串中的字符或者子串。

name = 'jason'
print(name[0])  # 输出j
print(name[1:3])  # 输出as,下标从1到3,但不包含3

2.2.2 反转

利用切片操作,或者reversed函数,将字符串反转:

name = 'Python'
print(name[::-1])
print(''.join(reversed(name)))  # reversed返回的是一个reversed object,要join一下

2.2.3 替换

name = 'jmeter'
name = name.replace('j', 'J')
print(name)  # Jmeter

2.2.4 拼接

两种拼接方法,一种是加号,一种是join方法。

加号拼接,就是直接将两个字符串连接在一起:

name = 'automation'
gender = 'testing'
print(name+gender)  # 输出automationtesting

另一种join方法,可以将多个独立的字符或者字符串,或者一个列表里面的数据,用特定的字符拼接起来。

print(' '.join([name, gender]))  # 输出automation testing ,中间一个空格
print('&'.join([name, gender]))  # 输出automation&testing ,中间一个&

2.2.5 分割

通过内置的split方法,可以将做一个字符串切分成一段一段的,放到一个列表中。实际工作中,再通过索引操作取得想要的数据。比如将Python单元测试框架Pytest的slogan用空格分割成列表:

string="pytest: helps you write better programs"  # 每个单词之间用一个空格隔开
print(string.split(' '))  # 输出 ['pytest:', 'helps', 'you', 'write', 'better', 'programs']

再来看一个例子,从web自动化测试工具selenium下载网址中提取域名:

path = 'https://www.selenium.dev/downloads/'
namespace = path.split('//')[1].split('/')[0] 
print(namespace))  # 返回'www.selenium.dev'

如果单词之间用多个空格,或者其他空白符隔开时,那么不给split方法传参就可以了:

pytest="pytest:   helps you write \t better \n programs" # helps前面有多个空格
print(pytest.split()) # ['pytest:', 'helps', 'you', 'write', 'better', 'programs']

这是因为split方法默认是使用ASCII whitespace characters对字符串进行分割的。具体描述可以参考split函数的官方文档(在Pycharm中通过command+点击split方式进入)。

2.2.6 去掉字符串两边的内容

在接收用户输入时,避免用户在输入数据两边意外加入空格或者其他空白符,可以对收到数据两边的空白(空格和制表符)进行去除,例如:

s = ' The Selenium Server is needed in order to run Remote Selenium WebDriver.\t'
print(s.strip())  # 输出'The Selenium Server is needed in order to run Remote Selenium WebDriver.'
print(s.rstrip())  # 输出 ' The Selenium Server is needed in order to run Remote Selenium WebDriver'
print(s.lstrip())  # 输出 'The Selenium Server is needed in order to run Remote Selenium WebDriver. '

还可以删除其他字符,将待删除的字符作为参数,比如:

s = '[Locust is an easy-to-use, distributed, user load testing tool.]'
print(s.rstrip(']'))  # 输出 '[Locust is an easy-to-use, distributed, user load testing tool.'
print(s.lstrip('['))  # 输出 'Locust is an easy-to-use, distributed, user load testing tool.]'

2.2.7 子串判断

判断 a 串是否为 b 串的子串。

selenium = "Selenium automates browsers. That's it!"
b = 'browsers'
r = True if b in selenium else False

2.2.8 子串查找

在主串中查找子串,返回主串中匹配子串的最小索引。

a = 'co'
b = 'Locust is completely event-based, and therefore it’s possible to support thousands of concurrent users on a single machine.'

print(b.find(a))  # 默认从下标0开始查找,返回10
print(b.find(a, 20))  # 从下标20开始查找,返回86

2.2.8 统计个数

'Locust supports running load tests distributed over multiple machines.'.count('o')  # 4

2.2.9 首字母大写

"split the argument into words using split".capitalize()  # 'Split the argument into words using split'

2.3 格式化

两种方式,一种是使用%,另外一种是使用format函数。

2.3.1 %格式化

%s表示字符串,%d表示十进制数,%%表示百分号。

print("The %s application is open source software, a %d%% pure Java application designed to load test functional behavior and measure performance. " % ("Apache JMeter™", 100))

2.3.2 format格式化

format格式化,需要在字符串模板中用{}占位,如果有多个{},format函数中的参数将按顺序传递给{}。例如:

print('{} is compatible with Java {} or higher.'.format('JMeter', 8))

Jmeter传递个第一个{},8传递给第二个{}。

字符串模板中{}里面还可以指定变量名,然后通过传入字典或者变量值给format函数,例如:

welcome_str = '{tool} is a tool that aims to bring the best of all packaging worlds (bundler, composer, npm, cargo, yarn, etc.) to the {language} world.'
welcome_dic = {'tool': 'Pipenv', 'language': 'Python'}
print(welcome_str.format(**welcome_dic))   # 传入字典
print(welcome_str.format(tool='Pipenv', language='Python'))  # 传入变量值

2.3.3 f字符串

f字符串是一种更加容易的、简便的格式化方法,在Python 3.6开始加入标准库。在一个字符串开头有一个大写的F或者小写的f,大括号中的变量被值替换。注意:!,:{} ;不能出现在{}里面否则会报错。

startwith = True
otherwise = False
print(f"str.startswith Return {startwith} if string starts with the prefix, otherwise return {otherwise}.")

更加强大的是,大括号{}可以对表达式求值,或者执行函数。

print(f"{ 2 * 3 + 1}")
name='pytest'
print(f"{name.title()} is a mature full-featured Python testing tool that helps you write better programs.")

2.4 不可改变性

Python字符串是不可改变的,Python的不可改变性体现在:

  • 不能按索引修改字符串的值,比如name[0]=‘J’
  • 通过加号修改字符串,并不是修改了源字符串而是新建了新的字符串。

下面这段代码,开始时通过id()查看变量str1的身份是4314674880,与str2拼接之后,str1指向了新的字符串,而不是在原来的str1上修改的。再次查看str1的身份,已经是4316047728了,与最开始身份已经不同了,说明不是原来的对象了。

str1="Selenium is not just one tool or API "
str2="but it composes many tools."
print(id(str1))   # 输出4314674880
str1+=str2
print(str1)  # 输出Selenium is not just one tool or API but it composes many tools.
print(id(str1))  # 输出 4316047728,可见str1的身份已经变了

2.5 转义字符

如果想在字符串中输入一些特殊字符,需要借助转义字符。比如:

  • '\n’表示换行符;
print("Hello\nWorld")  # Hello 和World会放在两行
  • '\"'表示双引号
print("Hello\"World\"")  # 输出 Hello"World"
  • 原始字符串

如果要取消转义字符的效果,可以在字符串前面添加一个r

print(r"Hello\"World\"")  # 原样输出Hello\"World\"

2.6 字符串加密

对字符串进行加密,可以使用hashlib库。

import hashlib
print(hashlib.md5("Nobody inspects".encode('utf-8')).hexdigest()) # md5加密
print(hashlib.sha224("Nobody inspects the spammish repetition".encode('utf-8')).hexdigest()) # sha224加密

2.7 正则表达式

字符串封装的方法,处理一般的字符串操作,还能应付。但是,稍微复杂点的字符串处理任务,需要靠正则表达式,简洁且强大。

Python中的正则,是通过模块re来支持的,因此使用正则表达式时,要先import re导入re模块。

首先,认识常用的元字符

  • . 匹配除 “\n” 和 “\r” 之外的任何单个字符。
  • ^ 匹配字符串开始位置
  • $ 匹配字符串中结束的位置
  • * 前面的原子重复 0 次、1 次、多次
  • ? 前面的原子重复 0 次或者 1 次
  • + 前面的原子重复 1 次或多次
  • {n} 前面的原子出现了 n 次
  • {n,} 前面的原子至少出现 n 次
  • {n,m} 前面的原子出现次数介于 n-m 之间
  • ( ) 分组,输出需要的部分

再认识常用的通用字符:

  • \s 匹配空白字符
  • \w 匹配任意字母/数字/下划线
  • \W 和小写 w 相反,匹配任意字母/数字/下划线以外的字符
  • \d 匹配十进制数字
  • \D 匹配除了十进制数以外的值
  • [0-9] 匹配一个 0~9 之间的数字
  • [a-z] 匹配小写英文字母
  • [A-Z] 匹配大写英文字母

正则表达式,常会涉及到以上这些元字符或通用字符。

2.7.1 匹配

  1. 使用正则模块,search 方法,返回最早匹配的位置
import re

s = 'Life is short, I use Python'
pattern = 'Python'
r = re.search(pattern, s)
print(r.span())  # (21, 27)

其实,正则模块中还有一个match方法,不过它只会从主串的第一个字符开始匹配。第一个字符不匹配就返回None。

recom = re.compile('Python')
print(recom.match(s))  # 返回 None,找不到匹配,match只能匹配Python开头的字符串
  1. findall查找出子串的所有匹配
import re

s = "当月最后一天:2019-12-31"
pattern = r"\d+"
r = re.findall(pattern, s)
print(r)
  1. finditer返回匹配的迭代器

findall如果匹配的数据太多,可能导致内存问题。可以使用finditer方法,返回一个迭代器,通过迭代器里面的对象 re.Match的 span 找出匹配位置。

import re

s = "当月最后一天:2019-12-31"
pattern = r"\d+"
r = re.finditer(pattern, s)
for i in r:
    print(i.span())

如果打算一个模式可以被多次使用,那么可以提前将模式pattern编译成对象,另外,优先编译成正则对象,然后再进行匹配,这样程序的效率更高。例如下面这样:

import re

s = "当月最后一天:2019-12-31"
pattern = re.compile(r"\d+")  # 编译成模式对象,以后可以多次使用
r = pattern.findall(s)
print(r)

s2 = "我的生日是:1990-12"
print(pattern.findall(s2))
  1. 匹配浮点数和整数
  • ? 表示前一个字符匹配 0 或 1 次
  • \.? 表示匹配小数点(.)0 次或 1 次。注意要叫一个反斜杠,表示后面的是小数点,而不是正则中的.
import re

s = "The most popular tools for Python development are PyCharm and VScode. The combined share of the PyCharm Community and Professional editions is 33%. And VS Code has experienced rapid growth. It started with 7.1% in 2017 and achieved 24% in 2019."
pattern = re.compile(r"\d+\.?\d*")  # \d+表示至少一个数字,\d*表示0个、1个或多个数字
r = pattern.findall(s)
print(r)
  1. 写出匹配所有正整数正则表达式。
import re

s = [-16, 1.5, 11.43, 10, 5, 0]

pattern_1 = r"^\d*$"  # 匹配到[10, 5, 0],多了0
print([i for i in s if re.match(pattern_1, str(i))])

pattern_2 = r"^[1-9]\d*"  # 匹配到 [1.5, 11.43, 10, 5],多了小数点,理解$,完全匹配的作用
print([i for i in s if re.match(pattern_2, str(i))])

pattern_2 = r"^[1-9]\d*$"  # 正确
print([i for i in s if re.match(pattern_2, str(i))])   # 正则只能用到字符串上,所以str()

  1. 找出字符串中所有字符 t 或 T 的位置,不区分大小写。
import re

s = "Selenium automates browsers. That's it!"
pattern = re.compile(r't')
print([i.span() for i in pattern.finditer(s, re.I)])  # [(11, 12), (15, 16), (32, 33), (37, 38)]

2.7.2 分割

如果一个规则简单的字符串,直接使用字符串,split 函数。

如下requests入门文档网址字符串,可以很方便通过split方法根据分割符 / 进行分割:

s = 'https://requests.readthedocs.io/en/master/user/quickstart/'
s.split('/')  # ['https:', '', 'requests.readthedocs.io', 'en', 'master', 'user', 'quickstart', '']

对于分隔符很复杂的字符串,split 函数就无能为力。这时就要用正则分割了。通过包含多个空格的句子中,单词个数,就用这个方法。

import re

s = 'This,,,   module ; \t   provides|| regular ; '  # 这里的分割符有, 空白(空格和制表符\t),|
pattern = r'[,\s;|]+'  #多个分割符放到[]括号中,+表示1个或多个前面的那些分割符,\s表示空白字符
words = re.split(pattern, s)
print(words)

2.7.3 替换

正则模块,sub 方法,替换匹配到的子串:

import re

content = "Life is short, I love Python, Python is the best programming language!"
pattern = re.compile('Python')  # 编译模式串
substitution = pattern.sub('PHP', content)  # 替换
print(substitution)

2.7.4 compile 预编译

如果要用同一匹配模式,做很多次匹配,可以使用 compile 预先编译串。

案例:从一系列字符串中,挑选出所有正浮点数。

import re

s = [-16, 'good', 1.5, 0.2, -0.1, '11.43', 10, '5e10']
rec = re.compile(r'^[1-9]\d*\.\d*|0\.\d*[1-9]\d*$')
print([i for i in s if rec.match(str(i))])  # 直接使用 rec,匹配列表中的每个元素,不用每次都预编译正则表达式,效率更高。

2.7.5 贪婪模式与非贪婪模式

现在想要在网页中提取 div 标签中的内容,需要用到括号()。

import re

content = """正则表达式谈贪婪模式与非贪婪模式
贪婪模式
非贪婪模式
"""
pattern_1 = r"
(.*)
"
# 贪婪模式,尽量多吃 r = re.findall(pattern_1, content) # ['贪婪模式
非贪婪模式'] print(r) pattern_2 = r"
(.*?)
"
# 非贪婪模式,少吃多餐 r = re.findall(pattern_2, content) # ['贪婪模式', '非贪婪模式'] print(r)

使用一对 () 去获取我们想要的部分。

  • (.*) 表示捕获任意多个字符,尽可能一次性地匹配更多字符,也被称为贪婪模式。尽量匹配更长的结果。

  • (.*?)被称为非贪婪模式。尽量匹配更多的结果。

2.8 面试题

  1. 从字符串 "https://httpbin.org/get?key1=value1 &key2=value2& key2=value3"中,提取出所有的参数键值对。
import re
s = "https://httpbin.org/get?key1=value1 &key2=value2& key2=value3"
pattern = re.compile(r"[?&\s]+")
print(pattern.split(s)[1:])
  1. 如何判断一个URI是一个图片的地址,我们假设以http或者https开头,以合法的图片后缀名结尾的URI就是合法的图片地址。

    比如https://requests.readthedocs.io/zh_CN/latest/_static/requests-sidebar.png就是合法的图片地址,因为是以png图片格式结尾的。https://farm5.staticflickr.com/4259/35163667010_8bfcaef274_k_d.jpg也是。

def is_network_picture(uri):
    if uri.endswith((".png", ".jpg", ".svg")) and uri.startswith(("http", "https")):
        return True
    else:
        return False
  1. 提取字符串"urllib3 [required: <1.23,>=1.21, installed: 1.22]"中所有的版本号。
import re

s = "urllib3 [required: <1.23,>=1.21, installed: 1.22]"
pattern = re.compile(r'(\d+.\d+)')

print(pattern.findall(s))
  1. 将字符串"pipenv is released at 5/28/2020" 中的日志改成2020-5-28这种格式。
import re

s = "pipenv is released at 5/28/2020"
pattern = re.compile(r'(\d+)/(\d+)/(\d+)')  # 三个匹配组,第一个是月,第二个是日子,第三个是年份

print(pattern.sub(r'\3-\1-2', s))  # 反斜杠加数字形式,表示匹配组的第一个,例如\3表示匹配组的第三个
  1. 删除文本文件中每一行文本前后的空白字符。
with open(filename) as f:
    for line in f:
        line.strip()
  1. 将一个句子中的所有单词位置反转。例如"Requests is an elegant and simple HTTP library for Python" 句子反转后是"Python for library HTTP simple and elegant an is Requests"。
s = "Requests is an elegant and simple HTTP library for Python"
print(" ".join(s.split(' ')[::-1]))
  1. 求字符串中最后一个单词的长度,单词之间用空格隔开。
len(s.split(' ')[-1])
  1. 有两个字符串,请从第一个字符串中删除第二个字符串中的字符。例如第一个字符串是"What you do with that power is entirely up to you.",第二个字符串是"aeio"。
first_str = "What you do with that power is entirely up to you."
second_str = "aeio"

for s in second_str:
    for f in first_str:
        if s == f:
            first_str = first_str.replace(s, "")

print(first_str)
  1. 定义一个函数 f(s),其中传入参数 s 是一个非空字符串;该函数的功能是统计 s 中(按字典序比较)最小字母的出现频次。
def count_smallest_char(s: str) -> int:
    s = sorted(s)
    return s.count(s[0])
  1. 将一个字符串中每个单词的首字母大写,其他字目小写。例如:" aBc dEf " -> “Abc Def”.
' '.join(x.capitalize() for x in s.split())

可以参考Python内置的string.py模块中的capwords函数的实现。

你可能感兴趣的:(Python,python)