第5章 数 学 运 算 本章的主要内容如下: 常用运算符; 浮点数的扩展功能; 指数运算与开平方根; 三角函数; 生成随机数; 常见的统计学函数; 日期/时间运算。 5.1运算符 5.1.1算术运算符 案例88四则计算器 四则运算符属于双目运算符——带有两个操作数,其格式一般为 <左侧操作数> [运算符] <右侧操作数> 以下表达式皆为四则运算符的运用: a + b# 加法 c – d# 减法 5 * 8# 乘法 var1 / var2# 除法 但以下运算方式是不允许的,因为缺少右侧操作数。 5 + b * 以下表达式并非四则运算,而是数值的符号(正值或负值)。 + 26 - 100 本案例实现一个简单的四则运算器。首先,用户输入两个浮点数,然后选择运算方式(加、减、乘、除),最后输出运算结果。 步骤1: 获取用户输入的两个浮点数值,类型为字符串。 num1_str = input('请输入第一个数值:') num2_str = input('请输入第二个数值:') 步骤2: 将输入的两个字符串类型的数值转换为float类型。 num1_fl = float(num1_str) num2_fl = float(num2_str) 步骤3: 通过键盘输入选择运算方式。 flag = input('您的选择是:') if flag == 'a': opr_char = '+' result = num1_fl + num2_fl elif flag == 'b': opr_char = '-' result = num1_fl - num2_fl elif flag == 'c': opr_char = '×' result = num1_fl * num2_fl elif flag == 'd': opr_char = '÷' result = num1_fl / num2_fl else: opr_char = '>﹏<' result = 0.0 如果输入的是“a”,则表示加法运算,“b”表示减法运算,“c”表示乘法运算,“d”表示除法运算。 步骤4: 输出运算结果。 print('{0:.2f} {1:s} {2:.2f} = {3:.2f}'.format(num1_fl, opr_char, num2_fl, result)) 调用format方法对字符串进行格式化,格式控制符“.2f”表示以浮点数值方式呈现字符串,并且保留两位小数。 步骤5: 运行案例代码,输出结果如下: 请输入第一个数值:15.7 请输入第二个数值:8.3 请选择运算方式: 加法运算请输入"a"; 减法运算请输入"b"; 乘法运算请输入"c"; 除法运算请输入"d" 您的选择是:a ---------------------计算结果-------------------- 15.70 + 8.30 = 24.00 请输入第一个数值:0.35 请输入第二个数值:12.3 请选择运算方式: 加法运算请输入"a"; 减法运算请输入"b"; 乘法运算请输入"c"; 除法运算请输入"d" 您的选择是:c ---------------------计算结果-------------------- 0.35 × 12.30 = 4.30 案例89指数运算符 指数运算除了使用pow函数,还可以使用更为简单方便的指数运算符——**(两个星号)。其格式为 x ** y 即x的y次方。如3**2表示3的平方,5**3表示5的立方,12**5表示12的5次方。 使用分数指数还可以用于开方运算,例如,求27的开立方根: 27 ** (1/3) 求16的开平方根: 16 ** (1/2) 步骤1: 求2的10次方。 a = 2 ** 10 步骤2: 还可以使用负指数。 b = 5 ** -2 步骤3: 求512的开立方根。 c = 512 ** (1 / 3) 步骤4: 求-15的平方。 d = (-15) ** 2 此处-15要加上括号,表示其应优先于**运算符进行计算。 步骤5: 运行案例代码,结果如下: 2的 10 次方:1024 5的-2 次方:0.04 512的立方根:8 -15的平方:225 案例90分解整数位 整除运算符(//)与取余运算符(%)是一对关系密切的运算符。“//”运算符进行除法运算,但只保留整数部分(即可以被整除的部分)。例如,下面表达式的计算结果是2。 21 // 9 如果使用“%”运算符,那么被保留的是余数部分。例如: 21 % 9 运算结果是3。 本案例实现整数位分解——即分解出各个位上的数字。例如,365可以分解出三个数字: 3、6、5,7362可以分解为: 7、3、6、2。 步骤1: 提示用户输入一个整数。 x = int(input('请输入一个整数: ')) 步骤2: 通过一个while循环分解整数位。 #用于存放结果的列表 w = [] i = x while i > 0: # 取余,可得到最低位的数 t = i % 10 w.append(t) # 与 10 整除,可以去掉最右边一位 i = i // 10 让变量i引用x的值,是为了保留变量x的值(不被修改),因为后面打印屏幕信息时还需要访问变量x的值。 循环原理: 假设要分解整数176,先计算176 % 10,得到余数6; 接着计算176 // 10,整除后得到17。用17进入第二轮循环,计算17 % 10,得到余数7,再计算17 // 10,整除后得到1。再用1进入第三轮循环,计算1 % 10,得到余数1,再计算1 // 10,得到结果0。最后,由于变量i的值为0,退出循环。于是得到分解后的三个数字: 6、7、1,随后将这个列表反转一下,就可以变成1、7、6了。 步骤3: 反转列表。 w. reverse() 反转列表只要调用案例方法reverse即可,该方法不返回内容,所以,直接调用即可。 步骤4: 在屏幕上输出处理结果。 print('输入的整数:{}'.format(x)) print('对整数的各个位进行分解:') for n in w: print(f'{n}', end='') 步骤5: 运行案例代码,输出信息如下: 输入的整数:2617 对整数的各个位进行分解: 2617 输入的整数:607 对整数的各个位进行分解: 607 输入的整数:52 对整数的各个位进行分解: 52 案例91连接字符串 “+”运算符用于数值类型的操作数(整型、浮点型等)时会进行加法运算,而当它用于两个字符串案例之间时,就会充当“连接符”的角色,把两个字符串案例直接连起来。 例如,下面两个字符串对象“相加”后得到新的字符串对象“abcde”。 "ab" + "cde" 步骤1: 声明两个变量,并使用字符串类型的对象初始化。 a = "wo" b = "rd" 步骤2: 将以上两个字符串案例用“+”运算符连接。 print(f'"{a}"与"{b}"连接后得到字符串:{a+b}') 步骤3: 再声明三个变量,同样以字符串案例初始化。 h = '亭' i = '玉' j = '立' 步骤4: 将以上三个变量进行拼接。 print(f'"{h}、{i}、{j}"组成一个成语:{h+h+i+j}') 在连接字符时,变量h使用了两次,产生字符串“亭亭”,再连接上i和j,就变成“亭亭玉立”。 步骤5: 运行案例代码,输出信息如下: "wo"与"rd"连接后得到字符串:word "亭、玉、立"组成一个成语:亭亭玉立 案例92当字符串遇上乘法运算符 字符串案例之间是不能用乘法(*)运算符进行计算的,不过,字符串与整型之间是可以使用“*”运算符的。例如: "read" * 3 其含义是将字符串“read”重复三次,得到 'readreadread' 还可以将左右操作数互换。 5 * 'abc' 得到结果: 'abcabcabcabcabc' 步骤1: 输出10个“f”字符。 print('f' * 10) 步骤2: 输出7个“#”字符。 print(7 * '#') 步骤3: 输出5个“@”字符和8个“->”字符串。 print('@' * 5 + 8 * '->') 因为“+”运算符可以用于连接字符串,并且“*”运算符的优先级高于“+”(先执行乘法运算,再进行加法运算),所以,'@' * 5表达式产生的“@@@@@”与8 * '->'表达式产生的“->->->->->->->->”可以拼接成一个新的字符串案例。 步骤4: 如果使用的整数值是负数,将不会重复字符。 print('^_^' * (-6)) 步骤5: 运行案例代码,结果如下: ffffffffff ####### @@@@@->->->->->->->-> 案例93运算优先级 算术运算符的优先级与常规的数学运算一致,乘法、除法的优先级高于加法、减法。因此,对于复杂的运算表达式来说,如果要改变默认的优先级,可以适当地使用括号(小括号)。 例如,下面表达式的计算结果为36。 2 * 3 + 6 * 5 由于乘法运算的优先级高于加法运算,表达式会先分别计算2*3和6*5的结果,然后再相加,即6+30,最后结果为36。 如果希望先计算3+6,再计算乘法,就要使用括号了,即 2 * (3 + 6) * 5 此时计算结果就变为90了。 步骤1: 使用括号让加法运算优先于乘法。 a = (3 + 7) / 5 先计算3 + 7,得到10,再除以5,结果是2。 步骤2: 以下两个表达式虽然优先级不同,但运算结果相同。 b = 10 * 5 / 25 c = 10 * (5 / 25) 第一个表达式先计算10 * 5,得到50,再除以25,结果是2; 第二个表达式先计算 5 / 25,得到0.2,再乘以10,结果也是2。 步骤3: 以下表达式也是加法运算优先。 d = 8 * (12 + 6) 先计算12 + 6,得到18,再乘以8,结果为144。 5.1.2比较运算符 案例94自定义的相等比较 判断两个对象是否相等,有两个运算符: “==”运算符计算两个对象是否相等,如果相等就产生结果True,否则产生结果False; 而“!=”运算符的含义与“==”正好相反,如果两个对象不相等,计算结果为True,否则为False。 要在自定义的类型中重新定义这两个运算符的运算逻辑,就需要在类中存在以下两个方法: __eq__(self, other) __ne__(self, other) 这两个方法都是案例方法,因此它们的第一个参数皆为self,表示引用当前类的案例,other参数表示当前类的其他案例。这两个方法的返回类型都是布尔类型。ep代表的是“==”运算符的执行逻辑,如果两个对象相等就返回True,不相等就返回False。ne代表的是“!=”运算符,如果两对象不相等就返回True,相等就返回False。 __eq__和__ne__方法都是在object类中定义的,所以,如果类型没有显式实现这两个方法,也不会影响“==”和“!=”运算符的使用。如果不存在自定义的__eq__和__ne__方法,在进行相等比较运算时会调用object类的默认实现。 本案例实现的相等比较方案为: Test类在初始化时需要提供两个数值,并分别保存在item_1和item_2两个属性中。如果两个Test对象的item_1和item_2的值相加后的和相等,就认为这两个对象相等,否则不相等。假设Test类有两个案例a和b,如果以下两个表达式的计算结果相同,那么a和b就相等。 a.item_1 + a.item_2 b.item_1 + b.item_2 步骤1: 定义Test类,并实现__eq__和__ne__方法。 class Test: def __init__(self, item1, item2): self.item_1 = item1 self.item_2 = item2 def __eq__(self, other): s1 = self.item_1 + self.item_2 s2 = other.item_1 + other.item_2 return s1 == s2 def __ne__(self, other): s1 = self.item_1 + self.item_2 s2 = other.item_1 + other.item_2 return s1 != s2 __init__方法在Test类初始化的时候会调用,而且需要传递item1和item2两个参数(self代表类案例本身,不需要在代码中明确指定),在方法中,用传进来的item1和item2参数分别设置类案例的item_1和item_2属性。 实现__eq__方法时,先分别计算两个参与比较对象的item_1 + item_2的和,得到s1和s2变量,然后将这两个变量的相等比较结果返回即可。若相等就返回True,若不相等就返回False。__ne__方法的实现原理与__eq__方法相同,只是结果相反。 步骤2: 案例化两个Test类的对象,变量名为x和y,然后对两者进行相等比较。 x = Test(5, 10) y = Test(7, 8) print(f'x和 y 相等吗?{x == y}') print(f'x和 y 不相等吗?{x != y}') 5 + 10的计算结果是15,7 + 8的计算结果也是15,按照Test类的定义,x变量和y变量相等。 步骤3: 再案例化两个Test对象,变量名依次为c、d。 c = Test(6, 2) d = Test(4, 7) print(f'c和 d 相等吗?{c == d}') print(f'c和 d 不相等吗?{c != d}') 6 + 2的计算结果为8,4 + 7的计算结果为11,因此,c和d是不相等的。 步骤4: 运行案例代码,屏幕输出结果如下: x和 y 相等吗?True x和 y 不相等吗?False c和 d 相等吗?False c和 d 不相等吗?True 案例95比较对象的大小 大小比较相关的运算符有以下四个: (1) “>”: 大于。例如a > b。 (2) “<”: 小于。例如a < b。 (3) “>=”: 大于或等于。例如 a >= b。 (4) “<=”: 小于或等于。例如a <= b。 大小比较运算符的运算结果为布尔类型。表达式成立时为True,若表达式不成立则为False。这几个运算符多用于数值之间的比较(int、float)。 步骤1: 声明两个变量,并用int类型的数值初始化。 a = 5 b = 3 步骤2: 对以上两个变量的值进行大小比较,并输出比较结果。 print(f'{a}小于 {b} 吗?{a < b}') print(f'{a}大于 {b} 吗?{a > b}') 步骤3: 创建元组对象,并初始化。 ns = -3, 6, -1, 7, 0, -2 步骤4: 通过大小运算符进行比较,确定上述元组对象中各个元素是否大于或等于0。 for x in ns: print(f'{x} 大于或等于 0 吗?{x >= 0}') 步骤5: 运行案例代码,得到的结果如下: 5小于 3 吗?False 5大于 3 吗?True -3大于或等于 0 吗?False 6大于或等于 0 吗?True -1大于或等于 0 吗?False 7大于或等于 0 吗?True 0大于或等于 0 吗?True -2大于或等于 0 吗?False 案例96自定义的大小比较 以下方法与大小比较运算符有对应关系: (1) __gt__: 大于,与“>”运算符对应。 (2) __ge__: 大于或等于,与“>=”运算符对应。 (3) __lt__: 小于,与“<”运算符对应。 (4) __le__: 小于或等于,与“<=”运算符对应。 本案例以Person类来做演示。Person类的案例对象之间的大小比较,将取决于其age属性。即如果a.age > b.age,那么就可以认为a > b。在默认情况下是不能这样进行比较的,所以Person类需要明确实现__gt__、__ge__等方法。 步骤1: 定义Person类,实现__gt__、__ge__、__lt__、__le__、__eq__和__ne__六个案例方法。 class Person: def __init__(self, name, age): self.name = name self.age = age # 实现自定义的大小比较 def __gt__(self, other): return self.age > other.age def __ge__(self, other): return self.age >= other.age def __lt__(self, other): return self.age < other.age def __le__(self, other): return self.age <= other.age def __eq__(self, other): return self.age == other.age def __ne__(self, other): return self.age != other.age 在初始化Person案例时需要提供name和age两个参数,分别用以设置name和age属性。在实现__gt__等方法时,只要对参与比较的两个对象的age属性进行处理即可。 步骤2: 案例化四个Person对象,验证一下上述的自定义大小比较是否有效。 p1 = Person('Jack', 28) p2 = Person('Tom', 31) print(f'{p1.name}比 {p2.name} 小吗?{p1 < p2}') p3 = Person('Lucy', 30) p4 = Person('Jim', 29) print(f'{p3.name}比 {p4.name} 大吗?{p3 > p4}') 步骤3: 运行案例代码,得到的结果如下: Jack比 Tom 小吗?True Lucy比 Jim 大吗?True 5.1.3位运算符 案例97二进制位的逻辑运算 位(bitwise)运算符大致可以分为两类。 第一类是逻辑运算,即 (1) “&”: 按位“与”,只有两者同时为1时结果才会是1,否则结果为0。 (2) “|”: 按位“或”,只要其中一个为1时,计算结果便为1。 (3) “^”: 按位“异或”,若两者相等,结果为0; 若两者不相等,结果为1。 (4) “~”: 取反,即0的变为1,1的变为0。 第二类是移动二进制位运算。 (1) “<<”: 向左移动若干个二进制位。例如a << 3表示将a的二进制位向左移动3位。 (2) “>>”: 向右移动若干个二进制位。例如b >> 2表示将b的二进制位向右移动2位。 本案例将演示二进制位的按位“与”“或”“异或”运算。 步骤1: 声明两个变量并初始化。 x, y = 12, 9 步骤2: 对变量x和y的二进制位进行“与”“或”和“异或”运算,并输出相关信息。 print(f'{x:b} & {y:b} = {x & y:04b}') print(f'{x:b} | {y:b} = {x | y:04b}') print(f'{x:b} ^ {y:b} = {x ^ y:04b}') 格式控制符“b”表示以二进制形式打印数值,“04b”表示以二进制形式打印数值,而且总宽度为4个字符,用字符“0”填充。如果计算结果为10,打印后的格式为0010。 步骤3: 运行案例代码,会得到以下运算结果: 1100 & 1001 = 1000 1100 | 1001 = 1101 1100 ^ 1001 = 0101 案例98移动二进制位 本案例将演示“<<”和“>>”运算符的用法。 假设二进制数值x的值为100011,表达式x >> 3表示将x的二进制位向右移动3位,得到结果000100; 而表达式x << 2表示将x的二进制位向左移动2位,结果为10001100。 步骤1: 声明一个变量,用二进制数值初始化。 n = int('01011011', base=2) 调用int类的构造函数(__init__)时,可以传递以字符串形式表示的整数值,然后base参数设置为2,表示二进制。 步骤2: 将变量n的二进制位向左移动4位。 c1 = n << 4 步骤3: 将变量n的二进制位向右移动3位。 c2 = n >> 3 步骤4: 在屏幕上打印运算结果。 print(f'原数值:{n:08b}') print(f'二进制位向左移动 4 位后:{c1:08b}') print(f'二进制位向右移动 3 位后:{c2:08b}') 格式控制符“08b”表示以二进制方式输出整数值,默认宽度为8个字符,空白位使用字符“0”来填充。 步骤5: 运行案例代码,输出结果如下: 原数值:01011011 二进制位向左移动 4 位后:10110110000 二进制位向右移动 3 位后:00001011 5.1.4逻辑运算符 案例99查找同时包含a、e两个字母的单词 运算符and进行逻辑“与”运算,只有当参与运算的所有表达式都返回True时,and运算符的运算结果才会为True。例如: A and B and C 如果A、B、C的结果都为True,那么整个表达式的运算结果为True。如果A、B为True,C为False,那么整个表达式的运算结果就是False。 本案例演示从一组英语单词中查找出同时包含字母a和e的单词。 步骤1: 创建一个元组对象,里面包含5个英语单词。 words = 'amount', 'wisdom', 'perfect', 'learn', 'daedal' 步骤2: 创建一个空白的列表对象,用于存放查找结果。 results = [] 步骤3: 枚举元组中的单词,逐个进行分析。如果单词中同时包含“a”和“e”两个字母,就将该单词添加到results列表中。 for w in words: if w.count('a') and w.count('e'): results.append(w) 字符串案例的count方法可以统计指定的字符串在原字符串中出现的次数。只有在单词同时找到a和e时,if语句的条件才成立(True)。由于在Python语言中,整数值0会被认为是False,非0的整数值被认为是True,所以,上述代码中不需显式地与0进行比较,因为一旦count方法返回0,就表示单词中找不到指定的字符串。 步骤4: 在屏幕上输出查找结果。 print(f'单词列表:\n{words}') print(f'其中同时包含字母"a"和"e"的单词有:\n{results}') 步骤5: 运行案例代码,得到的输出结果如下: ('amount', 'wisdom', 'perfect', 'learn', 'daedal') 其中同时包含字母"a"和"e"的单词有: ['learn', 'daedal'] 案例100or运算符 or运算符执行逻辑“或”运算,参与运算的表达式中只要有一个是True,其计算结果便是True。只有当所有参与运算的表达式皆为False时,or运算符的运算结果才会是False。 本案例将在100以内(包含100)的整数中找出能被12或者15整除的整数。两个条件中只要有一个成立即可,例如48、60、72等。 步骤1: 创建一个空的列表对象,用于存储查找结果。 res = [] 步骤2: 进行for循环,枚举0到100(包括0和100)的整数,在循环代码中判断数值能否被12或者15整除。 for i in range(0, 101): if (i % 12 == 0) or (i % 15 == 0): res.append(i) range函数产生的整数序列不包含末值,所以在调用时要把末值设定为101。 步骤3: 输出处理结果。 print('100以内能被 12 或 15 整除的整数有:') for x in res: print(x, end='') 步骤4: 运行案例代码,屏幕输出内容如下: 100以内能被 12 或 15 整除的整数有: 012152430364548607275849096 案例101自定义布尔运算 判断一个对象的布尔运算结果是否为True,Python程序会进行以下处理过程: (1) 检测对象是否存在__bool__方法,如果找到,就通过调用该方法来得到运算结果。 (2) 如果找不到__bool__方法,就去查找__len__方法,只要__len__方法返回非0值,就视为True; 如果返回0,则视为False; (3) 如果__bool__方法和__len__方法都找不到,那么该对象的布尔运算结果总是True。 由于bool类是从int类派生的,所以,对于整数值,只要不为0的值都会返回True。对于浮点型数值,非0值也会返回True,因为float类实现了__bool__方法。 本案例演示__bool__方法的实现,如果Demo类的案例中设置了动态属性,则它的布尔运算将返回True; 如果案例未曾设置过动态属性,则为False。分析的依据是对象案例的__dict__成员,它引用了一个字典案例,设置到对象案例上的动态属性会被存储到此字典案例中,所以,只要__dict__成员所引用的字典对象中存在数据,就表明对象案例被设置过动态属性。 步骤1: 定义Demo类,并且实现__bool__方法。 class Demo: def __bool__(self): if len(self.__dict__): return True return False len函数的功能是返回一个序列对象的长度(即其中包含的元素个数)。 步骤2: 案例化一个Demo类的对象,并赋值给变量x。 x =Demo() 步骤3: 变量x未设置任何动态属性,下面代码检测其返回的布尔值。 if x: print('x 案例中存在动态属性') else: print('x 案例未设置动态属性') 步骤4: 再创建一个Demo类的案例,赋值给变量y。 y =Demo() 步骤5: 为变量x所引用的对象设置两个动态属性。 y.test1 = 5 y.test2 = 15 步骤6: 检测变量y所返回的布尔值。 if y: print('y 案例中存在动态属性') else: print('y 案例未设置动态属性') 步骤7: 运行案例代码,得到如下结果: x案例未设置动态属性 y案例中存在动态属性 5.1.5其他运算符 案例102对象标识的比较运算 is运算符用于比较两个变量是否引用同一个案例,即判断两者是否为同一对象。is运算符的运算结果为布尔类型。当两个操作数的标识相同(它们引用同一个对象案例,其标识可通过调用id函数获得),is运算符的运算结果为True,否则为False。 is not运算将产生相反的运算结果。如果a is b的运算结果为True,那么a is not b的运算结果就是False。 步骤1: 声明变量x、y,并以字符串类型的案例来初始化。 x, y = 'abc', 'acd' 步骤2: 通过is运算符确认一下x和y两个变量是否引用同一个对象。 print(f'x与 y 是同一个对象吗?{x is y}') x和y所引用的字符串对象不相同(一个是“abc”,另一个是“acd”),所以,x is y表达式的运算结果为False。运行后输出以下文本: x与 y 是同一个对象吗? False 步骤3: 声明变量s和r,并使用相同的int数值赋值(赋值后都是7)。 s = 7 r = 7 步骤4: 判断一下,变量s和r是否为同一个对象。 print(f's与 r 不是同一个对象吗?{s is not r}') 相同的整数值引用的是同一块内存区域,s is r的运算结果为True,相反地,s is not r的运算结果就是False。代码输出以下内容: s与 r 不是同一个对象吗?False 步骤5: 声明变量m和n,同时引用类型float。 m = float n = float 步骤6: 判断一下变量m与n是否是同一个对象。 print(f'm与 n 是同一个对象吗?{m is n}') 变量m和n引用了类型float,它们的对象标识一致,因此,m is n的结果是True。输出结果如下: m与 n 是同一个对象吗?True 注意: 在Python中,类型本身也被视为对象,因此,类型之间也可以使用“is”或“is not”运算符进行对象标识比较。 案例103not运算符 not运算符会使表达式产生相反的布尔值。例如,3 == 5,由于3与5并不相等,所以此表达式的运算结果为False。如果加上not运算符,即not 3 == 5,那么运算结果就会变成True了。 步骤1: 将not运算符用在整数值5之前,得到的运算结果是False。 print(not 5) 对整数值5进行布尔运算,返回的结果为True,因为不为0的整数都被认为是True。加上not运算符后得到相反的结果,即False。 步骤2: 以下代码执行会打印出True。 print(not 10 < 6) 表达式10 < 6的运算结果为False,加上not运算符后结果变为True。 步骤3: 下面代码的执行结果为True。 print(not 1 > 0 or len('abc') == 3) not运算符的优先级高于or运算符,因此先计算not 1 > 0,再计算len('abc') == 3,最后才进行逻辑“或”运算。1 > 0的运算结果是True,加上not运算符后变为False; len函数获取字符串的长度,“abc”中字符个数为3,即表达式len('abc') == 3返回True。最后,两个表达式进行“或”运算后,结果为True。 案例104检查类型成员的存在性 检查类型(或该类型的案例对象)是否包含某个成员,最常用的方法是调用hasattr函数。例如,检查一下float类中是否存在__str__方法,可以这样处理: hasattr(float, '__str__') 由于float类中确实存在__str__方法,所以hasattr函数调用后返回True。 本案例将演示另一种方案——通过在类中实现__contains__方法来判断成员的存在性。在使用时可以通过“in”运算符来检查。例如,要判断Car类中是否存在cid属性,可以使用以下表达式: 'cid' in Car __contains__方法的签名如下: __contains__(self, item) self参数指的是当前类的案例引用,item参数就是要判断的属性名,例如上文中提到的cid属性,如果调用了__contains__方法,那么item参数就是“cid”。如果item参数提供的成员名称存在,__contains__方法就返回True,否则返回False。 步骤1: 声明test类,实现__contains__方法,用以分析指定的成员是否存在。 class test: def __contains__(self, item): # 先分析类型本身的成员 if item in self.__class__.__dict__: return True # 再分析类型案例的成员 if item in self.__dict__: return True # 指定的成员未找到 return False 在Python语言中,对象的成员会被存储在__dict__成员中,它是一个字典对象,而且,类型与案例之间的__dict__字典是相互独立的。也就是说,test类中的__dict__属性存放的是与test类相关的成员,而test案例的__dict__字典中存储的是与案例相关的成员,例如为案例设置的动态属性。 因此,在检查某个成员是否存在时,既要查找test类的__dict__字典,又要查找test案例的__dict__字典。 步骤2: 在test类中定义一个work方法。此方法不做任何操作,只用于做测试。 def work(self): pass 步骤3: 创建test类案例。 v =test() 步骤4: 为test案例设置动态属性。 v. label = 'test' 步骤5: 分别检查一下test案例是否存在work、label和count三个成员。 if 'work' in v: print('此对象存在 work 方法') else: print('未找到 work 方法') if 'label' in v: print('此对象存在 label 属性') else: print('未找到 label 属性') if 'count' in v: print('此对象存在 count 属性') else: print('未找到 count 属性') 步骤6: 运行案例程序,得到如下结果: 此对象存在 work 方法 此对象存在 label 属性 未找到 count 属性 work方法是在test类中声明的,label属性是动态赋值产生的,这两者都是存在的,而count属性是不存在的(test类在声明阶段未定义,而且test案例创建后也未进行过赋值)。 案例105复合赋值运算符 复合赋值运算符可以将当前变量的值进行运算后,再把运算结果重新赋值给当前变量。例如,下面代码声明变量a,并初始化为5。 a = 5 把变量a的值加3再赋值回变量a。 a = a + 3 使用复合赋值运算符,可以简写成 a += 3 类似地,还有 a -= 2 a *= 5 a /= 2 a >>= 1 a |= b a **= 3 a %= 3 步骤1: 声明变量m,初始化为200。 m = 200 步骤2: 通过id函数取得变量m的对象标识,并打印。 print(f'复合赋值前: {id(m)}') 步骤3: 对变量m进行复合赋值。 m *= 5 步骤4: 再次打印变量m的对象标识。 print(f'复合赋值后: {id(m)}') 步骤5: 运行案例代码,屏幕输出的信息如下: 复合赋值前: 140716601703472 复合赋值后: 3115029372688 从输出结果来看,复合赋值后,变量m的标识改变了,这说明复合赋值会使变量m引用新的对象案例(原有的对象案例被丢弃)。 案例106模拟C语言的“三目”运算符 在C语言(包括与C语言风格类似的语言)中,有这么一个运算符: <条件> ? <表达式1> : <表达式2> 当条件成立时,执行表达式1,如果条件不成立就执行表达式2。这是C语言中唯一的三目运算符,也称“三元”运算符。 Python中虽然没有这个运算符,但可以通过内联的if…else…语句(或称“条件表达式”)来模拟。其格式如下: <表达式1> if <条件> else <表达式2> 当条件成立时执行表达式1,否则执行表达式2。 步骤1: 声明变量x并赋值。 x = 175 步骤2: 使用条件表达式来控制输出的文本,然后将文本输出到屏幕上。 msg = '{0} {1}被 5 整除'.format(x, '能' if x % 5 == 0 else '不能') print(msg) msg = '{0} {1}被 3 整除'.format(x, '能' if x % 3 == 0 else '不能') print(msg) 上面代码依次分析了变量x能否被5和3整除,如果可以整除则产生字符串“能”,不能整除就产生字符串“不能”。 步骤3: 运行案例程序,屏幕输出如下: 175能被 5 整除 175不能被 3 整除 5.2浮点数的扩展功能 案例107Decimal类的简单使用 decimal模块提供了与浮点数相关的功能,可以弥补float类的不足。其中,Decimal类是此模块的核心类,它表示一个十进制的浮点数值,可以通过以下几种方式进行初始化: (1) 整数值,即int类型的值。 (2) 浮点数值,即float类型的值。 (3) 字符串,例如“1.0005”。 (4) 其他的Decimal案例。 (5) 一个元组案例,即tuple对象。该元组包含三个元素: 符号用0表示正值,用1表示负值; 各个位上的数字,用一个元组对象封装; 指数,即科学记数法中10(程序中用字母E或e表示)的指数,以表示小数位。 步骤1: 导入decimal模块。 import decimal 步骤2: 使用浮点数值来初始化Decimal对象。 d1 =decimal.Decimal(0.00001) 注意: 向Decimal的构造函数传递浮点数值会被转换为完整的十进制浮点数,小数位在53位以上。 步骤3: 使用字符串来初始化Decimal对象。 d2 = decimal.Decimal('3.156') 步骤4: 使用整数值来初始化Decimal对象。 d3 =decimal.Decimal(5520) 步骤5: 初始化Decimal对象,但不提供参数。 d4 =decimal.Decimal() value参数的默认值为0,因此未提供参数的初始化得到的浮点数为0。 步骤6: 输出上述各个Decimal对象的值。 print(f'd1: {d1}\nd2: {d2}\nd3: {d3}\nd4: {d4}') 步骤7: 运行案例程序,屏幕呈现结果如下: d1: 0.000010000000000000000818030539140313095458623138256371021270751953125 d2: 3.156 d3: 5520 d4: 0 案例108通过元组对象来初始化Decimal类 Decimal类支持通过一个元组对象来初始化。其中,传递给构造函数的元组对象要求包含三个元素: (<0或1>, (<数字组合>), <指数>) 例如,要构造浮点数1.27,应当将以下元组对象传递给Decimal类的构造函数。 (0, (1, 2, 7), -2) 其中,0表示正值,1、2、7表示要使用到的数位,-2是指数,即10-2,表示两个小数位,最终产生十进制浮点数值1.27。 如果需要产生负值,只要将元组序列中的第一个元素从0改为1即可。 (1, (1, 2, 7), -2) 若希望产生整数值的浮点数,将指数设定为0即可。例如: (0, (3,5,7,1), 0) 产生的浮点数值为3571。 同理,下面方法可以产生负整数值: (1, (4,2,9), 0) 产生的数值为-429。 步骤1: 从decimal模块中导入Decimal类。 from decimal import Decimal 步骤2: 产生浮点数值12.00006。 x1 =Decimal((0, (1, 2, 0, 0, 0, 0, 6), -5)) 在指定各个数位上的数字时,所提供的元素必须与每一个位相匹配,例如上面的代码,12.00006中有四个0,所以,提供数位的元组中也要依次指定四个0,如果只包含一个0就会变成0.01206了。 步骤3: 以下代码会产生数值-0.33。 x2 =Decimal((1, (0, 3, 3), -2)) 元组序列中,第一个元素如果为0表示正值,如果为1表示负值。 步骤4: 产生整数值960。 x3 =Decimal((0, (9, 6, 0), 0)) 指数为0可以产生不带小数部分的浮点数值。 步骤5: 将以上三个浮点数打印到屏幕上。 print(f'x1: {x1}\nx2: {x2}\nx3: {x3}') 步骤6: 运行案例程序,得到的结果如下: x1: 12.00006 x2: -0.33 x3: 960 案例109使用DecimalTuple来初始化Decimal对象 DecimalTuple类派生自内置的tuple类,这表明它属于元组序列。DecimalTuple类的构造函数签名如下: DecimalTuple(sign, digits, exponent) sign参数表示符号,0代表正值,1代表负值; digits参数表示组成浮点数的各个数位上的数字; exponent参数是指数,用于确定小数位。 步骤1: 从decimal模块中导入Decimal和DecimalTuple两个类。 from decimal import Decimal,DecimalTuple 步骤2: 创建DecimalTuple案例。 tp = DecimalTuple(0, (2, 5, 7, 2, 5), -3) 第一个参数0表示要产生的浮点数为正值。第二个参数指定组成浮点数的数字; 第三个参数-3表明产生的数值有3位小数位。 步骤3: 将已案例化的DecimalTuple对象传递给Decimal的构造函数。 dc = Decimal(tp) 步骤4: 打印浮点数的值。 print(dc) 步骤5: 运行案例程序,输出结果为: 25.725 案例110设置浮点数的精度 decimal模块中公开Context类,该类用于设定浮点数的约束环境,例如小数位的舍入规则,科学记数法中指数的有效范围等。 其中,prec字段用于指定浮点数的精度。此精度值并不仅仅包括小数位,而是整个浮点数值的有效位数,精度计算的起始位是非0的最高位。例如,prec = 3,从浮点数1.23654产生的Decimal案例的值会变成1.24,从浮点数0.00038162产生的Decimal案例的值就是0.000382。 虽然Decimal类的构造函数可以接收Context案例,但是,这种方法并不能使浮点数的约束起作用。有效的方法是调用Context类的create_decimal或者create_decimal_from_float案例方法。调用方法后会返回一个Decimal案例,此Decimal案例会自动应用Context对象所设置的约束规则。 步骤1: 从decimal模块中导入需要使用的类。 from decimal import Decimal, Context 步骤2: 创建Context案例,并设置prec字段为4,即有效位数为4。 c = Context(prec=4) 步骤3: 声明一个元组序列,其中包含4个元素,稍后代码会通过这4个元素来产生Decimal对象。 src = 0.00517926, 36029.33, 11.000037, -20.00491 步骤4: 使用for循环依次访问上述元组的元素,并调用create_decimal方法创建Decimal案例。 for n in src: dx = c.create_decimal(n) print(f'{n:<15f}>>>{dx}') 步骤5: 运行案例程序,输出结果如下: 0.005179>>>0.005179 36029.330000>>>3.603E+4 11.000037>>>11.00 -20.004910>>>-20.00 注意: 数值36029.33会转换为科学记数法,并保留4个有效位(prec = 4),即3.603×104。 案例111基于线程的浮点数环境 Context类可以设置浮点数值的一些环境条件,例如精度、舍入规则等。每个线程都拥有独立的Context案例。 调用getcontext函数可以获取当前线程的Context案例,然后可以对其进行修改。要替换当前线程的Context案例,可以调用setcontext函数。 对Context案例的修改是线程独立的,即修改后仅对当前线程中的代码起作用,不会影响其他线程代码中的Context案例。 本案例将演示在三个新线程上设置浮点数的精度,在新线程运行的函数中,使用相同的浮点数值来创建Decimal案例,三个线程的代码差异是使用不同的精度。 步骤1: 导入需要的模块。 import threading, decimal 步骤2: 定义一个函数,此函数接收一个prec参数,表示浮点数的精度。新线程在执行该函数时,会向prec参数传递数值,以便让每个线程使用独立的精度值。 def theFun(prec): # 获取当前线程的 Context 案例 ctx = decimal.getcontext() # 修改浮点数精度 ctx.prec = prec # 获取当前线程的名称 tname = threading.currentThread().name # 创建 Decimal 案例,进行测试 dx = ctx.create_decimal(150.6092738) # 打印信息 print(f'线程:{tname},精度:{prec},浮点数:{dx}', end='\n') 首先,调用getcontext函数,获得与当前线程关联的Context案例; 接着修改它prec字段; 最后用这个Context案例创建Decimal案例,并输出到屏幕上。 步骤3: 创建三个新线程,并与前面定义的theFun函数关联。 th1 = threading.Thread(target=theFun, kwargs={'prec': 2}, name='线程-1') th2 = threading.Thread(target=theFun, kwargs={'prec': 4}, name='线程-2') th3 = threading.Thread(target=theFun, kwargs={'prec': 5}, name='线程-3') target参数引用要在新线程上执行的函数,name参数用于为线程分配一个名称,作为线程的标识。 步骤4: 启动三个新线程。 th1.start() th2.start() th3.start() 步骤5: 等待线程完成。 th1.join() th2.join() th3.join() 步骤6: 运行案例程序,屏幕输出结果如下: 线程:线程-1,精度:2,浮点数:1.5E+2 线程:线程-2,精度:4,浮点数:150.6 线程:线程-3,精度:5,浮点数:150.61 5.3随机数 案例112产生一个随机整数 randrange函数的签名如下: randrange(start, stop=None, step=1) 此函数实际上引用了Random类的randrange方法。该方法通过range函数产生一个整数序列,然后从这个整数序列中随机挑选一个值并返回。由于其中调用了range方法,因此,产生的整数序列中不会包含stop参数的值(终值)。如果希望包含终值,可以将终值设定为stop+1,也可以改用randint函数。 randint函数的签名如下: randint(a, b) 这个函数内部也是调用了randrange函数的,但会将终值自动加1。最终使得产生的整数序列中会包含参数a、b的值,所生成的随机数值就有可能等于a或者等于b。 本案例分别使用randrange和randint函数产生20个随机整数。 步骤1: 从random模块中导入randrange、randint函数。 from random import randrange, randint 步骤2: 使用randrange函数生成20个随机整数,此过程用一个while循环来完成。 n = 20 while n > 0: x = randrange(100, 999) print(x, end=' ') n -= 1 随机整数的范围为100~999,包含100但不包含999。 步骤3: 再使用randint函数生成20个随机整数。 n = 20 while n > 0: x = randint(100, 999) print(x, end=' ') n -= 1 步骤4: 运行案例程序,屏幕输出信息如下: 第一组随机整数: 490 824 338 247 634 179 167 178 932 323 129 662 343 577 410 804 203 922 514 166 第二组随机整数: 551 554 163 595 486 175 685 324 277 549 140 286 410 610 162 274 460 803 310 498 案例113从序列中随机取出一个元素 choice函数实现从序列对象中随机抽取一个元素的功能。由于抽出的元素是随机的,所以,有可能多次抽取到同一个元素。 步骤1: 从random模块中导入choice函数。 from random import choice 步骤2: 创建元组对象。 fruit = 'grape', 'banana', 'carambola', 'plum', 'cherry', 'pitaya', 'durian', 'haw', 'bennet', 'cumquat', 'orange', 'pomelo', 'ginkgo', 'betelnut' 步骤3: 通过for循环,分6次从刚创建的元组对象中随机抽取元素,并输出到屏幕上。 for i in range(1, 7): item = choice(fruit) print(f'第 {i} 轮:{item}') 步骤4: 运行案例程序,屏幕输出内容如下: 第 1 轮:carambola 第 2 轮:haw 第 3 轮:orange 第 4 轮:plum 第 5 轮:durian 第 6 轮:grape 案例114生成0~1的随机数 random是一个较为基本的随机数生成函数,它无须提供参数,每次调用都会返回一个随机的浮点数。返回的浮点数值范围为0.0~1.0。 本案例的代码将产生30个范围为0~1的随机数。 步骤1: 从random模块中导入random函数。 from random import random 步骤2: 生成包含30个随机数值的列表。 list = [random() for x in range(0, 30)] 上述代码采用了推导式语法来创建列表元素。range函数会产生30个整数值(0~29),使for循环进行30次,而每一轮循环都会调用random函数来生成随机数。 步骤3: 将刚刚创建的列表输出。 for t in list: print(t) 步骤4: 运行案例程序,将得到以下结果: 0.7752921194672695 0.6179192477048415 0.544004827305869 0.7641340486889894 0.16455304316662278 0.322114982895767 0.8359993710563475 0.1002351001118369 0.06100987506652378 0.5759007835457446 0.7441508591311612 0.31176860536751505 0.3402127542523834 0.7018380242086919 0.8311313924774838 0.06465222120376235 0.9647640867597532 0.7524880871328589 0.8776546464812801 0.5833824681848158 0.34219217279738 0.6758185498350315 0.8717732356926061 0.4075969277139295 0.2589888735787854 0.3423943462120598 0.19306203974340952 0.6695575551319477 0.38946701326225497 0.4256406315237091 案例115从原序列中选取随机样本组成新序列 choice函数只从原序列随机抽取一个元素,如果希望从原序列中随机抽取若干个元素组成新的序列,则需要用到sample函数。sample函数的签名如下: sample(population, k) population参数指的是原始序列,k参数指定新序列的大小(元素个数)。k参数所指定的序列大小不能超过原序列的大小。假设原序列有3个元素,设置k=5会发生错误。 步骤1: 从random模块中导入sample函数。 from random import sample 步骤2: 创建一个整数列表。 src = [87, 19, 155, 23, 701, 46, 264, 198, 52, 12] 步骤3: 从上面的列表中随机选取5个元素组成新的列表。 new_list1 =sample(src, 5) 步骤4: 分别打印原始列表与新组成的列表。 print(f'原序列:\n{src}') print(f'随机生成的新序列:\n{new_list1}\n') 步骤5: 从1000以内的整数列表中随机选出80个元素,组成新的列表。 new_list2 = sample(range(1000), 80) 步骤6: 运行案例程序,结果如下: 原序列: [87, 19, 155, 23, 701, 46, 264, 198, 52, 12] 随机生成的新序列: [19, 87, 23, 155, 12] 从 1000 以内的整数序列中选取 80 个元素: [233, 473, 665, 866, 895, 168, 200, 928, 859, 228, 489, 485, 172, 641, 110, 381, 375, 227, 269, 610, 201, 540, 555, 335, 62, 707, 807, 134, 787, 642, 901, 804, 996, 106, 11, 811, 129, 584, 36, 222, 868, 90, 921, 572, 763, 309, 522, 671, 559, 290, 948, 631, 295, 263, 243, 274, 187, 628, 117, 560, 782, 249, 394, 892, 419, 697, 923, 951, 176, 795, 800, 238, 992, 234, 687, 660, 329, 97, 601, 861] 案例116打乱列表中的元素顺序 shuffle函数的功能是将一个列表中的元素打乱,即随机地进行重新排序。此函数在某些开发场景中非常有用。例如,在扑克牌游戏的开发过程中,每一轮游戏开始之前,可以使用shuffle函数来重新洗牌。 步骤1: 导入shuffle函数。 from random import shuffle 步骤2: 创建一个整数列表。 nums = [76, 19, 81, 192, 20, 83, 302, 185] 步骤3: 对上述列表进行三次顺序打乱。 shuffle(nums) print(f'第 1 次打乱顺序:{nums}') shuffle(nums) print(f'第 2 次打乱顺序:{nums}') shuffle(nums) print(f'第 3 次打乱顺序:{nums}') shuffle函数不会返回新的列表对象,而是对原列表中的元素重新排序。 步骤4: 运行案例程序,得到以下结果: 原列表:[76, 19, 81, 192, 20, 83, 302, 185] 第 1 次打乱顺序:[20, 76, 302, 192, 185, 19, 83, 81] 第 2 次打乱顺序:[83, 19, 185, 302, 76, 192, 20, 81] 第 3 次打乱顺序:[185, 81, 302, 20, 192, 76, 83, 19] 5.4数学函数 案例117取 整 函 数 取整运算可以向两个方向进行——上舍入与下舍入。上舍入就是获取比当前值大并且最接近的整数,例如7.4,比它大并且最接近的整数是8; 下舍入就是获取比当前值小并且最接近的整数,例如7.4,下舍入后就是7。 ceil函数执行上舍入取整运算,floor函数执行下舍入取整运算。这两个函数的功能是相对的。 步骤1: 导入ceil和floor函数。 from math import ceil, floor 步骤2: 声明三个变量,并用浮点数值赋值。 a, b, c = 17.669, 4.05, -11.63 步骤3: 使用ceil函数对上述三个浮点数进行取整。 print(f'{a} --> {ceil(a)}') print(f'{b} --> {ceil(b)}') print(f'{c} --> {ceil(c)}\n') 步骤4: 使用floor函数对三个浮点数取整。 print(f'{a} --> {floor(a)}') print(f'{b} --> {floor(b)}') print(f'{c} --> {floor(c)}') 步骤5: 运行案例程序,得到如下结果: 向上舍入: 17.669 --> 18 4.05 --> 5 -11.63 --> -11 向下舍入: 17.669 --> 17 4.05 --> 4 -11.63 --> -12 案例118“四舍六入五留双”算法 round函数的舍入算法并不是常见的“四舍五入”算法,而是采用“四舍六入五留双”算法。该算法的舍入步骤如下: (1) 当尾数小于或等于4时,直接舍去。 (2) 当尾数大于或等于6时,舍去尾数,并且前一位要进1。 (3) 当尾数等于5并且5之后的所有数位都为0时,如果前一位是偶数,就直接舍去; 如果前一位是奇数,则进1。 (4) 当尾数等于5但5之后的任意数位不为0时,就舍去尾数,并且前一位要进1,不考虑前一位是否为偶数。 步骤1: 浮点数1.227保留2位小数。 x = 1.227 print(f'{x} --> {round(x, 2)}') 尾数是7,大于6,舍去尾数后前一位进1,结果是1.23。 步骤2: 浮点数12.3928保留2位小数。 x = 12.3928 print(f'{x} --> {round(x, 2)}') 尾数是2,小于4,可直接舍去,结果是12.39。 步骤3: 浮点数7.015保留2位小数。 x = 7.015 print(f'{x} --> {round(x, 2)}') 尾数为5,并且5之后都是0,舍去尾数后,前一位为1且为奇数,需要加1,结果为702。但实际运行结果为7.01,根据Python官方文档的说明,此情况并非bug,而是二进制与浮点数之间的误差造成的。 步骤4: 浮点数24.865009保留2位小数。 x = 24.865009 print(f'{x} --> {round(x, 2)}') 尾数为5,但5之后出现有效数字,因此舍去5之后前一位要加1。结果为24.87。 案例119求 绝 对 值 内置函数abs实现了求绝对值的功能。如果传递给参数的是整数值,那么调用abs函数后也会以整数类型返回; 如果传入的是浮点数值,将返回浮点数值。 另外,在math模块中也有一个求绝对值的函数——fabs,该函数总是返回浮点数值类型,不管传递给参数的数值是整数还是浮点数。decimal模块中的BasicContext/Context类公开了一个abs方法,其功能也是用于求绝对值的。 步骤1: 使用内置的abs函数来求绝对值。 x1 = -3.33 a1 = abs(x1) x2 = 76.36 a2 = abs(x2) #输出结果 print(f'|{x1}| = {a1}') print(f'|{x2}| = {a2}') 输入的参数皆为float类型的数值,因此,abs函数返回的也是float类型的数值。运算结果如下: |-3.33| = 3.33 |76.36| = 76.36 步骤2: 使用math模块下的fabs函数来求绝对值。 import math x3 = 9.006 a3 = math.fabs(x3) x4 = -14.105 a4 = math.fabs(x4) #输出结果 print(f'|{x3}| = {a3}') print(f'|{x4}| = {a4}') 得到以下结果: |9.006| = 9.006 |-14.105| = 14.105 步骤3: 使用Context类的abs方法来求绝对值。 import decimal ctx = decimal.Context(prec=5) x5 = ctx.create_decimal(-26.2617) a5 = ctx.abs(x5) x6 = ctx.create_decimal(0.20000691) a6 = ctx.abs(x6) print(f'|{x5}| = {a5}') print(f'|{x6}| = {a6}') Context类的abs方法需要使用Decimal案例进行运算,所以不能直接传递int或float类型的数值,而是要先调用Context案例的create_decimal方法创建Decimal案例,然后再传递给abs方法。 计算结果为: |-26.262| = 26.262 |0.20001| = 0.20001 在案例化Context对象时,指定了prec = 5,表示其中所使用的浮点数只保留5位有效数字,因此,数值0.20000691被舍入为0.20001。 案例120最大值与最小值 max函数返回序列中的最大值,对应地,min函数则返回序列中的最小值。这两个函数可以这样使用: 用一个可迭代序列作为参数,例如: max( [1, 5, 6] ) 或者直接传递运态参数,即 min( 17, 15, 21, 65) 步骤1: 求四个整数中最小的值。 print('85,9,24,56 中最小的数值:') print(min([85, 9, 24, 56])) min函数在调用时,直接使用了一个列表案例作为参数。 步骤2: 求五个整数中的最大值。 print('7,49,100,72,18 中最大的数值:') print(max(7, 49, 100, 72, 18)) max函数在调用时,把要进行比较的整数值直接作为参数,依次传递给max函数。 步骤3: 运行案例程序,会得到以下结果: 85,9,24,56 中最小的数值: 9 7,49,100,72,18 中最大的数值: 100 案例121排序函数——sorted sorted是内置函数,其功能是将一组对象进行默认排序。排序方案有以下几种: (1) 对于数值序列,将按照从小到大进行排序,即升序。 (2) 对于英文字符,先按照A~Z排序,再按a~z排序。 (3) 对于中文字符,是按照字符编码进行升序排列。 (4) 对于自定义的类,可以实现__lt__、__gt__等方法以获得排序支持。如果自定义的类未实现这些方法并且没有为sorted函数的key参数设置恰当的函数来返回用于排序的有效值,那么sorted函数排序失败。 sorted函数的签名如下: sorted(iterable, key, reverse) iterable参数引用需要进行排序的序列。key参数引用一个函数,该函数要求接收一个参数,从iterable中取出来的每个子项都会被传递到key参数所引用的函数中,此函数必须返回用于排序的值(例如某个对象的某个属性)。reverse参数是布尔值,如果为True,表示将排序后的序列进行反转,默认为False。假设sorted函数返回已排序序列1、2、3,若reverse参数为True,那么得到的最终结果是3、2、1。 步骤1: 定义要进行排序的列表。 org = ['时', '升', '非', '张', '余'] 步骤2: 进行默认排序。 r1 = sorted(org) 步骤3: 进行默认排序,并反转排序结果。 r2 =sorted(org, reverse=True) 步骤4: 输出排序结果。 print(f'原字符序列:\n{org}\n') print(f'排序后:\n{r1}\n') print(f'排序并反转后:\n{r2}') 步骤5: 运行案例程序,排序前后的字符列表对比如下: 原字符序列: ['时', '升', '非', '张', '余'] 排序后: ['余', '升', '张', '时', '非'] 排序并反转后: ['非', '时', '张', '升', '余'] 注意: 本案例中用于排序的列表中使用了中文字符,其默认的排序方式是按照字符编码升序排列。Unicode字符的编码可以通过ord函数来获得。 案例122按照字符串的长度排序 sorted函数的key参数可以引用一个函数,通过这个函数可以返回自定义的用于参与排序的值。在本案例中,通过让key参数引用len函数来实现按照字符的长度(即字符个数)进行排序。 步骤1: 创建列表案例,包含若干个长度不等的字符对象。 src_list = ['abe', 'cake', 'xy', '6f7e8t3d', 'z'] 步骤2: 调用sorted函数进行排序,并使key参数引用len函数。 result = sorted(src_list, key=len) 步骤3: 分别输出排序前后的列表内容。 print(f'原序列:\n{src_list}\n') print(f'按字符串长度排序后:\n{result}') 步骤4: 运行案例程序,屏幕打印内容如下: 原序列: ['abe', 'cake', 'xy', '6f7e8t3d', 'z'] 按字符串长度排序后: ['z', 'xy', 'abe', 'cake', '6f7e8t3d'] 案例123依据员工的年龄排序 本案例定义了一个Employee类,用于表示员工信息。当将Employee序列传递到sorted函数时,会依据员工的年龄(age属性)排序。 此方案可以通过key参数引用一个返回员工年龄的函数来处理,例如: def test(emp): return emp.age res = sorted(emplist, key=test) 但本案例将采用另一种解决方案: 直接在类中实现比较算法。具体而言,就是实现__gt__、__lt__等方法。实际上,只要实现任意一个比较方法,sorted函数就可以进行排序了。 步骤1: 定义Employee类,其中包含name、age两个属性。 class Employee: name: str age: int # 构造函数 def __init__(self, name='', age=0): self.name = name self.age = age # 自定义输出文本 def __repr__(self): return f'name: {self.name}, age: {self.age}' # 自定义"大于"比较运算 def __gt__(self, other): return self.age > other.age 在构造函数(__init__)中通过传入的参数为name和age属性赋值。在本案例中,Employee类只实现__gt__方法,用于定义“>”(大于)运算符的算法。尽管未实现__eq__、__lt__等方法,但不影响sorted函数的正常工作。 Employee类还定义了__repr__方法,主要用于调用repr函数时的自定义输出。如果不实现此方法,在调试时Python应用程序只会打印Employee案例的内存地址。为了便于查看运行结果,自定义__repr__方法来输出name和age属性的值。 步骤2: 创建Employee的新案例(4个新案例)。 e1 = Employee(name='小王', age=28) e2 = Employee(name='小杜', age=35) e3 = Employee(name='小雷', age=32) e4 = Employee(name='小刘', age=27) 步骤3: 使用上面创建的4个Employee案例构建一个列表对象。 e_list = [e1, e2, e3, e4] #删除引用 del e1,e2,e3,e4 新创建的列表对象会保持对4个Employee案例的引用,而且后面的代码不再引用变量e1、e2、e3和e4,因此可以使用del语句删除它们。变量仅仅保存对案例的引用,删除变量不会删除其引用的案例。 步骤4: 调用sorted函数进行排序。 result = sorted(e_list) 步骤5: 输出排序前与排序后的Employee对象列表。 print('排序前:') for x in e_list: print(x) print('\n排序后:') for e in result: print(e) 步骤6: 运行案例程序,屏幕输出的内容如下: 排序前: name:小王, age: 28 name:小杜, age: 35 name:小雷, age: 32 name:小刘, age: 27 排序后: name:小刘, age: 27 name:小王, age: 28 name:小雷, age: 32 name:小杜, age: 35 案例124以自然常数为底的指数运算 exp函数的签名如下: exp(x) 此函数以自然常数e为底数,以参数x为指数进行运算,即ex。e是常数,它的值是固定的——2.718281828459045。 步骤1: 导入math模块。 import math 步骤2: 先计算e 0。 r1 = math.exp(0) print(f'e的 0 次方:{r1}') 步骤3: 计算e -1.5。 r2 = math.exp(-1.5) print(f'e的 -1.5 次方:{r2}') 步骤4: 计算e100。 r3 = math.exp(100) print(f'e的 100 次方:{r3}') 步骤5: 计算e0.5。 r4 = math.exp(0.5) print(f'e的 0.5 次方:{r4}') 步骤6: 计算eπ,其中,π是圆周率,即3.141592653589793。 r5 = math.exp(math.pi) print(f'e的 π 次方:{r5}') 步骤7: 运行案例程序,输出结果如下: e的 0 次方:1.0 e的 -1.5 次方:0.22313016014842982 e的 100 次方:2.6881171418161356e+43 e的 0.5 次方:1.6487212707001282 e的 π 次方:23.140692632779267 注意: 计算结果2.6881171……e+43中的e并非自然常数e,它是专用于科学记数法的符号,即2.6881171418161356×1043。 案例125求以10为底数的对数 对数是幂的逆运算。以a为底数,求N的对数,可以用符号记作 x=logaN x表示计算结果——指数。例如 x=log416 得到x的结果为2,因为16=42。 在math模块下有个log10函数,它的功能是求以10为底的x的对数,函数签名如下: log10(x) 例如,计算log101000,可以使用log10函数。 log10(1000) 得到结果3.0,因为1000=103。log10函数的返回结果为浮点数类型,哪怕计算结果是整数,它也是以浮点数值返回的。 步骤1: 导入log10函数。 from math import log10 步骤2: 求整数值的对数(以10为底数)。 a = log10(100) b = log10(100000) c = log10(7000) 步骤3: 也可以求浮点数的对数(以10为底数)。 d = log10(15.5) e = log10(8.3) 步骤4: 打印以上所有计算结果。 print(f'log10 100 --> {a}') print(f'log10 100000 --> {b}') print(f'log10 7000 -->{c}') print(f'log10 15.5 --> {d}') print(f'log10 8.3 --> {e}') 步骤5: 运行案例程序,屏幕输出内容为: log10 100 --> 2.0 log10 100000 --> 5.0 log10 7000 -->3.845098040014257 log10 15.5 --> 1.1903316981702914 log10 8.3 --> 0.919078092376074 注意: 调用log10函数时,所传递的参数值必须大于0。如果传递的参数是0或负值,会发生错误。 案例126获取浮点数的分数与整数部分 modf函数的参数x接收一个浮点数值,调用后会返回一个带有两个元素的元组对象。这两个元素依次是参数x的分数部分(小数部分)和整数部分。 例如,以下调用会返回分数0.14000000000000012与整数3。 modf(3.14) modf函数返回的数值的符号由参数x的符号决定。例如: m. modf(-1.2) 返回结果: (-0.19999999999999996, -1.0) 传入的参数若为负值,则modf函数返回的值皆为负值。 步骤1: 从math模块中导入modf函数。 from math import modf 步骤2: 创建一个列表对象,包含若干个数值,这些数值稍后会依次传递给modf函数。 numbers = [12.035, -9.00021, 4.25, 68, 21.909] 步骤3: 调用modf函数,将上述列表中的各个数值进行分数部分与整数部分的拆分。 for n in numbers: f, i = modf(n) print(f'{n} 拆分后\n分数:{f:.8f}\n整数:{i}\n') 步骤4: 运行案例程序,将得到以下结果: 12.035拆分后 分数:0.03500000 整数:12.0 -9.00021拆分后 分数:-0.00021000 整数:-9.0 4.25拆分后 分数:0.25000000 整数:4.0 68拆分后 分数:0.00000000 整数:68.0 21.909拆分后 分数:0.90900000 整数:21.0 案例127计算最大公约数 gcd函数返回两个整数的最大公约数,函数签名如下: gcd(a, b) 如果a、b皆为0,那么gcd函数也返回0。参数a、b必须是整数类型,不能使用浮点数(引发TypeError异常)。 步骤1: 从math模块中导入gcd函数。 from math import gcd 步骤2: 求15与150的最大公约数。 print(f'15与 150 的最大公约数:{gcd(15, 150)}') 步骤3: 求72与60的最大公约数。 print(f'72与 60 的最大公约数:{gcd(72, 60)}') 步骤4: 求135与250的最大公约数。 print(f'135与 250 的最大公约数:{gcd(135, 250)}') 步骤5: 运行案例程序,输出结果如下: 15与 150 的最大公约数:15 72与 60 的最大公约数:12 135与 250 的最大公约数:5 案例128阶 乘 运 算 阶乘是指小于或等于整数n的所有正整数的乘积,即 n!=1×2×3×…×n 例如,5的阶乘,计算过程为 5!=1×2×3×4×5 阶乘运算中有一个特例——0的阶乘是1。 实现阶乘运算,可以自己编写代码来完成。如下面代码所示,fact函数可以计算参数n的阶乘。 def fact(n): if n == 0: return 1 r = 1 for i in range(2, n + 1): r = r * i return r math模块中定义了一个factorial函数,其功能就是用来做阶乘运算的,直接调用即可,开发人员不需要自己编写计算代码。 本案例将演示factorial函数的使用。 步骤1: 导入factorial函数。 from math import factorial 步骤2: 创建一个元组案例,用于稍后计算测试。 nums = 6, 3, 9, 13, 10 步骤3: 依次计算上述元组对象中各个整数的阶乘。 for x in nums: r = factorial(x) print('{0} != {1}'.format(x, r)) 步骤4: 运行案例程序,将得到以下结果: 6 != 720 3 != 6 9 != 362880 13 != 6227020800 10 != 3628800 5.5三角函数 案例129弧度制与角度制之间的转换 弧度的定义: 长度等于半径的一段弧所对应的圆心角为1弧度,单位简写为rad。一个圆周的弧度为2π,即360°; 半圆的弧度为π,即180°。 由此可推导出 1°=π180rad 1rad=180°π 在三角函数中经常会使用弧度制,因此掌握角度制与弧度制之间的转换尤为重要。在Python中,math模块提供了相关的转换函数。degrees函数将弧度制的度数转换为角度制度数; radians函数将角度制的度数转换为弧度制度数。 步骤1: 从math模块中导入degrees、radians函数。 from math import degrees, radians 步骤2: 下面三行代码,将三个角度值转换为弧度角。 print(f'30° -> {radians(30):.4f} rad') print(f'45° -> {radians(45):.4f} rad') print(f'150° -> {radians(150):.4f} rad') 步骤3: 下面代码把弧度值转换为角度值。 print(f'2.5 rad -> {degrees(2.5):.4f}°') print(f'0.77 rad -> {degrees(0.77):.4f}°') print(f'4.2 rad -> {degrees(4.2):.4f}°') 其中,格式控制符“.4f”表示保留4位小数的浮点数值。 步骤4: 运行案例程序,会得到以下结果: 30° -> 0.5236 rad 45° -> 0.7854 rad 150° -> 2.6180 rad 2.5 rad -> 143.2394° 0.77 rad -> 44.1178° 4.2 rad -> 240.6423° 案例130常用的三角函数 Python的math模块提供了以下几个三角函数: (1) sin: 正弦函数。 (2) cos: 余弦函数。 (3) tan: 正切函数。 虽然math模块未提供与余切、正割、余割对应的函数,但可以通过三角函数之间的运算来获得需要的结果。例如 cotx=1tanx secx=1cosx cscx=1sinx 三角函数的参数皆使用弧度制的角度,如果原始数值使用的是角度制,需要调用radians函数转换为弧度角。 步骤1: 从math模块中导入需要使用的函数。 from math import sin, cos, tan, radians 步骤2: 根据三角函数之间的运算关系,自定义三个函数,分别用于计算正割、余割、余切。 #正割 def sec(x): return 1 / cos(x) #余割 def csc(x): return 1 / sin(x) #余切 def cot(x): return 1 / tan(x) 步骤3: 求90°的正弦值。 a = 90 rad = radians(a) res = sin(rad) print(f'sin {a}° = {res}') 步骤4: 求45°的余弦值。 a = 45 rad = radians(a) res = cos(rad) print(f'cos {a}° = {res}') 步骤5: 求60°的正切值。 a = 60 rad = radians(a) res = tan(rad) print(f'tan {a}° = {res}') 步骤6: 求15°的余切值。 a = 15 rad = radians(a) res = cot(rad) print(f'cot {a}° = {res}') 步骤7: 求120°的正割值。 a = 120 rad = radians(a) res = sec(rad) print(f'sec {a}° = {res}') 步骤8: 求75°的余割值。 a = 75 rad = radians(a) res = csc(rad) print(f'csc {a}° = {res}') 步骤9: 运行案例程序,计算结果如下: sin 90° = 1.0 cos 45° = 0.7071067811865476 tan 60° = 1.7320508075688767 cot 15° = 3.7320508075688776 sec 120° = -2.000000000000001 csc 75° = 1.035276180410083 案例131反三角函数 反三角函数,即根据三角函数值来计算出其所对应的角度。由于三角函数具有周期性,因此反三角函数的计算结果可以是多个值。例如,arc sin(-1)的结果可以是270°,也可以是-90°。但在Python中,反三角函数仅返回单个值。表51列出了各个反三角函数的参数区间与返回值区间。 表51各个反三角函数的区间说明 函 数 名 称说明参 数 区 间返回值区间 asin反正弦函数[-1,1]-π2,π2 acos反余弦函数[-1,1]0,π atan反正切函数R(实数集)-π2,π2(无限接近极限值) atan2(y, x)反正切函数,可以计算方位角,即与x轴的夹角。该函数返回的角度可以覆盖多个象限: 正值表示夹角为逆时针方向旋转; 负值表示夹角为顺时针方向旋转。例如,atan2(-1, -1)所返回的角度为-135°x,y∈R-π,π 反三角函数所返回的角度皆使用弧度制,如果需要转换为角度制,可以使用degrees函数。 math模块并未提供反余切、反正割、反余割三个函数,但可以通过三角函数关系进行换算。即 arccot x=π2-arctan x arcsec x=arccos1x arccsc x=arcsin1x 步骤1: 从math模块中导入需要使用的函数。 from math import asin, acos, atan, atan2, degrees, pi 步骤2: 自定义三个函数,分别用于计算反余切、反正割和反余割函数。 #反余切函数 def acot(x): return pi / 2 - atan(x) #反正割函数 def asec(x): return acos(1 / x) #反余割函数 def acsc(x): return asin(1 / x) 步骤3: 求0.5的反正弦值。 n = 0.5 a = asin(n) d = degrees(a) print(f'arcsin {n} = {d}°') 步骤4: 求1的反余弦值。 n = 1 a = acos(n) d = degrees(a) print(f'arccos {n} = {d}°') 步骤5: 求12.5的反正切值。 n = 12.5 a = atan(n) d = degrees(a) print(f'arctan {n} = {d}°') 步骤6: 求-15.21的反余切值。 n = -15.21 a = acot(n) d = degrees(a) print(f'arccot {n} = {d}°') 步骤7: 求4.5的反正割值。 n = 4.5 a = asec(n) d = degrees(a) print(f'arcsec {n} = {d}°') 步骤8: 求-20.6的反余割值。 n = -20.6 a = acsc(n) d = degrees(a) print(f'arccsc {n} = {d}°') 步骤9: 求经过坐标原点(0,0)和坐标点(15,-8)的直线与x轴的夹角,使用atan2函数。 x = 15 y = -8 a = atan2(y, x) d = degrees(a) print(f'arctan2 ({x}, {y}) = {d}°') 步骤10: 运行案例程序,结果如下: arcsin 0.5 = 30.000000000000004° arccos 1 = 0.0° arctan 12.5 = 85.42607874009914° arccot -15.21 = 176.2384327386068° arcsec 4.5 = 77.16041159309584° arccsc -20.6 = -2.7824420499416447° arctan2 (15, -8) = -28.072486935852957° 案例132欧 氏 距 离 欧氏距离(即“欧几里得度量”),用于计算两个坐标点之间的直线距离。对于n维空间,欧氏距离的通用公式为 dx,y=∑ni=1(xi-yi)2 或者 dx,y=∑ni=1xi-yi212 对于二维空间中的两个点的直线距离,可以直接写成 d=x2-x12+y2-y12 如果只是计算某个坐标点与原点之间的距离,还可以简化为 d=x2+y2 math模块提供的hypot函数用于计算坐标点(x,y)与原点(0,0)之间的距离。当然,也可以用于计算二维空间中的任意两点的距离,方法是令x=x2-x1,y=y2-y1。 步骤1: 导入hypot函数。 from math import hypot 步骤2: 计算平面中两个坐标点之间的距离。 x1, y1 = 16, -20 x2, y2 = 25, 16 d = hypot(x2-x1, y2-y1) print(f'点({x1},{y1}) 与点({x2},{y2}) 之间的直线距离为:{d}') 步骤3: 计算坐标点与原点之间的距离。 x, y = -100, -60 d = hypot(x, y) print(f'点({x,y}) 到原点的距离为:{d}') 步骤4: 运行案例程序,结果如下: 点(16,-20) 与点(25,16) 之间的直线距离为:37.107950630558946 点((-100, -60)) 到原点的距离为:116.61903789690601 案例133闵氏距离公式 本案例将演示编写用于计算闵氏距离(即“闵可夫斯基”距离)的函数。闵氏距离计算n维空间中两个坐标点的距离。其公式为 dx,y=∑ni=1xi-yip1p 或者 dx,y=p∑ni=1xi-yip 当常数p=1时,即为曼哈顿距离(绝对值距离); 当p=2时,就是欧几里得距离(欧氏距离); 当p趋向无穷大时,就是切比雪夫距离。 步骤1: 定义minkowski_distance函数,用于计算闵氏距离。 def minkowski_distance(x: Iterable, y: Iterable, p: float): """ 求闵氏距离的函数。 当 p=1 时,为曼哈顿距离; 当 p=2 时,为欧几里得距离; 当 p 趋于无穷大时,为切比雪夫距离 """ # 如果两个坐标的维度不同,抛出异常 if len(x) != len(y): raise Exception('两个坐标的维度不相等') # 合并两个坐标序列 cb = zip(x, y) # 求和 s = sum([abs(xu - yu)**p for xu,yu in cb]) # 开 p 次方,即指数为 1/p 的幂 return s ** (1 / p) 在求和之前,需要使用zip函数将两个坐标序列进行合并。例如,有以下两个序列: 序列1:2、7、9 序列2:5、4、8 使用zip函数合并后变成 [(2,5), (7,4), (9,8)] 步骤2: 下面代码将使用上面定义的minkowski_distance函数来计算三维空间中两个点之间的欧氏距离。 p1 = (15, 7, 24) p2 = (-30, 15, -9) de = minkowski_distance(p1, p2, 2.0) print(f'三维坐标 {p1} 与 {p2} 之间的欧氏距离:\n{de}\n') 调用minkowski_distance函数时,p参数设置为2.0,即为欧氏距离。 步骤3: 下面代码计算二维空间中两个点的曼哈顿距离。 p1 = 1, 5 p2 = 3, -2 dm = minkowski_distance(p1, p2, 1.0) print(f'二维坐标 {p1} 与 {p2} 之间的曼哈顿距离为:\n{dm}') 调用minkowski_distance函数时将p参数设置为1.0就用于计算曼哈顿距离。 步骤4: 运行案例程序,输出结果如下: 三维坐标 (15, 7, 24) 与 (-30, 15, -9) 之间的欧氏距离: 56.37375275782161 二维坐标 (1, 5) 与 (3, -2) 之间的曼哈顿距离为: 9.0 5.6统计学函数 案例134求 和 函 数 sum是内置函数,接收一个iterable类型的对象,并返回该iterable对象中元素(子项)的和,即求和运算。iterable参数支持如列表、元组等数据类型。 另外,sum函数有一个可选的start参数,默认值为0,该参数用于指定iterable参数中求和运算的开始位置。例如,要从iterable中第三个元素开始进行求和,start参数应指定为2。若要从一个序列中截取一部分进行运算,可以使用“切片”格式。例如: n = [5, 2, 3, 7, 12, 20, 15] #求第二、三、四个元素的和 s = sum(n[1:4]) [1:4]表示从原序列中截取索引1~4的元素,其中包含索引为1的元素,但不包含索引为4的元素,最后截取到索引分别为1、2、3的元素。 步骤1: 求一个整数类型元组的总和。 elems = (50, 80, 20, 30, 70) result = sum(elems) print(f'{elems}求和后:{result}') 步骤2: 求一个包含浮点数值的列表的总和。 elems = [0.0001, 0.002, 4.513, 0.886, 0.0003] result = sum(elems) print(f'{elems}求和后:{result}') 步骤3: 求一个字符列表的总和,此代码会发生错误,sum函数仅用于对数值(整数、浮点数)进行运算。 elems = ['x', 'y', 'z'] result = sum(elems) print(f'{elems}求和后:{result}') 要将字符(串)进行串联,应该使用字符串案例的join方法。 result = ''.join(elems) 步骤4: 运行案例程序,结果如下: (50, 80, 20, 30, 70)求和后:250 [0.0001, 0.002, 4.513, 0.886, 0.0003]求和后:5.401400000000001 ['x', 'y', 'z']串联后:xyz 案例135算术平均数 mean函数用于计算简单的算术平均数。设数据样本为x1,x2,x3,…,xn,均值M的计算公式为 M=∑ni=1xin 即所有数据样本的总和除以样本个数。算术平均数主要用于未分组的数据样本。 步骤1: 从statistics模块中导入mean函数。 from statistics import mean 步骤2: 创建列表对象,作为数据样本。 sample = [15, 70, 28, 19, 65, 46, 23] 步骤3: 调用mean函数,求算术平均数。 M = mean(sample) 步骤4: 输出相关的信息。 print(f'数据样本:\n{sample}\n算术平均值:{M}\n') 步骤5: 运行案例程序,结果如下: 数据样本: [15, 70, 28, 19, 65, 46, 23] 算术平均值:38 案例136求字符串样本的平均长度 本案例先创建一个字符串序列,然后使用mean函数计算序列中字符串的平均长度。由于mean函数只针对数值样本进行运算,所以不能直接将字符串序列传递给mean函数。 解决方案是生成由原字符串序列中各个字符串案例的长度组成的新序列,然后再传递给mean函数。获取字符串的长度可以调用len函数。 步骤1: 导入mean函数。 from statistics import mean 步骤2: 创建字符串样本。 sample = '桃李春风', 'act', 'may', '山河', 'tomato', '九里山前古战场,牧童拾得旧刀枪', 'disk space' 步骤3: 创建另一个数据样本,由sample样本中的字符串长度组成。 samplepoints = [len(x) for x in sample] 步骤4: 计算平均长度。 M = mean(samplepoints) 步骤5: 输出相关信息。 print('数据样本:', sample, sep='\n') print(f'平均长度:{M}') 步骤6: 运行案例程序,会得到以下计算结果: 数据样本: ('桃李春风', 'act', 'may', '山河', 'tomato', '九里山前古战场,牧童拾得旧刀枪', 'disk space') 平均长度:6.142857142857143 案例137调和平均数 调和平均数也称倒数平均数,因为它是样本变量倒数的算术平均数的倒数。假设样本数据为x1,x2,x3,…,xn,调和平均数H的计算公式为 H=11n∑ni=11xi=n∑ni=11xi 参与调和平均数计算的样本变量中不能出现0,因为如果存在某个变量的值为0,会使得公式的分母变得无穷大,导致无法求出有效的平均数。 步骤1: 导入harmonic_mean函数,此函数用于求一个数值序列的调和平均数。 from statistics import harmonic_mean 步骤2: 创建用于测试的数据样本。 sample = [56.7, 98.6, 44.12, 55, 16.8, 37.23] 步骤3: 计算数据样本的调和平均数。 M =harmonic_mean(sample) 步骤4: 输出相关信息。 print('数据样本:', sample, sep='\n') print(f'调和平均值:{M}') 步骤5: 运行案例程序,输出结果如下: 数据样本: [56.7, 98.6, 44.12, 55, 16.8, 37.23] 调和平均值:38.70722593516536 案例138中位数 将一组数值序列进行排序后(升序或降序),处于序列中间位置的数值便是中位数,也称中值。假设数据序列有n个变量,若n为奇数,那么处于中间位置的数值就是要求的中位数; 如果n是偶数,则中间两个数值的平均数为中位数。例如,序列1、3、5中,中位数为3; 在序列2、3、4、5中,中位数是3.5,即3+42。 median函数用于求样本序列的中位数,使用的是上文所述的算法。另外,当样本中变量个数为偶数时,有时候可能不希望使用中间两个数值的平均值,而是想取出其中一个作为中位数,这时可以考虑使用median_low或者median_high函数。举个例子,对于样本5、6、7、8,中间两个数为6和7,如果调用median_low函数将返回6,调用median_high函数则返回7。 如果样本中的变量个数为奇数,那么median_low和median_high函数的返回结果与median函数相同。 步骤1: 导入median、median_low、median_high三个函数。 from statistics import median,median_low, median_high 步骤2: 当样本中变量个数为奇数时,求中位数。 sample1 = [5, 9, 3, 6, 7] m1 = median(sample1) print('样本:', sample1) print('中位数:', m1) 步骤3: 当样本中变量个数为偶数时,可以用三种方式求中位数。 sample2 = [20, 12, 8, 16, 14, 6] #中间两项的平均值 m2 = median(sample2) #中间两个数中取出较大的一个 m3 = median_high(sample2) #取出较小的那个 m4 = median_low(sample2) print('样本:', sample2) print('中位数(均值):', m2) print('中位数(较大):', m3) print('中位数(较小):', m4) 步骤4: 运行案例程序,屏幕输出内容如下: -----------样本个数为奇数------------ 样本: [5, 9, 3, 6, 7] 中位数: 6 -----------样本个数为偶数------------ 样本: [20, 12, 8, 16, 14, 6] 中位数(均值): 13.0 中位数(较大): 14 中位数(较小): 12 注意: 调用median、median_low或median_high函数时,会自动对原序列进行排序。 案例139从分组数据中求中位数 当数据样本中出现重复的变量时,可以从数据分组中估计其中位数。median_grouped函数的计算公式如下 M=L+n2-cff×interval 其中,interval是组距,数据样本排序后,假设处于中间位置的变量为x。f是x重复出现的次数,称为频数; cf是变量x之前的所有变量的累积频数。n是数据样本的变量个数,L是处于中间分组的下限。其计算方法为 L=x-interval2 举例,某数据样本为4、3、2、3、5,排序之后为2、3、3、4、5,假定组距interval为1,于是 x=3,n=5,interval=1,cf=1,f=2 L=x-interval2=3-12=2.5 M=L+n2-cff×interval=2.5+52-12×1=3.25 median_grouped函数的签名如下: median_grouped(data, interval=1) interval参数如果忽略,则默认为1。 步骤1: 导入def median_grouped函数。 from statistics import median_grouped 步骤2: 当组距interval保持默认值1时,求数据列表的分组中位数。 sample = [25, 40, 80, 40, 25, 36, 40] mg = median_grouped(sample) print(f'样本:\n{sample}') print(f'分组中位数:{mg}\n') 步骤3: 当interval为2时,求数据列表的分组中位数。 sample = [7, 27, 27, 27, 65, 85, 85, 85] interval = 2 mg = median_grouped(sample, interval) print(f'样本:\n{sample}') print(f'组距:{interval}') print(f'分组中位数:{mg}\n') 步骤4: 当数据列表不包含重复变量时,求分组中位数。 sample = [55, 57, 52, 53, 61, 62] mg = median_grouped(sample) print(f'样本:\n{sample}') print(f'当样本不存在重复变量时,分组中位数为:{mg}') 步骤5: 运行应用程序,屏幕输出内容如下: 样本: [25, 40, 80, 40, 25, 36, 40] 分组中位数:39.666666666666664 样本: [7, 27, 27, 27, 65, 85, 85, 85] 组距:2 分组中位数:64.0 样本: [55, 57, 52, 53, 61, 62] 当样本不存在重复变量时,分组中位数为:56.5 注意: median_grouped函数会自动对data参数进行排序。 案例140众数 众数是指在数据样本中重复出现次数最多的变量,即频数最大的变量。mode函数可以返回一个序列的众数。如果序列中的数值没有重复项,或者无法确定哪个值的频数最大,就会发生错误。 步骤1: 导入mode函数。 from statistics import mode 步骤2: 求一个序列的众数。 sample = 1, 5, 6, 6, 5, 5, 5, 3 print(f'样本:{sample}') c = mode(sample) print(f'众数:{c}\n') 步骤3: 当一个序列中不存在重复出现的项时,会发生错误。 sample = 5, 2, 10, 7, 4, 11 print(f'无重复项的样本:{sample}') try: c = mode(sample) except: c = '<发生错误>' print(f'众数:{c}') 由于上述代码在运行的时候会发生异常,所以可以将mode函数的调用写在try语句块中,目的是捕捉异常。 步骤4: 运行案例程序,得到以下结果: 样本:(1, 5, 6, 6, 5, 5, 5, 3) 众数:5 无重复项的样本:(5, 2, 10, 7, 4, 11) 众数:<发生错误> 案例141方差 与方差有关的函数有两个——variance和pvariance。variance函数返回数据序列的样本方差,pvariance函数返回的是数据序列的总体方差。假设样本方差为Vs,总体方差为Vp,那么它们的计算公式分别为 Vs=1n-1∑ni=1xi-x-2 Vp=1n∑ni=1xi-x-2 其中,n表示数据样本中变量个数,x-表示样本中变量的平均值。 步骤1: 导入两个方差函数。 from statistics import variance,pvariance 步骤2: 假设对两批零件进行取样测量,分别得出10个测量数值。 sample1 = 37.1, 36.89, 37.2, 37.22, 37.01, 37.12, 36.92, 36.99, 37.03, 36.98 sample2 = 37.23, 37.012, 37.31, 36.84, 37.01, 36.91, 37.35, 36.97, 37.04, 37.11 步骤3: 对两组数据分别计算其样本方差与总体方差,并向屏幕打印计算结果。 print(f'样本1:\n{sample1}') print(f'样本方差:{variance(sample1)}') print(f'总体方差:{pvariance(sample1)}') print(f'样本2:\n{sample2}') print(f'样本方差:{variance(sample2)}') print(f'总体方差:{pvariance(sample2)}') 步骤4: 运行案例程序,输出结果如下: 样本1: (37.1, 36.89, 37.2, 37.22, 37.01, 37.12, 36.92, 36.99, 37.03, 36.98) 样本方差:0.012404444444444438 总体方差:0.011163999999999995 样本2: (37.23, 37.012, 37.31, 36.84, 37.01, 36.91, 37.35, 36.97, 37.04, 37.11) 样本方差:0.028765733333333432 总体方差:0.025889160000000088 案例142标准差 将方差的计算结果开平方根,就得到标准差。因此计算数据序列的标准差,也有两个函数——stdev和pstdev。stdev用于计算样本标准差,pstdev用于计算总体标准差。在实际应用中,标准差比方差更直观地反映数据离散(偏离均值)程度。 假设用Ss表示样本标准差,用Sp表示总体标准差,它们的计算公式依次为 Ss=1n-1∑ni=1xi-x-2 Sp=1n∑ni=1xi-x-2 在统计数据时,选用总体标准还是样本标准差,取决于数据是否完整全面。例如,要求全国人口每天饮水量的标准差,就要选用样本标准差,因为无法获取到全国所有人口每天喝多少水,而只能对1000人、5000人,或20000人进行抽样获取数据。 如果要计算某只股票在过去一年里涨跌的标准差,这种情况可以使用总体标准差,因为一只股票在一年内的涨跌数据是可以完整获得的。 步骤1: 导入stdev函数。 from statistics import stdev 步骤2: 假设随机抽取两位学生,让其各自进行三次肺活量测试,得到两组数据。 data1 = 2966, 3002, 2980 data2 = 3500, 3325, 3490 步骤3: 对两组数据分别计算标准差。 s1 =stdev(data1) s2 =stdev(data2) 步骤4: 向屏幕输出相关信息。 print(f'第一组样本:\n{data1}') print(f'标准差:{s1}\n') print(f'第二组样本:\n{data2}') print(f'标准差:{s2}') 步骤5: 运行案例程序,屏幕输出内容如下: 第一组样本: (2966, 3002, 2980) 标准差:18.14754345175493 第二组样本: (3500, 3325, 3490) 标准差:98.27681991870378 从计算结果来看,第一组数据的标准差较小,表明这位同学在三次肺活量测试中表现比较平稳。 5.7分式 案例143如何案例化Fraction类 与decimal模块的用途类似,fractions模块也是针对特殊的数字类型提供的。分数与浮点数之间虽然可以进行转换,但由于二进制处理上的误差,在计算机中,浮点数并不能完全等同于分数。fractions模块的核心是Fraction类,该类可以做一些专门针对分数的运算。 例如,计算12-13,按照一般数学计算,其结果应为16。但若以浮点数来计算,其结果为0.16666666666666669,而16转换为浮点数为0.16666666666666666,显然,尽管两者很接近,但还是有误差的。 Fraction类支持专用于分数运算的功能,如上面所举的例子,若使用Fraction类来计算12-13,就能得到结果16,而不是一个浮点数值。 Fraction类是用__new__方法来定义构造函数的,而不是__init__方法,原因是该类型为不可变类型。 __new__(numerator=0, denominator=None, *, _normalize=True) 其中,numerator参数指定分子,denominator参数指定分母,_normalize参数一般保持默认值(True),它指示是否对分数进行约分。例如,Fraction(6, 8),如果_normalize参数为True,则生成的分数为34,否则就生成68。 步骤1: 导入Fraction类。 from fractions import Fraction 步骤2: 使用字符串来初始化Fraction对象。 fac1 = Fraction('5/9') print(f'用字符串初始化:{fac1}') 初始化时表示分子是5,分母是9。 步骤3: 使用浮点数来初始化Fraction对象。 fac2 = Fraction(3.5) print(f'用浮点数初始化:{fac2}') 步骤4: 直接指定分子和分母来初始化Fraction对象。 fac3 = Fraction(2, 5) print(f'用分子与分母初始化:{fac3}') 步骤5: 使用Decimal案例来初始化Fraction对象。 from decimal import Decimal dcm = Decimal(0.4) fac4 = Fraction(dcm) print(f'用 Decimal 对象来初始化:{fac4}') 使用Decimal案例来初始化Fraction对象与使用浮点数来初始化相类似。 步骤6: 使用其他的Fraction案例来初始化。 fac5 = Fraction(Fraction(1, 3), Fraction(2, 7)) print(f'用其他 Fraction 对象初始化:{fac5}') 上述代码在创建Fraction案例时,指定了分子为13,分母为27,最后产生的分数为 1327=13×72=76 步骤7: 当然,分子/分母也可以是负值。 fac6 = Fraction(-3, -11) print(f'分子和分母都是负值:{fac6}') fac7 = Fraction(4, -9) print(f'分子是正值,分母是负值:{fac7}') fac8 = Fraction(-3, 5) print(f'分子是负值,分母是正值:{fac8}') 步骤8: 运行案例程序,屏幕输出内容如下: 用字符串初始化:5/9 用浮点数初始化:7/2 用分子与分母初始化:2/5 用 Decimal 对象来初始化:3602879701896397/9007199254740992 用其他 Fraction 对象初始化:7/6 分子和分母都是负值:3/11 分子是正值,分母是负值:-4/9 分子是负值,分母是正值:-3/5 案例144限制分母的大小 在使用浮点数初始化Fraction对象时,由于误差会导致产生较大的分母或分子。例如,浮点数值0.6产生的Fraction分式为54043195528445959007199254740992,而期望的分式应该为35。此时,可以调用limit_denominator方法,以返回一个最合适的分式。 limit_denominator方法有一个max_denominator参数,用来限制修正分式时分母的最大值,默认为1000000。例如,从浮点数值0.72创建的分式为 32425917317067574503599627370496 然后调用limit_denominator方法,将分母限制在10以内,得到近似分式 57 步骤1: 导入Fraction类。 from fractions import Fraction 步骤2: 创建一个5个浮点数值的元组,稍后分别使用它们来初始化Fraction对象。 floats = 0.2, 1.25, 0.875, 1.2, 8.5 步骤3: 通过for循环依次为元组中的浮点数创建分式,并输出分母大小被限制前后的值。 for n in floats: print(f'浮点数:{n}') fac = Fraction(n) print(f'产生的分式:{fac}') print(f'限制分母大小后:{fac.limit_denominator()}\n') 步骤4: 运行案例程序,输出结果如下: 浮点数:0.2 产生的分式:3602879701896397/18014398509481984 限制分母大小后:1/5 浮点数:1.25 产生的分式:5/4 限制分母大小后:5/4 浮点数:0.875 产生的分式:7/8 限制分母大小后:7/8 浮点数:1.2 产生的分式:5404319552844595/4503599627370496 限制分母大小后:6/5 浮点数:8.5 产生的分式:17/2 限制分母大小后:17/2 案例145常见的分式运算 Fraction类支持常见的数学运算,如四则运算、乘方运算、整除、取余、比较运算等。 步骤1: 导入Fraction类。 from fractions import Fraction 步骤2: 两个分式相加。 a = Fraction('1/5') b = Fraction('2/3') r = a + b print(f'{a} + {b} = {r}') 步骤3: 两个分式相减。 a = Fraction('4/11') b = Fraction('2/15') r = a - b print(f'{a} - {b} = {r}') 步骤4: 两个分式相乘。 a = Fraction(3, 8) b = Fraction(7, 12) r = a * b print(f'{a} * {b} = {r}') 步骤5: 两个分式相除。 a = Fraction('3/10') b = Fraction('1/2') r = a / b print(f'{a} / {b} = {r}') 步骤6: 求某个分式的立方。 a = Fraction('2/5') r = a ** 3 print(f'{a} ^ 3 = {r}') 步骤7: 两个分式相除,获取余数。 a = Fraction('6/25') b = Fraction('1/3') r = a % b print(f'{a} % {b} = {r}') 步骤8: 求分式的绝对值。 a = Fraction(-7, 13) r = abs(a) print(f'|{a}| = {r}') 步骤9: 运行应用程序,屏幕将输出以下结果: 1/5 + 2/3 = 13/15 4/11 - 2/15 = 38/165 3/8 * 7/12 = 7/32 3/10 / 1/2 = 3/5 2/5 ^ 3 = 8/125 6/25 % 1/3 = 6/25 |-7/13| = 7/13 5.8日期与时间 案例146日期之间的比较 date、time、datetime以及timedelta这些类都支持比较运算,因为它们都定义了__lt__、__ge__、__gt__、__le__、__eq__等方法。 本案例以date类为例来演示日期之间的比较运算。date类只用于表示日期部分的数据(不包含时间部分,若需要时间部分,可以使用datetime类),构造对象案例时使用以下3个参数: (1) year: 年份。 (2) month: 月份。 (3) day: 某月份中的一天。 year的有效值为[1,9999]; month的有效值为[1,12]; day的有效值视年份和月份而定,如果是2月并且是闰年,则为29天,否则为28天。其他的视常规月份来确定,例如7月是31天。 步骤1: 导入datetime模块。 import datetime 步骤2: 创建两个date案例。 d1 =datetime.date(year=2009, month=12, day=10) d2 =datetime.date(year=2012, month=10, day=3) 步骤3: 判断一下,d1是否比d2小。 print(f'{d1}比{d2}小吗?\n{"是的" if d1 < d2 else "不是"}\n') 步骤4: 再创建两个date案例。 d3 =datetime.date(year=2017, month=5, day=1) d4 =datetime.date(year=2017, month=4, day=15) 步骤5: 判断一下,d3是不是大于d4。 print(f'{d3}比{d4}大吗?\n{"是的" if d3 > d4 else "不是"}\n') 步骤6: 运行应用程序,得出以下结果: 2009-12-10比2012-10-03小吗? 是的 2017-05-01比2017-04-15大吗? 是的 案例147计算时间差 timedelta类表示的是时间段(两个时间点之间的间距),它依次以周、日、时、分、秒、毫秒、微秒来累计时间段的长短。 date与datetime类的案例在执行减法运算后会返回timedelta案例,表示这两个时间点之间的差。 步骤1: 从datetime模块中导入需要用到的类型。 from datetime import date, time, datetime,timedelta 步骤2: 计算两个日期之间相隔多少天。 d1 = date(year=2012, month=3, day=16) d2 = date(year=2014, month=3, day=16) dlt_day = d2 - d1 print(f'从{d1}到{d2}相距{dlt_day.days}天') timedelta类的days属性返回时间段的总天数。 步骤3: 计算两个时间点之间所包含的秒数。 t1 = datetime(year=2019, month=1, day=1, hour=15, minute=30, second=0) t2 = datetime(year=2019, month=1, day=1, hour=18, minute=45, second=0) delt_m = t2 - t1 print(f'从{t1.time()}到{t2.time()}共有{delt_m.seconds}秒') 在仅计算时间与时间的间隔时,年(year)、月(month)、日(day)这三个参数其实并不需要,但在案例化datetime类时必须提供。 步骤4: 运行案例程序,结果如下: 从2012-03-16到2014-03-16相距730天 从15:30:00到18:45:00共有11700秒 案例148timedelta类的乘法运算 timedelta类支持乘法与除法运算,但另一个操作数仅限于与整数类型(int)。即timedelta对象只可乘以或除以一个整数值。 timedelta对象的乘法运算会使时间间隔成倍增长,例如3个小时,乘以2之后就是6个小时; 同理,除法运算会使时间间隔成倍缩短,例如3个小时,除以3后就是1个小时。 步骤1: 导入timedelta类。 from datetime import timedelta 步骤2: 创建一个timedelta案例,时长为30分钟。 tlt = timedelta(minutes=30) 步骤3: 将上述时间段除以2。 print(f'{tlt}除以2之后: {tlt/2}') 步骤4: 将上述时间段乘以3。 print(f'{tlt}乘以3之后: {tlt*3}') 步骤5: 运行以上代码,屏幕输出内容如下: 0:30:00除以2之后:0:15:00 0:30:00乘以3之后:1:30:00