張永順 江蘇省清江中學
胡連國 山東省利津縣高級中學
王愛勝 山東省青州第一中學
由于信息科技課程中算法與程序設計教學、人工智能等內容與傳統文化知識差異很大,所以學習起來有一定的難度。因此,教師需要在教學中盡最大可能去剝開這些“知識”的堅硬果殼,才能讓學生吃到里面的果肉,吸收其營養,增長智慧。
數據的計算精度,其背后的原理是二進制。以往,在C、C++、Pascal等語言中是需要進行數據類型定義的,而在Python中相對粗獷,這就容易出現誤會。可這也為在發現問題中認識知識提供了機會。
一天,在講授運算符與表達式時,為了讓學生熟悉運算符、操作數,我給他們布置了一個小練習:利用Python內置的idle編輯器嘗試進行一些常見的數據運算,如加、減、乘、除,在給定幾個例子之外,我也請同學們發揮自己的主觀能動性,多嘗試幾組數據。當布置完任務之后,學生們都積極地投入到數據計算的測試之中。這時,一個平時比較活潑的學生在測試了幾組數據之后,突然舉手,我示意他站起來。他站起之后大聲說:“老師,我發現了一個Python程序的bug。”此時,班內氣氛頓時熱烈起來。
“哦,是什么bug呢?能不能給我演示一下?”
“老師,是這樣的,當我使用16.888乘以100時(如圖1),結果多了一個小尾巴!”

圖1
見此情景,我立即表揚了這位學生的積極探索,并趁熱打鐵給學生們深入解釋:①在Python中使用兩個浮點型的數據進行計算時,會發現某些時候計算并不準確,如1.1+2.2結果是3.3000000000000003。這主要是二進制無法精確表達十進制小數的結果。例如11.11,整數部分按權展開1*2**1+1*2**0=(3)10,小數部分1*2**-1+1*2**-2=(0.75)10,可見二進制小數能精確表達十進制小數。②反過來,如果把十進制中的小數部分轉為二進制,把該小數不斷乘2、取整,直至沒有小數為止,發現有時候并不能乘盡。也就是有的小數其實并不能精確地轉換成二進制的小數,限于數據類型的長度,只能近似地表達,如(0.45)10≈(0.0111001……)2無法精確表達。因為在計算十進制小數時要先轉化成二進制再進行計算,又因為二進制小數無法精確表達某些十進制小數,此時Python只能盡量保證精度。如果要求特別精確,在Python語言中需要用到decimal,這是Python內置標準庫,用它可以進行精確浮點數運算。
總之,這位細心的學生確實發現了Python運算的“bug”,通過分析探索,學生更深刻地了解了計算機進行計算的本質,即數據類型、數據計算、二進制等知識。
學生在遇見問題時有好奇心,能夠去思考與探索,這些發現不論大小都能夠增長學生的智慧,意義重大。在數據計算精度問題的研究中,還可以鼓勵學生搜索計算機語言在相關數據類型方面的規范,甚至嘗試其他計算機語言的簡單計算,構建更豐富的認知空間。
敢于對現象提出問題,正是思考的表現;敢于對知識提出質疑,正是主動認知的過程。例如,在循環中較少學生能夠提出range()的意義,大都局限在對其初值、終值、步長的思考。當有部分學生提出計數循環的步長是否可以更改時,教師可以鼓勵學生探索,使其在探索中深化對列表函數特點的認知。
“老師,range()是什么?”一個學生曾這樣問我。我解釋它就是一個列表函數。他又問怎么列表?我說:“在循環里不是講了嗎?”他說:“可老師講的是循環范圍,我不理解這是什么函數,為什么還自己產生這么多數呢?什么從初值開始,又到不了終值,步長還默認是1,好繞!”我鼓勵道:“你試著改改各個參數,觀察運行結果,在循環程序里體會!”過了一會兒,他又問道:“我試了試,步長可以是變量嗎?可是放在里面不變能呢?”(他給我看的程序如圖2所示)

圖2
我問道:“這說明什么?range()有什么特點?”他說:“步長不受后面變化的值影響?”我說:“總結一下就是range()一次性先完成了。”(學生讓我給再講講,我沒有細講,而是給了他如圖3所示的幾個程序,讓他試驗,并要求他試完之后總結:①list是做什么的?②m的測試跟循環有什么不同?③range()、list(range())的數據類型分別是什么?)

圖3
后來,他又問道:“range(1,30,0.5)能否構成小數列表?”我解釋道:“調試與試驗是個法寶,以后學了while循環可以考慮用追加方法構造一個小數列表,到時也可以做自己的自由變化的步長了。”
學生對函數的理解多數是單一的映射數值,并沒有多數據列表的認知,因此也可適當從集合角度去理解。range()值的特點、數據產生時間、整數特征等都可以在試驗中體會,可舉例證明。問題探究可以延伸出程序功能的升級實現,即學習while循環提高自由設計的思想。
列表是Python的重要知識,大量的程序設計基于列表進行。但學生還沒有學習數學的“數列”,因此也無法建立數據集合的概念,所以,使用實際生活中的事物類比是破解難題的重要方法之一。
有一天,一位學生歡喜地進門就喊道:“老師,我太想你了!我做夢都在編程序。”我贊道:“這說明你快成明白人了,就像學車做夢就快出手了。”學生說道:“可是夢里我什么都不會,夢見一抽屜的卡片上都是你演示的一段段的程序代碼,可就是看不清楚,都急死了!”我笑了,我是提過老圖書館抽屜里的卡片,但是我說的是貼個變量X、Y的標簽而已,哪有一大段代碼。正好當天的課上我要讓學生練列表,因此給學生提出了分步探索的要求:①根據代碼樣式,補充真實內容完成自己的程序。②每個環節的程序單獨從云課堂提交作業。
引導類比方式的探索如下:
(1)一抽屜的書卡是什么?(列表賦值)有多少張?(列表長度)哪張是第一張?(列表位置)程序如圖4所示。

圖4
(2)隨意抽出一張書卡是哪本書?(直接訪問列表數據)(刪除列表數據)程序如下頁圖5所示。

圖5
(3)怎樣全部查看書卡?(順序訪問列表數據、判斷數據)程序如圖6所示。

圖6
(4)有快速查找一本書的“方法”嗎?(列表索引位置的‘方法’)程序如圖7所示。

圖7
(5)拿出一摞卡片(列表切片),列表有很多用途,相應地有很多操作方式,還有列表排序list.sort()、列表切片list[x:y]等,學以致用是最好的學習。程序如圖8所示。

圖8
概念厘清,即一個概念及應用要列出1、2、3,搞清晰、有體驗,就算再做夢也不容易迷糊。操作都與需求相聯系。沒需求就不用發明這個操作了,所以編程也好、軟件也好,一個操作方法必然是對應著應用的,有什么用要對上號,自然就容易掌握。別強記規則,而是理解應用為宜。
比喻是最易理解的知識描述方式,因為它會更形象、生動、有趣,如把自定義函數比喻成自我DIY一個阿拉丁神燈,體現出可以“復用的代碼”的形象來。
每到“可以復用的代碼——自定義函數”這部分內容,我心里總是有個掙扎的聲音:內容太抽象了,學生理解起來太困難了,不上好像也不影響學業水平測試的結果,直接跳過吧!研讀課標與教材,發現程序中的自定義函數就是要讓學生理解:①為什么需要自定義函數?②自定義函數怎么寫?③自定義函數如何使用?我突發奇想,要不通過“DIY阿拉丁神燈”來講解這個自定義函數?
(1)視頻導入。播放“阿拉丁神燈”實現愿望的視頻片段,學生需要關注那盞“燈”的作用是什么?“燈神”住在哪兒?有什么作用?“燈神”是如何被喚醒的?
(2)程序觀察。向學生展示一段程序,提出問題:該程序實現什么功能?連續執行三次make_juice()能實現什么功能呢(如圖9)?

圖9
學生通過閱讀程序回答“make_juice( )是制作蘋果汁的流程,連續執行三次make_juice( )可實現制作三杯蘋果汁”等。學生通過代碼閱讀學會了自定義函數的定義和調用方法;通過觀察輸出的結果,明白了調用的作用是什么。繼續提出思考問題:這個程序能夠實現制作蘋果汁、香蕉汁、獼猴桃果汁各一杯嗎?
(3)模仿修改。模仿:在課堂教學中,學生通過復制制作蘋果汁函數,并將蘋果修改為香蕉和獼猴桃,調試運行,發現結果都是獼猴桃。在學生發現此路不通后,我適時提出了問題:make_juice( )函數被調用的時候,知道自己是做什么的嗎?如果不知道,該如何告知呢?
(4)增加參數。通過make_juice(‘蘋果’)語句告知我們要制作的是蘋果汁,這是在“召喚燈神”。①“神燈”設計。函數make_juice( )該如何接收“蘋果”這個值呢?通過增加參數,即用make_juice(fruit)形式,參數“fruit”用來接收水果名稱。②“神燈”使用。“燈”在接收到fruit參數后,在函數內部該如何來使用呢?以“將蘋果去皮”為例,通過將具體的水果名稱替換為變量“fruit”,將其修改為“將”+fruit+“去皮”,即可實現內部的名稱自適應功能。由此,make_juice(fruit)函數可以實現多種果汁制作了,這是“燈神”可以滿足不同要求的要訣。具體程序如圖10所示。

圖10
通過“神燈”設計,修改自定義函數的參數是一種良好的“適用不同對象”的“召喚燈神燈”的代碼復用。還可以繼續探討使用“循環結構”對實現某個功能的基本語句的重復式復用。自定義函數能夠把多次重復的功能抽象成固定符號表達,也屬于結構化程序模塊設計,在程序中通過調用函數名稱和傳遞參數達到簡潔、靈活的功能現實。