1. 程式人生 > >從零開始學習PYTHON3講義(十一)計算器升級啦

從零開始學習PYTHON3講義(十一)計算器升級啦



(內容需要,本講中再次使用了大量線上公式,如果因為轉帖網站不支援公式無法顯示的情況,歡迎訪問原始部落格。)

《從零開始PYTHON3》第十一講

第二講的時候,我們通過Python的互動模式來入門Python基本知識。當時把Python當成了一個計算器使用。隨後從第三講開始,一直到第十講,我們進入了程式設計的方式,並且不斷的深入,到第九講,我們已經完成了Python基本語言、語法部分的學習。

每一講都有大量的程式設計練習,估計大家也累了,這一講休息一下,我們回到把Python當做計算器的狀態。當然內容還是要更深入一些,介紹一些常用的高階數學運算功能。


Python的標準數學庫

標準庫、內建庫、官方庫這些詞其實說的都是一個意思,就是這個庫來自Python官方開發團隊,隨開發語言一同安裝無需另外下載的庫。

第二講的時候我們已經發現了,Python本身似乎只能做一些簡單的數學運算,加、減、乘、除、乘方。隨後整數運算還額外有取餘數、整除等幾個特別的運算。實際上Python更復雜的數學運算都在標準數學庫math之中。一直到第九講我們介紹了“庫”的概念,我們才能更多的介紹Python更高階的計算能力。

如同上一講說到sys庫的那樣,我們也可以使用Python的內部幫助來檢視math庫的詳細情況:

>>> import math
>>> dir(math)
['__doc__', '__file__', '__loader__', '__name__', '__package__', '__spec__', 'acos', 'acosh', 'asin', 'asinh', 'atan', 'atan2', 'atanh', 'ceil', 'copysign', 'cos', 'cosh', 'degrees', 'e', 'erf', 'erfc', 'exp', 'expm1', 'fabs', 'factorial', 'floor', 'fmod', 'frexp', 'fsum', 'gamma', 'gcd', 'hypot', 'inf', 'isclose', 'isfinite', 'isinf', 'isnan', 'ldexp', 'lgamma', 'log', 'log10', 'log1p', 'log2', 'modf', 'nan', 'pi', 'pow', 'radians', 'remainder', 'sin', 'sinh', 'sqrt', 'tan', 'tanh', 'tau', 'trunc']
>>> help(math)
...   #將有大量詳細的幫助資訊,這裡略去

下面我們介紹一些常用的math內建數學函式:

函式 功能
math.pi 數學常數π= 3.141592……
math.e 數學常數e = 2.718281….
math.tau 數學常數τ= 6.283185……
math.ceil(x) 返回x的上限,返回最小的整數A (A>=x)。如math.ceil(3.14)返回的整數為4官網math庫
math.fabs(x) 返回絕對值x。
math.factorial(x) 返回 x!。如果x不是積分或者是負的,就會產生ValueError。
math.floor(x) 返回x的下限,返回一個值最大整數A (A<=x)。如math.floor(3.14)返回的整數為3
math.exp(x) 返回 ex也就是 math.e ** x
math.pow(x,y) 返回x的y次方,即返回 x ** y
math.sqrt(x) 返回 $$ \sqrt x $$
math.degrees(x) 將角x從弧度轉換成角度。
math.radians(x) 把角x從度轉換成弧度。
math.acos(x) 返回 x 的反餘弦
math.asin(x) 返回 x 的反正弦。
math.atan(x) 返回 x 的反正切。
math.cos(x) 返回 x 的餘弦。
math.sin(x) 返回 x 的正弦。
math.tan(x) 返回 x 的正切。
math.log(x,a) 返回 $$ log;a^x $$,若不提供a引數,預設使用e

有了這些函式的幫助,我們一下從小學水平上升到了高中:),來看幾個使用的例子:

>>> import math #所有math的函式,使用之前必須引入庫,引入一次即可
>>> math.sin(1)    #1的正弦
0.8414709848078965
>>> math.pi     #π常量
3.141592653589793
>>> math.sqrt(3) #計算3的平方根
1.7320508075688772
>>> 

擴充套件庫和各種函式的學習,通常不需要你一次都記住,而是用的時候查資料會用即可。常用的函式,用的多的自然就記住了。
隨用隨查資料這種形式,不同於以前的課堂筆記,一般都是用網頁書籤來記錄下來常用的資料地址,這樣才能快速的查詢。比如math庫的部分常用函式的中文資料:https://www.cnblogs.com/renpingsheng/p/7171950.html
中文資料一般都更新慢一些,並且通常不是很完整,官方的英文資料則更快,但是需要你能閱讀英文。立志希望從事資訊科技行業的同學,英語的學習也要同時加強。官方英文資料地址:https://docs.python.org/3/library/math.html


第三方數學庫numpy

“第三方”是在計算機行業中很常用的概念,指的既不是開發者官方提供的,也不是使用者自己開發的。是由其它組織開發並提供服務的內容。可以把兩者做一個比較:

標準庫 第三方擴充套件庫
同為軟體庫,相同的使用方法 同為軟體庫,相同的使用方法
由PYTHON官方或認可的開發團隊開發維護 通常由世界範圍內許多不同公司或組織開發維護
通常只有一個最穩定的版本 同一個功能,可能有很多個團隊的不同產品,質量參差不齊
主要完成常用、基本、必備功能 解決各種各樣問題
隨PYTHON安裝,直接就可以使用,稱為標準庫 需要額外安裝,跟不同作業系統可能還有相容性問題,稱為第三方擴充套件庫
開發規範、命名習慣基本統一 各自有各自的標準、規範,互相之間有可能習慣差別很大

通常能生存並傳播很廣泛的第三方擴充套件庫都有驚人強大的功能。在享受這些“超級”功能的同時,每個第三方擴充套件庫都需要安裝之後才能被Python程式“引用”和“使用”,是第三方擴充套件庫最大的障礙。
為此Python發展出了很多擴充套件庫的管理工具來幫助開發人員安裝、管理、刪除擴充套件庫。我們第一講介紹了使用最多的pip管理工具。
使用pip管理工具安裝numpy數學庫的方法如下:

#在Windows中,首先退出當前的Python軟體
#使用管理員模式執行cmd命令列,然後執行如下命令:
pip install numpy
#某些windows系統需要使用pip3
pip3 install numpy

#linux和mac在命令列執行:
sudo pip3 install numpy

使用習慣之後,這樣一行的安裝命令根本不會對你使用擴充套件庫有什麼影響,而且只需要安裝一次,不換電腦就可以一直使用。

numpy的使用跟math的使用幾乎是相同的,但是相較於只有50多個預置數學函式的math,numpy包含了600多個。只要跟數學相關的,幾乎所有需要用到的函式和常量都已經有了。我們舉幾個例子:

#首先使用之前一樣是必須先引用
import numpy as np
    #as np表示引入後使用np的名字來呼叫,這樣每次都可以少敲幾個字母

np.sin(1)    #正弦函式
 => 0.8414709848078965
np.pi    #π常量
 => 3.141592653589793
np.sqrt(3)  #平方根
 => 1.7320508075688772
np.arccos(0)   #反餘弦函式  
 => 1.5707963267948966

#檢視幫助
help(np)       #第一次幫助會從網上獲取,速度比較慢

第九講我們曾經講過了使用列表型別儲存矩陣的方式,可惜就基本Python的功能來講,也只是能儲存而已,想要計算,需要自己使用複雜的迴圈巢狀來完成。但矩陣運算在numpy是直接內建的,比如:
$$
A = \left{
\begin{matrix}
2 & 3 & 4 \
5 & 6 & 7 \
8 & 9 & 10
\end{matrix}
\right} \times 3\tag{1}
$$

我們直接看numpy的計算方式:

>>> np.array([[2,3,4],[5,6,7],[8,9,10]])*3
array([[ 6,  9, 12],
       [15, 18, 21],
       [24, 27, 30]])

np.array函式,實際是numpy中的列表型別。列表的定義跟標準Python很像,是用巢狀的“[]”完成的。隨後numpy的型別直接就支援矩陣乘法,所以最後“*3”。執行後輸出了矩陣的計算結果。對比的如果使用標準的Python,肯定要使用兩個迴圈巢狀,然後逐項的進行乘法計算。速度會慢很多,程式設計也複雜很多。

再比較一個例子。第六講中我們講了range函式,是跟for迴圈一起介紹的,大家應當不陌生。當時重點說明了range返回的是一個整數的序列型別,那碰到需要使用小數的序列型別的時候怎麼辦呢?通常的辦法只能在迴圈體中增加一次整數同浮點小數的乘法運算來生成每次迴圈使用的小數。比如:

step=0.11
r=[]
for i in range(10):
    r.append(i * step)
#結果為:
[0.0, 0.11, 0.22, 0.33, 0.44, 0.55, 0.66, 0.77, 0.88, 0.99]

而numpy中,直接有支援小數序列的型別:

#Python內建的range函式
>>> list(range(10))
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]

#numpy中支援小數序列的linspace
>>> np.linspace(1,2,10)
array([1.        , 1.11111111, 1.22222222, 1.33333333, 1.44444444,
       1.55555556, 1.66666667, 1.77777778, 1.88888889, 2.        ])

linspace函式的三個引數跟range函式區別比較大,需要注意:第一個引數是指起始數值;第二個引數是指結束數值,注意這裡會包含結束數值,而range中是不包含結束數值;第三個引數是指從開始到結束,分為多少份,也就是最後序列的長度。

我們至今所看到的Python數學計算,都屬於數值計算的範疇。所謂“數值計算”就是指不管計算過程多麼複雜,最終以數值的形式得出計算結果。
數值計算在實際應用中使用的最多,但缺陷也比較明顯。比如從上面linspace的例子就能看出來,看起來所生成的浮點小數序列,並不是很整齊,幾乎可以確定有被省略的部分。計算機內部的儲存是2進位制數,我們平常習慣的計算方式是10進位制數,兩者之間的轉換會有誤差,無理數的多次擷取也會造成誤差。我們可以再舉一個更明顯的例子:

import math
math.sqrt(8)
結果:2.8284271247461903
math.sqrt(8)*math.sqrt(8)
結果:8.000000000000002

上例中,因為對8開平方的時候資料做了擷取,相乘計算回平方值之後,無法做到精確的得出8,只是一個很近似的值。這在存在大量計算而精度又要求比較高的情況下,仔細的考慮化簡的時機和計算的方法將會耗費大量的精力。
這種情況在第三方的numpy數學庫中同樣是存在的:

import numpy as np
np.sqrt(8)
結果:2.8284271247461903
np.sqrt(8)*np.sqrt(8)
結果:8.000000000000002

實際上只要是使用數值計算就會出現這種情況,尚無法避免。
為了應對這種方式,在數學中大量採用了符號計算。我們目前數學課上學到的方程式、多項式基本都屬於這個範疇。往往並不需要求出最終的計算結果。化簡到一些包含簡單符號和算式的結果就可以滿足應用。因此符號計算在科研、工程領域都有廣泛應用。

Python有一個第三方的符號計算擴充套件庫,名為sympy。安裝方式為(以後的安裝介紹均以windows為例,不再介紹linux及mac,相信參考windows的方法,在linux和mac安裝都不應當有問題):

#首先使用管理員模式開啟cmd命令列,然後執行:
pip install sympy  
#實際上pip工具可以同時安裝多個擴充套件庫,比如:
pip install numpy sympy

相對熟悉的數值計算,sympy符號計算庫理解起來會難一些。Sympy試圖建立一整套運算體系,對每次的結果進行符號計算,盡力保持計算的精確度。試圖建立一整套體系的原因是這樣:在Python中,加、減、乘、除包括等號等等所有字元,基本都已經有了預設的功能,比如通常的數學數值計算。
我們前面也講過了,這些符號本身屬於保留字的一種,是不能被我們用於其它用途的。因此在不會歧義的位置,會繼續使用原有計算符和函式,有歧義的位置,需要使用Sympy自己的函式,比如分數函式Rational(稍後會有講解)。
只要算式會被化簡從而成為小數的情況,都應當考慮使用Sympy自己的函式,通常都是分數、除法、數學函式的位置,否則就等於使用了原有的數值計算,可能導致精度降低。

sympy的使用方法,先來看一個例子:

#使用內建的數學庫
import math
math.sqrt(8)
結果:2.8284271247461903

3*5*math.sin(7) #numpy.sin(7)也是相同的
結果:9.854798980781837

#下面使用sympy
import sympy
sympy.sqrt(8)
結果:2*sqrt(2)   #注意結果的樣式

3*5*sympy.sin(7)
結果:15*sin(7)   #注意結果

乍看起來,sympy的結果似乎很怪異。其實如果把計算機函式翻譯為數學函式,這個結果非常類似我們學習數學的時候公式化間的結果。繼續看示例:

import sympy

#平方根
sympy.sqrt(8)
結果:2*sqrt(2)

sympy.sqrt(8)*sympy.sqrt(8)
結果:8

2*sympy.sqrt(2)
結果:2*sqrt(2)

#正弦函式
sympy.sin(1)
結果:sin(1)

#π常數
sympy.pi
結果:pi

#分數
sympy.Rational(1,2)
結果:1/2

注意上面的計算結果,都沒有字首的sympy,而是直接的sin/sqrt這樣的結果。這說明,其實sympy使用的時候,最好使用from sympy import *,還記得嗎?這相當於從sympy把所有可用資源都匯入到了當前檔案作用域,因此呼叫的時候可以完全省略sympy字首。
繼續說符號計算。上面使用的例子,你會發現使用符號計算的方法,因為可能會變成無理數的部分都使用了符號或者公式來表達了。所以兩個平方根相乘這樣的運算,是可以精確還原到原始值的。

既然是符號計算,直接使用符號量在數學表示式中也是很有特色的功能:

#符號宣告
#在第二講說變數的時候,
#我們特別說明變數是“已知數”
#這裡建立的符號變數,其實就是
#代表數學公式中的未知數
#當然最後這個未知數,還是使用Python變數來表示的,
#sympy.Symbol就是一個sympy庫中的型別。
x = sympy.Symbol('x')   #定義未知數x
y = sympy.Symbol('y')   #未知數y
m,n,z = sympy.symbols('m n z') #同時定義多個未知數

#以下是使用定義的未知數,進行帶未知數的數學符號計算
m*x*3+8
結果:3*m*x + 8

(x+y)*3
結果:3*x + 3*y

再強調一下,在sympy中定義的未知數型別,變數的確是Python的變數。所代表的含義可是sympy符號計算中的未知數,而不是我們常見的Python變數。


挑戰

下面我們利用強大的符號計算來進行一個多項式的化簡:
$$
(x + (2xy)^\frac{1}{2}+y)(x - (2xy)^\frac{1}{2}+y)
$$
建議你自己動手化簡一下,雖然我們不是數學課,但數學技能還是很重要的。
隨後你應當能得到正確答案:
$$
= x^2 + y^2
$$
上面是手工來化簡的結果。下面到了讓sympy上場的時間了:

#引入擴充套件庫
from sympy import *

#定義x/y兩個符號
x,y = symbols("x y")

#化簡函式simplify()
simplify((x+(2*x*y)**Rational(1, 2)+y)*(x-(2*x*y)**Rational(1, 2)+y))
#執行結果
 x**2 + y**2

其實同第二章一樣,這一章的難度,同樣是要用Python語言來描述數學公式。上例中的simplify函式式sympy中的一個函式,表示把引數當做數學表示式,然後進行化簡操作。加法、乘法、乘方都不會造成小數,也沒有語法上的歧義,所以直接使用了標準的數學運算子。1/2這種除法會有可能導致小數,從而有二進位制到十進位制轉換的誤差風險;並且1/2會直接使用數值計算,會導致算式過快的求值,導致最後化簡失敗,所以這裡使用sympy內建的分數函式Rational,這個函式有兩個引數,分別代表分子和分母。
經過這些解釋,你是不是能看懂了?最後看化簡的結果,跟我們手工的過程一模一樣。這些新的函式,希望你自己給自己找一些算式多練習,才能更快的掌握。


解方程

解方程在數學中簡直佔了半壁江山啊。我們仍然從第二講的老例子開始:

甲、乙兩人相距36千米,相向而行,如果甲比乙先走2小時,那麼他們在乙出發2.5小時後相遇;如果乙比甲先走2小時,那麼他們在甲出發3小時後相遇,甲、乙兩人每小時各走多少千米?(假設甲乙的速度均勻穩定)

希望你還記得原來的結題過程,我直接列出來吧,畢竟我們要學習的是Python不是數學:

  • 假設甲的速度為x千米/小時,假設乙的速度為y千米/小時
  • 列方程式(2.5+2)x+2.5y=36,3x+(3+2)y=36
  • 根據方程2推導為:x=(36-5y)/3,代入方程1
  • y=(124.5-36)/(4.55/3-2.5)
  • 最後得:y=3.6,x=6

解方程首先的問題是,“= ”已經被用作了賦值操作,跟前面/的原因一樣,不能直接用來描述等式。不然Python會直接報錯。

sympy定義了sympy.Eq()函式來描述等式,以上面的兩個方程為例,可以寫成這個樣子:sympy.Eq((2.5+2) * x+2.5 * y,36)sympy.Eq(3 * x+(3+2) * y,36)。逗號隔開的,就是等式兩端。其它的注意事項,跟上面“化簡”的時候講的一樣。下面看看我們解方程的過程:

#引入擴充套件庫
from sympy import *

#定義兩個未知數符號
x=Symbol('x')
y=Symbol('y')

#定義兩個等式
a = Eq((2.5+2)*x+2.5*y,36)
b = Eq(3*x+(3+2)*y,36)

#使用sympy.solve函式解方程組
solve([a,b],[x,y])

#執行結果:
{x: 6.00000000000000, y: 3.60000000000000}

嗯,說不程式設計序了,實際最後還是編了,好在比較簡單:)
程式中定義未知數符號、描述等式,重點是使用了sympy.solve函式來解方程。函式接受兩個引數,兩個引數都是列表。第一個列表中是方程式(等式),第二個列表是要求解的未知數。

我們再把程式簡化一下:

#引入擴充套件庫
from sympy import *

#在一行中直接定義兩個未知數符號
x,y = symbols("x y")

#使用sympy.solve函式解方程組
solve([Eq((2.5+2)*x+2.5*y,36),Eq(3*x+(3+2)*y,36)],[x,y])

結果:{x: 6.00000000000000, y: 3.60000000000000}

這樣看起來更清楚了。有沒有覺得sympy符號計算很強大?


練習時間

  1. 使用symbol解方程組:
    $$
    \begin{equation}
    \begin{cases}
    2x-y=5 \
    3x+4y=2
    \end{cases}
    \end{equation}
    $$

  2. 化簡表示式:
    $$
    \frac {\sin(60+\theta)+\cos(120)\sin(\theta)}{ cos(\theta)}
    $$
    這道題有一些提示:

    $$ \theta $$ 在Python中很難輸入,可以使用一個未知數x代替,不影響結果。

    Python的數學庫只接受$$\pi$$角度,也既我們習慣的180度,所以題目中的60度可以表示為$$\pi/3$$;120度則表示為$$\pi/3*2$$。

    式子中的分子、分母因為都有未知數,不會引起即時計算影響計算結果,也不會有歧義,所以就是用“/”計算符即可,不用使用Rational函式。

作為一門程式設計課,本講並未提供太多的數學算式來幫助你記憶新學的數學函式。建議你從自己的數學學習中尋找一些算式來多做一些練習。


本講小結

  • 複雜的數學計算是理工科必備的基本能力,也是最頻繁需要的
  • 數學計算在計算機應用中,分為求得結果的數值計算及公式化簡為主的符號計算,各有用途,都很重要
  • Python在多種擴充套件庫的幫助下有強大的計算能力
  • 本講的重點是公式化簡、解方程,要學會使用語言中的運算子、函式等來描述數學公式

學習資源

numpy:
https://docs.scipy.org/doc/numpy/user/quickstart.html
sympy:
http://docs.sympy.org/latest/tutorial/index.html


練習答案

  1. solve([Eq(2*x-y,5),Eq(3*x+4*y,2)])
  2. simplify((sin(pi/3+x)+cos(pi/3*2)*sin(x))/cos(x))

最終結果請在Python中試著執行看看,別忘了引入sympy庫。