# String(字符串)
# 定义
string(字符串)是由零个或多个字符组成的有限序列。几乎每种高级语言中都有这个类型,它是编程语言中表示文本的数据类型。string 在数据结构中又叫 “串”,串通常会连带着有一些基本的操作,比如在串中查找某个子串(模式匹配)、求取一个子串、根据某个字符拆分、在串的某个位置上插入一个子串以及删除一个子串等等。在 Python 中,字符串属于不可变对象,不支持原地修改,如果需要修改其中的值,只能重新创建一个新的字符串对象。如果确实需要一个支持原地修改的 unicode 数据对象,可以使用 io.StringIO 对象或 array 模块。通过 io.StringIO 对象的 seek () 方法定位并调用其 write () 方法重新写入新串。对于字符串是否可变,我们可以通过 id 函数查看变量的内存地址进行验证(或者使用十六进制 hex (id (i)) 可以查看 16 进制的内存地址)。
def string_immutable_test(): | |
from io import StringIO | |
""" | |
1.从下面测试来看,可以看出Python对字符串常量做了缓存 | |
2.表明python类似java一样存在一个字符串常量池,对于不可变对象可以重复利用 | |
3.使用str(num)创建字符串后id不同,说明python对于类型转换的变量不会缓存在常量池中 | |
""" | |
s1 = '123' | |
s2 = '123' | |
print(id(s1) == id(s2)) # True | |
print(s1 is s2) # True | |
s3 = str('123444') | |
s4 = str('123444') # 这里并不会开辟新的地址,s4 和 s3 指向同一块内存区域 | |
print(id(s3) == id(s4)) # True | |
print(s3 is s4) # True | |
s5 = str('1') | |
s6 = str('1') | |
print(id(s5) == id(s6)) # True | |
print(s5 is s6) # True | |
s7 = str(5) # 这里意思是将一个 number 类型数字转换为 string | |
s8 = str(5) | |
print(id(s7) == id(s8)) # False | |
print(s7 is s8) # False | |
s9 = str(1) | |
s10 = str(1) | |
print(id(s9) == id(s10)) # False | |
print(s9 is s10) # False | |
s11 = 'hello' | |
s12 = str('hello') | |
print(id(s11) == id(s12)) # True | |
print(s11 is s12) # True | |
name_io = StringIO() | |
name_io.seek(0) | |
name_io.write('jones') | |
print(name_io.getvalue()) | |
name_io.seek(0) | |
name_io.write('jalen') | |
print(name_io.getvalue()) # 原地修改字符串 | |
print('String object has changed.') | |
name_io.close() # 关闭 io 流,内存释放 | |
if __name__ == '__main__': | |
string_immutable_test() |
# 转义字符
在计算机科学与远程通信中,当转义字符(Escape Character)放在字符序列时,它将对它后续的几个字符进行替代并解释。转义字符是元字符的一种特殊情况。通常,判定某字符是否为转义字符由上下文确定。转义字符即标志着转义序列开始的那个字符。
Python 中常用的转义字符都是以反斜线 “\” 开始的,这和 C 语言、java 等其他一些高级语言的转义字符起始一样,不过需要注意的是,在 URI 协议中,转义字符是百分号 “%”,因为 urls 在网络传输中只能以 ASCII 字符集编码,对于超出 ASCII 编码的字符必须转换为 ASCII,所以 URL 编码将这些不安全的字符替换为以 “%” 开头,并且后面跟随两个十六进制数字,比如 url 中不能包含空格,如果出现了空格,url 会自动将空格替换为 “%20”。由于字符集编码的问题,也因此有时候我们在开发过程中会遇到 get 请求乱码,这些乱码通常就是因为受 URI 协议的约束,在我们传递了一些 url 无法识别的参数时,给了我们一个错误的输出,从而导致后端接受参数时出现乱码现象(具体 URI encoding 参考:https://www.w3schools.com/tags/ref_urlencode.ASP#::text=ASCII%20Encoding%20Reference)。注意区分 uri 转义和 html 转义是不一样的,html 转义通常是以 “&” 开始的(参考:https://en.wikipedia.org/wiki/HTML#::text=Example%20HTML%20Escape%20Sequences)。
转义字符 | 描述 |
---|---|
(在行尾时) | 续行符 |
\\ | 反斜杠符号 |
\' | 单引号 |
\" | 双引号 |
\a | 响铃 |
\b | 退格 (Backspace) |
\e | 转义 |
\000 | 空 |
\n | 换行 |
\v | 纵向制表符 |
\t | 横向制表符 |
\r | 回车 |
\f | 换页 |
\oyy | 八进制数,y 代表 0~7 的字符,例如:\012 代表换行。 |
\xyy | 十六进制数,以 \x 开头,yy 代表的字符,例如:\x0a 代表换行 |
\other | 其它的字符以普通格式输出 |
上面只是罗列了一些常用的转义字符,更多转义字符可以从 https://charbase.com 查看,这个网站是一个可视化的 unicode 数据库,上面有很多可爱的图标供您选择。
def show_escape_sequences(): | |
# 续行符 | |
s1 = 'Today is yesterday, ' \ | |
'and yesterday is tomorrow, ' \ | |
'so what date is tomorrow?' | |
print(s1) | |
# 打印反斜杠符号 | |
s2_1 = 'Cat eat fish, \\fish eat shrimp.' | |
print(s2_1) | |
s2_2 = 'Cat eat fish, \fish eat shrimp.' | |
print(s2_2) | |
# 单引号 | |
s3 = 'I\'m a boy.' | |
print(s3) | |
# 双引号 | |
s4 = "I\"m a boy." | |
print(s4) | |
# 响铃 | |
s5 = '\a Get up!' | |
print(s5) | |
if __name__ == '__main__': | |
show_escape_sequences() |
# 字符串的一些基本操作
这里列举一些经常使用的字符串操作,方便使用时拿来即用。首先列一些基本的操作符号。然后详细写出一些 demo。
操作符 | 描述 |
---|---|
+ | 字符串拼接 |
* | 重复输出字符串(或复制字符串) |
[] | 通过索引获取字符串中字符(通过下标获取,从 0 开始) |
[ : ] | 截取字符串中的一部分(比如截取第 2 位到第 5 位之间的数据,不包含第 2 位,可写成 [2:5]) |
in | 成员运算符 - 如果字符串中包含 in 前面的字符会返回 True |
not in | 成员运算符 - 如果字符串中不包含 in 前面的字符会返回 True |
r'xxx' | 原始字符串,没有转义。 即在字符串的第一个引号前加上字母 "r"(可以大小写)。在定义正则表达式时可能会用到 |
% | 格式化字符串,通常用于输出,或者拼接 |
def show_string_operation():
s1 = 'a' + 'b' # 'ab'
s2 = 'a' * 2 # 'aa'
s3 = 'abc'[0] # 'a'
s4 = 'abc'[1:2] # 'b'
s5 = 'a' in 'abc' # True
s6 = 'd' not in 'abc' # True
s7 = r'\not bad' # '\\not bad'
s8 = 'a%sc' % 'b' # 'abc'
print('End!')
if __name__ == '__main__':
show_string_operation()
# 1. 字符串拼接
字符串拼接是指将两个或者多个字符串连成一个字符串,就是组装字符串的操作。
def show_string_append(): | |
# 1. 使用 '+' 号拼接 | |
string_1 = 'cat' | |
string_2 = 'fish' | |
introduce = string_1 + ' like ' + string_2 | |
print(introduce) # cat like fish | |
# 2. 如果不是变量形式,可以直接书写(中间可以带或者不带空格) | |
string_3 = 'cat' ' like ' 'fish' | |
print(string_3) # cat like fish | |
string_4 = 'cat'' like ''fish' | |
print(string_4) # cat like fish | |
# 3.list 列表转换为 str | |
list_1 = ['cat', 'like', 'fish'] | |
string_5 = ' '.join(list_1) | |
print(string_5) # cat like fish | |
# 4. 使用占位符拼接 | |
string_6 = '%s like %s' % ('cat', 'fish') | |
print(string_6) # cat like fish | |
if __name__ == '__main__': | |
show_string_append() |
# 2. 字符串包含
字符串包含关系常常用于判断逻辑,比如判断一篇文章中是否出现 “吴亦凡” 的名字,或者当我们使用爬虫爬出想要的数据后对热点数据进行筛选。
def show_string_contains(): | |
string_foo = 'I always cheat my teammates when playing LOL!' | |
string_bar = 'cheat' | |
# 1. 使用 in 判断是否包含 | |
if string_bar in string_foo: | |
print('foo contains bar') | |
# 2.1 可以借助 find, find () 返回匹配到的第一个 mapping 上字符的下标(从 0 开始,第一位是 0), | |
if string_foo.find(string_bar) >= 0: # 此处返回下标为 9 | |
print('foo contains bar') | |
# 2.2 相反,可以借助 rfind () 返回匹配到的最后一个 mapping 上的字符的下标 | |
if string_foo.rfind(string_bar) >= 0: # 此处返回下标为 9 | |
print('foo contains bar') | |
# 3. 借助 re 模块的 findall 方法,如果返回的列表不为空,说明有匹配上 | |
import re | |
if re.findall(string_bar, string_foo, flags=re.I): # re.I 中的 I 意思是 ignorecase,表示忽略大小写 | |
print('foo contains bar') | |
# 4. 判断字符串从子串开始 | |
if string_foo.startswith('I always cheat'): | |
print('foo start with bar') | |
# 5. 判断字符串从子串结束 | |
if string_foo.endswith('LOL!'): | |
print('foo end with LOL!') | |
# 6.1 返回第一个匹配到的子字符串的索引号,注意如果不包含会抛出 ValueError 异常 | |
if string_foo.index(string_bar) >= 0: | |
print('foo contains bar') | |
# 6.2 返回最后一个匹配到的子字符串的索引号 | |
if string_foo.rindex(string_bar) >= 0: | |
print('foo contains bar') | |
if __name__ == '__main__': | |
show_string_contains() |
# 3. 字符串拆分
有一些字符串可能使用相同的间隔符隔开了,此时我们想要拆开来做一些操作,那么我们就可以使用下面一些方式拆分这个字符串为一个一个小的字符串。
def show_string_split(): | |
# 1. 使用内置的 split 函数拆分 | |
string_foo = 'Jalen,Jones,Jady' | |
list_foo = string_foo.split(',') | |
print(list_foo) | |
# 2. 使用 re 模块的 split 函数拆分 | |
import re | |
list_foo = re.split(',', string_foo) | |
print(list_foo) | |
# 3. 使用 re 模块的 split 函数通过正则表达式拆分 | |
text = "python is, an easy; language; to, learn. right? Yes. " | |
list_text = re.split(r'; |, |\\? |. ', text) | |
print(list_text) | |
# 4. 根据换行符 ('\r', '\r\n', \n') 按行拆分,适用于文章拆分 | |
# 注:通常 Linux 的换行符是 \n,而 Windows 的换行符是 \r\n | |
text = 'Hello Jones, \rI am new here!\n Thanks! \r\n My pleasure!' | |
split_text = text.splitlines() | |
print(split_text) | |
if __name__ == '__main__': | |
show_string_split() |
# 4. 字符串大小写转换
这个没啥可讲的,就是转换大小写,python 里转换大小写的花样比较多,也许能用上。
def show_lower_upper_transfer(): | |
text = "I am a boy!" | |
# 1. 全部转大写 | |
upper_text = text.upper() | |
print(upper_text) # I AM A BOY! | |
print(upper_text.isupper()) | |
# 2. 全部转小写 | |
lower_text = text.lower() | |
print(lower_text) # i am a boy! | |
print(lower_text.islower()) | |
# 3. 让句子中的所有单词都是以大写字母开始 | |
upper_camel = text.title() | |
print(upper_camel) # I Am A Boy! | |
print(upper_camel.istitle()) | |
# 4. 大小写字母反转 (大写变小写,小写变大写) | |
swap_text = text.swapcase() | |
print(swap_text) # i AM A BOY! | |
# 5. 把第一个字母转化为大写字母,其余小写 | |
text = 'i am a boy!' | |
cap_text = text.capitalize() | |
print(cap_text) | |
if __name__ == '__main__': | |
show_lower_upper_transfer() |
# 内存模型
串的两种最基本的存储方式是顺序存储方式和链式存储方式。
尝试执行以下代码:
if __name__ == '__main__': | |
string = 'abc' | |
print(id(string)) # 1947333829272 | |
print(id(string[0])) # 1947333727544 | |
print(id(string[1])) # 1947333709416 | |
print(id(string[2])) # 1947333592936 | |
string1 = 'abc' | |
print(id(string1)) # 1947333829272 和 string 地址相同,即 string1 和 string 指向同一内存地址 |
有人说不能通过 id 盲目比较,因为 id 所指向的对象有可能是一个临时对象,而这里的 string 通过下标分离出来的 string [0] 就是一个临时对象,所以 id (string)!=id (string [0]),并且因为是临时对象,当上一个临时对象释放(GC)掉以后,再次查看该内存,可能会发现又是一个新的对象,即便内存地址是相同的。
在 java 中,变量的内存分布有两种,一种是指针碰撞,即通过一个指针的移动判断哪片内存空间是空闲的,然后将对象放到内存中。还有一种是通过维护一个内存的空闲列表,在即将分配内存的时候通过空闲列表找到这片空闲区域(来自《深入理解 Java 虚拟机》)。我想在 python 中对象的内存分配大致也是这样的,下面接着验证。