计算机二级练习仓库
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

988 lines
32 KiB

пре 2 година
  1. {
  2. "cells": [
  3. {
  4. "cell_type": "markdown",
  5. "metadata": {},
  6. "source": [
  7. "# 实践12 再谈python函数\n",
  8. "\n",
  9. "1. 理解函数定义四要素:函数名、参数表、函数体和返回值,本章对每一个部分都进行了更深入的说明,尤其是一些特殊的用法;\n",
  10. "2. 理解 函数定义内外是两个不同的“**作用域**(*scope*)”,区分出全局变量和局部变量,需要充分理解其运作原理;\n"
  11. ]
  12. },
  13. {
  14. "cell_type": "markdown",
  15. "metadata": {},
  16. "source": [
  17. "我们已经见过不少函数,也自己写过一些函数。我们已经理解函数的概念来自代数:从**输入参数**出发,**计算**出函数的**返回值**;我们也知道可以用 `def foo():` 来定义函数。其实函数的定义非常复杂,我们不太能够在第一次介绍时就讲清楚,所以之前我们就采取“先引入用起来”的方法,这也是一种知识上的“提前引用”。\n",
  18. "\n",
  19. "本章将围绕函数几个重要的要素深入看看。"
  20. ]
  21. },
  22. {
  23. "cell_type": "markdown",
  24. "metadata": {},
  25. "source": [
  26. "### 1. 为函数命名"
  27. ]
  28. },
  29. {
  30. "cell_type": "markdown",
  31. "metadata": {},
  32. "source": [
  33. "哪怕一个函数内部什么都不干,它也得有个名字,然后名字后面要加上圆括号 `()`,以明示它是个函数,而不是某个变量。"
  34. ]
  35. },
  36. {
  37. "cell_type": "code",
  38. "execution_count": 1,
  39. "metadata": {},
  40. "outputs": [],
  41. "source": [
  42. "def do_nothing():\n",
  43. " pass\n",
  44. "\n",
  45. "do_nothing()"
  46. ]
  47. },
  48. {
  49. "cell_type": "markdown",
  50. "metadata": {},
  51. "source": [
  52. "这就是个“什么也不干”的函数,关键字 `pass` 就是什么也不干的意思。"
  53. ]
  54. },
  55. {
  56. "cell_type": "markdown",
  57. "metadata": {},
  58. "source": [
  59. "给函数命名(给变量命名也一样)需要遵循的一些规则如下:\n",
  60. "* 首先,名称不能以数字开头,能用在名称开头的只有大小写字母和下划线 `_`;\n",
  61. "* 其次,名称中不能有空格,如果一个名字里有好几个词汇,可以用下划线来分割(`do_nothing`),也可以用所谓 *Camel Case* 风格(*doNothing*),习惯上更推荐使用下划线;\n",
  62. "* 最后,绝对不能与 Python 语言的**关键字**(*keyword*)重复。\n",
  63. "\n"
  64. ]
  65. },
  66. {
  67. "cell_type": "markdown",
  68. "metadata": {},
  69. "source": [
  70. "在程序里给变量、函数命名是个挺重要的事情,影响到程序的可读性,就像小说的语言,最好能有一种流畅清晰、又始终一致的**风格**(*style*)。为了让全世界的 Python 程序员都有相对一致的风格,Python 社区有专门的一套建议规范,放在专门维护 Python 语言特性的社区 [PEP](https://www.python.org/dev/peps/) 上:\n",
  71. "\n",
  72. "* [PEP 8 -- Style Guide for Python Code: Naming Conventions](https://www.python.org/dev/peps/pep-0008/#naming-conventions)\n",
  73. "\n",
  74. "> PEP,是 *Python enhancement proposal* 的缩写,每当有重要的语言特性新需求新想法,就放在这里,经过广大 Python 用户和开发者的讨论完善,在某个版本放进 Python 中。很多 PEP 早已从 *proposal* 毕业变成官方特性,但也还在这里保留着。PEP 8 就是一个古老的 *proposal*,现在已为大多数 Python 用户采纳。"
  75. ]
  76. },
  77. {
  78. "cell_type": "markdown",
  79. "metadata": {},
  80. "source": [
  81. "### 2. 没有、一个和多个参数"
  82. ]
  83. },
  84. {
  85. "cell_type": "markdown",
  86. "metadata": {},
  87. "source": [
  88. "函数可以没有参数,也可以有一个或者多个参数。\n",
  89. "\n",
  90. "没有参数就意味着,这个函数执行不依赖于输入,比如我们定义一个函数来在程序结束时打印一句退出提示:"
  91. ]
  92. },
  93. {
  94. "cell_type": "code",
  95. "execution_count": 3,
  96. "metadata": {},
  97. "outputs": [
  98. {
  99. "name": "stdout",
  100. "output_type": "stream",
  101. "text": [
  102. "Program exits. Bye.\n"
  103. ]
  104. }
  105. ],
  106. "source": [
  107. "def exit_info():\n",
  108. " print('Program exits. Bye.')\n",
  109. " \n",
  110. "exit_info()"
  111. ]
  112. },
  113. {
  114. "cell_type": "markdown",
  115. "metadata": {},
  116. "source": [
  117. "注意即使没有参数,无论定义还是调用时,函数名后面的括号都是不可省略的,这是函数身份的标志。"
  118. ]
  119. },
  120. {
  121. "cell_type": "markdown",
  122. "metadata": {},
  123. "source": [
  124. "函数也可以有多个参数,调用时输入参数的值是严格按照参数的顺序去匹配的。比如我们写一个函数输出某年到某年之间的所有闰年:"
  125. ]
  126. },
  127. {
  128. "cell_type": "code",
  129. "execution_count": 4,
  130. "metadata": {},
  131. "outputs": [
  132. {
  133. "name": "stdout",
  134. "output_type": "stream",
  135. "text": [
  136. "2000\n",
  137. "2004\n",
  138. "2008\n",
  139. "2012\n",
  140. "2016\n"
  141. ]
  142. }
  143. ],
  144. "source": [
  145. "def leap_years(begin, end):\n",
  146. " year = begin\n",
  147. " while year < end:\n",
  148. " if (year % 4 == 0 and year % 100 != 0) or year % 400 == 0:\n",
  149. " print(year)\n",
  150. " year += 1\n",
  151. " \n",
  152. "leap_years(2000, 2020)"
  153. ]
  154. },
  155. {
  156. "cell_type": "markdown",
  157. "metadata": {},
  158. "source": [
  159. "当我们调用 `leap_years(2000, 2020)` 时,输入两个参数值 2000 和 2020,按照顺序匹配函数定义 `leap_years(begin, end)`,于是 `begin = 2000` `end = 2020`。所以参数的顺序是不能搞错的,有些函数参数很多,要是开发过程中还调整过顺序的话,那简直就是灾难,所以一般情况下还是保持函数参数不要乱动为好。\n",
  160. "\n",
  161. "顺便说一句,判断闰年的算法虽然不难,但要写的简洁也不容易。建议你可以先自己思考和实现一遍,然后尝试搞清楚为啥上面代码里的那行 `if` 是对的。实际上闰年的判断有很多正确的写法,你应该尝试写出自己的版本并确认它的正确性。"
  162. ]
  163. },
  164. {
  165. "cell_type": "markdown",
  166. "metadata": {},
  167. "source": [
  168. "### 3. 没有、一个和多个返回值"
  169. ]
  170. },
  171. {
  172. "cell_type": "markdown",
  173. "metadata": {},
  174. "source": [
  175. "和参数一样,Python 的函数可以没有返回值,也可以有一个或者多个返回值。\n",
  176. "\n",
  177. "上面的 `exit_info` 和 `leap_year` 也是没有返回值的例子,它们的效果都通过 `print` 函数来体现。实际上没有返回语句的函数,等价于在其最后有一句 `return None`,表示函数返回了一个空值 `None`,`None` 在 Python 中是一个合法的值,表示什么都没有,它在逻辑上等价于 `False`:"
  178. ]
  179. },
  180. {
  181. "cell_type": "code",
  182. "execution_count": 5,
  183. "metadata": {},
  184. "outputs": [
  185. {
  186. "data": {
  187. "text/plain": [
  188. "False"
  189. ]
  190. },
  191. "execution_count": 5,
  192. "metadata": {},
  193. "output_type": "execute_result"
  194. }
  195. ],
  196. "source": [
  197. "bool(None)"
  198. ]
  199. },
  200. {
  201. "cell_type": "markdown",
  202. "metadata": {},
  203. "source": [
  204. "所以即使没有返回值的函数,也可以用在 `if` 后面做逻辑表达式,不过我们并不推荐这么做,因为可读性很差。"
  205. ]
  206. },
  207. {
  208. "cell_type": "markdown",
  209. "metadata": {},
  210. "source": [
  211. "大部分情况下函数是有返回值的,因为绝大部分情况下函数的作用都是做“数据处理”,从输入出发得到输出。\n",
  212. "\n",
  213. "一般情况下函数都只有一个返回值,我们已经见过不少例子;但 Python 也允许多返回值,比如我们想用一个函数来计算两个整数相除的商和余数,可以这么写:"
  214. ]
  215. },
  216. {
  217. "cell_type": "code",
  218. "execution_count": 6,
  219. "metadata": {},
  220. "outputs": [
  221. {
  222. "name": "stdout",
  223. "output_type": "stream",
  224. "text": [
  225. "8 2\n"
  226. ]
  227. }
  228. ],
  229. "source": [
  230. "def idiv(a, b):\n",
  231. " quotient = a // b\n",
  232. " remainder = a % b\n",
  233. " return quotient, remainder\n",
  234. "\n",
  235. "q, r = idiv(50, 6)\n",
  236. "print(q, r)"
  237. ]
  238. },
  239. {
  240. "cell_type": "markdown",
  241. "metadata": {},
  242. "source": [
  243. "和多参数的情况类似,多返回值的情况下,赋值也是按照顺序匹配的,上面的代码中赋值语句左边的 `q` 匹配到第一个返回值,`r` 匹配第二个。"
  244. ]
  245. },
  246. {
  247. "cell_type": "markdown",
  248. "metadata": {},
  249. "source": [
  250. "### 4. 函数内与函数外:变量的作用域"
  251. ]
  252. },
  253. {
  254. "cell_type": "markdown",
  255. "metadata": {},
  256. "source": [
  257. "下面的代码经常会把人搞晕:"
  258. ]
  259. },
  260. {
  261. "cell_type": "code",
  262. "execution_count": 7,
  263. "metadata": {},
  264. "outputs": [
  265. {
  266. "name": "stdout",
  267. "output_type": "stream",
  268. "text": [
  269. "2\n",
  270. "1\n"
  271. ]
  272. }
  273. ],
  274. "source": [
  275. "def increase_one(n):\n",
  276. " n += 1\n",
  277. " return n\n",
  278. "\n",
  279. "n = 1\n",
  280. "print(increase_one(n))\n",
  281. "print(n)"
  282. ]
  283. },
  284. {
  285. "cell_type": "markdown",
  286. "metadata": {},
  287. "source": [
  288. "请你思考一下,为什么这段代码里的两个 `print` 函数输出分别是 2 和 1。\n",
  289. "\n",
  290. "这个问题就涉及到变量的**作用域**(*scope*)问题,也就是说在不同地方出现的同名变量和函数,可能是完全不同的两个东西:\n",
  291. "* 函数定义体中的变量的作用域是该函数内,程序的其他部分不知道其存在,这种变量叫**局部变量**(*local variable*);函数的输入参数也是局部变量,也只在函数定义体中有效;\n",
  292. "* 不在任何函数、类定义体中的变量的作用域是全局的,在任何地方都可以访问,这种变量称为**全局变量**(*global variable*);\n",
  293. "* 如果局部变量和全局变量同名,函数定义体内会优先局部变量,不会把它当做全局变量。\n",
  294. "\n",
  295. "这样我们就能理解上面代码输出的 2 和 1 了:\n",
  296. "* 第一个 `print()` 打印的是函数调用 `increase_one(n)` 的返回值,这个语句不在任何函数定义体中,所以它里面用到的变量都是全局变量:\n",
  297. " * 在调用 `increase_one()` 时参数 `n`,按照作用域原理,是全局变量 `n` 当时的值,也就是 1;\n",
  298. " * 在 `increase_one()` 函数定义内,参数 `n` 是输入参数即局部变量,带着传进来的值 1,经过加一之后返回,返回值是 2;\n",
  299. " * `print` 打印这个返回值,输出 2;\n",
  300. " * 这个过程中处理的都是局部变量,完全不影响全局变量 `n` 的值;\n",
  301. "* 第二个 `print()` 打印的是全局变量 `n` 的值,输出出 1。"
  302. ]
  303. },
  304. {
  305. "cell_type": "markdown",
  306. "metadata": {},
  307. "source": [
  308. "以上的文字,可能需要反复阅读若干遍;几遍下来,消除了疑惑,以后就彻底没问题了;若是这个疑惑并未消除,或者关键点并未消化,以后则会反复被这个疑惑所坑害,浪费无数时间。\n",
  309. "\n",
  310. "顺便说一句,上面这个例子用来说明作用域的概念很有用,但是平时写程序最好别这么写,减少重名的变量可以提升代码的清晰度和可读性。"
  311. ]
  312. },
  313. {
  314. "cell_type": "markdown",
  315. "metadata": {},
  316. "source": [
  317. "与此相关的,我们在介绍列表等数据容器时,会为上面的规则作出重要的补充,这里先留一个伏笔。"
  318. ]
  319. },
  320. {
  321. "cell_type": "markdown",
  322. "metadata": {},
  323. "source": [
  324. "### 5. 带缺省值的参数"
  325. ]
  326. },
  327. {
  328. "cell_type": "markdown",
  329. "metadata": {},
  330. "source": [
  331. "我们其实已经见过带缺省值的参数(*argument with default value*),这里我们更细致的看看这个特性。\n",
  332. "\n",
  333. "在函数定义中可以在某个参数后面用等号 `=` 给它一个缺省值,调用时可以省略传入这个参数的值,直接采用缺省值;当然也可以在调用时传入这个参数的值来覆盖掉缺省值。这种特性相当于给了这个函数两个版本,一个带某个参数,一个不带,不带的版本就当该参数是某个缺省值。看看下面的例子:"
  334. ]
  335. },
  336. {
  337. "cell_type": "code",
  338. "execution_count": 8,
  339. "metadata": {},
  340. "outputs": [],
  341. "source": [
  342. "def greeting(name, msg='Hi'):\n",
  343. " print(f'{msg}, {name}!')"
  344. ]
  345. },
  346. {
  347. "cell_type": "code",
  348. "execution_count": 9,
  349. "metadata": {},
  350. "outputs": [
  351. {
  352. "name": "stdout",
  353. "output_type": "stream",
  354. "text": [
  355. "Hi, Neo!\n"
  356. ]
  357. }
  358. ],
  359. "source": [
  360. "greeting('Neo')"
  361. ]
  362. },
  363. {
  364. "cell_type": "code",
  365. "execution_count": 10,
  366. "metadata": {},
  367. "outputs": [
  368. {
  369. "name": "stdout",
  370. "output_type": "stream",
  371. "text": [
  372. "Good morning, Neo!\n"
  373. ]
  374. }
  375. ],
  376. "source": [
  377. "greeting('Neo', 'Good morning')"
  378. ]
  379. },
  380. {
  381. "cell_type": "markdown",
  382. "metadata": {},
  383. "source": [
  384. "一个函数可以有多个带缺省值的参数,但有一个限制:所有这些带缺省值的参数只能堆在参数表的最后,也就是说你定义的参数表里,出现一个带缺省值的参数,则它后面的都必须带缺省值。如果把上面的 `greeting()` 函数的两个参数调换一下,会扔出一个 `SyntaxError: non-default argument follows default argument` 的异常。"
  385. ]
  386. },
  387. {
  388. "cell_type": "markdown",
  389. "metadata": {},
  390. "source": [
  391. "### 6. 指定参数名来调用函数"
  392. ]
  393. },
  394. {
  395. "cell_type": "markdown",
  396. "metadata": {},
  397. "source": [
  398. "我们前面说过,调用函数时传入的参数值会严格按照顺序去匹配参数变量,第一个输入值赋给第一个参数变量,第二个值赋给第二个参数变量,依此类推。因为有了上面说的带缺省值参数,这个规则出现了变通的可能。\n",
  399. "\n",
  400. "如果一个函数有多个带缺省值的参数,我们想忽略掉某几个参数(就用其缺省值),但指定后面某一个参数的值(覆盖缺省值),例如下面这个函数:"
  401. ]
  402. },
  403. {
  404. "cell_type": "code",
  405. "execution_count": 11,
  406. "metadata": {},
  407. "outputs": [],
  408. "source": [
  409. "def greeting(name, msg='Hi', punc='!'):\n",
  410. " print(f'{msg}, {name}{punc}')"
  411. ]
  412. },
  413. {
  414. "cell_type": "markdown",
  415. "metadata": {},
  416. "source": [
  417. "在这个版本的 `greeting()` 函数中,包含一个普通参数 `name` 和两个带缺省值的参数 `msg` `punc`,如果我们想跳过 `msg` 只传入 `name`(这个是必须的,因为没有缺省值)和 `punc` 的值,那么就可用下面的语法:"
  418. ]
  419. },
  420. {
  421. "cell_type": "code",
  422. "execution_count": 12,
  423. "metadata": {},
  424. "outputs": [
  425. {
  426. "name": "stdout",
  427. "output_type": "stream",
  428. "text": [
  429. "Hi, Neo.\n"
  430. ]
  431. }
  432. ],
  433. "source": [
  434. "greeting('Neo', punc='.')"
  435. ]
  436. },
  437. {
  438. "cell_type": "markdown",
  439. "metadata": {},
  440. "source": [
  441. "这里第一个值按照顺序位置匹配到参数变量 `name`,这叫 *positional argument*(即“按照位置顺序匹配的参数”),而按照位置下一个是 `msg`,是我们想跳过的,所以要注明参数变量名,说明下一个传入的值 `'.'` 是给 `punc` 参数变量的,这叫 *keyword argument*(即“按照参数名匹配的参数”)。\n",
  442. "\n",
  443. "由于所有带缺省值的参数都在普通参数的后面,所以我们只要记住:\n",
  444. "* 调用函数时先传入所有不带缺省值的参数的值,严格按照函数定义的位置顺序(*positional*);\n",
  445. "* 然后想指定哪些带缺省值参数的值,就用 `变量名=值` 这样的格式在后面列出(*keyword*),未列出的就还用缺省值了。\n",
  446. "\n",
  447. "在后半部分,顺序就无所谓了,可以和定义时不一样,反正是用名字指定的(*keyword*),比如我们完全可以这么干:"
  448. ]
  449. },
  450. {
  451. "cell_type": "code",
  452. "execution_count": 13,
  453. "metadata": {},
  454. "outputs": [
  455. {
  456. "name": "stdout",
  457. "output_type": "stream",
  458. "text": [
  459. "Good nite, Neo.\n"
  460. ]
  461. }
  462. ],
  463. "source": [
  464. "greeting('Neo', punc='.', msg='Good nite')"
  465. ]
  466. },
  467. {
  468. "cell_type": "markdown",
  469. "metadata": {},
  470. "source": [
  471. "### 7. 变长参数"
  472. ]
  473. },
  474. {
  475. "cell_type": "markdown",
  476. "metadata": {},
  477. "source": [
  478. "到目前为止,Python 的函数定义还是很简单清晰的,无论参数还是返回值,都没什么难懂的。下面开始就要进入比较混沌的领域了。\n",
  479. "\n",
  480. "所谓变长参数就是函数定义时名字前面带个星号 `*` 的参数变量,这表示这个变量其实是一组值,多少个都可以。我们先来看个简单的例子:"
  481. ]
  482. },
  483. {
  484. "cell_type": "code",
  485. "execution_count": 14,
  486. "metadata": {},
  487. "outputs": [],
  488. "source": [
  489. "def say_hi(*names):\n",
  490. " for name in names:\n",
  491. " print('Hi,', name)"
  492. ]
  493. },
  494. {
  495. "cell_type": "code",
  496. "execution_count": 15,
  497. "metadata": {},
  498. "outputs": [
  499. {
  500. "name": "stdout",
  501. "output_type": "stream",
  502. "text": [
  503. "Hi, Neo\n"
  504. ]
  505. }
  506. ],
  507. "source": [
  508. "say_hi('Neo')"
  509. ]
  510. },
  511. {
  512. "cell_type": "code",
  513. "execution_count": 16,
  514. "metadata": {},
  515. "outputs": [
  516. {
  517. "name": "stdout",
  518. "output_type": "stream",
  519. "text": [
  520. "Hi, Neo\n",
  521. "Hi, Trinity\n"
  522. ]
  523. }
  524. ],
  525. "source": [
  526. "say_hi('Neo', 'Trinity')"
  527. ]
  528. },
  529. {
  530. "cell_type": "code",
  531. "execution_count": 17,
  532. "metadata": {},
  533. "outputs": [
  534. {
  535. "name": "stdout",
  536. "output_type": "stream",
  537. "text": [
  538. "Hi, Neo\n",
  539. "Hi, Trinity\n",
  540. "Hi, Morpheus\n"
  541. ]
  542. }
  543. ],
  544. "source": [
  545. "say_hi('Neo', 'Trinity', 'Morpheus')"
  546. ]
  547. },
  548. {
  549. "cell_type": "markdown",
  550. "metadata": {},
  551. "source": [
  552. "在这个例子里,`*names` 是一个变长参数(*arbitrary argument*),调用时可以传入一个或者多个值,函数会把这些值看做一个列表,赋给局部变量 `names`——后面我们会知道,其实不是**列表**(*list*),而是一个**元组**(*tuple*)——然后我们在函数体中可以用 `for...in` 来对这个 `names` 做循环。\n",
  553. "\n",
  554. "> 有些中文书籍把 *arbitrary arguments* 翻译成“可变参数”或者“任意参数”。事实上,在这样的地方,无论怎样的中文翻译都是很难准确表达原意的。这还算好的,甚至还见过翻译成“武断的参数”的——这样的翻译肯定会使读者产生说不明道不白的疑惑。\n",
  555. ">\n",
  556. "> 所以,**入门之后就尽量只用英文**是个好策略。虽然刚开始有点吃力,但后面会很省心,很长寿——是呀,少浪费时间、少浪费生命,其实就相当于更长寿了呀!"
  557. ]
  558. },
  559. {
  560. "cell_type": "markdown",
  561. "metadata": {},
  562. "source": [
  563. "在使用 *arbitrary argument* 的场合,有几点需要注意:\n",
  564. "* 参数变量名最好用复数单词,一看就知道是一组数据;这个变量在函数里通常都会被 `for...in` 循环处理,用复数名词在写类似 `for name in names` 的循环语句时会很舒服、很地道(*idiomatic*),是的,写程序和学外语一样,不写则已,写就要尽量写得“地道”;\n",
  565. "* 这种参数变量只能有一个,因为从它开始后面的输入值都会被当做它的一部分,多了就不知道怎么分了,显然,如果有这种参数,必须放在参数表的最后。\n",
  566. "\n",
  567. "上面的第二点,有一个不太常见的例外,那就是一个函数既有 *arbitrary arguments* 又有 *arguments with default values* 的情况,那么可以有两个 *arbitrary arguments*,其中第二个必须带缺省值,然后参数表排列成这样:\n",
  568. "\n",
  569. "`def monstrosity(*normal arguments*, *normal arbitrary argument*, *arguments with defaults*, *arbitrary argument with default*)`\n",
  570. "\n",
  571. "这样是完全符合语法要求的,调用时传入参数值还是按照前面讲的规则,先按照位置顺序匹配前两部分,多出来的都归 *normal arbitrary argument*;然后按照参数变量名指定对应值,没指定的都用缺省值。不过这实在是太麻烦了,不知道什么情况下才必须用这么可怕的函数,还是祈祷我们不会碰到这样的场景吧!\n",
  572. "\n",
  573. "当然,只有上面列出的前三个部分的情况还是有的,比如下面的例子:"
  574. ]
  575. },
  576. {
  577. "cell_type": "code",
  578. "execution_count": 18,
  579. "metadata": {},
  580. "outputs": [],
  581. "source": [
  582. "def say_hi(*names, msg='Hi', punc='!'):\n",
  583. " for name in names:\n",
  584. " print(f'{msg}, {name}{punc}')"
  585. ]
  586. },
  587. {
  588. "cell_type": "code",
  589. "execution_count": 19,
  590. "metadata": {},
  591. "outputs": [
  592. {
  593. "name": "stdout",
  594. "output_type": "stream",
  595. "text": [
  596. "Hi, Neo.\n",
  597. "Hi, Trinity.\n"
  598. ]
  599. }
  600. ],
  601. "source": [
  602. "say_hi('Neo', 'Trinity', punc='.')"
  603. ]
  604. },
  605. {
  606. "cell_type": "markdown",
  607. "metadata": {},
  608. "source": [
  609. "## 小试身手(1)\n",
  610. "阅读可变参数的vfunc函数,仿写milti函数实现返回所有参数的乘积。"
  611. ]
  612. },
  613. {
  614. "cell_type": "code",
  615. "execution_count": 7,
  616. "metadata": {},
  617. "outputs": [],
  618. "source": [
  619. "def vfunc(*b):\n",
  620. " a=0\n",
  621. " for n in b:\n",
  622. " a+=n\n",
  623. " return a\n"
  624. ]
  625. },
  626. {
  627. "cell_type": "code",
  628. "execution_count": 8,
  629. "metadata": {},
  630. "outputs": [
  631. {
  632. "data": {
  633. "text/plain": [
  634. "15"
  635. ]
  636. },
  637. "execution_count": 8,
  638. "metadata": {},
  639. "output_type": "execute_result"
  640. }
  641. ],
  642. "source": [
  643. "vfunc(1,2,3,4,5)"
  644. ]
  645. },
  646. {
  647. "cell_type": "markdown",
  648. "metadata": {},
  649. "source": [
  650. "## 函数别名"
  651. ]
  652. },
  653. {
  654. "cell_type": "markdown",
  655. "metadata": {},
  656. "source": [
  657. "在 Python 中,所有函数也是对象,证据就是它们都有对象 id。Python 会为创建的每一个对象(不管基本数据类型,还是某个 *class* 的实例)指定一个唯一的 id,可以用内置函数 `id()` 来查看,比如:"
  658. ]
  659. },
  660. {
  661. "cell_type": "code",
  662. "execution_count": null,
  663. "metadata": {},
  664. "outputs": [],
  665. "source": [
  666. "n = 42\n",
  667. "id(n)"
  668. ]
  669. },
  670. {
  671. "cell_type": "markdown",
  672. "metadata": {},
  673. "source": [
  674. "函数也有这个 id,比如:"
  675. ]
  676. },
  677. {
  678. "cell_type": "code",
  679. "execution_count": 3,
  680. "metadata": {},
  681. "outputs": [],
  682. "source": [
  683. "def _is_leap(year):\n",
  684. " return (year % 4 == 0 and year % 100 != 0) or year % 400 == 0"
  685. ]
  686. },
  687. {
  688. "cell_type": "code",
  689. "execution_count": 4,
  690. "metadata": {},
  691. "outputs": [
  692. {
  693. "data": {
  694. "text/plain": [
  695. "2989669354808"
  696. ]
  697. },
  698. "execution_count": 4,
  699. "metadata": {},
  700. "output_type": "execute_result"
  701. }
  702. ],
  703. "source": [
  704. "id(_is_leap)"
  705. ]
  706. },
  707. {
  708. "cell_type": "markdown",
  709. "metadata": {},
  710. "source": [
  711. "既然函数有 id,是个对象,那是什么类型的对象呢?可以用内置函数 `type` 来看:"
  712. ]
  713. },
  714. {
  715. "cell_type": "code",
  716. "execution_count": null,
  717. "metadata": {},
  718. "outputs": [],
  719. "source": [
  720. "type(_is_leap)"
  721. ]
  722. },
  723. {
  724. "cell_type": "markdown",
  725. "metadata": {},
  726. "source": [
  727. "所以函数是个 `function` 类型的对象。\n",
  728. "\n",
  729. "既然是个对象,我们就可以用赋值语句来创建函数的**别名**(*alias*):"
  730. ]
  731. },
  732. {
  733. "cell_type": "code",
  734. "execution_count": null,
  735. "metadata": {},
  736. "outputs": [],
  737. "source": [
  738. "is_leap = _is_leap"
  739. ]
  740. },
  741. {
  742. "cell_type": "code",
  743. "execution_count": null,
  744. "metadata": {},
  745. "outputs": [],
  746. "source": [
  747. "id(is_leap)"
  748. ]
  749. },
  750. {
  751. "cell_type": "markdown",
  752. "metadata": {},
  753. "source": [
  754. "可以看到,这两个函数的 id 完全一样,是同一个对象的两个名字而已。我们可以用这两个名字来调用这个函数,完全没区别:"
  755. ]
  756. },
  757. {
  758. "cell_type": "code",
  759. "execution_count": null,
  760. "metadata": {},
  761. "outputs": [],
  762. "source": [
  763. "_is_leap(2018)"
  764. ]
  765. },
  766. {
  767. "cell_type": "code",
  768. "execution_count": null,
  769. "metadata": {},
  770. "outputs": [],
  771. "source": [
  772. "is_leap(2018)"
  773. ]
  774. },
  775. {
  776. "cell_type": "markdown",
  777. "metadata": {},
  778. "source": [
  779. "那么,我们为什么需要给函数取别名呢?\n",
  780. "\n",
  781. "很多时候是为了提供更好的代码可读性,比如在特定上下文让某个函数的作用更显而易见,比如以前的例子里,我们曾经在 `Cat` 类里给父类 `Animal` 的 `voice()` 方法定义别名叫 `meow()`。\n",
  782. "\n",
  783. "还有一种情况是一个函数需要在运行时动态指向不同的实现版本。这里只简单描述一个典型场景:假定我们要渲染一段视频,如果系统里有兼容的显卡(GPU),就调用显卡来渲染,会更快更省电,如果没有则用 CPU 来渲染,会慢一点和更耗电一点,于是我们把用 GPU 渲染的算法写成函数 `_render_by_gpu()`,用 CPU 渲染的算法写成函数 `_render_by_cpu()`,而检测是否存在可用 GPU 的算法写成函数 `is_gpu_available()`,然后可以用下面的方法来定义一个函数 `render`:\n",
  784. "\n",
  785. "```python\n",
  786. "if is_gpu_available():\n",
  787. " render = _render_by_gpu\n",
  788. "else:\n",
  789. " render = _render_by_cpu\n",
  790. "```\n",
  791. "\n",
  792. "这样 `render()` 就成为一个当前系统中最优化的渲染函数,在程序的其他地方就不用管细节,直接用这个函数就好。这就是动态函数别名的价值。\n",
  793. "\n",
  794. "顺便说一句,在任何一个工程里,为函数或者变量取名都是**很简单却不容易**的事情——因为可能会重名(虽然已经尽量用变量的作用域隔离了),可能会因取名含混而令后来者费解,所以,仅仅为了少敲几下键盘而给一个函数取个更短的别名,实际上并不是好主意,更不是好习惯。尤其现在的编辑器都支持自动补全和多光标编辑的功能,变量名长点不是什么大问题。"
  795. ]
  796. },
  797. {
  798. "cell_type": "markdown",
  799. "metadata": {},
  800. "source": [
  801. "## 匿名函数"
  802. ]
  803. },
  804. {
  805. "cell_type": "markdown",
  806. "metadata": {},
  807. "source": [
  808. "有的函数需要两个甚至更多名字,有的函数却一个也不要,人生就是这么丰富多彩啊!\n",
  809. "\n",
  810. "所谓匿名函数,就是有时候我们需要一个函数,但就在一个地方,用完就扔,再也不会用了,Python 对这种情况提供了一个方便的语法,不需要 `def` 那套严肃完整的语法,一行就可以写完一个函数,这个语法使用关键字 `lambda`。`lambda` 是希腊字母 `λ` 的英语音译,在计算机领域是个来头不小的词儿,代表了一系列高深的理论,[和阿伦佐·丘奇(Alonzo Church)的理论有关](https://en.wikipedia.org/wiki/Lambda_calculus),有兴趣的话可以自行研究。\n",
  811. "\n",
  812. "不过目前我们不需要管那么多,只要了解怎么快速创建“用过即扔”的小函数就好了。"
  813. ]
  814. },
  815. {
  816. "cell_type": "code",
  817. "execution_count": null,
  818. "metadata": {},
  819. "outputs": [],
  820. "source": [
  821. "def add(x, y):\n",
  822. " return x + y\n",
  823. "add(3, 5)"
  824. ]
  825. },
  826. {
  827. "cell_type": "markdown",
  828. "metadata": {},
  829. "source": [
  830. "我们可以用 lambda 来改写:"
  831. ]
  832. },
  833. {
  834. "cell_type": "code",
  835. "execution_count": null,
  836. "metadata": {},
  837. "outputs": [],
  838. "source": [
  839. "add = lambda x, y: x + y\n",
  840. "add(3, 5)"
  841. ]
  842. },
  843. {
  844. "cell_type": "markdown",
  845. "metadata": {},
  846. "source": [
  847. "甚至更简单一点,名字也不要了:"
  848. ]
  849. },
  850. {
  851. "cell_type": "code",
  852. "execution_count": null,
  853. "metadata": {},
  854. "outputs": [],
  855. "source": [
  856. "(lambda x, y: x + y)(3, 5)"
  857. ]
  858. },
  859. {
  860. "cell_type": "markdown",
  861. "metadata": {},
  862. "source": [
  863. "最后这种形式,就是典型的匿名函数了。简单地说,`lambda` 可以生成一个函数对象,出现在所有需要一个函数的地方,可以将其赋给一个变量(如上面的 `add`),这个变量就称为函数变量(别名),可以当函数用;也可以直接把 `lambda` 语句用括号括起来当一个函数用(上面后一种形式)。\n",
  864. "\n",
  865. "在 Python 官方文档中,`lambda` 语句的语法定义是这样的:\n",
  866. "\n",
  867. "`lambda_expr ::= \"lambda\" [parameter_list] \":\" expression`\n",
  868. "\n",
  869. "这个语法定义采用的是 [巴克斯范式(BNF)](https://en.wikipedia.org/wiki/Backus%E2%80%93Naur_form)标注,现在不明白没关系(虽然对照上面的例子也能猜出个大概吧),以后我们会专门介绍。\n",
  870. "\n",
  871. "其实也很简单,就是这个样子:\n",
  872. "\n",
  873. "```python\n",
  874. "lambda x, y: x + y\n",
  875. "```\n",
  876. "\n",
  877. "先写上 `lambda` 关键字,其后分为两个部分,`:` 之前是参数表,之后是表达式,这个表达式的值,就是这个函数的返回值。**注意**:`lambda` 语句中,`:` 之后有且只能有一个表达式,所以它搞不出很复杂的函数,比较适合一句话的函数。\n",
  878. "\n",
  879. "而这个函数呢,没有名字,所以被称为 “匿名函数”。\n",
  880. "\n",
  881. "`add = lambda x, y: x + y`\n",
  882. "\n",
  883. "就相当于是给一个没有名字的函数取了个名字。"
  884. ]
  885. },
  886. {
  887. "cell_type": "markdown",
  888. "metadata": {},
  889. "source": [
  890. "## 小试身手(2)"
  891. ]
  892. },
  893. {
  894. "cell_type": "raw",
  895. "metadata": {},
  896. "source": [
  897. "1.判断奇数 \n",
  898. "判断奇数的函数lambda函数的定义和使用 \n",
  899. "(1)使用匿名函数编写一个函数isOdd,能够判断一个整数是否是奇数,返回True或False。\n",
  900. "isodd = lambda x: x%2 == 1\n",
  901. "(2)请写出输入一个数x,调用isOdd函数判断x是否是奇数的程序段。 \n",
  902. "(3)请写出求50-100之间所有奇数的和的程序段,(使用isOdd实现奇数判断)。 \n",
  903. "(4)请写出求50-100之间所有偶数的和的程序段(使用isOdd实现偶数判断)。\n"
  904. ]
  905. },
  906. {
  907. "cell_type": "code",
  908. "execution_count": 9,
  909. "metadata": {},
  910. "outputs": [],
  911. "source": [
  912. "isodd = lambda x: x%2 == 1"
  913. ]
  914. },
  915. {
  916. "cell_type": "code",
  917. "execution_count": null,
  918. "metadata": {},
  919. "outputs": [],
  920. "source": []
  921. },
  922. {
  923. "cell_type": "markdown",
  924. "metadata": {},
  925. "source": [
  926. "2.构造整数\n",
  927. "问题描述\n",
  928. "求由任意个整数上的个位数构造的新整数并输出。\n",
  929. "具体要求\n",
  930. "(1)编写lambda函数getLastBit,该函数返回正整数number的个位数,例如正整数1234,则返回4。\n",
  931. "(2)编写可变参数的函数makeNumber,求任意个整数的个位数构成的一个新整数。\n",
  932. "(3)输出(45、81、673、938)4个数的个位数构成的新整数,得到的新的整数为:5138。请填空完整程序。\n",
  933. "\n",
  934. "新整数是5138"
  935. ]
  936. },
  937. {
  938. "cell_type": "code",
  939. "execution_count": null,
  940. "metadata": {},
  941. "outputs": [],
  942. "source": [
  943. "#定义lambda函数getLastBit返回一个数的个位数\n",
  944. "getLastBit= \n",
  945. "\n",
  946. "#定义一个可变参数的函数makeNumber,求任意个整数的个位数构成的一个新整数\n",
  947. "def makeNumber(a,*b):\n",
  948. " s=getLastBit(a)\n",
  949. " \n",
  950. "\n",
  951. "\n",
  952. " return s\n",
  953. "\n",
  954. "#输出(45、81、673、938)4个数的个位数构成的新整数。\n",
  955. "print(f\"新整数是{makeNumber(45,81,673,938)}\")\n",
  956. "\n"
  957. ]
  958. },
  959. {
  960. "cell_type": "code",
  961. "execution_count": null,
  962. "metadata": {},
  963. "outputs": [],
  964. "source": []
  965. }
  966. ],
  967. "metadata": {
  968. "kernelspec": {
  969. "display_name": "Python 3",
  970. "language": "python",
  971. "name": "python3"
  972. },
  973. "language_info": {
  974. "codemirror_mode": {
  975. "name": "ipython",
  976. "version": 3
  977. },
  978. "file_extension": ".py",
  979. "mimetype": "text/x-python",
  980. "name": "python",
  981. "nbconvert_exporter": "python",
  982. "pygments_lexer": "ipython3",
  983. "version": "3.7.10"
  984. }
  985. },
  986. "nbformat": 4,
  987. "nbformat_minor": 4
  988. }