提交 585b0977 编写于 作者: 骆昊的技术专栏's avatar 骆昊的技术专栏

update Day06

上级 76a92c14
"""
函数的定义和使用 - 计算组合数C(7,3)
Version: 0.1
Author: 骆昊
Date: 2018-03-05
"""
# 将求阶乘的功能封装成一个函数
def factorial(n):
result = 1
for num in range(1, n + 1):
result *= num
return result
print(factorial(7) // factorial(3) // factorial(4))
"""
函数的定义和使用 - 求最大公约数和最小公倍数
Version: 0.1
Author: 骆昊
Date: 2018-03-05
"""
def gcd(x, y):
if x > y:
(x, y) = (y, x)
for factor in range(x, 1, -1):
if x % factor == 0 and y % factor == 0:
return factor
return 1
def lcm(x, y):
return x * y // gcd(x, y)
print(gcd(15, 27))
print(lcm(15, 27))
"""
Python的内置函数
- 数学相关: abs / divmod / pow / round / min / max / sum
- 序列相关: len / range / next / filter / map / sorted / slice / reversed
- 类型转换: chr / ord / str / bool / int / float / complex / bin / oct / hex
- 数据结构: dict / list / set / tuple
- 其他函数: all / any / id / input / open / print / type
Version: 0.1
Author: 骆昊
Date: 2018-03-05
"""
def myfilter(mystr):
return len(mystr) == 6
# help()
print(chr(0x9a86))
print(hex(ord('骆')))
print(abs(-1.2345))
print(round(-1.2345))
print(pow(1.2345, 5))
fruits = ['orange', 'peach', 'durian', 'watermelon']
print(fruits[slice(1, 3)])
fruits2 = list(filter(myfilter, fruits))
print(fruits)
print(fruits2)
"""
Python常用模块
- 运行时服务相关模块: copy / pickle / sys / ...
- 数学相关模块: decimal / math / random / ...
- 字符串处理模块: codecs / re / ...
- 文件处理相关模块: shutil / gzip / ...
- 操作系统服务相关模块: datetime / os / time / logging / io / ...
- 进程和线程相关模块: multiprocessing / threading / queue
- 网络应用相关模块: ftplib / http / smtplib / urllib / ...
- Web编程相关模块: cgi / webbrowser
- 数据处理和编码模块: base64 / csv / html.parser / json / xml / ...
Version: 0.1
Author: 骆昊
Date: 2018-03-05
"""
import time
import shutil
import os
seconds = time.time()
print(seconds)
localtime = time.localtime(seconds)
print(localtime)
print(localtime.tm_year)
print(localtime.tm_mon)
print(localtime.tm_mday)
asctime = time.asctime(localtime)
print(asctime)
strtime = time.strftime('%Y-%m-%d %H:%M:%S', localtime)
print(strtime)
mydate = time.strptime('2018-1-1', '%Y-%m-%d')
print(mydate)
shutil.copy('/Users/Hao/hello.py', '/Users/Hao/Desktop/first.py')
os.system('ls -l')
os.chdir('/Users/Hao')
os.system('ls -l')
os.mkdir('test')
"""
函数的参数
- 默认参数
- 可变参数
- 关键字参数
- 命名关键字参数
Version: 0.1
Author: 骆昊
Date: 2018-03-05
"""
# 参数默认值
def f1(a, b=5, c=10):
return a + b * 2 + c * 3
print(f1(1, 2, 3))
print(f1(100, 200))
print(f1(100))
print(f1(c=2, b=3, a=1))
# 可变参数
def f2(*args):
sum = 0
for num in args:
sum += num
return sum
print(f2(1, 2, 3))
print(f2(1, 2, 3, 4, 5))
print(f2())
# 关键字参数
def f3(**kw):
if 'name' in kw:
print('欢迎你%s!' % kw['name'])
elif 'tel' in kw:
print('你的联系电话是: %s!' % kw['tel'])
else:
print('没找到你的个人信息!')
param = {'name': '骆昊', 'age': 38}
f3(**param)
f3(name='骆昊', age=38, tel='13866778899')
f3(user='骆昊', age=38, tel='13866778899')
f3(user='骆昊', age=38, mobile='13866778899')
"""
作用域问题
Version: 0.1
Author: 骆昊
Date: 2018-03-05
"""
# 局部作用域
def foo1():
a = 5
foo1()
# print(a) # NameError
# 全局作用域
b = 10
def foo2():
print(b)
foo2()
def foo3():
b = 100 # 局部变量
print(b)
foo3()
print(b)
def foo4():
global b
b = 200 # 全局变量
print(b)
foo4()
print(b)
## 函数和模块的使用
在讲解本章节的内容之前,我们先来研究一道数学题,请说出下面的方程有多少组正整数解。
$$x_1 + x_2 + x_3 + x_4 = 8$$
事实上,上面的问题等同于将8个苹果分成四组每组至少一个苹果有多少种方案。想到这一点问题的答案就呼之欲出了。
$$C_M^N =\frac{M!}{N!(M-N)!}, \text{(M=7, N=3)} $$
可以用Python的程序来计算出这个值,代码如下所示。
```Python
"""
输入M和N计算C(M,N)
"""
m = int(input('m = '))
n = int(input('n = '))
fm = 1
for num in range(1, m + 1):
fm *= num
fn = 1
for num in range(1, n + 1):
fn *= num
fmn = 1
for num in range(1, m - n + 1):
fmn *= num
print(fm // fn // fmn)
```
### 函数的作用
不知道大家是否注意到,在上面的代码中,我们做了3次求阶乘,这样的代码实际上就是重复代码。编程大师Martin Fowler先生曾经说过:“代码有很多种坏味道,重复是最坏的一种!”,要写出高质量的代码首先要解决的就是重复代码的问题。对于上面的代码来说,我们可以将计算阶乘的功能封装到一个称之为“函数”的功能模块中,在需要计算阶乘的地方,我们只需要“调用”这个“函数”就可以了。
### 定义函数
在Python中可以使用`def`关键字来定义函数,和变量一样每个函数也有一个响亮的名字,而且命名规则跟变量的命名规则是一致的。在函数名后面的圆括号中可以放置传递给函数的参数,这一点和数学上的函数非常相似,程序中函数的参数就相当于是数学上说的函数的自变量,而函数执行完成后我们可以通过`return`关键字来返回一个值,这相当于数学上说的函数的因变量。
在了解了如何定义函数后,我们可以对上面的代码进行重构,所谓重构就是在不影响代码执行结果的前提下对代码的结构进行调整,重构之后的代码如下所示。
```Python
def factorial(num):
"""
求阶乘
:param num: 非负整数
:return: num的阶乘
"""
result = 1
for n in range(1, num + 1):
result *= n
return result
m = int(input('m = '))
n = int(input('n = '))
# 当需要计算阶乘的时候不用再写循环求阶乘而是直接调用已经定义好的函数
print(factorial(m) // factorial(n) // factorial(m - n))
```
> **说明:**Python的math模块中其实已经有一个factorial函数了,事实上要计算阶乘可以直接使用这个现成的函数而不用自己定义。下面例子中的某些函数其实Python中也是内置了,我们这里是为了讲解函数的定义和使用才把它们又实现了一遍,实际开发中不建议做这种低级的重复性的工作。
### 函数的参数
函数是绝大多数编程语言中都支持的一个代码的“构建块”,但是Python中的函数与其他语言中的函数还是有很多不太相同的地方,其中一个显著的区别就是Python对函数参数的处理。在Python中,函数的参数可以有默认值,也支持使用可变参数,所以Python并不需要像其他语言一样支持[函数的重载](https://zh.wikipedia.org/wiki/%E5%87%BD%E6%95%B0%E9%87%8D%E8%BD%BD),因为我们在定义一个函数的时候可以让它有多种不同的使用方式,下面是两个小例子。
```Python
from random import randint
def roll_dice(n=2):
"""
摇色子
:param n: 色子的个数
:return: n颗色子点数之和
"""
total = 0
for _ in range(n):
total += randint(1, 6)
return total
def add(a=0, b=0, c=0):
return a + b + c
# 如果没有指定参数那么使用默认值摇两颗色子
print(roll_dice())
# 摇三颗色子
print(roll_dice(3))
print(add())
print(add(1))
print(add(1, 2))
print(add(1, 2, 3))
# 传递参数时可以不按照设定的顺序进行传递
print(add(c=50, a=100, b=200))
```
我们给上面两个函数的参数都设定了默认值,这也就意味着如果在调用函数的时候如果没有传入对应参数的值时将使用该参数的默认值,所以在上面的代码中我们可以用各种不同的方式去调用`add`函数,这跟其他很多语言中函数重载的效果是一致的。
其实上面的`add`函数还有更好的实现方案,因为我们可能会对0个或多个参数进行加法运算,而具体有多少个参数是由调用者来决定,我们作为函数的设计者对这一点是一无所知的,因此在不确定参数个数的时候,我们可以使用可变参数,代码如下所示。
```Python
# 在参数前使用*表示args是可变参数
# 也就是说调用add函数时传入的参数个数可以是0个或多个
def add(*args):
total = 0
for val in args:
total += val
return total
print(add())
print(add(1))
print(add(1, 2))
print(add(1, 2, 3))
print(add(1, 3, 5, 7, 9))
```
### 用模块管理函数
对于任何一种编程语言来说,给变量、函数这样的标识符起名字都是一个让人头疼的问题,因为我们会遇到命名冲突这种尴尬的情况。最简单的场景就是在同一个.py文件中定义了两个同名函数,由于Python没有函数重载的概念,那么后面的定义会覆盖之前的定义,也就意味着两个函数同名函数实际上只有一个是存在的。
```Python
def foo():
print('hello, world!')
def foo():
print('goodbye, world!')
foo() # 输出goodbye, world!
```
当然上面的这种情况我们很容易就能避免,但是如果项目是由多人协作进行团队开发的时候,团队中可能有多个程序员都定义了名为`foo`的函数,那么怎么解决这种命名冲突呢?答案其实很简单,Python中每个文件就代表了一个模块(module),我们在不同的模块中可以有同名的函数,在使用函数的时候我们通过`import`关键字导入指定的模块就可以区分到底要使用的是哪个模块中的`foo`函数,代码如下所示。
module1.py
```Python
def foo():
print('hello, world!')
```
module2.py
```Python
def foo():
print('goodbye, world!')
```
test.py
```Python
from module1 import foo
foo() # 输出hello, world!
from module2 import foo
foo() # 输出goodbye, world!
```
也可以按照如下所示的方式来区分到底要使用哪一个`foo`函数。
test.py
```Python
import module1 as m1
import module2 as m2
m1.foo()
m2.foo()
```
但是如果将代码写成了下面的样子,那么程序中调用的是最后导入的那个`foo`,因为后导入的foo覆盖了之前导入的`foo`
test.py
```Python
from module1 import foo
from module2 import foo
foo() # 输出goodbye, world!
```
test.py
```Python
from module2 import foo
from module1 import foo
foo() # 输出hello, world!
```
需要说明的是,如果我们导入的模块除了定义函数之外还中有可以执行代码,那么Python解释器在导入这个模块时就会执行这些代码,事实上我们可能并不希望如此,因此如果我们在模块中编写了执行代码,最好是将这些执行代码放入如下所示的条件中,这样的话除非直接运行该模块,if条件下的这些代码是不会执行的,因为只有直接执行的模块的名字才是“\_\_main\_\_”。
module3.py
```Python
def foo():
pass
def bar():
pass
# __name__是Python中一个隐含的变量它代表了模块的名字
# 只有被Python解释器直接执行的模块的名字才是__main__
if __name__ == '__main__':
print('call foo()')
foo()
print('call bar()')
bar()
```
test.py
```Python
import module3
# 导入module3时 不会执行模块中if条件成立时的代码 因为模块的名字是module3而不是__main__
```
### 练习
#### 练习1:实现计算求最大公约数和最小公倍数的函数。
```Python
def gcd(x, y):
(x, y) = (y, x) if x > y else (x, y)
for factor in range(x, 0, -1):
if x % factor == 0 and y % factor == 0:
return factor
def lcm(x, y):
return x * y // gcd(x, y)
```
#### 练习2:实现判断一个数是不是回文数的函数。
```Python
def is_palindrome(num):
temp = num
total = 0
while temp > 0:
total = total * 10 + temp % 10
temp //= 10
return total == num
```
#### 练习3:实现判断一个数是不是素数的函数。
```Python
def is_prime(num):
for factor in range(2, num):
if num % factor == 0:
return False
return True if num != 1 else False
```
#### 练习4:写一个程序判断输入的正整数是不是回文素数。
```Python
if __name__ == '__main__':
num = int(input('请输入正整数: '))
if is_palindrome(num) and is_prime(num):
print('%d是回文素数' % num)
```
通过上面的程序可以看出,当我们将代码中重复出现的和相对独立的功能抽取成函数后,我们可以组合使用这些函数来解决更为复杂的问题,这也是我们为什么要定义和使用函数的一个非常重要的原因。
\ No newline at end of file
## Python从新手到大师
## Python教学大纲
### Python应用领域和就业形势分析
Python语言自身的优势:优雅、明确、简单
简单的说,Python是一个“优雅”、“明确”、“简单”的编程语言
- 学习曲线低,尤其适合非专业人士
- 开源软件,大量的三方库和强大的生态圈
- 学习曲线低,适合非专业人士
- 开源软件,强大的生态圈
- 解释型语言,完美的平台可移植性
- 支持两种主流的编程范式,面向对象和函数式编程
- 可扩展性和可嵌入性,可以调用C/C++代码反之亦可以
- 支持面向对象和函数式编程
- 可扩展性,能调用C/C++代码
- 代码规范程度高,可读性强
目前几个比较流行的领域,Python都有用武之地。
......@@ -43,7 +43,7 @@ Python的就业市场分析:相同工作职位和要求,薪资普遍高3k-5k
![成都Python职位和招聘信息](./res/python-job-chengdu.png)
给初学者的几个建议:一个老司机的忠告。
给初学者的几个建议(老司机的忠告):
- Make English as your working language.
- Practice makes perfect.
......@@ -79,73 +79,77 @@ Python的就业市场分析:相同工作职位和要求,薪资普遍高3k-5k
- 循环结构的应用场景 - 条件 / 缩进 / 代码块 / 流程图
- while循环 - 基本结构 / break语句 / continue语句
- for循环 - 基本结构 / range类型 / 在循环中使用分支结构 / 提前结束程序
- 应用案例 - 1~100求和 / 求阶乘 / 判断素数 / 求最大公约数和最小公倍数 / 打印三角形图案
- for循环 - 基本结构 / range类型 / 循环中的分支结构 / 嵌套的循环 / 提前结束程序
- 应用案例 - 1~100求和 / 判断素数 / 猜数字游戏 / 打印九九表 / 打印三角形图案 / 猴子吃桃 / 百钱百鸡
#### Day05 - [总结和练习](./Day05/练习.md)
- 基础练习 - 水仙花数 / 完美数 / 打印1~100之间的素数 / 九九表 / Fibonacci数列 / 回文数 / 百钱百鸡
- 综合练习 - 猜数字游戏 / Craps赌博游戏
- 基础练习 - 水仙花数 / 完美数 / 五人分鱼 / Fibonacci数列 / 回文素数
- 综合练习 - Craps赌博游戏
#### Day06 - [列表和元组](./Day06/列表和元组.md)
#### Day06 - [函数和模块的使用](./Day08/函数和模块的使用.md)
- 函数的作用 - 代码的坏味道 / 用函数封装功能模块
- 定义函数 - def语句 / 函数名 / 参数列表 / return语句 / 调用自定义函数
- 调用函数 - Python内置函数 / 导入模块和函数
- 函数的参数 - 默认参数 / 可变参数 / 关键字参数(\*) / 命名关键字参数(\*)
- 函数的返回值 - 没有返回值 / 返回单个值 / 返回多个值(\*)
- 作用域问题 - 局部作用域 / 嵌套作用域 / 全局作用域 / 内置作用域 / 和作用域相关的关键字
- 用模块管理函数 - 模块的概念 / 用自定义模块管理函数 / 命名冲突的时候会怎样(同一个模块和不同的模块)
> **说明:**用\*标记的内容建议放到第8天再讲,因为讲完列表、元组、字典等内容后学生才更容易理解这些特殊的用法。
#### Day07 - [字符串和常用数据结构](./Day07/字符串和常用数据结构.md)
- 字符串的使用 - 计算长度 / 下标运算 / 切片 / 常用方法
- 列表基本用法 - 定义列表 / 用下表访问元素 / 下标越界 / 添加元素 / 删除元素 / 修改元素 / 切片 / 循环遍历
- 列表常用操作 - 连接 / 复制(复制元素和复制数组) / 长度 / 排序 / 倒转 / 查找
- 生成列表 - 使用range创建数字列表 / 生成表达式 / 生成器
- 元组的使用 - 定义元组 / 使用元组中的值 / 修改元组变量 / 元组和列表转换
- 基础练习 - 列表找最大元素 / 统计考试成绩的平均分 / 跑马灯效果 / Fibonacci数列 / 杨辉三角 / 学生成绩统计表
- 综合练习 - 双色球随机选号
#### Day07 - [集合和字典](./Day07/集合和字典.md)
- 集合基本用法 - 集合和列表的区别 / 创建集合 / 添加元素 / 删除元素 / 清空
- 集合常用操作 - 交集 / 并集 / 差集 / 对称差 / 子集 / 超集
- 字典的基本用法 - 字典的特点 / 创建字典 / 添加元素 / 删除元素 / 取值 / 清空
- 字典常用操作 - keys()方法 / values()方法 / items()方法 / setdefault()方法
- 数据结构和算法 - 常用数据结构简介 / 数据结构和算法应用举例
- 综合练习 - 井字棋游戏
>**说明:**此处还没有接触到面向对象编程和函数的概念,在讲授该知识点时可以先对这两个概念稍作说明,也可以先让学生接受这种语法稍后再反过来推敲相关的概念,学生可能对什么时候该调用函数,什么时候应该使用方法产生疑问,这个需要进行强调。另外一种授课的顺序是先讲函数和面向对象的入门知识,再讲解如何使用列表、集合、字典、元组这些Python内置的类型。
#### Day08 - [函数基础](./Day08/函数基础.md)
- 基础练习 - 跑马灯效果 / 列表找最大元素 / 统计考试成绩的平均分 / Fibonacci数列 / 杨辉三角
- 综合案例 - 双色球选号 / 井字棋
- 函数的作用 - 代码的坏味道 / 用函数封装功能模块
- 定义函数 - def语句 / 函数名 / 参数列表 / return语句 / 调用自定义函数
- 调用函数 - Python内置函数 / 导入常用模块和常用函数
- 函数的参数 - 默认参数 / 可变参数 / 关键字参数 / 命名关键字参数
- 函数的返回值 - 没有返回值 / 返回单个值 / 返回多个值
- 作用域 - 局部作用域 / 全局作用域 / global语句
#### Day09 - [函数的应用和函数式编程](./Day09/函数的应用和函数式编程.md)
#### Day08 - [函数的高级用法](./Day08/函数的高级用法.md)
- 遗留问题 - 关键字参数 / 命名关键字参数 / 返回多个值
-
- 函数的递归调用 - 递归定义 / 递归公式 / 收敛条件 / 用递归解决实际问题
- 实现常用工具函数 - 生成指定长度的验证码 / 获取文件的后缀名 / 生成随机文件名
- 用模块管理函数 - 模块的概念 / 用自定义模块管理函数 / 导入模块和特定函数 / 第三方模块介绍
- 高阶函数 - 函数名也是变量 / 将函数传入函数 / 利用高阶函数实现代码的解耦合 / 匿名函数(Lambda函数) / 包装器(代理模式)
- 函数返回函数 - 用函数做返回值 / 闭包
- 偏函数和柯里化 - 偏函数的意义 / 使用partial函数创建偏函数 / 柯里化的概念
>**说明:**函数式编程相关的内容属于比较尴尬的知识点,需要做一个简要的介绍,但是对学生来说暂时是用不上这些知识的,因此不要花太多的时间去纠结这些内容,让学生知道函数可以接受函数作为参数也可以返回函数即可,否则大多数学生一定是懵的。
>**说明:**函数式编程相关的内容属于比较尴尬的知识点,需要做一个简要的介绍,但是对学生来说暂时是用不上这些知识的,因此不要花太多的时间去纠结这些内容,事实上Python语言对函数式编程的支持也是非常有限的,让学生知道函数可以接受函数作为参数也可以返回函数就已经很足够了,如果讲得太深大多数学生一定是懵的。
#### Day10 - [面向对象基础](./Day10/面向对象基础.md)
#### Day09 - [面向对象基础](./Day10/面向对象基础.md)
- 类和对象 - 什么是类 / 什么是对象 / 面向对象其他相关概念
- 定义类 - 基本结构 / 属性和方法 / 构造器 / 析构器 / \_\_str\_\_方法
- 使用对象 - 创建对象 / 给对象发消息
- 面向对象的四大支柱 - 抽象 / 封装 / 继承 / 多态
- 基础练习 - 定义学生类 / 定义时钟类 / 定义图形类 / 定义汽车类
> **说明:**面向对象基础部分一定要跟学生进行大量的互动,帮助学生理解类和对象的概念,面向对象编程的两步走(创建对象+发消息)和三步走模式(定义类+创建对象+发消息),可以让学生自己列举出能想到的类和对象的例子,并且在讲完本章内容将自己想到的例子变成代码。
#### Day10 - [总结和练习](./Day10/练习.md)
- 综合案例 - 奥特曼打小怪兽 / 扑克游戏
#### Day11 - [面向对象高级](./Day11/面向对象高级.md)
- 属性 - 类属性 / 实例属性 / 属性访问器 / 属性修改器 / 属性删除器 / 使用\_\_slots\_\_
- 类中的方法 - 实例方法 / 类方法 / 静态方法
- 运算符重载 - \_\_add\_\_ / \_\_sub\_\_ / \_\_or\_\_ /\_\_getitem\_\_ / \_\_setitem\_\_ / \_\_len\_\_ / \_\_repr\_\_ / \_\_gt\_\_ / \_\_lt\_\_ / \_\_le\_\_ / \_\_ge\_\_ / \_\_eq\_\_ / \_\_ne\_\_ / \_\_contains\_\_
- 类(的对象)之间的关系 - 关联 / 继承 / 依赖
- 继承和多态 - 什么是继承 / 继承的语法 / 调用父类构造器 / 方法重写 / 类型判定 / 多重继承 / 菱形继承(钻石继承)和C3算法
- 综合案例 - 工资结算系统 / 图书自动折扣系统 / 自定义分数类
> **说明:**菱形继承和C3算法方面的内容可以根据实际授课情况进行取舍,因为与其把时间花在一些学生暂时难以理解的知识点上,还不如用这些时间让学生通过大量的练习理解面向对象的意义。
#### Day12 - [文件和异常](./Day12/文件和异常.md)
- 读文件 - 读取整个文件 / 逐行读取 / 文件路径
......@@ -227,7 +231,7 @@ Python的就业市场分析:相同工作职位和要求,薪资普遍高3k-5k
- NoSQL - 非关系型数据库的概念 / 非关系型数据库的分类 / 非关系型数据库的应用场景
- MongoDB和Redis - 安装和使用MongoDB / MongoDB中的基本概念 / 安装和使用Redis / Redis中的数据类型 / 使用PyMongo操作MongoDB / 使用redis模块操作Redis
> **说明:**在时间不充足的情况下建议将NoSQL的知识转移到后面的Web项目优化中进行,尤其是涉及到缓存和非结构化数据的处理时候再引入NoSQL的东西学生可能更容易理解和接受,而且强烈建议让学生在阿里云的Linux服务器上安装redis和mongodb,这样在也可以让学生相互协作配置这些服务的主从模式和集群模式,从这一点上来考虑在一阶段讲解这些内容也会显得很苍白无力
> **说明:**在时间不充足的情况下建议将NoSQL的知识转移到后面的Web项目优化中进行,尤其是涉及到缓存和非结构化数据的处理时候再引入NoSQL的东西学生可能更容易理解和接受,而且强烈建议让学生在阿里云的Linux服务器上安装redis和mongodb,这样在也可以让学生相互协作配置这些服务的主从模式和集群模式。
#### Day25 - [总结和考试](./Day25/考试.md)
......
## 要不要使用复杂表达式
Perl语言的原作者Larry Wall曾经说过,伟大的程序员都有三个优点:懒惰、暴躁和自负。乍一看这三个词语没有一个是褒义词,但在程序员的世界里,这三个词有不同的意义。首先,懒惰会促使程序员去写一些省事儿的程序来辅助自己或别人更好的完成工作,这样我们就无需做那些重复和繁琐的劳动;同理能够用3行代码解决的事情,我们也绝不会写出10行代码来。其次,暴躁会让程序员主动的去完成一些你还没有提出的工作,去优化自己的代码让它更有效率,能够3秒钟完成的任务,我们绝不能容忍1分钟的等待。最后,自负会促使程序员写出可靠无误的代码,我们写代码不是为了接受批评和指责,而是为了让其他人来膜拜。
那么接下来就有一个很有意思的问题值得探讨一下,我们需要一个程序从输入的三个数中找出最大的那个数。这个程序对任何会编程的人来说都是小菜一碟,甚至不会编程的人经过10分钟的学习也能搞定。下面是用来解决这个问题的Python代码。
```Python
a = int(input('a = '))
b = int(input('b = '))
c = int(input('c = '))
if a > b:
the_max = a
else:
the_max = b
if c > the_max:
the_max = c
print('The max is:', the_max)
```
但是我们刚才说了,程序员都是懒惰的,很多程序员都会使用三元条件运算符来改写上面的代码。
```Python
a = int(input('a = '))
b = int(input('b = '))
c = int(input('c = '))
the_max = a if a > b else b
the_max = c if c > the_max else the_max
print('The max is:', the_max)
```
需要说明的是,Python在2.5版本以前是没有上面代码第4行和第5行中使用的三元条件运算符的,究其原因是Guido van Rossum(Python之父)认为三元条件运算符并不能帮助 Python变得更加简洁,于是那些习惯了在C/C++或Java中使用三元条件运算符(在这些语言中,三元条件运算符也称为“Elvis运算符”,因为`?:`放在一起很像著名摇滚歌手猫王Elvis的大背头)的程序员试着用`and``or`运算符的短路特性来模拟出三元操作符,于是在那个年代,上面的代码是这样写的。
```Python
a = int(input('a = '))
b = int(input('b = '))
c = int(input('c = '))
the_max = a > b and a or b
the_max = c > the_max and c or the_max
print('The max is:', the_max)
```
但是这种做法在某些场景下是不能成立的,且看下面的代码。
```Python
a = 0
b = -100
# 下面的代码本来预期输出a的值,结果却得到了b的值
# 因为a的值0在进行逻辑运算时会被视为False来处理
print(True and a or b)
# print(a if True else b)
```
所以在Python 2.5以后引入了三元条件运算符来避免上面的风险(上面代码被注释掉的最后一句话)。那么,问题又来了,上面的代码还可以写得更简短吗?答案是肯定的。
```Python
a = int(input('a = '))
b = int(input('b = '))
c = int(input('c = '))
print('The max is:', (a if a > b else b) if (a if a > b else b) > c else c)
```
但是,这样做真的好吗?如此复杂的表达式是不是让代码变得晦涩了很多呢?我们发现,在实际开发中很多开发者都喜欢过度的使用某种语言的特性或语法糖,于是简单的多行代码变成了复杂的单行表达式,这样做真的好吗?这个问题我也不止一次的问过自己,现在我能给出的答案是下面的代码,使用辅助函数。
```Python
def the_max(x, y):
return x if x > y else y
a = int(input('a = '))
b = int(input('b = '))
c = int(input('c = '))
print('The max is:', the_max(the_max(a, b), c))
```
上面的代码中,我定义了一个辅助函数`the_max`用来找出参数传入的两个值中较大的那一个,于是下面的输出语句可以通过两次调用`the_max`函数来找出三个数中的最大值,现在代码的可读性是不是好了很多。用辅助函数来替代复杂的表达式真的是一个不错的选择,关键是比较大小的逻辑转移到这个辅助函数后不仅可以反复调用它,而且还可以进行级联操作。
当然,很多语言中比较大小的函数根本没有必要自己来实现(通常都是内置函数),Python也是如此。Python内置的max函数利用了Python对可变参数的支持,允许一次性传入多个值或者一个迭代器并找出那个最大值,所以上面讨论的问题在Python中也就是一句话的事,但是从复杂表达式到使用辅助函数简化复杂表达式这个思想是非常值得玩味的,所以分享出来跟大家做一个交流。
```Python
a = int(input('a = '))
b = int(input('b = '))
c = int(input('c = '))
print('The max is:', max(a, b, c))
```
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册