task 06 循环 && task 07 字符串

发布时间 2023-12-02 22:49:34作者: Rahull

Chap 5 循环

for 循环和循环范围

for循环的特点

基于提供的范围,重复执行特定次数的操作

def sumFromMToN(m, n):
   total = 0
   # 注意: range(x, y) 是左闭右开区间,包含 x,不包含 y
   for x in range(m, n+1): # 记住for函数后面有冒号
       total += x # total = total + x
   return total
sumFromMToN(5, 10)

45

sumFromMToN(5, 10) == 5+6+7+8+9+10

True

range() 是什么

其实在这里,我们也可以不用循环来完成同样的任务

def sumFromMToN(m, n):
   return sum(range(m, n+1))
sumFromMToN(5, 10)

45

如果我们省略第一个参数会发生什么?

def sumToN(n):
   total = 0
   # range 起始范围默认为 0
   for x in range(n+1):
       total += x
   return total
sumToN(5)

range()本质上可以认为是一个数列生成器

那如果我们添加第三个参数呢?

def sumEveryKthFromMToN(m, n, k):
   total = 0
   # 第三个参数k为 “步长” step,可以理解成等差数列的公差
   for x in range(m, n+1, k):
       total += x
   return total
sumEveryKthFromMToN(5, 20, 7) == (5 + 12 + 19)

True

如果我们想要求和的数字并不是等差的,那么就无法使用步长step来控制数字。

那可不可以换个方法控制数字?

看看这个例子:只对从 mn奇数求和

# 我们也可以通过修改循环内部的代码来改变步长

def sumOfOddsFromMToN(m, n):
   total = 0
   for x in range(m, n+1):
       if x % 2 == 1:
           total += x
   return total
sumOfOddsFromMToN(4, 10) == sumOfOddsFromMToN(5,9) == (5+7+9)

True

现在我们反着来试一下!

# 我们将生成一个反向数字序列
# (仅供演示使用,代码实践中不建议这么做)

def sumOfOddsFromMToN(m, n):
   total = 0
   for x in range(n, m-1, -1):
       if x % 2 == 1:
           total += x
   return total
sumOfOddsFromMToN(4, 10) == sumOfOddsFromMToN(5,9) == (5+7+9)

True

怎样写反向数字序列 (实际中不建议使用!!!)

当写一个反向数字序列时,step是负数,并且m>n

for i in range(10,0,-1) :
print(i)

不推荐直接写反向数字序列的原因是,可以使用reversed函数达成一样的效果

for i in reversed(range(0,10,1)) :
print(i)

for 嵌套循环

Python原生for循环语法其实是非常非常慢的,所以尽量不要使用Python写多层for循环。

# 下面的代码将输出二维坐标

def printCoordinates(xMax, yMax):
   for x in range(1, xMax+1):
       for y in range(1, yMax+1):
           print(f"( {x} , {y} ) ", end="")
       print()
printCoordinates(5, 5)

image-20231129131433055

如果换成 * 呢?

def Stars(n, m):
   # 输出一个 n*m 的星型矩阵图
   for row in range(n):
       for col in range(m):
           print("*", end="")
       print() # 这一行的作用是换行,如果没有这一行, 所有的*都在同一行
Stars(5, 5)

image-20231129131416490

为什么这里有5个\*而不是4个?因为从0到4一共是5个数字,0也是一个数字。

这里如果吧*换成emoji也能达成同样的效果哦。

换一种写法

# be careful! 这些代码与之前的有什么不同?

def printMysteryStarShape(n):
   for row in range(n):
       print(row, end=" ")
       for col in range(row): # 如果想依次控制循环内容,可以用外层变量值当做内层变量的范围
           print("*", end=" ")
       print()
printMysteryStarShape(5)

image-20231129131606917

while 循环

当你不知道循环什么时候停下来的时候,为什么不试试 while

找出一个数最左边的那一位的数值(123451

# 我不知道它什么时候停下来

def leftmostDigit(n):
   n = abs(n)
   while n >= 10: # 当while判断布尔值为真的时候,执行下一行代码,否则跳过
       n = n//10
   return n
leftmostDigit(46535248)

4

依次找出 n 个 4 或者 7 的整数倍非负整数

def isMultipleOf4or7(x):
   return ((x % 4) == 0) or ((x % 7) == 0)

def nthMultipleOf4or7(n):
   found = 0
   guess = -1 # 因为我们想要从0开始找,并且后面一步写了+=1,所以这里guess = -1
   while found <= n:
       guess += 1
       if isMultipleOf4or7(guess):
           found += 1
   return guess
print("4 或 7 的倍数: ", end="")
for n in range(15):
   print(nthMultipleOf4or7(n), end=" ")

4 或 7 的倍数: 0 4 7 8 12 14 16 20 21 24 28 32 35 36 40

可以类比听音乐,for循环就像列表播放,一直播放,直到播完整个列表才停下来。

而while循环像定时播放,每次播完一首歌看看有没有到时间,没有到时间继续播。

Bad Style:在知道循环范围的情况下使用 while

def sumToN(n):
   # 尽管它能正确运行,但是这是非常不推荐的做法!
   # 应该使用 for 循环而不是 while 循环
   total = 0
   counter = 1
   while counter <= n:
       total += counter
       counter += 1
   return total
sumToN(5) == 1+2+3+4+5

True

break 与 continue 语句

for i in range(5):
for n in range(200):
   if n % 3 == 0:
       continue # 跳过这次循环
print("hello") # 因为上面有continue跳出了该次循环,所以这一行代码不会被执行
   elif n == 8:
       break # 跳出当前整个循环(是离他最近的循环,并不是全部循环)
   else:
       pass # 啥也不做,占位符(不会被运行)
   print(n, end=" ")
print("xx")

1 2 4 5 7 xx

1 2 4 5 7 xx

1 2 4 5 7 xx

1 2 4 5 7 xx

1 2 4 5 7 xx

(假)死循环

与环境交互后,在特定条件下终止的循环

死循环,但是假的死循环。因为任何计算机的计算资源都是有限的,不存在真正的死循环

image-20231129132430304

isPrime

判断是不是质数

# 不是最快的写法,但最容易理解

def isPrime(n):
   if n < 2:
       return False # 这里不认为1是质数。(素数)
   for factor in range(2,n):
       if n % factor == 0:
           return False
   return True
for n in range(100):
   if isPrime(n):
       print(n, end=" ")

2 3 5 7 11 13 17 19 23 29 31 37 41 43 47 53 59 61 67 71 73 79 83 89 97

faster IsPrime:

# 上面的方法一共要计算98次,太慢啦
# 这种方法可以快一点

def fasterIsPrime(n):
   if n < 2:
       return False
   if n == 2:
       return True
   if n % 2 == 0:
       return False # 排除了所有偶数,少了一半的计算量了!
   maxFactor = round(n**0.5)
   for factor in range(3, maxFactor+1, 2): # 这两步也是排除一些明显不是质数的数字进入for循环
       if n % factor == 0:
           return False
   return True
for n in range(100):
   if fasterIsPrime(n):
       print(n, end=" ")

2 3 5 7 11 13 17 19 23 29 31 37 41 43 47 53 59 61 67 71 73 79 83 89 97

真的快了吗?

在讨论一个程序/算法是不是更快的前提是,它必须得是对的。

# 验证他它俩结果是一样的
for n in range(100):
   assert(isPrime(n) == fasterIsPrime(n))
print("两种解法的结果一致")

两种解法的结果一致

import time # 导入time库,可以记录时间

bigPrime = 102030407
print("Timing isPrime(",bigPrime,")", end=" ")

# isPrime
time0 = time.time()
print(", returns ", isPrime(bigPrime), end=" ")

time1 = time.time()
print(", time = ",(time1-time0)*1000,"ms\n")

# fasterIsPrime
print("Timing fasterIsPrime(",bigPrime,")", end=" ")
time0 = time.time()

print(", returns ", fasterIsPrime(bigPrime), end=" ")
time1 = time.time()

# result
print(", time = ",(time1-time0)*1000,"ms")

Timing isPrime( 102030407 ) , returns True , time = 4708.568811416626 ms

Timing fasterIsPrime( 102030407 ) , returns True , time = 0.4515647888183594 ms

明显fasterIsPrime快了很多,说明算法的提速是非常重要的。

这种操作叫做“剪枝”,可以缩小搜索空间,提升搜索速度

总结

  • For 循环用于指定范围的重复操作。

  • range() 可以生成一个数字范围。

  • 在不知道循环什么时间停止的时候,应该试试 While 循环。

  • 循环同样也是可以嵌套的。

  • 巧妙地使用 breakcontinue 语句。

  • 合理的剪枝,缩小搜索范围/循环范围,可以大幅提高程序运行效率。

Chap 6 字符串

字符串文字

四种引号

引号的作用就是将文字包裹起来,告诉 Python "这是个字符串!"

单引号 ‘ 和双引号 “是最常见的两种字符串引号,他们没有什么区别。

三个引号的情况不太常见,但是它在一些场合有特定的作用(如函数文档 doc-strings)

我们为什么需要两种不同的引号?

# 为了写出这样的句子
print("聪明办法学 Python 第二版的课程简称是 'P2S'")

聪明办法学 Python 第二版的课程简称是 'P2S’

如果我们偏要只用一种引号呢?

# 这会导致语法错误,Python 无法正确判断一个字符串的终止位置
print("聪明办法学 Python 第二版的课程简称是 "P2S"")
#     1                                 2   3 4
# Python匹配的是1和2、3和4,不会匹配2和3

SyntaxError: invalid syntax. Perhaps you forgot a comma?

字符串中的换行符

前面有反斜杠 \ 的字符,叫做转义序列(有特定含义)

比如 \n 代表换行,尽管它看起来像两个字符,但是 Python 依然把它视为一个特殊的字符

下面两个例子中的 print()在做同样的事情

# 这两个 print() 在做同样的事情 
print("Data\nwhale")  # \n 是一个单独的换行符号

Data

whale

print("""Data
whale""")

Data

whale

print("""你可以在字符串后面使用 反斜杠 `\`  来排除后面的换行。\
比如这里是第二行文字,但是你会看到它会紧跟在上一行句号后面。\
这种做法在 CIL 里面经常使用(多个 Flag 并排保持美观),\
但是在编程中的应用比较少。\
""")

你可以在字符串后面使用 反斜杠 \ 来排除后面的换行。比如这里是第二行文字,但是你会看到它会紧跟在上一行句号后面。这种做法在 CIL 里面经常使用(多个 Flag 并排保持美观),但是在编程中的应用比较少。

# 没有反斜杠 `\` 就有换行

print("""你可以在字符串后面使用 反斜杠 `\` 来排除后面的换行。
比如这里是第二行文字,但是你会看到它会紧跟在上一行句号后面。
这种做法在 CIL 里面经常使用(多个 Flag 并排保持美观),
但是在编程中的应用比较少。
""")

你可以在字符串后面使用 反斜杠 \ 来排除后面的换行。 比如这里是第二行文字,但是你会看到它会紧跟在上一行句号后面。 这种做法在 CIL 里面经常使用(多个 Flag 并排保持美观), 但是在编程中的应用比较少。

其他的转义序列

输出单个引号

print("双引号:\"")

双引号:”

输出反斜线

print("反斜线:\\")

反斜线:\

跳格

print("这个是\t制\t表\t符\n也叫\t跳\t格\t键")

这个是

也叫

转义序列只作为一个字符存在

s = "D\\a\"t\ta"
print("s =", s)
print("\ns 的长度为:", len(s))

s = D\a"t a

s 的长度为: 7

repr() vs. print()

我们现在有两个字符串

s1 = "Data\tWhale"
s2 = "Data   Whale"

它俩看起来似乎是一样的

print("s1:", s1)
print("s2:", s2)

s1: Data Whale

s2: Data Whale

但是它们真的一样吗?

s1 == s2

False

reper()就像西游记里面谛听分辨真假美猴王一样,可以显示出字符串的真身

print(repr(s1))
print(repr(s2))

'Data\tWhale'

'Data Whale’

它的作用是什么?

防止一些恶意机制破坏我们的系统。

举一个例子,假如现在要匹配用户的密码和存在服务器上的密码看看是否一致

# 密码应当大于 8 个字符,小于 16 个字符,包含大写字母、小写字母、数字和特殊符号
hack_text = 123456"\t\t\t\t\t\t\t\t\t\t\t\t\t"
print(hack_text)

123456

现在我们看设置的密码,只能看到前面的123456,看不到后面的\t。如果我们后面设置的不是\t,而是木马代码片段,那么在运行代码时就会中毒。

多行字符串作为注释

"""
Python 本身是没有多行注释功能的,
但是你可以用多行字符串实现同样的操作,
还记得我们之前学过的“表达式”吗?
多行注释本身是一个表达式,
它的原理就是 Python 会运行这个表达式,
但由于没有被赋给任何值,所以会马上扔掉!(垃圾回收机制)
"""
print("Amazing!")

Amazing!

还有在写函数时会经常使用多行字符串作[doc-strings]

一些字符串常量

import string
print(string.ascii_letters)

abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ

print(string.ascii_lowercase)

abcdefghijklmnopqrstuvwxyz

print(string.ascii_uppercase)

ABCDEFGHIJKLMNOPQRSTUVWXYZ

还可以进行判断

print("a" in string.ascii_uppercase)
print("A" in string.ascii_uppercase)

False

True

print(string.digits)

0123456789

print(string.punctuation)

!"#$%&'()*+,-./:;<=>?@[]^_`{|}~

# 打印所有Python支持的字符
print(string.printable)

# 打印所有空白字符
print(string.whitespace)

image-20231130132744073

小问号是乱码

可以用repr()看一看空白字符里都有啥

print(repr(string.whitespace))

' \t\n\r\x0b\x0c’

一些字符串的运算

字符串的加乘

print("abc" + "def")
print("abc" * 3)

abcdef

abcabcabc

print("abc" + 3)

TypeError: can only concatenate str (not "int") to str

in 运算(超级好用!!!)

print("ring" in "strings") # True
print("wow" in "amazing!") # False
print("Yes" in "yes!") # False
print("" in "No way!") # True
print("聪明" in "聪明办法学 Python") # True

True

False

False

True

True

字符串索引和切片

单个字符索引

索引可以让我们在特定位置找到一个字符

s = "Datawhale"
print(s)
print(s[0])
print(s[1])
print(s[2])
print(s[3])

Datawhale

D

a

t

a

len(s) # 查看字符串的长度

9

print(s[len(s)-1]) # 查看最后一个字母

e

# 因为是从0开始计数,所以不可以直接用s9,因为没有第十个数字
print(s[len(s)])

IndexError: string index out of range

负数索引

print(s)
print(s[-5])
print(s[-4])
print(s[-3])
print(s[-2])
print(s[-1]) # 也可以使用s[len(s)-n], 用法比如像是在循环里依次找-n位
# 负数索引是从右往左数

Datawhale

w

h

a

l

e

用切片来获取字符串的一部分

print(s[0:4]) # 依然是左闭右开
print(s[4:9])

Data

whale

print(s[0:2])
print(s[2:4])
print(s[5:7])
print(s[7:9])

Da

ta

ha

le

切片的默认参数

print(s[:4]) # 左边不写东西默认[0:4]
print(s[4:]) # 右边不写东西默认是[4:-1]
print(s[:]) # 两边都不写东西默认[0,-1]

Data

whale

Datawhale

切片的第三个参数 step

# Datawhale

print(s[:9:3]) # s[::3]
print(s[1:4:2])

Daa

aa

翻转字符串

# 可以,但是不优雅
print(s[::-1])

elahwataD

join()函数可以把序列依次添加到字符串中

# 也可以,但是还是不够优雅
print("".join(reversed(s))) # join()=[0]+""+[1]+""+……+""+[-1]

elahwataD

# 实在是太优雅辣!(只要丑陋的地方不被看见,就足够优雅)
def reverseString(s):
   return s[::-1]

print(reverseString(s))

elahwataD

字符串的循环

用索引的for循环

for i in range(len(s)):
print(i,s[i])

0 D

1 a

2 t

3 a

4 w

5 h

6 a

7 l

8 e

其实也可以不用索引(超级好用的 in)

for c in s:
   print(c)

D

a

t

a

w

h

a

l

e

也可以使用 enumerate() 获得元素的序号

for idx, c in enumerate(s):
   print(idx, c) # 等于print(inx, s[idx])
#     序号 具体内容

0 D

1 a

2 t

3 a

4 w

5 h

6 a

7 l

8 e

zip(a,b) 可以在一次循环中,分别从a和b里同时取出一个元素

for a, b in zip(s, reverseString(s)):
   print(a, b)

D e

a l

t a

a h

w w

h a

a t

l a

e D

我们也可以将enumerate()和zip(a, b)组合起来使用(比较高级,关于解包后续在元组中会进行讲解)

for idx, (a, b) in enuerate(zip(s, reverseString(s))):
   print(idx, a, b)
# 这里使用了一个解包的手法,他会先把(a, b)视为一个整体,再分别赋给a和b

0 D e

1 a l

2 t a

3 a h

4 w w

5 h a

6 a t

7 l a

8 e D

用 split() 来循环

# class_name.split() 本身会产生一个新的叫做“列表”的东西,但是它不存储任何内容

class_name = "learn python the smart way 2nd edition"
for word in class_name.split():
   print(word)

learn

python

the

smart

way

2nd

edition

class_name.split()

[’learn’ , ‘python’, ‘the’, ‘smart’, ‘way’, ‘2nd’, ‘edition’]

用 splitlines() 来循环

# 跟上面一样,class_info.splitlines() 也会产生一个列表,但不存储任何内容,但他是以行为分各单位

class_info = """\
聪明办法学 Python 第二版是 Datawhale 基于第一版教程的一次大幅更新。我们尝试在教程中融入更多计算机科学与人工智能相关的内容,制作“面向人工智能的 Python 专项教程”。

我们的课程简称为 P2S,有两个含义:

Learn Python The Smart Way V2,“聪明办法学 Python 第二版”的缩写。
Prepare To Be Smart, 我们希望同学们学习这个教程后能学习到聪明的办法,从容的迈入人工智能的后续学习。
"""
for line in class_info.splitlines():
   if (line.startswith("Prepare To Be Smart")):
       print(line)

例子:回文判断

如果一个句子正着读、反着读都是一样的,那它就叫做“回文”

def isPalindrome1(s):
   return (s == reverseString(s)) # 判断是否和倒序字符串一致
def isPalindrome2(s):
   for i in range(len(s)):
       if (s[i] != s[len(s)-1-i]): # 依次判断首尾
           return False
   return True
def isPalindrome3(s):
   for i in range(len(s)):
       if (s[i] != s[-1-i]): # 也是依次判断首尾
           return False
   return True
def isPalindrome4(s):
   while (len(s) > 1):
       if (s[0] != s[-1]):
           return False
       s = s[1:-1]
   return True

其实最推荐第一种办法,因为最简洁直观,其他只是展示各种方法

print(isPalindrome1("abcba"), isPalindrome1("abca"))
print(isPalindrome2("abcba"), isPalindrome2("abca"))
print(isPalindrome3("abcba"), isPalindrome3("abca"))
print(isPalindrome4("abcba"), isPalindrome4("abca"))

True False

True False

True False

True False

一些跟字符串相关的内置函数

str()和len()

name = input("输入你的名字: ")
print("Hi, " + name + ", 你的名字有 " + str(len(name)) + " 个字!")
# 因为字符串不可以和数字相加,所以我们用str()将数字转化为字符串

输入你的名字: Datawhale

Hi, Datawhale, 你的名字有 9 个字!

chr() 和 ord()

ASCII表相关

print(ord("A"))

65

print(chr(65))

A

print(chr(ord("A") + ord(" "))) # A的序号加上空格的序号,65+32

a

print(ord("a"))

97

# 它可以正常运行,但是不推荐使用
s = "(3**2 + 4**2)**0.5"
print(eval(s))

5.0

eval()是执行表达式里的内容,如果里面是恶意代码,就会有风险,所以尽量不要用.

# 推荐使用 ast.literal_eval()

import ast
s_safe = "['p', 2, 's']"
s_safe_result = ast.literal_eval(s_safe)
print(s_safe_result)
print(type(s_safe_result))

['p', 2, 's']

<class 'list'>

一些字符串方法

判断类型的小功能

def p(test):
   print("True     " if test else "False   ", end="")
def printRow(s):
   print(" " + s + " ", end="")
   p(s.isalnum()) # 字符或整数
   p(s.isalpha()) # 字符
   p(s.isdigit()) # 数字
   p(s.islower()) # 小写
   p(s.isspace()) # 空格
   p(s.isupper()) # 大写
   print()
def printTable():
   print(" s   isalnum isalpha isdigit islower isspace isupper")
   for s in "ABCD,ABcd,abcd,ab12,1234,   ,AB?!".split(","):
       printRow(s)
printTable()

image-20231202130836656

切换大小写

print("YYDS YYSY XSWL DDDD".lower())
print("fbi! open the door!!!".upper())

yyds yysy xswl dddd

FBI! OPEN THE DOOR!!!

删除首尾多余空格

print("   strip() 可以将字符串首尾的空格删除    ".strip())

strip() 可以将字符串首尾的空格删除

替换和删除

print("聪明办法学 Python".replace("Python", "C"))
print("Hugging LLM, Hugging LLM".replace("LLM", "SD", 1)) # count = 1,只换一次

聪明办法学 C

Hugging SD, Hugging LLM

s = "聪明办法学Python, 就找 Datawhale"
t = s.replace("聪明办法", "") # 删掉,就是替换成空值
print(t)

学Python, 就找 Datawhale

查找子串出现次数

print("This is a history test".count("is"))
print("This IS a history test".count("is")) # 大小写敏感

3

2

是否以xx开头

print("Dogs and cats!".startswith("Do"))
print("Dogs and cats!".startswith("Don't"))

True

False

是否以xx结尾

print("Dogs and cats!".endswith("!"))
print("Dogs and cats!".endswith("rats!"))

True

False

匹配起始位置

print("Dogs and cats!".find("and"))
print("Dogs and cats!".find("or")) # 没有找到就会显示-1
# 使用时必须注意-1,没有找到的情况下直接使用s[-1]会找到"!",不是我们要的"or"

5

-1

返回序列

# index和find不一样的地方在于,index没找到会报错

print("Dogs and cats!".index("and"))
print("Dogs and cats!".index("or"))

5

ValueError: substring not found

用 f-string 格式化字符串

x = 42
y = 99

print(f'你知道{x} + "{y}" 是{x+y}吗?')

你知道42 + "99" 是 141吗?

其他格式化字符串的方法

如果要格式化字符串的话,f-string是个很棒的方法,Python还有其他方法去格式化字符串

  • %操作

  • format()方法

参考资料:

字符串是不可变的

s = "Datawhale"
s[3] = "e" #Datewhale

TypeError: 'str' object does not support item assignment

只能创建一个新的字符串

s = s[:3] + "e" + s[4:]
print(s)

Datewhale

变量只是一个标签

s = 'Data'  # s 引用了字符串 “Data”
t = s      # t 只是 “Data” 的一个只读别名
s += 'whale' # 此时s已经是一个新的字符串“Datawhale"了,t的“门牌号”此时没有变化
print(s)
print(t)

Datawhale

Data

字符串是不可变的,所以它的别名也是不可变的。

t[3] = "e"

TypeError: 'str' object does not support item assignment

基础文件操作

Open() 函数

Python open() 函数用于打开一个文件,并返回文件对象,在对文件进行处理过程都需要使用到这个函数。

open(file, mode) 函数主要有 file 和 mode两个参数,其中 file 为需要读写文件的路径。mode 为读取文件时的模式,常用的模式有以下几个:

  • r:以字符串的形式读取文件。

  • rb:以二进制的形式读取文件。

  • w:写入文件。

  • a:追加写入文件。

不同模式下返回的文件对象功能也会不同。

file = open("chap6_demo.txt", "w")
dw_text = "Datawhale"
file.write(dw_text)
file.close()