2022年8月31日 星期三

Python 學習筆記 : 用 Folium 套件繪製地圖 (三)

Folium 這套件看似簡單易用, 但其 API 文件內容非常豐富, 還有非常多功能有待探究. 本篇主要是我閱讀日本人小久保奈都彌寫的 "Python 資料可視化攻略 (碁峰 2021)" 第六章關於 Folium 用法之測試筆記 :


Source : 博客來


本系列之前的文章參考 :

# Python 學習筆記 : 用 Folium 套件繪製地圖 (一)
Python 學習筆記 : 用 Folium 套件繪製地圖 (二)

由於 Folium 繪製的是網頁地圖, 故以下測試需在 Colab 或 JupyterLab 上進行. 


1. 用 folium.PolyLine() 畫直線 : 

在地圖中繪製直線通常是用來標示地圖中兩地的直線距離, 或顯示兩地之間的資訊流動 (人, 事, 物等). Folium 套件中用來繪製直線是使用 PolyLine 物件, 其類別之 API 如下 : 

class folium.vector_layers.PolyLine(locations, popup=None, tooltip=None, **kwargs)

參考 : 


只要呼叫其建構函式 folium.PolyLine() 並傳入如下參數會建立 PolyLine 物件 : 
  • locations : 直線兩端座標經緯度之兩層串列或元組
  • popup : 點擊直線會跳出的文字
  • color : 線條顏色, 可用顏色名稱字串如 'red' 或色碼如 '#1e227d'
然後呼叫其 add_to() 方法將自己添加到 Map 物件上即可 :

folium.PolyLine(locations=[latlng1, latlng2], popup=popup).add_to(map)

以繪製連接台北 101 與高雄 85 大樓的直線為例 : 

import folium   
import geocoder 

loc1="高雄85大樓"   
loc2="台北101"   
latlng1=geocoder.osm(loc1).latlng   
latlng2=geocoder.osm(loc2).latlng   
print(latlng1)   
print(latlng2)   
map=folium.Map(location=geocoder.osm('台灣').latlng, zoom_start=7.5)   
folium.Marker(location=latlng1, popup=loc1).add_to(map)   
folium.Marker(location=latlng2, popup=loc2).add_to(map)   
popup=f"{loc1}-{loc2}直線距離"   
folium.PolyLine(locations=[latlng1, latlng2], popup=popup).add_to(map)   
map   

此例先用 geocoder 查詢台北 101 與高雄 85 大樓之座標經緯度, 用 folium.Marker() 在此兩座標上打標記, 然後用 folium.PolyLine() 在此兩座標間畫一條直線, 結果如下 :




點擊此直線會彈出說明文字, 但可惜是直的. 


2. 用 folium.Circle() 畫圓 : 

呼叫 folium.Circle() 可以在地圖上畫圓, 這是 Circle 類別的建構函式 : 

class folium.vector_layers.Circle(location=None, radius=50, popup=None, tooltip=None, **kwargs)

參考 : 


主要參數如下 : 
  • location : 圓心座標經緯度
  • radius : 半徑 (單位為公尺, 預設為 50m)
  • color : 圓線顏色, 可用顏色名稱字串如 'red' 或色碼如 '#1e227d'
  • fill_color :  圓內填充顏色, 可用顏色名稱字串如 'red' 或色碼如 '#1e227d'
  • popup : 點擊圓會跳出的文字
呼叫 folium.Circle() 建立 Circle 物件後呼叫其 add_to() 方法將自己添加到 Map 物件上即可 :

folium.Circle(location=latlng, color='blue', file_color='ivory', popup=popup).add_to(map)

以繪製核三廠萬一發生核災的 80 公里避難圈為例, 程式碼如下 :

import folium  
import geocoder   
place="核三廠"     
latlng=geocoder.osm(place).latlng   
map=folium.Map(location=geocoder.osm(place).latlng, zoom_start=8)   
folium.Marker(location=latlng).add_to(map)    
folium.Circle(location=latlng, radius=80000, color='red', file_color='yellow').add_to(map)   
map   

結果如下 : 





可見 80 公里的避難圈幾乎包括整個屏東縣, 且已經到達小港機場了. 其實就日本福島核災經驗, 在 100 公里外還偵測到危險的輻射物. 


Python 學習筆記 : 用 Folium 套件繪製地圖 (二)

學會以 Folium 繪製地圖的基本用法後, 本篇繼續來測試課堂中一個較實用的範例, 即結合網頁爬蟲從政府資料開放平台擷取 Ubike 的 JSON 檔, 然後在地圖上標示其位置. 以下是課後重看時所做的測試紀錄. 本系列之前的文章參考 :


首先來看一下資料源: 政府資料開放平台, 其網址如下 :


Ubike 的資料集放在 "交通及通訊" 類別下 : 




進去後點選左方 "地方機關" 下的 "台北市" : 




第一個就是 Ubike 的即時資訊 :




進去會發現一個 JSON 按鈕, 按此按鈕會顯示資料集內容 : 





將上方的網址複製起來, 這在爬蟲程式要用來擷取裡面的 json 格式資料集 :


先在命令列擷取與檢視此資料集 :

>>> import requests 
>>> url="https://tcgbusfs.blob.core.windows.net/dotapp/youbike/v2/youbike_immediate.json"   
>>> data=requests.get(url).json()   
>>> type(data)   
<class 'list'>   
>>> data[0]    
{'sno': '500101001', 'sna': 'YouBike2.0_捷運科技大樓站', 'tot': 28, 'sbi': 16, 'sarea': '大安區', 'mday': '2022-08-31 15:17:38', 'lat': 25.02605, 'lng': 121.5436, 'ar': '復興南路二段235號前', 'sareaen': 'Daan Dist.', 'snaen': 'YouBike2.0_MRT Technology Bldg. Sta.', 'aren': 'No.235, Sec. 2, Fuxing S. Rd.', 'bemp': 12, 'act': '1', 'srcUpdateTime': '2022-08-31 15:19:13', 'updateTime': '2022-08-31 15:19:07', 'infoTime': '2022-08-31 15:17:38', 'infoDate': '2022-08-31'}
>>> len(data)   
1146

此處使用 requests.get() 取得此 json 格式資料集, 並直接串用 Response 物件的 json() 方法轉成 list 型態, 可見整個資料集目前有 1146 筆, 每筆都是字典型態的站點訊息, 但其中只有 sna (站點名稱), lat (緯度), 與 lng (經度) 這三個是在網頁地圖上標示 Ubike 站點位置時所需的資料. 

可以用迴圈列出各站點的這三個資訊 (很長, 有 1146 筆) :

>>> for i in data:
    print(f"{i['sna']} : [{i['lat']}, {i['lng']}]")       

要在地圖上標示標示 Ubike 站點標記只要在迴圈中呼叫 folium.Marker().add_to() 即可 :

for i in data:
    folium.Marker(location=(i['lat'], i['lng']) , popup=i['sna']).add_to(map)

接下來要將上面的程式碼移到 JupyterLab 或 Colab 上執行 :

import requests  
import folium    
import geocoder   
place='台北市'    
latlng=geocoder.osm(place).latlng   
map=folium.Map(location=latlng, zoom_start=16)   
url="https://tcgbusfs.blob.core.windows.net/dotapp/youbike/v2/youbike_immediate.json"   
data=requests.get(url).json()   
for i in data:   
    folium.Marker(location=(i['lat'], i['lng']) , popup=i['sna']).add_to(map)   
map   

結果如下 : 




這在以前是很複雜的功能, 但 Python 只要 10 行左右程式碼就搞定, 站上巨人的肩膀果然不一樣. 


Python 學習筆記 : 用 Folium 套件繪製地圖 (一)

這兩天參加 Python 內訓課程時學會如何用 Folium 套件繪製網頁地圖, 以及如何用 GeoCoder 將地標轉成經緯度再傳給 Folium 使用, 以下是課後的測試紀錄. 

Folium 其實是一個以 Javascript 地圖函式庫 Leaflet 為基礎的 Python 網頁地圖套件, 其 API 可讓 Python 使用者傳入經緯度在網頁介面上繪製互動式網頁地圖, 主要應用在搭配 Flask 或 Django 等框架在網頁上繪製地圖, 參考 : 

 
因為 Folium 所繪製的是網頁地圖, 其結果必須在瀏覽器上呈現, 故以下測試是在 JupyterLab 或 Colab 等網頁介面上進行 (在命令列值行只會顯示地圖物件名稱與地址, 無法顯示地圖). 


1. 安裝 Folium : 

如果要在本機的 JupyterLab/Notebook 上顯示地圖須先在命令列安裝 folium : 

C:\Users\User>pip install folium   
Collecting folium
  Downloading folium-0.12.1.post1-py2.py3-none-any.whl (95 kB)
     ---------------------------------------- 95.0/95.0 kB 1.1 MB/s eta 0:00:00
Requirement already satisfied: requests in c:\python37\lib\site-packages (from folium) (2.21.0)
Requirement already satisfied: jinja2>=2.9 in c:\python37\lib\site-packages (from folium) (2.10.1)
Requirement already satisfied: numpy in c:\python37\lib\site-packages (from folium) (1.19.4)
Collecting branca>=0.3.0
  Downloading branca-0.5.0-py3-none-any.whl (24 kB)
Requirement already satisfied: MarkupSafe>=0.23 in c:\python37\lib\site-packages (from jinja2>=2.9->folium) (1.1.1)
Requirement already satisfied: idna<2.9,>=2.5 in c:\python37\lib\site-packages (from requests->folium) (2.8)
Requirement already satisfied: certifi>=2017.4.17 in c:\python37\lib\site-packages (from requests->folium) (2018.11.29)
Requirement already satisfied: chardet<3.1.0,>=3.0.2 in c:\python37\lib\site-packages (from requests->folium) (3.0.4)
Requirement already satisfied: urllib3<1.25,>=1.21.1 in c:\python37\lib\site-packages (from requests->folium) (1.24.1)
Installing collected packages: branca, folium
Successfully installed branca-0.5.0 folium-0.12.1.post1

在 Colab 上安裝 Folium 是在儲存格輸入 !pip install 指令 :

!pip install folium 




安裝好後先來檢視 Folium 套件的成員, 以下使用一個自訂模組 members, 其 list_members() 函式會列出模組或套件中的公開成員 (即屬性與方法), 參考 :

Python 學習筆記 : 檢視物件成員與取得變數名稱字串的方法

使用 Folium 前須先 import folium :

>>> import folium     
>>> import members   
>>> members.list_members(folium)    
Choropleth <class 'type'>
Circle <class 'type'>
CircleMarker <class 'type'>
ClickForMarker <class 'type'>
ColorLine <class 'type'>
ColorMap <class 'type'>
CssLink <class 'type'>
CustomIcon <class 'type'>
Div <class 'type'>
DivIcon <class 'type'>
Element <class 'type'>
FeatureGroup <class 'type'>
Figure <class 'type'>
FitBounds <class 'type'>
GeoJson <class 'type'>
GeoJsonPopup <class 'type'>
GeoJsonTooltip <class 'type'>
Html <class 'type'>
IFrame <class 'type'>
Icon <class 'type'>
JavascriptLink <class 'type'>
LatLngPopup <class 'type'>
LayerControl <class 'type'>
LinearColormap <class 'type'>
Link <class 'type'>
MacroElement <class 'type'>
Map <class 'type'>
Marker <class 'type'>
PolyLine <class 'type'>
Polygon <class 'type'>
Popup <class 'type'>
Rectangle <class 'type'>
RegularPolygonMarker <class 'type'>
StepColormap <class 'type'>
TileLayer <class 'type'>
Tooltip <class 'type'>
TopoJson <class 'type'>
Vega <class 'type'>
VegaLite <class 'type'>
WmsTileLayer <class 'type'>
branca <class 'module'>
elements <class 'module'>
features <class 'module'>
folium <class 'module'>
map <class 'module'>
raster_layers <class 'module'>
sys <class 'module'>
utilities <class 'module'>
vector_layers <class 'module'>

可見 Folium 的功能非常豐富, 但繪製網頁地圖主要會用到 Map 與 Marker 這兩個類別, Map 用來建立地圖物件, 而 Marker 則用來在地圖上打標記, 可透過呼叫其建構函式來建立物件. 


2. 呼叫 folium.Map() 繪製地圖 : 

呼叫 Map 類別的建構函式 folium.Map() 會建立一個 Map 物件, 其 API 如下 :

class folium.folium.Map(location=None, width='100%', height='100%', left='0%', top='0%', position='relative', tiles='OpenStreetMap', attr=None, min_zoom=0, max_zoom=18, zoom_start=10, min_lat=- 90, max_lat=90, min_lon=- 180, max_lon=180, max_bounds=False, crs='EPSG3857', control_scale=False, prefer_canvas=False, no_touch=False, disable_3d=False, png_enabled=False, zoom_control=True, **kwargs)

參考 :


其中最常用的參數如下 :
  • location : 用來指定地圖中心點的位置座標, 其值為經度 (longitude) 與緯度 (latitude) 組成的 tuple 或 list, 如果不指定 location 預設 None 會顯示全世界地圖. 
  • zoom_start : 用來指定放大倍數 (預設是 10 倍) : 
常用語法如下 :

map=folium.Map(location=[緯度, 經度], zoom_start=放大倍數)    

這會傳回一個 Map 物件, 可用 members 模組來檢視其成員 : 

>>> import members 
>>> members.list_members(folium.Map)      
add_child <class 'function'>
add_children <class 'function'>
add_to <class 'function'>
choropleth <class 'function'>
default_css <class 'list'>
default_js <class 'list'>
fit_bounds <class 'function'>
get_bounds <class 'function'>
get_name <class 'function'>
get_root <class 'function'>
keep_in_front <class 'function'>
render <class 'function'>
save <class 'function'>
to_dict <class 'function'>
to_json <class 'function'>

其中較常用的方法是 save(), 可以傳入一個 HTML 檔名字串 (含路徑), 例如 "map.htm" 或 "D:\\test\\python\\map.htm", 會將所繪製的地圖存成 Leaflet 原生的網頁檔.   

如果在命令列執行下列指令, 只會顯示 Map 物件的資訊 : 

>>> import folium      
>>> map=folium.Map()  
>>> type(map)    
<class 'folium.folium.Map'>       
>>> map     
<folium.folium.Map object at 0x0000020DB6E492B0>

要在 JupyterLab 或 Colab 執行才會顯示地圖 : 




如果傳入 location 參數指定地圖中心點的經緯度與起始放大倍數 zoom_start 就可以顯示該位置附近的地圖, 例如高雄 '澄清湖' 座標緯度為 22.66, 經度為 120.351 :

import folium      
map=folium.Map(location=[22.66, 120.35], zoom_start=16) 
map 

結果如下 :




繪製地圖後還可以利用 Marker 物件在地圖上的座標位置, 此類別之 API 如下 :

class folium.map.Marker(location=None, popup=None, tooltip=None, icon=None, draggable=False, **kwargs)    

較常用的參數如下 :
  • location : 要打標記的座標位置, 可用串列或元組表示, 例如 [緯度, 經度]
  • popup : 滑鼠移到該標記上方時的彈出訊息 (字串)
  • icon : 是一個 folium.Icon 物件
icon 參數用來指定標記的樣式, 預設為藍色的 Bootstrap 3 的圖示文字, 其 API 如下 :

class folium.map.Icon(color='blue', icon_color='white', icon='info-sign', angle=0, prefix='glyphicon', **kwargs)   

常用參數為 color (設定圖示標記顏色), 參考 : 


首先用 members 模組檢視 Marker 物件之成員 : 

>>> import members 
>>> members.list_members(folium.Marker)     
add_child <class 'function'>
add_children <class 'function'>
add_to <class 'function'>
get_bounds <class 'function'>
get_name <class 'function'>
get_root <class 'function'>
render <class 'function'>
save <class 'function'>
to_dict <class 'function'>
to_json <class 'function'>

其中常用的方法是 add_to(), 傳入參數為 Map 物件, 表示要將此標記添加到該地圖物件上. 其次來檢視 Icon 物件成員 :

>>> import members 
>>> members.list_members(folium.Icon)     
add_child <class 'function'>
add_children <class 'function'>
add_to <class 'function'>
color_options <class 'set'>
get_bounds <class 'function'>
get_name <class 'function'>
get_root <class 'function'>
render <class 'function'>
save <class 'function'>
to_dict <class 'function'>
to_json <class 'function'>

其中的 add_to(marker) 方法也是用來將 Iccon 物件添加到 Marker 物件上, 但通常會直接將 Icon 物件傳入 Marker() 建構函式的 icon 參數中, 常用語法如下 : 

folium.Marker([緯度, 經度] , popup="說明", icon=folium.Icon(color="red")).add_to(map)    

例如高雄澄清湖座標為 [22.66, 120.35], 在 Colab 或 JupyterLab 中輸入如下指令 : 

import folium   
map=folium.Map(location=[22.66, 120.35], zoom_start=16)     
folium.Marker(location=[22.66, 120.35] , popup="澄清湖", icon=folium.Icon(color="red")).add_to(map) 
map   

這樣就會在地圖中心點座標緯度 22.66 經度 120.35 上打上標記, 結果如下 : 




用滑鼠點一下紅色標記就會跳出 "澄清湖" 字樣. 


3. 安裝 GeoCoder : 

上面的範例是已知座標經緯度情況下繪製地圖, 通常可利用 Google 搜尋而得, 但若要在程式中查詢地標或城市知經緯度, 可用 GeoCoder 套件, 參考 :

使用此套件前須用先安裝 : 

C:\Users\User>pip install geocoder    
Collecting geocoder
  Downloading geocoder-1.38.1-py2.py3-none-any.whl (98 kB)
     ---------------------------------------- 98.6/98.6 kB 1.1 MB/s eta 0:00:00
Requirement already satisfied: requests in c:\python37\lib\site-packages (from geocoder) (2.21.0)
Requirement already satisfied: future in c:\python37\lib\site-packages (from geocoder) (0.18.2)
Requirement already satisfied: click in c:\python37\lib\site-packages (from geocoder) (7.1.2)
Collecting ratelim
  Downloading ratelim-0.1.6-py2.py3-none-any.whl (4.0 kB)
Requirement already satisfied: six in c:\python37\lib\site-packages (from geocoder) (1.12.0)
Requirement already satisfied: decorator in c:\python37\lib\site-packages (from ratelim->geocoder) (4.3.2)
Requirement already satisfied: idna<2.9,>=2.5 in c:\python37\lib\site-packages (from requests->geocoder) (2.8)
Requirement already satisfied: certifi>=2017.4.17 in c:\python37\lib\site-packages (from requests->geocoder) (2018.11.29)
Requirement already satisfied: urllib3<1.25,>=1.21.1 in c:\python37\lib\site-packages (from requests->geocoder) (1.24.1)
Requirement already satisfied: chardet<3.1.0,>=3.0.2 in c:\python37\lib\site-packages (from requests->geocoder) (3.0.4)
Installing collected packages: ratelim, geocoder
Successfully installed geocoder-1.38.1 ratelim-0.1.6

在 Colab 安裝 GeoCoder 要用 !pip install geocoder :

!pip install geocoder   




4. 使用 GeoCoder 查詢經緯度 : 

首先用 members 模組來檢視 GeoCoder 套件的成員 : 

>>> import geocoder 
>>> import members 
>>> members.list_members(geocoder)     
absolute_import <class '__future__._Feature'>
api <class 'module'>
arcgis <class 'function'>
arcgis_reverse <class 'module'>
baidu <class 'function'>
baidu_reverse <class 'module'>
base <class 'module'>
bing <class 'function'>
bing_batch <class 'module'>
bing_batch_forward <class 'module'>
bing_batch_reverse <class 'module'>
bing_reverse <class 'module'>
canadapost <class 'function'>
cli <class 'click.core.Command'>
distance <class 'function'>
elevation <class 'function'>
freegeoip <class 'function'>
gaode <class 'function'>
gaode_reverse <class 'module'>
geocodefarm <class 'function'>
geocodefarm_reverse <class 'module'>
geolytica <class 'function'>
geonames <class 'function'>
geonames_children <class 'module'>
geonames_details <class 'module'>
geonames_hierarchy <class 'module'>
get <class 'function'>
gisgraphy <class 'function'>
gisgraphy_reverse <class 'module'>
google <class 'function'>
google_elevation <class 'module'>
google_places <class 'module'>
google_reverse <class 'module'>
google_timezone <class 'module'>
here <class 'function'>
here_reverse <class 'module'>
ip <class 'function'>
ipinfo <class 'function'>
keys <class 'module'>
komoot <class 'function'>
komoot_reverse <class 'module'>
location <class 'function'>
locationiq <class 'function'>
locationiq_reverse <class 'module'>
mapbox <class 'function'>
mapbox_reverse <class 'module'>
mapquest <class 'function'>
mapquest_batch <class 'module'>
mapquest_reverse <class 'module'>
mapzen <class 'function'>
mapzen_reverse <class 'module'>
maxmind <class 'function'>
nokia <class 'function'>
opencage <class 'function'>
opencage_reverse <class 'module'>
osm <class 'function'>
osm_reverse <class 'module'>
ottawa <class 'function'>
places <class 'function'>
reverse <class 'function'>
tamu <class 'function'>
tgos <class 'function'>
timezone <class 'function'>
tomtom <class 'function'>
uscensus <class 'function'>
uscensus_batch <class 'module'>
uscensus_reverse <class 'module'>
w3w <class 'function'>
w3w_reverse <class 'module'>
yahoo <class 'function'>
yandex <class 'function'>
yandex_reverse <class 'module'>

可見 GeoCoder 套件功能非常多, 但此處只會用到其中的 osm() 函式來查詢地標名稱之座標經緯度, 其傳入參數為地標名稱字串, 它會傳回一個 OsmQuery 物件, 根據官網文件說明, 此物件的 latlng 屬性即為查詢之結果 (是一個 [緯度, 經度] 串列), 例如 : 

>>> import geocoder   
>>> osm=geocoder.osm('澄清湖')      
>>> type(osm)      
<class 'geocoder.osm.OsmQuery'>       
>>> osm.latlng    
[22.660662549999998, 120.35109204721456]     

前者 22.66 為緯度, 後者 120.35 為經度. 

上面的程式碼可以改成如下用 GeoCoder 查詢經緯度的版本 (地標改為台北101) : 

import folium    
import geocoder      
place='台北101'    
latlng=geocoder.osm(place).latlng      
map=folium.Map(location=latlng, zoom_start=16)        
folium.Marker(location=latlng , popup=place, icon=folium.Icon(color="red")).add_to(map)        
map      

結果如下 :




不過很奇怪的是, 我用 members 模組或 dir() 去檢視 OsmQuery 物件的成員, 卻沒有發現 latlng 這個屬性, 非常奇怪 : 

>>> import geocoder 
>>> import members 
>>> osm=geocoder.osm('台北101')
>>> members.list_members(osm)    
add <class 'method'>
append <class 'method'>
clear <class 'method'>
count <class 'method'>
current_result <class 'geocoder.osm.OsmResult'>
debug <class 'method'>
encoding <class 'str'>
error <class 'bool'>
extend <class 'method'>
geojson <class 'dict'>
headers <class 'dict'>
index <class 'method'>
insert <class 'method'>
location <class 'str'>
method <class 'str'>
ok <class 'bool'>
one_result <class 'type'>
params <class 'collections.OrderedDict'>
pop <class 'method'>
provider <class 'str'>
proxies <class 'str'>
rate_limited_get <class 'method'>
remove <class 'method'>
response <class 'requests.models.Response'>
reverse <class 'method'>
session <class 'requests.sessions.Session'>
set_default_result <class 'method'>
status <class 'str'>
status_code <class 'int'>
timeout <class 'float'>
url <class 'str'>

原以為是 members 模組遺漏了, 但用 dir() 去檢視也沒看到 latlng 屬性, 太奇怪了. 

順便檢視了 OsmQuery 物件的幾個屬性如下 :

>>> osm.location   
'台北101'   
>>> osm.current_result   
[台北101, 7, 信義路五段, 西村里, 信義區, 信義商圈, 臺北市, 11049, 臺灣]


5. 利用 Sellenium 將地圖存成 png 圖片 : 

由於 Folium 所繪製的是以 Leafty 為基礎的網頁地圖, 其 API 並未提供將網頁地圖匯出為圖檔的函式或方法, 只能透過螢幕截圖的方式手動處理. 如果要在程式中自動處理存成圖檔的動作, 可以利用 Selenium 的網頁快照 (save_screenshot) 功能, 參考下面這個影片 :





關於 Selenium 用法參考 : 


延續上面澄清湖的地圖物件 map, 首先要將網頁地圖物件輸出存成 htm 檔案, 參考上面檢視 Map 物件的成員, 可知它有一個 save() 方法, 呼叫此方法並傳入檔名 (含路徑) 字串, 例如 "map.htm" 或 "D:\\test\\python\\map.htm" 即可將此地圖存成網頁 :

>>> map   
<folium.folium.Map object at 0x0000020DCA803780>
>>> map.save('map.htm')       

這時在目前目錄底下就會出現一個檔案 map.htm, 用瀏覽器開啟如下 :




檢視原始碼可知此網頁地圖使用了 Leaflet, jQuery, 以及 Bootstrapt 等函式庫 : 




接下來要從 sellenium 匯入 webdriver, 呼叫 Chrome() 建立瀏覽器物件, 呼叫其 get() 方法開啟上面所輸出的網頁後再呼叫 save_screenshot() 擷取網頁快照儲存為 PNG 圖檔 :

>>> from selenium import webdriver      
>>> browser=webdriver.Chrome()    
>>> browser.get('d:\\test\python\\map.htm')   
>>> browser.save_screenshot("map.png")       
True

傳回 True 表示存檔成功, 這時工作目錄下就會出現 map.png 檔了 :



 
參考 : 

2022年8月29日 星期一

Heroku 即將取消免費用戶

今天在上高老師的 Python 課程時聽他說 Heroku 即將取消免費用戶, 查詢發現官網確實有如下宣布, 新計畫將自 2022-11-28 開始施行, 原免費用戶須在那之前升級到收費方案 : 


重點是這一段 :

What products are impacted by the removal of free plans?

free dynos, hobby-dev Heroku Postgres and hobby-dev Heroku Data for Redis plans.

What happens if I take no action on my free apps or databases or do not upgrade to a paid plan?


取消免費的 dynos 與 Postgre 資料庫想架個免費的 Python 後台就沒戲唱啦! 我檢查信箱, 發現 8/26 也收到 Heroku 的來信, 內容如下 : 

Dear Customer,

Thank you for being a Heroku user. Starting November 28, 2022, free Heroku Dynos, free Heroku Postgres, and free Heroku Data for Redis® will no longer be available. You can learn more about these and other important changes from our GM, Bob Wise, on the Heroku blog.

Existing free dynos and Heroku data add-ons will be impacted, so action by you is required. To prevent any disruption to your apps or data using free plans, you will need to upgrade from a free plan to a paid plan before November 28, 2022.

For instructions on how to upgrade and for other questions, please visit our FAQ.

Thank you,

The Heroku Team

厲害國的網站也發布並翻譯了信件內容 : 


目前的免費帳戶可用資源如下 : 




升級為收費會員最便宜的是每個月 7 美元的 hobby 帳戶 :




真是太令人失望了, 我原本還打算年底結束 Hostinger 的租約, 將網站移到 Heroku 呢! 這下得考慮 Pythonanywhere 或自己架網站了. 

2022年8月28日 星期日

2022 年第 35 周記事

本周為了還兩本母校圖書館的書而忙著整理筆記, 雖然是很基礎的 Python 書籍, 但裡面還是有值得記錄的東西, 例如數學與 random 模組, 日期時間模組等都很常用, 但卻不曾有系統地學過並測試, 感覺並不踏實, 所以在還書之前都會好好地 K 過, 熟的部分當複習; 不熟的地方當學習. 

本周請了兩次半天公假, 周二早上去聖功做公司的年度體檢, 自從 5/18 在家上班以來, 每天下班後都去河堤快走 3.3 公里, 幾乎不間斷, 三個月下來鮪魚肚已明顯縮小, 體重下降約 3 公斤, 單槓已可啦六下, 體檢 BMI 也從去年 23 降到 17, 超音波也沒說脂肪肝了, 快走真的有效, 繼續走下去! 

週五下午則去打第四劑, 這次是打 Novavax. 因為整個下午沒事, 中午照計畫去大樂吃懷念已久的吉野家, 他們的牛井飯真是好滋味, 牛肉切得非常薄, 所以醬油與洋蔥的香氣都能完整穿透肉片, 我在全聯買的火鍋肉片比起來太厚, 雖然每次做豬肉井時, 小狐狸們都說好吃, 但如果肉片能像吉野家那樣薄會更好, 有空來找一下肉片機. 

下午滷好蔭瓜苦瓜後上頂樓把太陽能板支架移到目標區, 用油性筆在樓板上打標記, 原本想要鑽孔用地虎固定, 但看到烏雲密布即將下雨, 且矽膠也忘記帶回來, 只好下周再做. 傍晚去機構探望阿蘭, 出來時與社工王先生在門口聊了很久, 原本只是感謝他幫我安排氧氣機驗證一事, 但兩人話甚投機, 一聊就半小時, 遇到的前後兩任社工人都很好, 讓人覺得非常溫暖. 

Python 學習筆記 : 時間模組 time

Python 並沒有提供時間的資料型態 (像 int, float, str, tuple, list 等那樣), 而是在標準函式庫中以 time, datetime, 與 calender 這三個模組來處理日期與時間資訊. 本篇測試並整理 time 模組的用法. 

time 模組主要是依賴底層的 C 函式庫來取得系統的時鐘資訊與處理器的執行時間, 還提供了基本的剖析與字串格式化功能. 在 MicroPython 的物聯網應用中, time 模組的 sleep() 函式常用來控制致動器 (actuator) 的延遲時間.  

本篇主要是我閱讀下列書籍時隨手測試所做的整理 :
  1. Python 零基礎入門班 (碁峰 2018, 文淵閣工作室)
  2. 增壓的 Python-讓程式碼進化到全新境界 (碁峰 2020)
  3. Python Bible 自學聖經 (碁峰 2020, 文淵閣工作室)
首先來檢視 random 模組的公開成員, 以下使用一個自訂模組 members, 其 list_members() 函式會列出模組或套件中的公開成員 (即屬性與方法), 參考 :

# Python 學習筆記 : 檢視物件成員與取得變數名稱字串的方法

使用 time 模組之前須先用 import 匯入 : 

>>> import time   
>>> members.list_members(time)   
altzone <class 'int'>
asctime <class 'builtin_function_or_method'>
clock <class 'builtin_function_or_method'>
ctime <class 'builtin_function_or_method'>
daylight <class 'int'>
get_clock_info <class 'builtin_function_or_method'>
gmtime <class 'builtin_function_or_method'>
localtime <class 'builtin_function_or_method'>
mktime <class 'builtin_function_or_method'>
monotonic <class 'builtin_function_or_method'>
monotonic_ns <class 'builtin_function_or_method'>
perf_counter <class 'builtin_function_or_method'>
perf_counter_ns <class 'builtin_function_or_method'>
process_time <class 'builtin_function_or_method'>
process_time_ns <class 'builtin_function_or_method'>
sleep <class 'builtin_function_or_method'>
strftime <class 'builtin_function_or_method'>
strptime <class 'builtin_function_or_method'>
struct_time <class 'type'>
thread_time <class 'builtin_function_or_method'>
thread_time_ns <class 'builtin_function_or_method'>
time <class 'builtin_function_or_method'>
time_ns <class 'builtin_function_or_method'>
timezone <class 'int'>
tzname <class 'tuple'>

常用函式如下表 : 


 time 模組常用函式 說明
 time() 傳回自初始計時 1970/1/1 至今的系統秒數時戳 (精確至微秒)
 localtime([sec]) 傳回目前時間或指定時戳秒數 sec 的 struct_time 物件
 ctime([sec]) 傳回目前時間或指定時戳秒數 sec 的時間字串
 sleep(sec) 程序暫停執行 sec 秒 (可為浮點數)
 process_time() 傳回程序執行至此之時戳
 mktime(t) 將傳入之 struct_time 物件或元組 t 轉成時戳傳回, 乃 localtime() 反函數
 strftime(format [, t]) 以格式化字串顯示 localtime() 傳回之時戳 t


Python 處理時間的一個 tick 是微秒 (百萬分之一秒), 呼叫 time() 函式所傳回的時戳是一個浮點數, 單位為秒, 此時戳為自初始計時日 1970/1/1 日零時零分零秒開始計算到目前的秒數, 例如 : 

>>> time.time()    
1661485827.710501   

這表示自 1970/1/1 日至目前已過了 1661485827.710501 秒. 這時戳必須經過換算才知道是幾年幾個月幾日幾時幾分幾秒, 看起來似乎實用性不高, 但其實它是一個基礎函式, 主要是用來給 time 模組內其他函式計算之用. 

localtime([sec]) 函式會傳回目前或指定時戳秒數之 struct_time 物件, 例如 : 

>>> local=time.localtime()   
>>> local   
time.struct_time(tm_year=2022, tm_mon=8, tm_mday=26, tm_hour=14, tm_min=51, tm_sec=3, tm_wday=4, tm_yday=238, tm_isdst=0)
>>> type(local)   
<class 'time.struct_time'>   


 struct_time 物件屬性 說明
 tm_year 年
 tm_mon 月 (1~12)
 tm_mday 日 (1~31)
 tm_hour 時 (0~23)
 tm_min 分 (0~59)
 tm_sec 秒 (0~59)
 tm_wday 一星期中的第幾天 (0~6, 0=星期一, 6=星期日)
 tm_yday 一年中的第幾天 (1~366)
 tm_isdst 是否在日光節約時間, 0=否, 1=是


可以透過 struct_time 物件之屬性取得日期時間資訊, 例如 : 

>>> local.tm_year         # 年
2022
>>> local.tm_mon         # 月
8
>>> local.tm_mday       # 日
26
>>> local.tm_hour         # 時
14
>>> local.tm_min          # 分
51
>>> local.tm_sec            # 秒
3
>>> local.tm_wday        # 周
4
>>> local.tm_yday         # 年中之日
238
>>> local.tm_isdst          # 是否在日光節約時間
0

因為 localtime() 的傳入參數是時戳秒數, 故可以將 time.time() 的秒數傳給 localtime() :

>>> time.localtime(time.time())    
time.struct_time(tm_year=2022, tm_mon=8, tm_mday=26, tm_hour=19, tm_min=38, tm_sec=10, tm_wday=4, tm_yday=238, tm_isdst=0)

另一個函式 ctime() 功能與 localtime() 相同, 差別是 ctime() 的傳回值是可讀性較高的時間字串 (但時間精度僅到秒), 預設傳回目前時間, 例如 : 

>>> time.ctime()                        # 未傳入參數預設為目前時間
'Fri Aug 26 19:47:55 2022'

也可以將時戳秒數傳入 ctime() 轉成時間字串, 例如 :

>>> time.ctime(1661514622)       # 傳入時戳秒數
'Fri Aug 26 19:50:22 2022'

sleep() 函式會讓程序暫停指定之秒數進入休眠狀態, 等過了這時間再繼續往下執行, 在互動環境下提示號會在這段時間過後才會出現 :

>>> time.sleep(10)       # 等待 10 秒
>>> 

process_time() 函式會傳回程序執行時的時戳, 前後兩次呼叫 process_time() 之差值即為程序執行所花的時間, 此函式是 Python 3.3 版新增的, 用來取代舊版的 clock(), 在 Python 3.8 版中已正式移除 clock() 了. 

process_time() 與 time() 都可以用來計算程式執行時間, 但 time() 是牆上時鐘 (wall clock), 包含了 sleep() 的時間; 而 process_time() 則是程序時鐘, 不包含 sleep() 秒數, 參考 : 


我把該文之範例改編成如下程式碼 : 

# elapsed_time.py 
import time

process_start=time.process_time()
time_start=time.time()
i=0
for i in range(100000000):
    i += 1
time.sleep(5)    
process_end=time.process_time()
time_end=time.time()
print("process_time() elapsed : %f" % (process_end - process_start))
print("time() elapsed : %f" % (time_end - time_start))

結果如下 : 

>>> %Run elapsed_time.py
process_time() elapsed : 9.609375    
time() elapsed : 14.626676   

可見呼叫 process_time() 不會將 time.sleep() 休眠時間算進去. 如果將 time.sleep() 這列註解掉後重新執行, 結果會差不多 : 

>>> %Run test2.py
process_time() elapsed : 9.203125     
time() elapsed : 9.200775    

接下來看 mktime(t) 函式. 它是 localtime() 的反函式, 其傳入參數有兩種, 一是 time_struct 物件, 例如 local_time() 的傳回值就是此種物件, mktime() 的傳回值為傳入參數所代表的時戳 :

>>> time.mktime(time.localtime())      
1661653676.0

將 localtime() 傳回之 time_struct 物件傳入 mktime() 會得到該物件所代表之時戳. 

第二種傳入參數是一個 9 元素的 tuple (一定要 9 個元素, 少一個都不行, 前六個年月日時分秒決定傳回值, 後面三個元素可隨意填, 不影響傳回之時戳), 其元素依序是 time_struct 物件的屬性值, 例如 :

>>> time.localtime()     
time.struct_time(tm_year=2022, tm_mon=8, tm_mday=28, tm_hour=10, tm_min=28, tm_sec=31, tm_wday=6, tm_yday=240, tm_isdst=0)
>>> time.mktime((2022, 8, 28, 10, 26, 31, 6, 240, 0))       
1661653591.0

strftime(fomat [, t]) 函式用來將 localtime() 等函式傳回之時戳 t 轉成 format 字串所指定之格式, 如果沒有傳入時戳預設為目前時間, 可用之格式化字串 foormat 如下表 :


 format 格式化字串 說明
 %a 星期的縮寫, 例如 Sun, Mon, Tue 等
 %A 星期的全寫, 例如 Sunday, Monday, Tuesday 等
 %b 月份的縮寫, 例如 Jan, Feb, Mar 等
 %B 月份的全寫, 例如 January, Feburary, March 等
 %c 本地之日期時間, 例如 'Sun Aug 28 13:17:18 2022'
 %d 一個月中的第幾日 (1~31)
 %H 24 小時制的時 (0~23), 例如 13 (下午一點)
 %I 12 小時制的時 (0~12), 例如 01 (下午一點或凌晨一點)
 %j 一年中的第幾日 (1~366)
 %m 數字月份 (1~12)
 %M 分鐘 (0~59)
 %p 上午 ('AM') 或下午 ('PM')
 %S 秒 (0~59)
 %U 一年中的第幾周 (0~53), 第一個星期日之前為第0 周 (週日為一周之始)
 %w 一周中的第幾天 (0~6), 週日為 0
 %W 一年中的第幾周 (0~53), 第一個星期一之前為第0 周 (週一為一周之始)
 %x 本地之日期, 格式=月/日/年(兩位), 例如 '08/28/22'
 %X 本地之日期, 格式=時:分:秒, 例如 '14:14:48'
 %y 無世紀之年份 (2 位數), 例如 '22' (表示 2022 年)
 %Y 有世紀之年份 (4 位數), 例如 '2022' (表示 2022 年)
 %z 相對於 GMT 的時區偏移值 (24 小時制), 例如台灣是 '+0800' 表示 GMT+8 
 %Z 時區名稱 (已棄用)
 %% 顯示 '%' 字元


例如 :

>>> time.strftime('%a')   
'Sun'
>>> time.strftime('%A')   
'Sunday'
>>> time.strftime('%b')   
'Aug'
>>> time.strftime('%B')   
'August'
>>> time.strftime('%C')   
'20'
>>> time.strftime('%c')   
'Sun Aug 28 13:17:18 2022'
>>> time.strftime('%d')   
'28'
>>> time.strftime('%H')   
'13'
>>> time.strftime('%I')   
'01'
>>> time.strftime('%j')   
'240'
>>> time.strftime('%m')     
'08'
>>> time.strftime('%M')    
'04'
>>> time.strftime('%p')    
'PM'
>>> time.strftime('%U')    
'35'
>>> time.strftime('%x')     
'08/28/22'
>>> time.strftime('%X')       
'14:14:48'
>>> time.strftime('%y')     
'22'
>>> time.strftime('%Y')    
'2022'
>>> time.strftime('%Z')     
'¥x¥_¼Ð·Ç®É¶¡'
>>> time.strftime('%z')     
'+0800'
>>> time.strftime('%%') 
'%'

注意, 格式 '%Z' 傳回的時區是亂碼, 因為那是 byte 資料, 參考下面這篇文章用 encode('latin1') 可顯示 16 進位編碼, 但接著用 decode('utf-8') 或 decode('utf-8') 卻出現錯誤 :


>>> time.strftime('%Z').encode('latin1')   
b'\xa5x\xa5_\xbc\xd0\xb7\xc7\xae\xc9\xb6\xa1'
>>> time.strftime('%Z').encode('latin1').decode('utf8')     
Traceback (most recent call last):
  File "<pyshell>", line 1, in <module>
UnicodeDecodeError: 'utf-8' codec can't decode byte 0xa5 in position 0: invalid start byte

不過此格式字串已被棄用了. 

參考 : 


好站 : 好用工具箱

最近在測試 Python 的 random 模組中的排列函式時, 偶然找到這個好站 :


裡面分門別類提供了時間, 數學, 文字, 財務等線上工具, 例如股票賣多少不吃虧, 我以前也想過這問題, 打算用 Javascript 寫個程式來算, 但有現成的就不用啦 !




觀察原始碼, 原來這網站是用 Bootstrap 設計的, 有空可以回頭觀摩他的做法.

2022年8月27日 星期六

momo 買書兩本 (量化交易)

上週忙完網頁設計講座後, 終於能回到 Python 量化投資的學習了, 昨天在臉書看到一本介紹 Ta-Lib 的好書 (也是劉承彥寫的), 預購 78 折, 我想 momo 做活動價格更優, 果不其然, 今天上 momo 一查是 72 折, 因要滿 499 才免運, 所以又買了另一本 :





原定價共 1270 元, 打折後總價 901, 抵用 momo 幣 53 元, 實付 848 元, 約 67 折 (爽). 

本來也想買下面這本 Python & Excel 的好書, 但查母校圖書館已進此書, 那就用借的唄 :


QRV 一萬兩千公里保養

早上去 Nissan 大中廠做 12000 km 保養, 還好早去, 因為等待廳整修, 移到後面較窄的空間, 太晚去就沒椅子坐了. 這次檢查主要是發現副水箱是空的, 黃技師說是暖氣管 (熱水排) 漏水所致, 換新須花幾天因為要拆駕駛台, 但我下午要回鄉下啊, 所以先將管子改道回灌避免漏水, 缺點是冬天暖氣調節會失效, 等有時間在預約更換. 另外一個問題是風扇太舊, 下次保養須要更換. 另外還購買機油抵用套餐, 總共費用 9307 元.

QRV 已開了 16 年了 (2007 年換車), 難免機件會老化須換新, 但都在原廠保養車況保持得不錯, 以前孩子小時假日都全家回鄉下, 這台車體寬大坐起來舒服, 當初換這台除了這考量外, 主要是第一次載媽上台北是跟岳父借車, 回來後就打算換台大車全家出遊也方便, 換車後果然載媽與大阿姨上清境農場, 以及上台北幾次, 總之, 車大真便利. 

雖然現在滿載的情況較少了 (去飯店吃大餐就用得到, 哈), 但我不會想換回小車, 應該是一種懷念吧, 每次一個人開車時, 總會想起媽坐在副駕跟我聊親戚, 聊過去的情景, 打算至少要開超過 25 年變老爺車為止. 所以今天上網查 "QRV 安卓機" (黃師傅建議的), 想找時間把音響系統更新. 

打第四劑疫苗 (Novavax)

昨天下午請公假半天去黃昭文診所打第四劑疫苗, 我第三劑是 2/10 打的 (莫德納), 已超過半年多了, 因最近疫情指揮官說 BA5 可能會攀升, 所以就打算預約看看, 上週在高雄市疫苗網站預約時, 剛好週五 8/26 下午黃昭文診所只剩下 16:00~16:30 一個名額, 馬上預約下來. 因為是下午, 我中午下班就可以先回鄉下, 去機構載阿蘭的氧氣機回輔具中心驗證, 然後再返回高雄趕 16:30 打疫苗, 不過前天打給機構社工約會面時間時, 我跟他提到因為要抓時間, 希望 14:00 可以出發去輔具中心, 但社工說已經載去驗證過啦! 原來我上周去機構時提到驗證問題, 但我不好意思麻煩他們沒開口, 結果社工卻已幫我安排載去處理好了, 哇, 真是太感謝這位社工了, 這樣我就不必往返一趟了, 他說其實驗證會花一點時間, 我要回高雄打疫苗可能會很趕, 真的好在他有幫我處理啊! 不然公假請了又沒打到疫苗, 人資那邊要改假單很麻煩. 

今天聽說辦公室同事 (我的搭檔) 確診, 週日要整層清消, 接觸同事也要快篩陰周一才能進辦公室. 上週上面有通知分艙到 9/4, 我原以為下下周就要回去上班, 看來還會再延長啊! 我就繼續在家上班唄, 反正我做資料庫管理遠端連線就能搞定, 今年辦公電腦換新我要爭取改配筆電, 這樣我就是在鄉下也能辦公, 目前用的桌機要搬來搬去著實不方便. 

2022年8月25日 星期四

Python 學習筆記 : 數學模組 math

Python 標準函式庫中的 math 模組提供大部分科學運算所需之常數與函式, 不過它只支援純量運算, 不支援向量運算, 向量運算須使用第三方套件 Numpy. 

本篇測試參考了如下書籍 :
  1. Python 零基礎入門班 (碁峰 2018, 文淵閣工作室)
  2. Python 函式庫語法範例字典 (旗標 2019)
  3. Python 程式設計學習經典 (碁峰 2018)
  4. The Python 3 Standard Library by Examples (Edison Wesley 2017)
math 模組教學文件參考 :


Python 內建數值處理相關函式參考 :


首先來檢視 random 模組的公開成員, 以下使用一個自訂模組 members, 其 list_members() 函式會列出模組或套件中的公開成員 (即屬性與方法), 參考 : 


math 使用前須先用 import 匯入 : 

>>> import math 
>>> import members   
>>> members.list_members(math)    
acos <class 'builtin_function_or_method'>
acosh <class 'builtin_function_or_method'>
asin <class 'builtin_function_or_method'>
asinh <class 'builtin_function_or_method'>
atan <class 'builtin_function_or_method'>
atan2 <class 'builtin_function_or_method'>
atanh <class 'builtin_function_or_method'>
ceil <class 'builtin_function_or_method'>
copysign <class 'builtin_function_or_method'>
cos <class 'builtin_function_or_method'>
cosh <class 'builtin_function_or_method'>
degrees <class 'builtin_function_or_method'>
e <class 'float'>
erf <class 'builtin_function_or_method'>
erfc <class 'builtin_function_or_method'>
exp <class 'builtin_function_or_method'>
expm1 <class 'builtin_function_or_method'>
fabs <class 'builtin_function_or_method'>
factorial <class 'builtin_function_or_method'>
floor <class 'builtin_function_or_method'>
fmod <class 'builtin_function_or_method'>
frexp <class 'builtin_function_or_method'>
fsum <class 'builtin_function_or_method'>
gamma <class 'builtin_function_or_method'>
gcd <class 'builtin_function_or_method'>
hypot <class 'builtin_function_or_method'>
inf <class 'float'>
isclose <class 'builtin_function_or_method'>
isfinite <class 'builtin_function_or_method'>
isinf <class 'builtin_function_or_method'>
isnan <class 'builtin_function_or_method'>
ldexp <class 'builtin_function_or_method'>
lgamma <class 'builtin_function_or_method'>
log <class 'builtin_function_or_method'>
log10 <class 'builtin_function_or_method'>
log1p <class 'builtin_function_or_method'>
log2 <class 'builtin_function_or_method'>
modf <class 'builtin_function_or_method'>
nan <class 'float'>
pi <class 'float'>
pow <class 'builtin_function_or_method'>
radians <class 'builtin_function_or_method'>
remainder <class 'builtin_function_or_method'>
sin <class 'builtin_function_or_method'>
sinh <class 'builtin_function_or_method'>
sqrt <class 'builtin_function_or_method'>
tan <class 'builtin_function_or_method'>
tanh <class 'builtin_function_or_method'>
tau <class 'float'>
trunc <class 'builtin_function_or_method'>

可見 math 模組的成員包含了 5 個數學常數與許多數學函數. 

 
1. 常數 :   

math 模組內建了五個科學計算中最常用的常數 : 

 math 模組的常數 說明
 e 自然指數=2.718281828459045
 pi 圓周率=3.141592653589793
 tau 圓常數 (圓周與半徑之比, 即 2*pi)=6.283185307179586
 inf 無限大=inf (很大的浮點數)
 nan 非數值=nan (不是數值的一個 float 型態值)

例如 : 

>>> print("math.pi=", math.pi)     
math.pi= 3.141592653589793
>>> print("math.e=", math.pi)        
math.e= 3.141592653589793
>>> print("math.tau=", math.pi)    
math.tau= 3.141592653589793
>>> print("math.nan=", math.nan)    # 非數值
math.nan= nan
>>> print("math.inf=", math.inf)        # 無窮大
math.inf= inf  
>>> math.tau    
6.283185307179586    
>>> math.pi * 2    
6.283185307179586

注意, math.nan 與 math.inf 的資料型態均為 float : 

>>> print("type(math.nan)=", type(math.nan))   
type(math.nan)= <class 'float'>
>>> print("type(math.inf)=", type(math.inf))    
type(math.inf)= <class 'float'>

可以將字串 'nan' 與 'inf' 傳給 float() 函式以產生 nan 與 inf, 例如 :

>>> float('nan')    
nan
>>> float('inf')   
inf

可以用 math.isnan() 函式來檢驗是否為 nan, 例如 :

>>> math.isnan(math.nan)    
True
>>> math.isnan(float('nan'))      
True
>>> math.isinf(math.inf)    
True
>>> math.isinf(float('inf'))   
True

如果要用到負無窮大, 就直接加個負號即可, 即 -math.inf. 


2. 函式 :   

math 模組提供的函式如下表 : 

 math 模組的函式 說明
 fabs(x) 傳回 x 的絕對值
 fsum(x) 傳回 x (可迭代物件, 串列或元組, 集合) 的元素總和
 remainder(x, y) 傳回 x/y 的餘數=x-n*y, n 為最接近商之整數 (Python 3.7 版新增)
 fmod(x, y) 傳回 x/y 的模數, 計算整數模式使用 %, 浮點數模數則用 fmod()
 modf(x) 傳回 x 小數與整數部分組成的 tuple 
 sqrt(x) 傳回 x 的平方根 (x 須為正數)
 pow(x, y) 傳回 x 的 y 次方
 floor(x) 傳回小於或等於 x 的最大整數 (進位函式)
 ceil(x) 傳回大於或等於 x 的最小整數 (捨位函式)
 trunc(x) 傳回 x 的整數部分 (無條件捨棄小數)
 gcd(a, b, c, d, ....) 傳回 a, b, c, d, ... 的最大公因數 
 lcm(a, b, c, d, ....) 傳回 a, b, c, d, ... 的最小公倍數 (Python 3.9 版新增)
 comb(n, k) 傳回從 n 個元素中取出 k 個的組合數=n!/(n-k)!, 3.8 版後
 perm(n, k) 傳回從 n 個元素中取出 k 個的排列數=n!/(k!(n-k)!), 3.8 版後
 prod(x) 傳回 x (串列, 元組, 集合) 所有元素之乘積 (Python 3.8 版新增)
 exp(x) 傳回 x 的自然指數
 expm1(x) 傳回 x 的自然指數減 1 (Python 3.2 新增)
 log(x) 傳回 x 的自然對數 (以 e 為底)
 log2(x) 傳回 x 的以 2 為底的對數 (Python 3.3 新增)
 log10(x) 傳回 x 的自然對數 (以 10 為底)
 log1p(x) 傳回 x + 1 的自然對數 (以 e 為底)
 factorial(x) 傳回 x 的階乘 x!
 sin(x) 傳回 x 的正弦值
 cos(x) 傳回 x 的餘弦值
 tan(x) 傳回 x 的正切值
 asin(x) 傳回 x 的反正弦值
 acos(x) 傳回 x 的反餘弦值
 atan(x) 傳回 x 的反正切值
 atan2(y,x) 傳回 y/x 的反正切值
 radians(x) 傳回角度 x 之弧度 (角度轉弧度)
 degrees(x) 傳回弧度 x 之角度 (弧度轉角度)
 sinh(x) 傳回 x 的雙曲正弦值
 cosh(x) 傳回 x 的雙曲餘弦值
 tanh(x) 傳回 x 的雙曲正切值
 isinf(x) 判斷 x 是否為 inf (傳回 True/False)
 isnan(x) 判斷 x 是否為 NaN (傳回 True/False)

fabs() 會傳回絕對值 (正數), 例如 : 

>>> math.fabs(-1.234)       # 絕對值
1.234 

內建函式 sum() 可以傳入數列 (多參數), 串列, 或元組, 但 fsum() 不能傳入數列, 因為它只能傳入一個可迭代的容器型態參數, 例如串列, 元組, 或集合等, fsum() 會傳回可迭代物件所有元素之和, 例如 : 

>>> math.fsum([1, 2, 3])    # 計算串列元素和     
6.0
>>> math.fsum((1, 2, 3))    # 計算元組元素和
6.0
>>> math.fsum({1, 2, 3})    # 計算集合元素和
6.0 

remainder(x, y) 函式顧名思義是傳回 x/y 之餘數, 但其計算方式為 x-n*y, 其中 n 是最接近 x/y 商之整數, 例如 : 

>>> math.remainder(13, 3)       # 13/3 餘數是 1=13-4*3
1.0
>>> math.remainder(23.5, 5)    # 傳回 -1,5=23.5-5*5
-1.5

此處餘數並非 0, 因 23.5/5 商為 4.7, 最接近之整數為 5, 故傳回 23.5-5*5=-1.5. 

fmod(x, y) 會傳回 x/y 之模數, 計算整數模數會用 % 運算子; 計算浮點數模數則用 fmod() :

>>> 23 % 5   
3
>>> math.fmod(23.5, 5)   
3.5 
>>> math.fmod(5, -2)    
1.0
>>> 5 % -2   
-1

modf(x) 則會把浮點數的小數與整數部分拆開, 組成 (小數, 整數) 之元組傳回 (帶符號) :

>>> math.modf(3.14159)   
(0.14158999999999988, 3.0)
>>> math.modf(-3.14159)    
(-0.14158999999999988, -3.0)  
>>> math.modf(-3.14159e2)     
(-0.15899999999999181, -314.0) 

開平方函式 sqrt() 與次方函式 pow() 都屬於冪函數, sqrt(x) 其實等於 pow(x, 0.5) :

>>> math.sqrt(2)               # 開平方
1.4142135623730951
>>> math.pow(2, 4)           # 2 的 4 次方
16.0
>>> math.pow(2, 0.5)        # 開平方就是 0.5 次方
1.4142135623730951
>>> math.pow(8, 1/3)        # 開立方就是 1/3 次方
2.0

floor(x), ceil(x), 與 trunc(x) 是近似函式 (math 模組沒有四捨五入或五捨六入函式), 它們會傳回浮點數的近似值, floor(x) 傳回 x 的地板整數; ceil(x) 傳回 x 的天花板整數; 而 trunc() 則是將小數部分完全捨去只傳回整數部分, 例如 : 

>>> math.floor(3.14)          # 小於或等於 3.14 的最大整數=3
3
>>> math.floor(-3.14)         # 小於或等於 -3.14 的最大整數=-4
-4
>>> math.ceil(3.14)             # 大於或等於 3.14 的最小整數=3
4
>>> math.ceil(-3.14)            # 大於或等於 -3.14 的最小整數=-3
-3
>>> math.trunc(3.14)          # 捨去小數部分
3
>>> math.trunc(-3.14)         # 捨去小數部分
-3

gcd() 會傳回傳入參數數列 (正整數) 的最大公因數, 而 lcm() 則會傳回其最小公倍數 (Python 3.9 新增功能) :

>>> math.gcd(6, 21)            # 最大公因數
3
>>> math.lcm(12, 8, 3, 4)    # 最小公倍數
24   

perm(n, k) 是從 n 個元素中取出 k 個不重複的排列方式, 等於 n!/(n-k)!, 此函式須在 Python 3.8 以上版本才有支援, 例如 : 

>>> math.perm(6, 2)     
30  
>>> math.factorial(6)/(math.factorial(6-2))     
30.0

exp(x) 會傳回 x 的自然指數值; log(x) 則會傳回 x 以 e 為底的自然對數值, 兩者互為反函數 :

>>> math.exp(1)                  # 自然指數
2.718281828459045   
>>> math.log(1)                   # 自然對數
0.0
>>> math.log(math.e)         # ln(e)=1
1.0

在 Python 3.2 版新增了 expm1(x) 函式, 其值為 exp(x)-1, 對於曉得浮點數 x 而言, exp(x)-1 可能會有誤差, expm1(x) 則能保持同樣運算的浮點數精度, 例如 :

>>> math.exp(1e-5)-1   
1.0000050000069649e-05
>>> math.expm1(1e-5)      
1.0000050000166667e-05

當 x 很小時, 用 exp(x)-1 來運算的話, 小數部分精確度會不足, 應改用 expm1(). 

任何基底的 1 的對數結果都是 0, 當 0 < x < 1 時對數都是負值, 越接近 0 越負, 0 的對數是負無限大, 故為了讓很小的數取對數仍是正值, 會將其加 1 後再取對數, 即 log1p() 之用途, 例如 : 

>>> math.log(1e-5)  
-11.512925464970229
>>> math.log(1e-10)   
-23.025850929940457
>>> math.log(1e-100)    
-230.25850929940458
>>> math.log1p(1e-5)    
9.99995000033333e-06
>>> math.log1p(1e-10)   
9.999999999500001e-11
>>> math.log1p(1e-100)   
1e-100

可見當 x 越小時, log1p() 的值越接近 x. Python 3.3 版新增了以 2 為底的 log2(x) : 

>>> math.log2(2)   
1.0
>>> math.log2(4)   
2.0
>>> math.log2(64)   
6.0

常用對數則是以 10 為底, 例如 : 

>>> math.log10(10)            # 常用對數
1.0
>>> math.log10(100)           # 常用對數
2.0

factorial(x) 會傳回 x 階乘 :

>>> math.factorial(3)         # 3!=3*2*1=6
6

sin(x), cos(x), tan(x) 為三角函數, 其中 tan(x)=sin(x)/cos(x), 數學上的另外三個三角函數 csc(), sec(), 與 cot(x) 分別為其倒數, 故 math 並未提供. 注意, 傳入參數均為弧度, 角度須先用 radians() 轉成弧度, 例如 :

>>> math.radians(30)         # 30 度=math.pi/6
0.5235987755982988
>>> math.radians(45)         # 45 度=math.pi/4
0.7853981633974483
>>> math.radians(60)         # 60 度=math.pi/3
1.0471975511965976
>>> math.radians(90)         # 90 度=math.pi/2
1.5707963267948966
>>> math.radians(180)       # 180 度=math.pi
3.141592653589793
>>> math.degrees(math.pi/6)      # math.pi/6=30 度
29.999999999999996
>>> math.degrees(math.pi/4)      # math.pi/4=45 度
45.0
>>> math.degrees(math.pi/3)      # math.pi/3=60 度
59.99999999999999
>>> math.degrees(math.pi/2)      # math.pi/2=90 度
90.0
>>> math.degrees(math.pi)         # 180 度=math.pi/2
180.0
>>> math.sin(math.radians(30))       # sin(30)=0.5
0.49999999999999994
>>> math.sin(math.radians(45))       # sin(45)=math.sqrt(2)/2
0.7071067811865476
>>> math.sqrt(2)/2     
0.7071067811865476
>>> math.sin(math.radians(60))       # sin(60)=math.sqrt(3)/2
0.8660254037844386
>>> math.sqrt(3)/2     
0.8660254037844386
>>> math.sin(math.radians(90))       # sin(90)=1
1.0
>>> math.cos(math.radians(30))       # cos(30)=math.sqrt(3)/2
0.8660254037844387
>>> math.cos(math.radians(45))       # cos(45)=math.sqrt(2)/2
0.7071067811865476
>>> math.cos(math.radians(60))      # cos(30)=0.5
0.5000000000000001
>>> math.cos(math.radians(90))      # cos(90)=0
6.123233995736766e-17

可見 cos(90) 趨近於 0, 使得 tan(90) 趨近於無限大, 例如 : 

>>> math.tan(math.radians(30))         
0.5773502691896257
>>> math.sin(math.radians(30))/math.cos(math.radians(30))    
0.5773502691896256
>>> math.tan(math.radians(45))    
0.9999999999999999
>>> math.sin(math.radians(45))/math.cos(math.radians(45))    
1.0
>>> math.tan(math.radians(60))    
1.7320508075688767
>>> math.sin(math.radians(60))/math.cos(math.radians(60))    
1.7320508075688767
>>> math.tan(math.radians(90))    
1.633123935319537e+16
>>> math.sin(math.radians(90))/math.cos(math.radians(90))      
1.633123935319537e+16

asin(x), acos(x), 與 atan(x) 分別為 sin(x), cos(x), 與 tan(x) 的反函數, 其傳回值同樣是弧度, 須利用 degrees() 轉成角度, 例如 :

>>> math.asin(0.5)      
0.5235987755982989                          # 這是弧度
>>> math.degrees(math.asin(0.5))   
30.000000000000004
>>> math.degrees(math.asin(0.7071))   
44.99945053347443
>>> math.degrees(math.asin(0.866))   
59.997089068811974
>>> math.degrees(math.asin(1))   
90.0
>>> math.degrees(math.acos(0.866))   
30.002910931188026
>>> math.degrees(math.acos(0.7071))        
45.00054946652557
>>> math.degrees(math.acos(0.5))    
60.00000000000001
>>> math.degrees(math.acos(0))      
90.0
>>> math.degrees(math.atan(0.5/0.866))   
30.000727780827372
>>> math.degrees(math.atan(0.701/0.701))   
45.0
>>> math.degrees(math.atan(0.866/0.5))   
59.999272219172624
>>> math.degrees(math.atan(math.inf))       # 1/0 為無限大
90.0
>>> math.degrees(math.atan2(0.5, 0.866))        # 第一參數 sin 值, 第二參數 cos 值
30.000727780827372
>>> math.degrees(math.atan2(0.701, 0.701))   
45.0
>>> math.degrees(math.atan2(0.866, 0.5))   
59.999272219172624
>>> math.degrees(math.atan2(1, 0))   
90.0

math 模組提供的雙曲函式有三個 : sinh(x), cosh(h), 與 tanh(x), 與三角函數類似, 其餘的三個雙曲函數 csch, cosh, 以及 coth 分別是其倒數, 即 csch=1/sinh, sech=cosh, coth=1/tanh. 例如 :

>>> math.sinh(0)    
0.0
>>> math.sinh(1)   
1.1752011936438014
>>> math.sinh(-1)       
-1.1752011936438014
>>> math.cosh(0)   
1.0
>>> math.cosh(1)   
1.5430806348152437
>>> math.cosh(-1)   
1.5430806348152437