HTML5 知識一覽,10分鐘搞定它

HTML5知識點匯總

HTML5 中的一些有趣的新特性

用於繪畫的 canvas 元素
用於媒介回放的 video 和 audio 元素
對本地離線存儲的更好的支持
新的特殊內容元素,比如 article、footer、header、nav、section
新的表單控件,比如 calendar(日曆)、date(日期)、time(時間格式)、email(郵件)、url(鏈接)、search(搜尋),可以方便用戶填寫或者方便格式驗證。

目錄:

  • 新增內容元素解釋及表單控件舉例
  • 視頻
  • 音頻
  • 拖放
  • 畫布
  • SVG
  • 地理定位
  • Web存儲
  • 應用緩存
  • Web Workers
  • 服務器發送事件

PS以下內容只是基於個人對HTML5的大致理解,做個筆記,若有錯誤,請不要吝嗇指出哈,詳情可以查看 https://www.w3school.com.cn/html5/index.asp

表單控件舉例

1、date

<input type="date" >

效果圖:

HTML5視頻

直到現在,仍然不存在一項旨在網頁上显示視頻的標準。
今天,大多數視頻是通過插件(比如Flash)來显示的。然而,並非所有瀏覽器都擁有同樣的插件。
HTML5 規定了一種通過 video 元素來包含視頻的標準方法。
#最簡單的用法

  <video src="movie.ogg" controls="controls">不支持H5時显示的內容</video>   //controls="controls"是自帶簡單的控制組件
#兼容瀏覽器,運用<source>標籤,嵌入多個視頻格式鏈接,瀏覽器只識別第一個可識別的格式

<video width="320" height="240" controls="controls">
<source src="movie.ogg" type="video/ogg">
<source src="movie.mp4" type="video/mp4">
Your browser does not support the video tag.
</video>

HTML5音頻

直到現在,仍然不存在一項旨在網頁上播放音頻的標準。
今天,大多數音頻是通過插件(比如Flash)來播放的。然而,並非所有瀏覽器都擁有同樣的插件。
HTML5 規定了一種通過 audio 元素來包含音頻的標準方法。
audio 元素能夠播放聲音文件或者音頻流。

<audio src="song.ogg" controls="controls">網頁不支持H5時显示的內容</audio>
#兼容瀏覽器,瀏覽器只識別第一個可播放的音頻文件格式

<audio controls="controls">
<source src="song.ogg" type="audio/ogg">
<source src="song.mp3" type="audio/mpeg">
Your browser does not support the audio tag.
</audio>

先明確一個點:網頁中默認元素是不可拖動的,也是不可把其他元素放在另一個元素上面的,以下ev為DOM傳遞的參數event,以下為拖動步驟:
1、拖放的元素
(1)設置元素可拉動—— draggable=”true”,如

<img draggable="true" />

(2)元素被拖動時保存數據——ondragstart=”ev.dataTransfer.setData(“變量名”,ev.target.id)”
2、放的地方
(1)其他元素懸空至本元素時,設置本元素可放置其他元素,這樣才可以將其放進去——ev.preventDefault();

##設置要被放置元素的標籤的事件ondragover="allowDrop(event)"

function allowDrop(ev)
{
ev.preventDefault();
}

(2)放下被拖動的元素時,再次設置本元素可放置其他元素,並獲取元素,將元素添加為本元素的子元素(本質)

<!- 相對應的放置其他元素的容器設置,ondrop為放下時,ondragover是有其他元素懸空至本元素時->
<div id="div1" ondrop="drop(event)"
ondragover="allowDrop(event)"></div>

function drop(ev)
{
ev.preventDefault();
var data=ev.dataTransfer.getData("Text");
ev.target.appendChild(document.getElementById(data));
}

畫布

介紹:畫布就是利用javascript在網頁上繪製圖像的一種元素,畫布是一塊舉行區域,我們可以控制它的每一個像素
使用(三步走):
1、創建Canvas元素:eg:

<canvas id="myCanvas" width="200" height="100"></canvas>

2、獲取canvas元素,創建context對象:eg:

var c=document.getElementById("myCanvas");

3、繪製,eg:

cxt.fillStyle="#FF0000";
cxt.fillRect(0,0,150,75);

SVG

定義

  • SVG 指可伸縮矢量圖形 (Scalable Vector Graphics)
  • SVG 用於定義用於網絡的基於矢量的圖形
  • SVG 使用 XML 格式定義圖形
  • SVG 圖像在放大或改變尺寸的情況下其圖形質量不會有損失
  • SVG 是萬維網聯盟的標準
    總結:內容為純粹的XML格式,可無線放大拉伸像素不變,尺寸比jpeg、gif等小,圖像中有文本還可以被搜索到(適合做地圖等)。
    競爭對手:Flash,SVG優勢:與其他標準(比如 XSL 和 DOM)相兼容,而 Flash 則是未開源的私有技術

使用方法:

(1)使用標籤引入

  • 優勢:所有主要瀏覽器都支持,並允許使用腳本

  • 缺點:不推薦在HTML4和XHTML中使用(但在HTML5允許)

    <embed src="circle1.svg" type="image/svg+xml" /> 
    

(2)使用標籤引入

  • 優勢:所有主要瀏覽器都支持,並支持HTML4,XHTML和HTML5標準

  • 缺點:不允許使用腳本。

    <object data="circle1.svg" type="image/svg+xml"></object> 
    

(3)使用 iframe 標籤引入

  • 優勢:所有主要瀏覽器都支持,並允許使用腳本

  • 缺點:不推薦在HTML4和XHTML中使用(但在HTML5允許)

    //<iframe src="circle1.svg"></iframe> 
    

(4)直接在HTML嵌入SVG代碼
在Firefox、Internet Explorer9、谷歌Chrome和Safari中,你可以直接在HTML嵌入SVG代碼。

  <svg xmlns="http://www.w3.org/2000/svg" version="1.1">
     <circle cx="100" cy="50" r="40" stroke="black" stroke-width="2" fill="red" />
  </svg>   

(5)直接使用鏈接嵌入

  <a href="circle1.svg">View SVG file</a> 

地理定位

HTML5 Geolocation API 用於獲得用戶的地理位置。

用法:

主要使用navigator.geolocation對象來獲取地理位置信息,它主要有getCurrentPosition(),watchPosition(),clearWatch()三個方法
(1)getCurrentPosition

(2)watchPosition
返回用戶的當前位置,並繼續返回用戶移動時的更新位置(就像汽車上的 GPS)。
(3)clearWatch(),停止 watchPosition() 方法

例子:

  //例子關鍵代碼:返回用戶位置的經度和緯度
   function getLocation(){
        if (navigator.geolocation){
            navigator.geolocation.getCurrentPosition(showPosition);
      }}

效果:

Web 存儲

瀏覽器中的數據本來是由 cookie 完成的。但是 cookie不適合大量數據的存儲,因為它們由每個對服務器的請求來傳遞,這使得 cookie 速度很慢而且效率也不高。
在 HTML5 中,數據不是由每個服務器請求傳遞的,而是只有在請求時使用數據。它使在不影響網站性能的情況下存儲大量數據成為可能。
對於不同的網站,數據存儲於不同的區域,並且一個網站只能訪問其自身的數據
HTML5 使用 JavaScript 來存儲和訪問數據。

介紹

1、localStorage 方法存儲的數據沒有時間限制。第二天、第二周或下一年之後,數據依然可用。
2、sessionStorage方法用於本地存儲一個會話(session)中的數據,這些數據只有在同一個會話中的頁面才能訪問並且當會話結束后數據也隨之銷毀。因此sessionStorage不是一種持久化的本地存儲,僅、是會話級別的存儲。只允許同一窗口訪問。 (同一個頁面才可以訪問存儲的元素,刷新也可以繼續訪問,但是關閉頁面重新打開,數據就不見了)

使用

1、先判斷(瀏覽器是否支持)——if(typeof(Storage)!==”undefine”){若支持執行的代碼段}else{不支持執行代碼段}
2、localStorage的增刪改查
增:localStorage.setItem(“propName”,”value”)
刪:localStorage.removeItem(‘propName’)
改:localStotage.propNmae=newValue
查:localStorage.getItem(propName)
3、sessionStorage的增刪改查
增:sessionStorage.setItem(“propName”,”value”)
刪:sessionStorage.removeItem(‘propName’)
改:sessionStotage.propNmae=newValue
查:sessionStorage.getItem(propName)

例子(localstorage,session同下):

<body>
<div id="result"></div>
<script>
// Check browser support
if (typeof(Storage) !== "undefined") {
// Store
    localStorage.setItem("lastname", "Gates");
// Retrieve
    document.getElementById("result").innerHTML = localStorage.getItem("lastname");
} else {
    document.getElementById("result").innerHTML = "抱歉!您的瀏覽器不支持 Web Storage ...";
}
</script>
</body>

sessionStorage 、localStorage和cookei的區別:

應用場景

因為考慮到每個 HTTP 請求都會帶着 Cookie 的信息,所以 Cookie 當然是能精簡就精簡啦,比較常用的一個應用場景就是判斷用戶是否登錄。針對登錄過的用戶,服務器端會在他登錄時往 Cookie 中插入一段加密過的唯一辨識單一用戶的辨識碼,下次只要讀取這個值就可以判斷當前用戶是否登錄啦。曾經還使用 Cookie 來保存用戶在電商網站的購物車信息,如今有了 localStorage,似乎在這個方面也可以給 Cookie 放個假了~
而另一方面 localStorage 接替了 Cookie 管理購物車的工作,同時也能勝任其他一些工作。比如HTML5遊戲通常會產生一些本地數據,localStorage 也是非常適用的。如果遇到一些內容特別多的表單,為了優化用戶體驗,我們可能要把表單頁面拆分成多個子頁面,然後按步驟引導用戶填寫。這時候 sessionStorage 的作用就發揮出來了。

應用緩存——Application Cache

HTML5 引入了應用程序緩存,這意味着 web 應用可進行緩存,並可在沒有因特網連接時進行訪問。
應用程序緩存為應用帶來三個優勢:
離線瀏覽 – 用戶可在應用離線時使用它們
速度 – 已緩存資源加載得更快
減少服務器負載 – 瀏覽器將只從服務器下載更新過或更改過的資源。

使用:

1、manifest文件配置:
manifest 文件可分為三個部分:

CACHE MANIFEST(必須)- 在此標題下列出的文件將在首次下載後進行緩存
NETWORK – 在此標題下列出的文件需要與服務器的連接,且不會被緩存
FALLBACK – 在此標題下列出的文件規定當頁面無法訪問時的回退頁面(比如 404 頁面)

例子:
CACHE MANIFEST

# 2012-02-21 v1.0.0    //註釋行,在服務器中更改一個函數或者一幅圖片,此文件都不會被修改,但更新此備註卻可以更新緩存文件的方法
/theme.css
/logo.gif
/main.js
NETWORK:
login.asp
FALLBACK:
/html5/ /404.html
PS:瀏覽器對緩存數據的容量限制可能不太一樣(某些瀏覽器設置的限制是每個站點 5MB)
##以下為html文件中的使用直接在html標籤中將其引入
<html manifest="demo.appcache">  //直接在html文件中引入appcache文件

web Workers

介紹:運行在後台的一個js腳本,獨立於其他腳本,不影響頁面性能

使用:

0、創建一個需要用於後台運行的js文本文件——需要一個postMessage(data) //向某個頁面傳達信息
1、先判斷瀏覽器是否支持:if(typeof(Worker)!==”undefined”)
2、創建new Worker(‘js文本路徑’)
3、使用onmessage監聽是否有消息傳來,所有則接收js傳來的信息,並加以處理運用
4、終止Web Worker 終止 web worker,並釋放瀏覽器/計算機資源,請使用 terminate() 方法 //終止后需刷新頁面才可重新使用

例子:

創建計數腳本,該腳本存儲於 “demo_workers.js” 文件中:

var i=0;
function timedCount()
{
i=i+1;
postMessage(i);
setTimeout("timedCount()",500);
}
timedCount();

以下是html文件:

<!DOCTYPE html>
<html>
<body>

<p>Count numbers: <output id="result"></output></p>
<button onclick="startWorker()">Start Worker</button>
<button onclick="stopWorker()">Stop Worker</button>
<br /><br />

<script>
var w;

function startWorker()
{
if(typeof(Worker)!=="undefined")
{
  if(typeof(w)=="undefined")
    {
    w=new Worker("demo_workers.js");
    }
  w.onmessage = function (event) {
    document.getElementById("result").innerHTML=event.data;
  };
}
else
{
document.getElementById("result").innerHTML="Sorry, your browser
 does not support Web Workers...";
}
}

function stopWorker()
{
w.terminate();
}
</script>

</body>
</html>

效果圖:

補充:

onOpen :當通往服務器的連接被打開
onMessage:當接收到消息
onerror :當錯誤發生

注意:

1、web worker 通常不用於如此簡單的腳本,而是用於更耗費 CPU 資源的任務。
2、由於 web worker 位於外部文件中,它們無法訪問下例 JavaScript 對象:
window 對象
document 對象
parent 對象

服務器發送事件:

介紹:Server-Sent 事件 – 單向消息傳遞

Server-Sent 事件指的是網頁自動獲取來自服務器的更新。
以前也可能做到這一點,前提是網頁不得不詢問是否有可用的更新。通過服務器發送事件,更新能夠自動到達。
例子:Facebook/Twitter 更新、估價更新、新的博文、賽事結果等。

使用: 判、創、監聽、使用。

html文件:

if(typeof(EventSource)!=="undefined"){           //判
var source=new EventSource("demo_sse.php");       //創
source.onmessage=function(event)                  //監聽
  {
  document.getElementById("result").innerHTML+=event.data + "<br />";  //使用
  };
  else
    {
         document.getElementById("result").innerHTML="抱歉,您的瀏覽器不支持 server-sent 事件 ...";
     }

demo_sse.php文件

<?php
header('Content-Type: text/event-stream');   //把報頭 "Content-Type" 設置為 "text/event-stream"
header('Cache-Control: no-cache');           //規定不對頁面進行緩存
$time = date('r');                         
echo "data: The server time is: {$time}\n\n";  //輸出發送日期(始終以 "data: " 開頭) 
flush();                                       ////向網頁刷新輸出數據
?>

posted on
2020-06-20 11:34  哎呀呀大池  閱讀(
…)  評論(
…)  編輯  收藏
刷新評論 刷新頁面 返回頂部

Powered by: 博客園 Copyright © 2020 哎呀呀大池


Powered by .NET Core on Kubernetes

本站聲明:網站內容來源於博客園,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

※廣告預算用在刀口上,台北網頁設計公司幫您達到更多曝光效益

※別再煩惱如何寫文案,掌握八大原則!

※教你寫出一流的銷售文案?

※超省錢租車方案

※FB行銷專家,教你從零開始的技巧

004.OpenShift命令及故障排查

一 CLI訪問OpenShift資源

1.1 資源操作


OCP將OpenShift集群中的為由主節點管理的對象統稱為資源,如:node、service、pod、project、deployment、user。

即使針對的是不同的資源,OpenShift命令行工具也提供了一種統一的、一致的方法來更新、修改、刪除和查詢這些資源。

oc命令行工具提供了在軟件開發項目的整個交付生命周期中修改和管理資源的常見操作。

1.2 安裝oc工具


在OpenShift安裝過程中,oc命令行工具安裝在所有master和node節點上,還可以在不屬於OpenShift集群的機器。

安裝后,可以使用用戶名和密碼對任何主節點通過身份驗證后執行相關命令。

根據使用的平台,安裝oc命令行工具有以下幾種方式:

yum安裝:在RHEL平台上,可通過以下命令安裝oc客戶端命令。

[user@host ~]$ sudo yum install atomic-openshift-clients

其它 Linux 發行版本和操作系統,需在擁有 OpenShift 訂閱后,在 Red Hat Customer Portal 中下載。

提示:oc安裝完成后自動補全需要退出一次才可生效,或者source /etc/bash_completion.d/oc。

1.3 oc主要查詢命令


[student@workstation ~]$ oc –help #显示幫助信息

[student@workstation ~]$ oc login -u developer -p redhat https://master.lab.example.com #登錄到OpenShift集群

提示:從client成功通過身份驗證之後,OpenShift將授權令牌保存在用戶的主文件夾中。此令牌用於後續請求,從而無需重新輸入憑據或完整的主URL。

  1 [root@master ~]# oc whoami
  2 system:admin					#master的root用戶為集群的最高權限的用戶
  3 [student@workstation ~]$ oc whoami		        #查看當前用戶
  4 developer
  5 [student@workstation ~]$ oc new-project working	#創建project
  6 [student@workstation ~]$ oc status		        #查看項目狀態
  7 In project working on server https://master.lab.example.com:443
  8 You have no services, deployment configs, or build configs.
  9 Run 'oc new-app' to create an application.
 10 [student@workstation ~]$ oc delete project working	#刪除project
 11 [student@workstation ~]$ oc logout		        #退出該集群。
 12 [student@workstation ~]$ oc get pods		#查看pod
 13 NAME                      READY     STATUS    RESTARTS   AGE
 14 hello-openshift-1-6ls8z   1/1       Running   0          4h
 15 [student@workstation ~]$ oc get all		        #查看所有主要組件信息
 16 [student@workstation ~]$ oc get pods -w		#-w表示以監視模式運行


1.4 oc 其他命令


oc describe:如果oc get提供的摘要不夠,可以使用oc describe命令檢索關於資源的更詳細信息。

[student@workstation ~]$ oc describe pod hello-openshift-1-6ls8z

oc export:使用oc export命令導出資源的定義。典型的用例包括創建備份,或者用於修改定義。默認情況下,export命令以YAML格式輸出對象表示,但是可以通過提供-o選項來更改。

oc create:使用oc create命令從資源定義創建資源。通常,這與用於編輯定義的oc export命令相匹配。

oc delete RESOURCE_TYPE name:使用oc delete命令從OpenShift集群中刪除資源。

注意:部分資源直接刪除後會重新創建,如基於rc的pod,需要對OpenShift體系資源展示形式有一個基本的了解。

oc exec:使用oc exec命令在容器中執行命令,可以使用此命令作為腳本的一部分運行交互式和非交互式批處理命令。

oc rsh POD:oc rsh pod命令打開到容器的遠程shell會話,要遠程登錄到容器shell並執行命令,請運行以下命令。

[student@workstation ~]$ oc rsh <pod>

注意:oc rsh需要pod中存在相應的shell,如bash。

二 OpenShift資源類型

2.1 常見資源


OpenShift容器平台中的應用程序由不同類型的資源組成,主要常見的類型有:

  • Container:如何在可移植Linux環境中運行一個或多個進程的定義。容器從一個映像啟動,並且通常與同一機器上的其他容器隔離。
  • Image:一個分層的Linux文件系統,包含應用程序代碼、依賴關係和函數庫等。image由一個名稱標識,該名稱可以是當前集群的本地名稱,也可以指向遠程Docker倉庫。
  • Pod:部署在節點上並共享唯一IP地址和卷(持久存儲)的一個或多個容器,Pods還為每個容器定義安全性和運行時策略。
  • Label:標籤是鍵值對,可以分配給系統中的任何資源進行分組和選擇。通常資源使用標籤來標識其他資源集。
  • Volume:默認情況下容器不是持久性的,即容器的內容在重新啟動時被清除。volume是掛載在pod及其容器上的文件系統,它們可能由許多本地或網絡的存儲提供。最簡單的卷類型是EmptyDir,它是一台機器上的臨時目錄。
  • Node:node是集群中用來運行容器的節點,node通常由管理員管理,而不是由最終用戶管理。
  • Service:service是表示一組pod的邏輯名稱,service被分配一個IP地址和一個DNS名稱,可以通過端口或route向集群外部公開。名為SERVICE_HOST的環境變量會自動注入到其他pod中。
  • Route:route是一個DNS條目,創建它是為了指向一個service,以便可以從集群外部訪問它。可以配置一個或多個路由器來處理這些route,通常通過HAProxy負載均衡器。
  • Replication Controller:Replication Controller基於匹配一組label的Templates維護特定數量的pod。如果刪除了pod,控制器將創建該pod的新副本。Replication Controller最常用來表示基於image的應用程序部分的單個部署。
  • Deployment Configuration:deployment configuration定義pod的模板,並在屬性更改時管理部署新映像或配置更改。單個deployment configuration通常類似於單個微服務。deployment configuration可以支持許多不同的部署模式,包括完全重啟、可定製的滾動更新以及生命周期前後的順序。每個deployment都表示為一個replication controller。
  • Build Configuration:build configuration包含如何將源代碼和基本image構建為新image的描述。Build可以是基於源代碼的,可以為常見語言(如Java、PHP、Ruby或Python)或基於docker的(從Dockerfile創建構建)使用構建器映像。每個build configuration都有webhook,可以通過對其基本映像的更改自動觸發。
  • Build:構建從源代碼、其他圖像、Dockerfiles或二進制輸入創建新image。Build在容器中運行,具有與普通pod相同的限制。Build通常會導致將image推入Docker倉庫中,但也可以選擇運行post-build測試而不push到image倉庫。
  • Image Streams and Image Stream Tags:IS使用標記名稱對相關is進行分組。它類似於源代碼倉庫中的分支。每個is可以有一個或多個標記(默認標記稱為“latest”),這些標記可能指向外部Docker倉庫、同一is中的其他標記,或者被控製為直接指向已知image。此外,可以通過集成的Docker倉庫直接將image push到docker倉庫。
  • Secret:secret資源可以保存文本或二進制secrets,以便注入至pod。默認情況下,在/var/run/secrets/kubernetes.io/serviceaccount上,每個容器都有一個secret,其中包含訪問API有限特權的令牌。可以創建新的secret並將它們掛載到自己的pod中,也可以引用構建中的secret(用於連接遠程服務器),或者使用它們將遠程image導入到is中。
  • Project:所有上述資源(node除外)都存在於項目中。項目具有成員列表及其role(如view、edit或admin),以及運行的pod上的一組安全控制,並限制項目可以使用多少資源,資源名稱在項目中是惟一的。


使用oc types命令快速查看可用的概念和類型。

2.2 創建應用


簡單的應用程序、複雜的多層應用程序和微服務應用程序都可以使用資源定義文件來描述。

這個文件包含許多pod定義、連接這些pod的服務定義、用於水平伸縮應用程序pod的rc或dc、用於持久存儲應用程序數據的持久卷,以及OpenShift可以管理的任何其他需要的內容。

oc new-app命令可以使用-o json或-o yaml選項分別創建以json或yaml格式的定義文件的資源。可以使用oc create -f <filename>命令調用定義文件,並將其用於創建應用程序,或者與其他資源定義文件合併以創建複合應用程序。

oc new-app命令可以以許多不同的方式創建在OpenShift上運行的pod應用程序。它可以使用source-to-image (S2I)流程從現有docker映像、Dockerfiles或原始源代碼創建pod。

運行oc new-app -h命令,了解在OpenShift上創建新應用程序的所有不同選項。最常見的選項如下:

運行以下命令創建應用程序。OpenShift根據Docker配置文件的ADD_REGISTRY選項定義的倉庫 pull image。

$ oc new-app mysql MYSQL_USER=user MYSQL_PASSWORD=pass MYSQL_DATABASE=testdb -l

db=mysql

根據私有倉庫中的image創建應用程序。

$ oc new-app –docker-image=myregistry.com/mycompany/myapp –name=myapp

根據存儲在Git庫中的源代碼創建應用程序。

$ oc new-app https://github.com/openshift/ruby-hello-world –name=ruby-hello

創建基於存儲在Git庫中的源代碼並引用IS的應用程序。

$ oc new-app https://mygitrepo/php-hello -i php:7.0 –name=php-hello

從Docker配置文件的ADD_REGISTRY指令定義的可用倉庫之一創建一個基於mysql映像的應用程序。l db=mysql選項定義了一個值為mysql的db標籤。

$ oc new-app mysql MYSQL_USER=user \

MYSQL_PASSWORD=pass \

MYSQL_DATABASE=testdb \

-l db=mysql

下圖显示了oc new-app命令在參數為容器image時創建的Kubernetes和OpenShift資源。該命令創建dc、is和svc,可以通過端口或route從外部訪問。



提示:通過使用帶有源代碼的oc new-app,將創建一個build configuration,而bc又從源代碼創建一個新的應用程序。但是,如果命令中沒有使用源代碼,則不會創建gc。該命令始終為應用程序創建dc和svc。

三 oc使用練習

3.1 前置準備


準備完整的OpenShift集群,參考《003.OpenShift網絡》2.1。

3.2 本練習準備


[student@workstation ~]$ lab manage-oc setup

3.3 驗證OpenShift

  1 [student@workstation ~]$ oc login -u admin -p redhat https://master.lab.example.com
  2 [student@workstation ~]$ oc project default
  3 Already on project "default" on server "https://master.lab.example.com:443".
  4 [student@workstation ~]$ oc project default
  5 Already on project "default" on server "https://master.lab.example.com:443".
  6 [student@workstation ~]$ oc get nodes
  7 NAME                     STATUS    ROLES     AGE       VERSION
  8 master.lab.example.com   Ready     master    23h       v1.9.1+a0ce1bc657
  9 node1.lab.example.com    Ready     compute   23h       v1.9.1+a0ce1bc657
 10 node2.lab.example.com    Ready     compute   23h       v1.9.1+a0ce1bc657
 11 [student@workstation ~]$ oc describe node master.lab.example.com		#查看master節點詳情
 12 [student@workstation ~]$ oc describe node node1.lab.example.com
 13 [student@workstation ~]$ oc describe node node2.lab.example.com
 14 [student@workstation ~]$ oc get pods -o wide
 15 NAME                       READY     STATUS    RESTARTS   AGE       IP              NODE
 16 docker-registry-1-8v7sh    1/1       Running   4          23h       10.129.0.30     node2.lab.example.com
 17 docker-registry-1-rrmhm    1/1       Running   2          23h       10.128.0.12     node1.lab.example.com
 18 registry-console-1-xzxxp   1/1       Running   4          23h       10.129.0.31     node2.lab.example.com
 19 router-1-fwttd             1/1       Running   4          23h       172.25.250.12   node2.lab.example.com
 20 router-1-xdw84             1/1       Running   2          23h       172.25.250.11   node1.lab.example.com
 21 [student@workstation ~]$ oc  describe pod docker-registry-1-8v7sh		#查看pod詳情


3.4 pod操作


[student@workstation ~]$ oc exec docker-registry-1-8v7sh hostname #執行pod中命令

docker-registry-1-8v7sh

[student@workstation ~]$ oc exec router-1-fwttd ls /

[student@workstation ~]$ oc exec docker-registry-1-8v7sh cat /etc/resolv.conf

提示:只要pod中存在的命令,都可以通過oc exec直接執行。

[student@workstation ~]$ oc rsh docker-registry-1-8v7sh #進入pod的shell

sh-4.2$ ls /

3.5 oc其他操作


[student@workstation ~]$ oc status -v #現實詳細的狀態



[student@workstation ~]$ oc get events #查看集群生命周期事件

[student@workstation ~]$ oc get all #獲取所有資源信息

3.6 導出資源


[student@workstation ~]$ oc export pod docker-registry-1-8v7sh

提示:oc export命令通常用於導出現有資源,並將它們轉換為配置文件(YAML或JSON),以便備份或在集群的其他地方重新創建資源。

[student@workstation ~]$ oc export svc,dc docker-registry –as-template=docker-registry

#通過將–as-template選項傳遞給oc export命令,將多個資源作為OpenShift模板同時導出。

[student@workstation ~]$ oc export svc,dc docker-registry > docker-registry.yaml #也可以使用重定嚮導出

[student@workstation ~]$ oc export –help #查看幫助

四 oc常見故障排除

4.1 常見環境信息


使用RPM安裝的OCP,那麼master和node的ocp相關服務將作為Red Hat Enterprise Linux服務運行。從master和node使用標準的sosreport實用程序,收集關於環境的信息,以及docker和openshift相關的信息。

[root@master ~]# sosreport -k docker.all=on -k docker.logs=on

sosreport命令創建一個包含所有相關信息的壓縮歸檔文件,並將其保存在/var/tmp目錄中。

另一個有用的診斷工具是oc adm diagnostics命令,能夠在OpenShift集群上運行多個診斷檢查,包括network、日誌、內部倉庫、master節點和node節點的服務檢查等等。oc adm diagnostics –help命令,獲取幫助。

4.2 常見診斷命令


oc客戶端命令是用來檢測和排除OpenShift集群中的問題的主要工具。它有許多選項,能夠檢測、診斷和修復由集群管理的主機和節點、服務和資源的問題。若已授權所需的權限,可以直接編輯集群中大多數託管資源的配置。

  • oc get events


事件允許OpenShift記錄集群中生命周期事件的信息,以統一的方式查看關於OpenShift組件的信息。oc get events命令提供OpenShift namespace的事件信息,可實現以下事件的捕獲:

    • Pod創建和刪除
    • pod調度的節點
    • master和node節點的狀態


事件通常用於故障排除,從而獲得關於集群中的故障和問題的高級信息,然後使用日誌文件和其他oc子命令進一步定位。

示例:使用以下命令獲得特定項目中的事件列表。

[student@workstation ~]$ oc get events -n <project>

也可以通過Web控制台進行事件的查看events。

  • oc log


oc logs命令查看build、deployment或pod的日誌輸出,。

示例1:使用oc命令查看pod的日誌。

[student@workstation ~]$ oc logs pod

示例2:使用oc命令查看build的日誌。

[student@workstation ~]$ oc logs bc/build-name

使用oc logs命令和-f選項實時跟蹤日誌輸出。例如,這對於連續監視build的進度和檢查錯誤非常有用。

也可以通過Web控制台進行事件的查看log。

  • oc rsync


oc rsync命令將內容複製到正在運行的pod中的目錄或從目錄複製內容。如果一個pod有多個容器,可以使用-c選項指定容器ID。否則,它默認為pod中的第一個容器。通常用於從容器傳輸日誌文件和配置文件。

示例1:將pod目錄中的內容複製到本地目錄。

[student@workstation ~]$ oc rsync <pod>:<pod_dir> <local_dir> -c <container>

示例2:將內容從本地目錄複製到pod的目錄中。

[student@workstation ~]$ oc rsync <local_dir> <pod>:<pod_dir> -c <container>

  • oc port-forward


使用oc port-forward命令將一個或多個本地端口轉發到pod。這允許在本地監聽特定或隨機端口,並將數據轉發到pod中的特定端口。

示例1:本地監聽3306並轉發到pod的3306.

[student@workstation ~]$ oc port-forward <pod> 3306:3306

五 TS常見故障

5.1 資源限制和配額問題


對於設置了資源限制和配額的項目,不適當的資源配置將導致部署失敗。使用oc get events和oc describe命令來排查失敗的原因。

例如試圖創建超過項目中pod數量配額限制的pod數量,那麼在運行oc get events命令時會提示:

Warning FailedCreate {hello-1-deploy} Error creating: pods “hello-1” is forbidden:

exceeded quota: project-quota, requested: cpu=250m, used: cpu=750m, limited: cpu=900m

5.2 S2I build失敗


使用oc logs命令查看S2I構建失敗。例如,要查看名為hello的構建配置的日誌:

[student@workstation ~]$ oc logs bc/hello

例如可以通過在build configuration策略中指定BUILD_LOGLEVEL環境變量來調整build日誌的詳細程度。

  1 {
  2 "sourceStrategy": {
  3 ...
  4 "env": [
  5 {
  6 "name": "BUILD_LOGLEVEL",
  7 "value": "5"
  8 }
  9 ]
 10 }
 11 }


5.3 ErrImagePull和imgpullback錯誤


通常是由不正確的deployment configuration造成、部署期間引用的錯誤或缺少image或Docker配置不當造成。

使用oc get events和oc describe命令排查,通過使用oc edit dc/<deploymentconfig>編輯deployment configuration來修復錯誤。

5.4 docker配置異常


master和node上不正確的docker配置可能會在部署期間導致許多錯誤。

通常檢查ADD_REGISTRY、INSECURE_REGISTRY和BLOCK_REGISTRY設置。使用systemctl status, oc logs, oc get events和oc describe命令對問題進行排查。

可以通添加/etc/sysconfig/docker配置文件中的–log-level參數來更改docker服務日誌級別。

示例:將日誌級別設置為debug。

OPTIONS=’–insecure-registry=172.30.0.0/16 –selinux-enabled –log-level=debug’

5.5 master和node節點失敗


運行systemctl status命令,對atomicopenshift-master、atom-openshift-node、etcd和docker服務中的問題進行排查。使用journalctl -u <unit-name>命令查看與前面列出的服務相關的系統日誌。

可以通過在各自的配置文件中編輯–loglevel變量,然後重新啟動關聯的服務,來增加來自atom-openshift-node、atomicopenshift-master-controllers和atom-openshift-master-api服務的詳細日誌記錄。

示例:設置OpenShift主控制器log level為debug級別,修改/etc/sysconfig/atomic-openshift-master-controllers文件。

OPTIONS=–loglevel=4 –listen=https://0.0.0.0:8444

延伸:

Red Hat OpenShift容器平台有五個級別的日誌詳細程度,無論日誌配置如何,日誌中都會出現帶有致命、錯誤、警告和某些信息嚴重程度的消息。

  • 0:只有錯誤和警告
  • 2:正常信息(默認)
  • 4:debug級信息
  • 6:api級debug信息(請求/響應)
  • 8:帶有完整請求體的API debug信息

5.6 調度pod失敗


OpenShift master調度pod在node上運行,通常由於node本身沒有處於就緒狀態,也由於資源限制和配額,pod無法運行。

使用oc get nodes命令驗證節點的狀態。在調度失敗期間,pod將處於掛起狀態,可以使用oc get pods -o wide命令進行檢查,該命令還显示了計劃在哪個節點上運行pod。使用oc get events和oc describe pod命令檢查調度失敗的詳細信息。

示例1:如下所示pod調度失敗,原因是CPU不足。

{default-scheduler } Warning FailedScheduling pod (FIXEDhello-phb4j) failed to

fit in any node

fit failure on node (hello-wx0s): Insufficient cpu

fit failure on node (hello-tgfm): Insufficient cpu

fit failure on node (hello-qwds): Insufficient cpu

示例2:如下所示pod調度失敗,原因是節點沒有處於就緒狀態,可通過oc describe排查。

{default-scheduler } Warning FailedScheduling pod (hello-phb4j): no nodes

available to schedule pods

六 常見問題排查

6.1 前置準備


準備完整的OpenShift集群,參考《003.OpenShift網絡》2.1。

6.2 本練習準備


[student@workstation ~]$ lab common-troubleshoot setup

6.3 創建應用


[student@workstation ~]$ oc new-project common-troubleshoot

[student@workstation ~]$ oc new-app –name=hello -i php:5.4 \ #從源代碼創建應用

> http://services.lab.example.com/php-helloworld


6.4 查看詳情


[student@workstation ~]$ oc describe is php -n openshift





結論:由上可知,倉庫中不存在所需鏡像。

6.5 修正錯誤


[student@workstation ~]$ oc new-app –name=hello -i php:7.0 http://services.lab.example.com/php-helloworld

[student@workstation ~]$ oc get pod -o wide #再次查看發現一隻出於pending

NAME READY STATUS RESTARTS AGE IP NODE

hello-1-build 0/1 Pending 0 40s <none> <none>

6.6 查看詳情

  1 [student@workstation ~]$ oc log hello-1-build		#查看log
  2 W0720 20:22:16.455008   18942 cmd.go:358] log is DEPRECATED and will be removed in a future version. Use logs instead.
  3 [student@workstation ~]$ oc get events			#查看事件
  4 LAST SEEN   FIRST SEEN   COUNT     NAME                             KIND      SUBOBJECT   TYPE      REASON             SOURCE              MESSAGE
  5 56s         4m           15        hello-1-build.15b31cbd8da8ff1e   Pod                   Warning   FailedScheduling   default-scheduler   0/3 nodes are available: 1 MatchNodeSelector, 2 NodeNotReady.
  6 [student@workstation ~]$ oc describe pod hello-1-build	#查看詳情
  7 ……
  8 Warning  FailedScheduling  31s (x22 over 5m)  default-scheduler  0/3 nodes are available: 1 MatchNodeSelector, 2 NodeNotReady.
  9 結論:由上可知,沒有node可供調度此pod。
 10 [root@master ~]# oc get nodes				#在master節點進一步排查node情況
 11 NAME                     STATUS     ROLES     AGE       VERSION
 12 master.lab.example.com   Ready      master    1d        v1.9.1+a0ce1bc657
 13 node1.lab.example.com    NotReady   compute   1d        v1.9.1+a0ce1bc657
 14 node2.lab.example.com    NotReady   compute   1d        v1.9.1+a0ce1bc657
 15 結論:由上可知,node狀態異常,都未出於ready狀態。


6.7 檢查服務


[root@node1 ~]# systemctl status atomic-openshift-node.service

[root@node2 ~]# systemctl status atomic-openshift-node.service

[root@node1 ~]# systemctl status docker

[root@node2 ~]# systemctl status docker




結論:由上可知,node節點的docker異常。

6.8 啟動服務


[root@node1 ~]# systemctl start docker

[root@node2 ~]# systemctl start docker

6.9 確認驗證


[root@master ~]# oc get nodes #再次查看node狀態

NAME STATUS ROLES AGE VERSION

master.lab.example.com Ready master 1d v1.9.1+a0ce1bc657

node1.lab.example.com Ready compute 1d v1.9.1+a0ce1bc657

node2.lab.example.com Ready compute 1d v1.9.1+a0ce1bc657

[student@workstation ~]$ oc get pods #確認pod是否正常調度至node

NAME READY STATUS RESTARTS AGE

hello-1-build 1/1 Running 0 22m

[student@workstation ~]$ oc describe is #查看is詳情




結論:由上可知,IS也將image推送至內部倉庫。

七 oc命令綜合實驗

7.1 前置準備


準備完整的OpenShift集群,參考《003.OpenShift網絡》2.1。

7.2 本練習準備


[student@workstation ~]$ lab execute-review setup

7.3 git項目至本地


[student@workstation ~]$ cd /home/student/DO280/labs/execute-review/

[student@workstation execute-review]$ git clone http://services.lab.example.com/node-hello

7.4 docker構建image


[student@workstation execute-review]$ cd node-hello/

[student@workstation node-hello]$ docker build -t node-hello:latest .

[student@workstation node-hello]$ docker images #查看image

REPOSITORY TAG IMAGE ID CREATED SIZE

node-hello latest ff48daa00d8e 12 seconds ago 495 MB

registry.lab.example.com/rhscl/nodejs-6-rhel7 latest fba56b5381b7 22 months ago 489 MB

7.5 修改docker tag


[student@workstation node-hello]$ docker tag ff48daa00d8e \

> registry.lab.example.com/node-hello:latest

[student@workstation node-hello]$ docker images

REPOSITORY TAG IMAGE ID CREATED SIZE

node-hello latest ff48daa00d8e About a minute ago 495 MB

registry.lab.example.com/node-hello latest ff48daa00d8e About a minute ago 495 MB

registry.lab.example.com/rhscl/nodejs-6-rhel7 latest fba56b5381b7 22 months ago 489 MB

7.6 push image

[student@workstation node-hello]$ docker push registry.lab.example.com/node-hello:latest

7.7 創建project


[student@workstation ~]$ oc login -u developer -p redhat \

> https://master.lab.example.com

[student@workstation ~]$ oc projects

[student@workstation ~]$ oc project execute-review

[student@workstation ~]$ oc new-app registry.lab.example.com/node-hello –name hello

[student@workstation ~]$ oc get all #查看全部資源


7.8 排查ImagePullBackOff


[student@workstation ~]$ oc logs hello-1-2jkkj #查看日誌

Error from server (BadRequest): container “hello” in pod “hello-1-2jkkj” is waiting to start: trying and failing to pull image

[student@workstation ~]$ oc describe pod hello-1-2jkkj #查看詳情

[student@workstation ~]$ oc get events –sort-by=’.metadata.creationTimestamp’ #查看事件

結論:由上可知,為image pull失敗。

7.9 手動pull鏡像


[student@workstation ~]$ oc get pod -o wide

NAME READY STATUS RESTARTS AGE IP NODE

hello-1-2jkkj 0/1 ImagePullBackOff 0 8m 10.128.0.45 node1.lab.example.com

hello-1-deploy 1/1 Running 0 8m 10.129.0.72 node2.lab.example.com

[root@node1 ~]# docker pull registry.lab.example.com/node-hello #手動拉去也失敗

Using default tag: latest

Trying to pull repository registry.lab.example.com/node-hello …

All endpoints blocked.

結論:由上可知,所有endpoint都被阻塞了。這種類型的錯誤通常發生在OpenShift中,原因是不正確的部署配置或無效docker配置。

7.10 修正docker配置


[root@node1 ~]# vi /etc/sysconfig/docker

將BLOCK_REGISTRY=’–block-registry registry.access.redhat.com –block-registry docker.io –block-registry registry.

lab.example.com’

修改為

BLOCK_REGISTRY=’–block-registry registry.access.redhat.com –block-registry docker.io’

[root@node1 ~]# systemctl restart docker

提示:node2也需要如上操作。

7.11 更新pod


[student@workstation ~]$ oc rollout latest hello

[student@workstation ~]$ oc get pods #確認

NAME READY STATUS RESTARTS AGE

hello-1-deploy 0/1 Error 0 22m

hello-2-75x9t 1/1 Running 0 47s

7.12 確認驗證


[student@workstation ~]$ oc logs hello-2-75x9t #查看log

nodejs server running on http://0.0.0.0:3000

7.13 暴露服務


[student@workstation ~]$ oc expose svc hello –hostname=hello.apps.lab.example.com

route “hello” exposed

7.14 測試服務


[student@workstation ~]$ curl http://hello.apps.lab.example.com

Hi! I am running on host -> hello-2-75x9t

[student@workstation ~]$ lab execute-review grade #腳本驗證試驗

本站聲明:網站內容來源於博客園,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

※別再煩惱如何寫文案,掌握八大原則!

※網頁設計一頭霧水該從何著手呢? 台北網頁設計公司幫您輕鬆架站!

※超省錢租車方案

※教你寫出一流的銷售文案?

※網頁設計最專業,超強功能平台可客製化

設計模式系列之組合模式(Composite Pattern)——樹形結構的處理

說明:設計模式系列文章是讀劉偉所著《設計模式的藝術之道(軟件開發人員內功修鍊之道)》一書的閱讀筆記。個人感覺這本書講的不錯,有興趣推薦讀一讀。詳細內容也可以看看此書作者的博客https://blog.csdn.net/LoveLion/article/details/17517213

模式概述

樹形結構在軟件中隨處可見,例如操作系統中的目錄結構、應用軟件中的菜單、辦公系統中的公司組織結構等等,如何運用面向對象的方式來處理這種樹形結構是組合模式需要解決的問題。組合模式通過一種巧妙的設計方案使得用戶可以一致性地處理整個樹形結構或者樹形結構的一部分,也可以一致性地處理樹形結構中的恭弘=叶 恭弘子節點(不包含子節點的節點)和容器節點(包含子節點的節點)。

模式定義

組合模式(Composite Pattern):組合多個對象形成樹形結構以表示具有“整體—部分”關係的層次結構。組合模式對單個對象(即恭弘=叶 恭弘子對象)和組合對象(即容器對象)的使用具有一致性,組合模式又可以稱為“整體—部分”(Part-Whole)模式,它是一種對象結構型模式。

模式結構圖

組合模式結構圖如下所示:

在組合模式結構圖中包含如下幾個角色:

  • Component(抽象構件):它可以是接口或抽象類,為恭弘=叶 恭弘子構件和容器構件對象聲明接口,在該角色中可以包含所有子類共有行為的聲明和實現。在抽象構件中定義了訪問及管理它的子構件的方法,如增加子構件、刪除子構件、獲取子構件等。

  • Leaf(恭弘=叶 恭弘子構件):它在組合結構中表示恭弘=叶 恭弘子節點對象,恭弘=叶 恭弘子節點沒有子節點,它實現了在抽象構件中定義的行為。對於那些訪問及管理子構件的方法,可以通過異常等方式進行處理。

  • Composite(容器構件):它在組合結構中表示容器節點對象,容器節點包含子節點,其子節點可以是恭弘=叶 恭弘子節點,也可以是容器節點,它提供一個集合用於存儲子節點,實現了在抽象構件中定義的行為,包括那些訪問及管理子構件的方法,在其業務方法中可以遞歸調用其子節點的業務方法。

組合模式的關鍵是定義了一個抽象構件類,它既可以代表恭弘=叶 恭弘子,又可以代表容器,而客戶端針對該抽象構件類進行編程,無須知道它到底表示的是恭弘=叶 恭弘子還是容器,可以對其進行統一處理。同時容器對象與抽象構件類之間還建立一個聚合關聯關係,在容器對象中既可以包含恭弘=叶 恭弘子,也可以包含容器,以此實現遞歸組合,形成一個樹形結構。

模式偽代碼

對於客戶端而言,一般針對抽象構件編程,而無須關心其具體子類是容器構件還是恭弘=叶 恭弘子構件。抽象構建類典型代碼如下:

public abstract class Component {
    public abstract void add(Component c); //增加成員

    public abstract void remove(Component c); //刪除成員

    public abstract Component getChild(int i); //獲取成員

    public abstract void operation();  //業務方法
}

如果繼承抽象構件的是恭弘=叶 恭弘子構件,則其典型代碼如下所示:

public class Leaf extends Component {
    @Override
    public void add(Component c) {
        //異常處理或錯誤提示 
    }

    @Override
    public void remove(Component c) {
        //異常處理或錯誤提示 
    }

    @Override
    public Component getChild(int i) {
        //異常處理或錯誤提示 
        return null;
    }

    @Override
    public void operation() {
        //恭弘=叶 恭弘子構件具體業務方法的實現
    }
}

如果繼承抽象構件的是容器構件,則其典型代碼如下所示:

public class Composite extends Component {

    private List<Component> list = new ArrayList<>();

    @Override
    public void add(Component c) {
        list.add(c);
    }

    @Override
    public void remove(Component c) {
        list.remove(c);
    }

    @Override
    public Component getChild(int i) {
        return (Component) list.get(i);
    }

    @Override
    public void operation() {
        //容器構件具體業務方法的實現
        //遞歸調用成員構件的業務方法
        for (Object obj : list) {
            ((Component) obj).operation();
        }
    }
}

客戶端對抽象構件類進行編程

public class Client {
    public static void main(String[] args) {
        Component component;
        component = new Leaf();
        //component = new Composite();

        // 無須知道到底是恭弘=叶 恭弘子還是容器
        // 可以對其進行統一處理
        component.operation();
    }
}

模式簡化

透明組合模式

透明組合模式中,抽象構件Component中聲明了所有用於管理成員對象的方法,包括add()、remove()以及getChild()等方法,這樣做的好處是確保所有的構件類都有相同的接口。在客戶端看來,恭弘=叶 恭弘子對象與容器對象所提供的方法是一致的,客戶端可以相同地對待所有的對象。透明組合模式也是組合模式的標準形式。

透明組合模式的完整結構圖如下:

也可以將恭弘=叶 恭弘子構件的add()、remove()等方法的實現代碼移至Component中,由Component提供統一的默認實現,這樣子類就不必強制去實現管理子Component。代碼如下所示:

public abstract class Component {
    public void add(Component c) {
        throw new RuntimeException("不支持的操作");
    }

    public void remove(Component c) {
        throw new RuntimeException("不支持的操作");
    }

    public Component getChild(int i) {
        throw new RuntimeException("不支持的操作");
    }

    public abstract void operation();  //業務方法
}

透明組合模式的缺點是不夠安全,因為恭弘=叶 恭弘子對象和容器對象在本質上是有區別的。恭弘=叶 恭弘子對象不可能有下一個層次的對象,即不可能包含成員對象,因此為其提供add()、remove()以及getChild()等方法是沒有意義的,這在編譯階段不會出錯,但在運行階段如果調用這些方法可能會出錯(如果沒有提供相應的錯誤處理代碼)。

安全組合模式

安全組合模式中,在抽象構件Component中沒有聲明任何用於管理成員對象的方法,而是在Composite類中聲明並實現這些方法。

安全組合模式的完整結構圖如下:

此時Component就應該這樣定義了

public abstract class Component {
    // 業務方法
    public abstract void operation();
}

安全組合模式的缺點是不夠透明,因為恭弘=叶 恭弘子構件和容器構件具有不同的方法,且容器構件中那些用於管理成員對象的方法沒有在抽象構件類中定義,因此客戶端不能完全針對抽象編程,必須有區別地對待恭弘=叶 恭弘子構件和容器構件。在實際應用中,安全組合模式的使用頻率也非常高,在Java AWT中使用的組合模式就是安全組合模式。

模式應用

模式在JDK中的應用

Java SE中的AWT和Swing包的設計就基於組合模式,在這些界麵包中為用戶提供了大量的容器構件(如Container)和成員構件(如Checkbox、Button和TextComponent等),其結構如下圖所示

Component類是抽象構件,Checkbox、Button和TextComponent是恭弘=叶 恭弘子構件,而Container是容器構件,在AWT中包含的恭弘=叶 恭弘子構件還有很多。在一個容器構件中可以包含恭弘=叶 恭弘子構件,也可以繼續包含容器構件,這些恭弘=叶 恭弘子構件和容器構件一起組成了複雜的GUI界面。除此以外,在XML解析、組織結構樹處理、文件系統設計等領域,組合模式都得到了廣泛應用。

模式在開源項目中的應用

Spring中org.springframework.web.method.support.HandlerMethodArgumentResolver使用了安全組合模式。提取關鍵代碼如下:

public interface HandlerMethodArgumentResolver {
    
    boolean supportsParameter(MethodParameter parameter);

    Object resolveArgument(MethodParameter parameter, @Nullable ModelAndViewContainer mavContainer,
                           NativeWebRequest webRequest, @Nullable WebDataBinderFactory binderFactory) throws Exception;

}

再看下它的一個實現類org.springframework.web.method.support.HandlerMethodArgumentResolverComposite

public class HandlerMethodArgumentResolverComposite implements HandlerMethodArgumentResolver {


    private final List<HandlerMethodArgumentResolver> argumentResolvers = new LinkedList<>();

    /**
     * Add the given {@link HandlerMethodArgumentResolver}.
     */
    public HandlerMethodArgumentResolverComposite addResolver(HandlerMethodArgumentResolver resolver) {
        this.argumentResolvers.add(resolver);
        return this;
    }

    /**
     * Add the given {@link HandlerMethodArgumentResolver HandlerMethodArgumentResolvers}.
     */
    public HandlerMethodArgumentResolverComposite addResolvers(
            @Nullable HandlerMethodArgumentResolver... resolvers) {

        if (resolvers != null) {
            Collections.addAll(this.argumentResolvers, resolvers);
        }
        return this;
    }

    /**
     * Clear the list of configured resolvers.
     */
    public void clear() {
        this.argumentResolvers.clear();
    }


    @Override
    public boolean supportsParameter(MethodParameter parameter) {
        return getArgumentResolver(parameter) != null;
    }


    @Override
    public Object resolveArgument(MethodParameter parameter, @Nullable ModelAndViewContainer mavContainer,
                                  NativeWebRequest webRequest, @Nullable WebDataBinderFactory binderFactory) throws Exception {

        HandlerMethodArgumentResolver resolver = getArgumentResolver(parameter);
        if (resolver == null) {
            throw new IllegalArgumentException("Unsupported parameter type [" +
                    parameter.getParameterType().getName() + "]. supportsParameter should be called first.");
        }
        return resolver.resolveArgument(parameter, mavContainer, webRequest, binderFactory);
    }
}

模式總結

主要優點

  1. 組合模式可以清楚地定義分層次的複雜對象,表示對象的全部或部分層次,它讓客戶端忽略了層次的差異,方便對整個層次結構進行控制。

  2. 客戶端可以一致地使用一個組合結構或其中單個對象,不必關心處理的是單個對象還是整個組合結構,簡化了客戶端代碼。

  3. 組合模式為樹形結構的面向對象實現提供了一種靈活的解決方案,通過恭弘=叶 恭弘子對象和容器對象的遞歸組合,可以形成複雜的樹形結構,但對樹形結構的控制卻非常簡單。

適用場景

(1) 在具有整體和部分的層次結構中,希望通過一種方式忽略整體與部分的差異,客戶端可以一致地對待它們。

(2) 在一個使用面向對象語言開發的系統中需要處理一個樹形結構。

(3) 在一個系統中能夠分離出恭弘=叶 恭弘子對象和容器對象,而且它們的類型不固定,需要增加一些新的類型。

本站聲明:網站內容來源於博客園,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

※教你寫出一流的銷售文案?

※廣告預算用在刀口上,台北網頁設計公司幫您達到更多曝光效益

※回頭車貨運收費標準

※別再煩惱如何寫文案,掌握八大原則!

※超省錢租車方案

※產品缺大量曝光嗎?你需要的是一流包裝設計!

Python的多繼承問題-MRO和C3算法

Python 中的方法解析順序(Method Resolution Order, MRO)定義了多繼承存在時 Python 解釋器查找函數解析的正確方式。當 Python 版本從 2.2 發展到 2.3 再到現在的 Python 3,MRO算法也隨之發生了相應的變化。這種變化在很多時候影響了我們使用不同版本 Python 編程的過程。

大部分內容轉載自C3 線性化算法與 MRO 理解Python中的多繼承

什麼是 MRO

MRO 全稱方法解析順序(Method Resolution Order)。它定義了 Python 中多繼承存在的情況下,解釋器查找函數解析的具體順序。什麼是函數解析順序?我們首先用一個簡單的例子來說明。請仔細看下面代碼:

class A():
    def who_am_i(self):
        print("I am A")
        
class B(A):
    pass
        
class C(A):
    def who_am_i(self):
        print("I am C")

class D(B,C):
    pass
    
d = D()

如果我問在 Python 2 中使用 D 的實例調用 d.who_am_i(),究竟執行的是 A 中的 who_am_i() 還是 C 中的 who_am_i(),我想百分之九十以上的人都會不假思索地回答:肯定是 C 中的 who_am_i(),因為 C 是 D 的直接父類。然而,如果你把代碼用 Python 2 運行一下就可以看到 d.who_am_i() 打印的是 I am A。

是不是覺得很混亂很奇怪?感到奇怪就對了!!!

這個例子充分展示了 MRO 的作用:決定基類中的函數到底應該以什麼樣的順序調用父類中的函數。可以明確地說,Python 發展到現在,MRO 算法已經不是一個憑藉著執行結果就能猜出來的算法了。如果沒有深入到 MRO 算法的細節,稍微複雜一點的繼承關係和方法調用都能徹底繞暈你。

New-style Class vs. Old-style Class

在介紹不同版本的 MRO 算法之前,我們有必要簡單地回顧一下 Python 中類定義方式的發展歷史。儘管在 Python 3 中已經廢除了老式的類定義方式和 MRO 算法,但對於仍然廣泛使用的 Python 2 來說,不同的類定義方式與 MRO 算法之間具有緊密的聯繫。了解這一點將幫助我們從 Python 2 向 Python 3 遷移時不會出現莫名其妙的錯誤。

在 Python 2.1 及以前,我們定義一個類的時候往往是這個樣子(我們把這種類稱為 old-style class):

class A:
    def __init__(self):
        pass

Python 2.2 引入了新的模型對象(new-style class),其建議新的類型通過如下方式定義:

class A(object):
    def __init__(self):
        pass

注意后一種定義方式显示註明類 A 繼承自 object。Python 2.3 及後續版本為了保持向下兼容,同時提供以上兩種類定義用以區分 old-style class 和 new-style class。Python 3 則完全廢棄了 old-style class 的概念,不論你通過以上哪種方式書寫代碼,Python 3 都將明確認為類 A 繼承自 object。這裏我們只是引入 old-style 和 new-style 的概念,如果你對他們的區別感興趣,可以自行看 stackoverflow 上有關該問題的解釋。

理解 old-style class 的 MRO

我們使用前文中的類繼承關係來介紹 Python 2 中針對 old-style class 的 MRO 算法。如果你在前面執行過那段代碼,你可以看到調用 d.who_am_i() 打印的應該是 I am A。為什麼 Python 2 的解釋器在確定 D 中的函數調用時要先搜索 A 而不是先搜索 D 的直接父類 C 呢?

這是由於 Python 2 對於 old-style class 使用了非常簡單的基於深度優先遍歷的 MRO 算法(關於深度優先遍歷,我想大家肯定都不陌生)。當一個類繼承自多個類時,Python 2 按照從左到右的順序深度遍歷類的繼承圖,從而確定類中函數的調用順序。這個過程具體如下:

  1. 檢查當前的類裏面是否有該函數,如果有則直接調用。
  2. 檢查當前類的第一個父類裏面是否有該函數,如果沒有則檢查父類的第一個父類是否有該函數,以此遞歸深度遍歷。
  3. 如果沒有則回溯一層,檢查下一個父類裏面是否有該函數並按照 2 中的方式遞歸。

上面的過程與標準的深度優先遍歷只有一點細微的差別:步驟 2 總是按照繼承列表中類的先後順序來選擇分支的遍歷順序。具體來說,類 D 的繼承列表中類順序為 B, C,因此,類 D 按照先遍歷 B 分支再遍歷 C 分支的順序來確定 MRO。

我們繼續用第一個例子中的函數繼承圖來說明這個過程:

按照上述深度遞歸的方式,函數 d.who_am_i() 調用的搜索順序是 D, B, A, C, A。由於一個類不能兩次出現,因此在搜索路徑中去除掉重複出現的 A,得到最終的方法解析順序是 D, B, A, C。這樣一來你就明白了為什麼 d.who_am_i() 打印的是 I am A 了。

在 Python 2 中,我們可以通過如下方式來查看 old-style class 的 MRO:

>>> import inspect
>>> inspect.getmro(D)

理解 new-style class 的 MRO

從上面的結果可以看到,使用深度優先遍歷的查找算法並不合理。因此,Python 3 以及 Python 2 針對 new-style class 採用了新的 MRO 算法。如果你使用 Python 3 重新運行一遍上述腳本,你就可以看到函數 d.who_am_i() 的打印結果是 I am C。

>>> d.who_am_i()
I am C
>>> D.__mro__
(<class 'test.D'>, <class 'test.B'>, <class 'test.C'>, <class 'test.A'>, <class 'object'>)

新算法與基於深度遍歷的算法類似,但是不同在於新算法會對深度優先遍歷得到的搜索路徑進行額外的檢查。其從左到右掃描得到的搜索路徑,對於每一個節點解釋器都會判斷該節點是不是好的節點。如果不是好的節點,那麼將其從當前的搜索路徑中移除。

那麼問題在於,什麼是一個好的節點?我們說 N 是一個好的節點當且僅當搜索路徑中 N 之後的節點都不繼承自 N。我們還以上述的類繼承圖為例,按照深度優先遍歷得到類 D 中函數的搜索路徑 D, B, A, C, A。之後 Python 解釋器從左向右檢查時發現第三個節點 A 不是一個好的節點,因為 A 之後的節點 C 繼承自 A。因此其將 A 從搜索路徑中移除,然後得到最後的調用順序 D, B, C, A。

採用上述算法,D 中的函數調用將優先查找其直接父類 B 和 C 中的相應函數。

C3線性化算法

上一小結我們從直觀上概述了針對 new-style class 的 MRO 算法過程。事實上這個算法有一個明確的名字 C3 linearization。下面我們給出其形式化的計算過程。

上面的過程看起來好像很複雜,我們用一個例子來具體執行一下,你就會覺得其實還是挺簡單的。假設我們有如下的一個類繼承關係:

參考來源:Understanding Python MRO – Class search path

class X():
    def who_am_i(self):
        print("I am a X")
        
class Y():
    def who_am_i(self):
        print("I am a Y")
        
class A(X, Y):
    def who_am_i(self):
        print("I am a A")
        
class B(Y, X):
     def who_am_i(self):
         print("I am a B")
         
class F(A, B):
    def who_am_i(self):
        print("I am a F")

Traceback (most recent call last):
  File "test.py", line 17, in <module>
    class F(A, B):
TypeError: Cannot create a consistent method resolution
order (MRO) for bases X, Y

為什麼採用C3算法

上圖中都是使用BFS算法來尋找繼承鏈,但是都會有問題,左邊的繼承模式會違背單調性的原則,右邊的棱形繼承鏈,如果是C重寫了繼承於A的方法,B沒有,但是根據MRO繼承鏈,最終調用的都是A類的方法,C中實現的方法永遠不會被調用,這些都是再python2的問題,python引入C3算法后就解決了這些問題。

C3算法最早被提出是用於Lisp的,應用在Python中是為了解決原來基於深度優先搜索算法不滿足本地優先級,和單調性的問題。
本地優先級:指聲明時父類的順序,比如C(A,B),如果訪問C類對象屬性時,應該根據聲明順序,優先查找A類,然後再查找B類。
單調性:如果在C的解析順序中,A排在B的前面,那麼在C的所有子類里,也必須滿足這個順序。

在Python官網的The Python 2.3 Method Resolution Order中作者舉了例子,說明這一情況

F=type('Food', (), {remember2buy:'spam'})
E=type('Eggs', (F,), {remember2buy:'eggs'})
G=type('GoodFood', (F,E), {})

根據本地優先級在調用G類對象屬性時應該優先查找F類,而在Python2.3之前的算法給出的順序是G E F O,而在心得C3算法中通過阻止類層次不清晰的聲明來解決這一問題,以上聲明在C3算法中就是非法的。

小結

C3算法的核心 :

  1. 遍歷執行merge操作的序列,如果一個序列的第一個元素,在其他序列中也是第一個元素,或不在其他序列出現,則從所有執行merge操作序列中刪除這個元素,合併到當前的mro中。

  2. merge操作后的序列,繼續執行merge操作,直到merge操作的序列為空。

  3. 如果merge操作的序列無法為空,則說明不合法。

參考資料

理解Python中的多繼承-C3 線性化算法

Python的多重繼承問題-MRO和C3算法

Deep Thoughts by Raymond Hettinger

C3 linearization

本站聲明:網站內容來源於博客園,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

※超省錢租車方案

※別再煩惱如何寫文案,掌握八大原則!

※回頭車貨運收費標準

※教你寫出一流的銷售文案?

※FB行銷專家,教你從零開始的技巧

股價飆升有理,大摩指Tesla潛在市場大

 

美國電動車廠特斯拉(Tesla),股價一度飆破300美元,並超越福特汽車,躍居美國第二大車廠。到底特斯拉值不值得此一高價?特斯拉多頭出面力挺,指出特斯拉不只是電動車業者,也是運輸和基礎建設公司,潛在市場極大,300美元合情合理。

MarketWatch、巴倫(Barron’s)報導,4日特斯拉股價首次突破300美元大關,漲勢之快讓大多頭也吃驚。摩根士丹利(大摩)分析師Adam Jonas等人報告稱,他們訝異於特斯拉近來漲勢,在短短時間內就站上300美元,認為技術因素戰勝基本面。

儘管如此,大摩認為,特斯拉並非配不上此一價碼。報告稱,他們和投資人交談後發現,大家比預期更快了解特斯拉的潛在市場。大摩說,特斯拉潛在市場包括10兆美元的輕型車移動市場、1兆美元的物流市場、2~3兆美元的能源市場。另外,消費者待在車上時間高達6,000億小時,特斯拉能從數兆美元的影音內容傳輸等服務獲利。

大摩指出,上次特斯拉飆高時,是在2014年9月,當時少有投資人以此種角度看待特斯拉,多認為大摩報告衝太快,那個時候特斯拉被視為純電動車廠,有機會觸及電池/能源儲存系統。但是這2.5年來情況已大幅轉變。特斯拉佔據三大重要趨勢的中心位置:電動車、自駕系統、車輛共享。

大摩說,特斯拉的「Tesla Mobility」將在2018年問世,此一隨選服務提供自動化運輸功能,預期特斯拉將在共享移動(shared mobility)革命中,扮演領導角色。

需要強調的是,Benzinga報導,Jonas向來看來極為看好特斯拉,2015年年中曾預測特斯股價將站上465美元,被外界稱為「特斯拉啦啦隊」。

特斯拉5日股價拉回,下跌2.86%收在295美元。

特斯拉第1季交車量意外跳高,帶動股價一飛衝天,如今已超越福特(Ford Motor Co.),成為全美市值第二大的汽車製造商。

特斯拉4日終場勁揚1.74%、收303.70美元,創歷史收盤新高。MarketWatch報導,根據計算,特斯拉的市值已達495億美元,一口氣超車福特的444億美元。在汽車類股中,特斯拉的市值如今僅次於通用汽車(General Motors Co.)的513億美元。

特斯拉甫於週日(4月2日)宣布,第1季的Model S、Model X交貨量超過了25,000台,創單季歷史高,比去年同期大增了69%之多。這高於分析師原先預估的23,000~24,500台。特斯拉並預估,今(2017)年上半年的汽車交貨量有機會達到47,000~50,000台。

(本文內容由授權使用。圖片出處:Public Domain CC0)

本站聲明:網站內容來源於EnergyTrend https://www.energytrend.com.tw/ev/,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

※網頁設計一頭霧水該從何著手呢? 台北網頁設計公司幫您輕鬆架站!

※網頁設計公司推薦不同的風格,搶佔消費者視覺第一線

※Google地圖已可更新顯示潭子電動車充電站設置地點!!

※廣告預算用在刀口上,台北網頁設計公司幫您達到更多曝光效益

※別再煩惱如何寫文案,掌握八大原則!

※網頁設計最專業,超強功能平台可客製化

一起玩轉微服務(7)——單一職責

 

單一職責

單一職責原則(Single Responsibility Principle, SRP):一個類只負責一個功能領域中的相應職責,或者可以定義為:就一個類而言,應該只有一個引起它變化的原因。

單一職責原則是實現高內聚、低耦合的指導方針,它是最簡單但又最難運用的原則

單一職責原則是最簡單的面向對象設計原則,它用於控制類的粒度大小

設計原則很重要的一點就是簡單,單一職責,也就是我們經常所說的專人干專事。

一個單元(一個類、函數或者微服務)應該有且只有一個職責。無論如何,一個微服務不應該包含多於一個的職責。職責單一的後果之一就是職責單位(微服務,類,接口,函數)的數量劇增。據說Amazon,Netflix這些採用微服務架構的網站一個小功能就會調用幾十上百個微服務。但是相較於每個函數都是多個業務邏輯或職責功能的混合體的情況,維護成本還是低很多的。 SRP中的“單一職責”是個比較模糊的概念。對於函數,它可能指單一的功能,不涉及複雜邏輯;但對於類或者接口,它可能是指對單一對象的操作,也可能是指對該對象單一屬性的操作。總而言之,單一職責原則就是為了讓代碼邏輯更加清晰,可維護性更好,定位問題更快的一種設計原則。

什麼是高內聚低耦合?

這犀利的措辭一看就是來自開發界的術語。高內聚是說一個功能模塊最好僅完成一個獨立的子功能並且完成的很好。低耦合是指模塊與模塊之間盡量獨立/聯繫少/接口簡單。

這個原則出現的背景是為了讓程序“可復用/可擴展/夠靈活/可維護”。干過一陣子產品的人對這幾個詞應該都不陌生。對於程序設計者來說,這幾個詞是十分重要的,不亞於產品經理口中的“用戶體驗”(原則or擋箭牌)。

優點

單一職責的優點如下:

•類的複雜性降低,實現什麼職責都有清晰明確的定義。•可讀性提高,複雜性降低。•可維護性提高,可讀性提高。•變更引起的風險降低,變更是必不可少的,如果接口的單一職責做得好,一個接口修改只對相應的實現類有影響,對其他的接口無影響,這對系統的擴展性、維護性都有非常大的幫助。

記得在三字經裡邊有這樣一段 教之道,貴以專(出自三字經) 說的就是無論學習還是構建團隊,最重要的是專才,而不是全才。就好比一個足球隊,如果都是前鋒或者都是後衛,那麼這樣的球隊一定不能出成績,反而是將各個位置上的人進行統一協調,根據分工不同,共同協作,形成1+1>2的效果,那麼這樣的團隊就非常容易出成績。

有很多公司為了趕進度,經常會招聘一些所謂的全能型人才,但是這種人往往專業的程度不夠,當遇到某些棘手的問題的時候,往往不能夠非常快速的解決問題。從而導致最終交付的質量較差。

單一職責的目的

實施單一職責的目的如下:

•以類來隔離需求功能點,這樣當一個點的需求發生變化的時候,不會影響別的類的邏輯,這個和設計模式中的開閉原則類似,對於擴展持開放態度,對於修改持關閉態度。•是一個原子模塊級的粒度,至於原子的粒度到底是什麼樣的,應該因業務而異,設計的過程中同時考慮業務的擴展,所以這就要求在設計的過程中,必須有業務專家共同參与,共同規避風險。•粒度小,靈活,復用性強,方便更高一級別的抽象。

每個微服務單獨運行在獨立的進程中,能夠實現松耦合,並且獨立部署。

如何做

分3步:

1.把一個具體的問題抽象成一類問題;

2.根據用戶體驗流程劃分功能模塊;

3.針對每個功能設計封閉的解決方案。

最佳實踐

在實際工作中,有一個經常會用到的設計模式,DAO模式,又叫數據訪問對象,裏面定義了數據庫中表的增、刪、改、查操作,按照單一職責原則,為什麼不把增、刪、改、查分別定義成四種接口?這是因為數據庫的表操作,基本上就是這四種類型,不可能變化,所以沒有必要分開定義,反而經常變化的是數據庫的表結構,表結構一變,這四種操作都要跟着變。所以通常我們會針對一張表實現一個DAO,一張表就代表一種類型的職責。

本站聲明:網站內容來源於博客園,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

※網頁設計一頭霧水該從何著手呢? 台北網頁設計公司幫您輕鬆架站!

※網頁設計公司推薦不同的風格,搶佔消費者視覺第一線

※Google地圖已可更新顯示潭子電動車充電站設置地點!!

※廣告預算用在刀口上,台北網頁設計公司幫您達到更多曝光效益

※別再煩惱如何寫文案,掌握八大原則!

※網頁設計最專業,超強功能平台可客製化

教你一招另闢蹊徑抓取美團火鍋數據

最近有個小夥伴在群里問美團數據怎麼獲取,而且她只要火鍋數據,她在上海,只要求抓上海美團火鍋的數據,而且要求也不高,只要100條,想做個簡單的分析,相關的字段如下圖所示。

乍一看,這個問題還真的是蠻難的,畢竟美團也不是那麼好抓,什麼驗證碼,模擬登陸等一大堆拂面而來,嚇得小夥伴都倒地了。

通過F12查看,抓包,分析URL,找規律,等等操作。

不過白慌,今天小編給大家介紹一個小技巧,另闢蹊徑去搞定美團的數據,這裏需要用到抓包工具Fiddler。講道理,之前我開始接觸網絡爬蟲的時候也沒有聽過這個東東,後來就慢慢知道了,而且它真的蠻實用的,建議大家都能學會用它。這個工具專門用於抓包,而且其安裝包也非常小,如下圖所示。

接下來,我們開始進行抓取信息。

1、在Fiddler的左側找到meituan網站的鏈接,如下圖所示。鏈接的左邊返回的response(響應)的文件類型,可以看到是JSON文件,爾後雙擊這一行鏈接。

2、此時在右側會显示下圖的界面,點擊黃色區域內的那串英文“Responsebody is encoded. Click to decode.”意思是response是加密的,點擊此處進行解碼,對返回的網頁進行解碼。

3、此時會彈出下圖所示的界面,在WebView中可以看到返回的數據,與網頁中的內容對應一致。

4、不過美團網限制一頁最多显示32條火鍋信息,如下圖所示。

5、如果我想獲取100條信息的話,那得前後找4頁,才能夠滿足要求。有沒有辦法讓其一次性多显示一些數據呢?答案是可以的,操作方法如下。

在左側找到對應的美團網鏈接,然後點擊右鍵一次選擇CopyàJustUrl,如下圖所示。

7、將得到的URL放到瀏覽器中去進行訪問,如下圖所示。可以看到limit=32,即代表可以獲取到32條相關的火鍋信息,並且返回的內容和Fiddler抓包工具返回的信息是一致的。

8、此時,我們直接在瀏覽器中將limit=32這個參數改為limit=100,也就是說將32更改為100,讓其一次性返回100條火鍋數據,天助我也,竟然可以一次性訪問到,如下圖所示。就這樣,輕輕鬆松的拿到了一百條數據。

9、接下來,可以將瀏覽器返回的數據進行Ctrl+A全部選中,放到一個本地文件中去,存為txt格式,在sublime中打開,如下圖所示。

10、其實乍一看覺得很亂,其實它就是一個JSON文件,剩下的工作就是對這個JSON文件做字符串的提取,寫個代碼,提取我們的目標信息,包括店門、星級、評論數、關鍵詞、地址、人均消費等,如下圖所示。

11、運行程序之後,我們會得到一個txt文件,列與列之間以製表符分開,如下圖所示。

12、在txt文件中看上去很是費勁,將其導入到Excel文件中去,就清晰多了,如下圖所示。接下來就可以很方便的對數據做分析什麼的了。

13、至此,抓取美團火鍋數據的簡易方法就介紹到這裏了,希望小夥伴們都可以學會,以後抓取類似的數據就不用找他人幫你寫程序啦~~

14、關於本文涉及的部分代碼,小編已經上傳到github了,後台回復【美團火鍋】四個字即可獲取。

看完本文有收穫?請轉發分享給更多的人

IT共享之家

入群請在微信後台回復【入群】

想學習更多Python網絡爬蟲與數據挖掘知識,可前往專業網站:http://pdcfighting.com/

本站聲明:網站內容來源於博客園,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

※自行創業缺乏曝光? 網頁設計幫您第一時間規劃公司的形象門面

※網頁設計一頭霧水該從何著手呢? 台北網頁設計公司幫您輕鬆架站!

※想知道最厲害的網頁設計公司"嚨底家"!

※別再煩惱如何寫文案,掌握八大原則!

※產品缺大量曝光嗎?你需要的是一流包裝設計!

TestLoader源碼解析

 1 def loadTestsFromTestCase(self, testCaseClass) #看名稱分析:從TestCase找測試集--那麼就是把我們的def用例加載到testSuit裏面
 2 def loadTestsFromModule(self, module, *args, pattern=None, **kws) #看名稱分析:從模塊裏面找測試集,那麼 模塊>類>test_方法>添加到testSuit裏面
 3 def loadTestsFromName(self, name, module=None)  #看名稱分析: 接收到name直接添加到testSuit裏面
 4 def loadTestsFromNames(self, names, module=None) #看名稱分析:接受到的是一個包含測試test_方法的列表
 5 def getTestCaseNames(self, testCaseClass) # 看名稱分析:  取出一個包含test_方法的列表
 6 def discover(self, start_dir, pattern='test*.py', top_level_dir=None) ## 看名稱分析:發現--找test_方法
 7 def _get_directory_containing_module(self, module_name) #獲取目錄包含的模塊
 8 def _get_name_from_path(self, path)  #從路徑從找名稱
 9 def _get_module_from_name(self, name)  #從名稱找模塊
10 def _match_path(self, path, full_path, pattern)   #正則匹配路徑--參數包含pattern 那估計是匹配我們測試腳本格式的
11 def _find_tests(self, start_dir, pattern, namespace=False)  #找測試集合
12 def _find_test_path(self, full_path, pattern, namespace=False)  #找測試集合的路徑

View Code

 1 那就是1234
 2 一個discover,getTest,_match_path
 3 二個find
 4 三個_get
 5 四個loadTests
 6 
 7 discover  邏輯
 8 >
 9 _find_tests【兩個處理邏輯  一個是本次傳的目錄和上次傳的一樣或不一樣,】
10 【一樣:直接從我們傳的目錄下面繼續去找testcaose---】
11 【不一樣:會從我們傳的目錄下面去執行os.path.listdir找到所有的子文件列表paths(文件)),然後遍歷得到單獨的path做 start_dir+path拼接】
12 >
13 ①—get_name_from_path【傳入start_dir,判斷當前傳入的目錄是否為上次傳入的頂級目錄返回".",不一樣可能有點繞-並返回一個值這個值有四種情況 . test  ...test dir.tests--正常應該是返回test文件名
14 ②_find_test_path(self, full_path, pattern, namespace=False)
15 【執行這個從路徑中找test,那麼很明顯 一樣:傳目錄路徑   不一樣傳文件路徑  】
16 _find_test_path

View Code

 1 class TestLoader(object):
 2     """
 3     This class is responsible for loading tests according to various criteria
 4     and returning them wrapped in a TestSuite
 5     """
 6     testMethodPrefix = 'test'
 7     sortTestMethodsUsing = staticmethod(util.three_way_cmp)
 8     suiteClass = suite.TestSuite
 9     _top_level_dir = None
10 
11     def __init__(self):
12         super(TestLoader, self).__init__()
13         self.errors = []
14         # Tracks packages which we have called into via load_tests, to
15         # avoid infinite re-entrancy.
16         self._loading_packages = set()  #這裏創建了一個空的self._loading_packages={}無序且不重複的元素集合

View Code 1.discover方法:unittest.defaultTestLoader a.定義了三個布爾值屬性 is_not_importable==True則是不能導入,is_namespace ,set_implicit_top b.對頂層目錄做了處理–當服務首次啟動 執行unittest.defaultTestLoader.discover(“文件目錄A”,pattern,top_level_dir=None):self._top_level_dir = top_level_dir = start_dir 這三個相等。 再次執行unittest.defaultTestLoader.discover(“文件目錄B”,pattern,top_level_dir=None): top_level_dir=self._top_level_dir【也就是他會默認上次start_dir 為頂層目錄】– 無論是首次還是複次–上面的操作完成之後 self._top_level_dir = top_level_dir 仍然繼續執行了一句這個—也就是說 self._top_level_dir == top_level_dir 始終一樣 c.針對頂層目錄不是一個目錄文件做了一系列的處理如果你傳的目錄是一個可導入的模塊-他在這個異常處理中.會重新自導入這個模塊。並開始追尋他的絕對路徑,判斷其模塊的可用性,然後執行_find_tests()尋找用例 d.如果是一個目錄就直接開始執行_find_tests()尋找用例 e.所以他這裏分兩種情況可以找到用例 第一種:傳的目錄 第二種:傳入的可導入模塊-這種情況self._top_level_dir 最終也是一個絕對路徑

  1 def discover(self, start_dir, pattern='test*.py', top_level_dir=None):  #一般我們top_level_dir傳的都None
  2     set_implicit_top = False   #是否存在頂層目錄
  3     if top_level_dir is None and self._top_level_dir is not None:
  4         # make top_level_dir optional if called from load_tests in a package
  5         top_level_dir = self._top_level_dir  #複次走這裏
  6     elif top_level_dir is None:  #初次走這裏
  7         set_implicit_top = True
  8         top_level_dir = start_dir
  9     #上面這一串花里胡哨的東西就是處理頂層目錄-如果是第一次啟動服務-
 10     #就走elif-top_level_dir==我們下面傳的值--之後--self._top_level_dir就不為空了,
 11     #但是top_level_dir  頂部是處理==None所以會走if=True
 12     top_level_dir = os.path.abspath(top_level_dir)#轉絕對路徑
 13     if not top_level_dir in sys.path:
 14         #這裡是防止重複將top_level_dir加入執行目錄--BUT如果我第一次傳的start_dir=a,第二次傳的start_dir=b
 15         #分析一下--第一次就是把a加入到了執行目錄---下面self._top_level_dir=a 二次(複次)傳b的時候,會出現top_level_dir=a---並沒有判斷b是否在執行目錄
 16         #這裏加一波問號?????????????????????
 17         #但是一般情況 我們目錄就只有一個--所以這裏--先放着。。。先看後面再來看這裏
 18         # all test modules must be importable from the top level directory
 19         # should we *unconditionally* put the start directory in first
 20         # in sys.path to minimise likelihood of conflicts between installed
 21         # modules and development versions?
 22         sys.path.insert(0, top_level_dir)
 23     self._top_level_dir = top_level_dir
 24     #如果top_level_dir我們傳的目錄不在可執行目錄--則臨時添加進去
 25     is_not_importable = False  #是否  不能導入
 26     is_namespace = False    #is_namespace那麼這個字段的意思就是是否可以找到傳入的路徑
 27     tests = []
 28     if os.path.isdir(os.path.abspath(start_dir)):  #判斷我們傳的是否為一個目錄--實際上這裏直接用top_level_dir不香嗎--
 29         start_dir = os.path.abspath(start_dir)
 30         # 之前把top_level_dir = start_dir
 31         # 然後top_level_dir = os.path.abspath(top_level_dir)
 32         #現在start_dir = os.path.abspath(start_dir)
 33         #為什麼不   直接用top_level_dir?  小朋友你是否有許多問號
 34         # 問題出在上面--複次的時候--並沒有走 top_level_dir = start_dir 而是走的 top_level_dir = self._top_level_dir ,
 35         #所以如果我們上次傳的路徑如果和這次不一樣--那麼top_level_dir是不等於start_dir--而start_dir才是我們傳的--
 36         if start_dir != top_level_dir:  #所以這裏相當於判斷前後傳的路徑是否一樣--一般來說我們的start_dir都是等於top_level_dir的
 37             is_not_importable = not os.path.isfile(os.path.join(start_dir, '__init__.py'))#如果是一個文件返回false
 38             #如果不一樣--則判斷我們當前傳入的start_dir/__init__.py是不是一個正確的文件路徑..os.path.isfile() 返回布爾值
 39     else: #如果我們傳入的不是一個目錄,就開始一堆花里胡哨的報錯東西了。。暫時不用管
 40         # support for discovery from dotted module names
 41         try:
 42             __import__(start_dir)
 43             #這裏就很有意思了---__impor__("PyFiles.Besettest")那就是導入PyFiles
 44             #那麼也就是說這個97.33的概率會報錯--也就是說你如果目錄錯了--下面的else基本不會走。。。除非你很神奇的填的路徑右側是一個可導入的模塊
 45         except ImportError:
 46             is_not_importable = True   #如果導入不鳥--就is_not_importable設置為true  在這裏我清楚了這個字段的含義--不能導入=true
 47         else:#那麼這裏假設導入成功之後
 48             the_module = sys.modules[start_dir]  #這裡是如果我們導入成功--就走這裏-取出start_dir導入的賦值給the_module
 49             top_part = start_dir.split('.')[0]    #這裡是將我們導入的模塊名稱取出來
 50             try:
 51                 start_dir = os.path.abspath( #打印導入模塊所在目錄的絕對路徑
 52                    os.path.dirname((the_module.__file__)))
 53             except AttributeError:     #這裡是如果導入模塊成功了---但是尼瑪打印導入模塊的絕對路徑又報錯--不想看了+2
 54                 # look for namespace packages
 55                 try:  #然後有開始進行模塊導入檢查---日了狗了。。。。。
 56                     #  fuck----想直接關機了+1,這裏估計是想找到為什麼不能導入的原因。。大神的思路就是完美,如果是我就拋出一個目錄不對就完事
 57                     #這一塊的學習 文檔 Python標準模塊--import
 58                     spec = the_module.__spec__
 59                     #將導入成功的模塊的規格說明賦值給spec--
 60                     #打印出來就是ModuleSpec(name='besettest.interface', loader=<_frozen_importlib_external.SourceFileLoader object at 0x0000000003D6E780>, origin='E:\\PyFiles\\Besettest\\besettest\\interface\\__init__.py', submodule_search_locations=['E:\\PyFiles\\Besettest\\besettest\\interface'])
 61                     #這麼一串東西--也沒用過。。。。大概就是模塊名稱、路徑、導入的模塊對象吧
 62                     #origin 加載模塊的位置--
 63                     #loader_state模塊特定數據的容器
 64                 except AttributeError:  #如果模塊的規格說明取不出來。。。。。。。
 65                     spec = None   #我查閱了一下。。。的確存在部分模塊規格說明為None的--所以還得繼續往下看
 66 
 67                 if spec and spec.loader is None:   #如果存在規格說明 且 數據容器為None。
 68                     if spec.submodule_search_locations is not None: 
 69                         #這是個什麼玩意呢--模塊 搜索 位置s(列表)。。。
 70                         is_namespace = True    #如果spec.submodule_search_locations不為none -----
 71                         # 2.5級英文翻譯 就是模塊的路徑 如果模塊路徑不為空is_namespace可以找到---is_namespace那麼這個字段的意思就是存在命名空間。。就是可以找到這個模塊
 72                         for path in the_module.__path__:   #這裏我研究懷疑是故意提升逼格。。the_module.__path__==spec.submodule_search_locations
 73                             if (not set_implicit_top and     #首次set_implicit_top==True  複次set_implicit_top==Fase
 74                                 not path.startswith(top_level_dir)):
 75                                 continue
 76                                 #這裏讓我稍微有點疑惑。。為什麼要判斷是首次還是複次--我猜是判斷the_module.__path__列表裡面有幾個某塊的路徑
 77                                 #如果是首次直接下一步--如果是複次會有多個路徑。但是如果是複次top_level_dir這個路徑又是上次的。。。日
 78                                 #他的作用是找到導入模塊的路徑-知道這個就行。。。
 79                             self._top_level_dir = \
 80                                 (path.split(the_module.__name__
 81                                      .replace(".", os.path.sep))[0])   #取出導入模塊的上級目錄絕對路徑。。。
 82                             #the_module.__name__.replace(".", os.path.sep) 這一串我看來是沒有必要的。。因為  the_module.__name__既然取到了模塊名稱那他肯定是一個字符串
 83                             tests.extend(self._find_tests(path,     #然後調用_find_tests 尋找測試。加入tests列表--這個有點熟悉的味道---
 84                                                           pattern,  #我覺得基本不會走這裏去找---腳本路徑一般都會填對  填錯了,都不知道執行到哪裡去了。。。
 85                                                           namespace=True))
 86                 elif the_module.__name__ in sys.builtin_module_names:
 87                     #判斷 sys.builtin_module_names返回一個列表,包含所有已經編譯到Python解釋器里的模塊的名字 和sys.models是一個字典
 88                     #就是沒法導入報錯
 89                     # builtin module
 90                     raise TypeError('Can not use builtin modules '
 91                                     'as dotted module names') from None
 92                 else:  #沒發現這個模塊
 93                     raise TypeError(
 94                         'don\'t know how to discover from {!r}'
 95                         .format(the_module)) from None
 96 
 97             if set_implicit_top:     #如果是首次。。。。
 98                 if not is_namespace:  #is_namespace默認的是false-且可以找到模塊相關規格
 99                     self._top_level_dir = \
100                        self._get_directory_containing_module(top_part)  #interface.testFiles   interface假設這個是導入的 -self._top_level_dir 是一個目錄的絕對路徑
101                     #top_part導入的模塊名稱-----
102                     sys.path.remove(top_level_dir)    #只知道是從系統路徑移除--但是不知道為什麼移除。。。。
103                 else:
104                     sys.path.remove(top_level_dir)   #
105 
106     if is_not_importable:   #如果我們傳的文件不能導入---就直接拋出異常
107         raise ImportError('Start directory is not importable: %r' % start_dir)
108 
109     if not is_namespace:  #is_namespace默認的是false--這裏就是可以找到模塊。。。。
110         tests = list(self._find_tests(start_dir, pattern))
111     return self.suiteClass(tests)

View Code 2._find_tests()–尋找testCase並生成測試套件tests=[]

 1 def _find_tests(self, start_dir, pattern, namespace=False):   #注意這裏如果我們傳的不是腳本目錄而是一個可導入的模塊namespace是等於True的
 2     """Used by discovery. Yields test suites it loads."""
 3     # Handle the __init__ in this package
 4     name = self._get_name_from_path(start_dir)   #返回一個name   name存在三種返回情況  "."-當本次和上次傳入的start_dir一致     不一致  "文件名"    "...文件名"
 5      #get_name_from_path的邏輯在這裏就很清晰了 
 6           
 7     # name is '.' when start_dir == top_level_dir (and top_level_dir is by
 8     # definition not a package).
 9     if name != '.' and name not in self._loading_packages:  
10     #當name最少有一個且也不再self._loading_packages.【self._loading_packages初始化的時候建的空集合】 走下面這個
11         # name is in self._loading_packages while we have called into
12         # loadTestsFromModule with name.
13         tests, should_recurse = self._find_test_path(      #然後這裏start_dir是我們傳的模塊--他就去找。。這裏就恢復到了傳測試目錄的邏輯了
14             start_dir, pattern, namespace)
15         if tests is not None:
16             yield tests
17         if not should_recurse:
18             # Either an error occurred, or load_tests was used by the
19             # package.
20             return
21     # Handle the contents.
22     paths = sorted(os.listdir(start_dir))  #那就從這裏開始--當我們穿的目錄和上次一樣-他會找到目錄下所有的文件然後排序--我們的用例執行順序就是從這裏開始搞了。。
23     for path in paths:       #遍歷我們傳的目錄下的所有文件
24         full_path = os.path.join(start_dir, path)     將我們傳入的目錄和目錄下的py文件拼接的完整路徑
25         tests, should_recurse = self._find_test_path(    #把我們文件路徑和我們的文件格式傳入_find_test_path這個方法--
26             full_path, pattern, namespace)
27         如果當前傳的是一個目錄-會返回should_recurse=True--這個英文直譯是應該_遞歸--下面yield from 就是執行遞歸的操作
28         if tests is not None:
29             yield tests
30         if should_recurse:  #這句是判斷他是不是一個目錄
31             # we found a package that didn't use load_tests.
32             name = self._get_name_from_path(full_path)
33             self._loading_packages.add(name)
34             try:
35                 yield from self._find_tests(full_path, pattern, namespace)
36             finally:
37                 self._loading_packages.discard(name)

View Code yield與yield from

 1 def  a(n):
 2     testList=b(n)
 3     return testList
 4 
 5 def  b(n,m=1):
 6     print("執行第%s次"%m)
 7     for a in range(n):
 8         if not divmod(a,2)[1] and a!=0:
 9             print(a)
10             yield a   #是用yield之後返回的是一個生成器
11             if divmod(a,3)[1]:
12                 m =m+1
13                 yield from b(a,m)  #重新執行b方法
14 
15 print(list(a(7)))
16 
17 執行第1次
18 2
19 執行第2次
20 4
21 執行第3次
22 2
23 執行第4次
24 6
25 [2, 4, 2, 6]

View Code _get_name_from_path 他主要做了:從我們傳的路徑裏面找腳本文件 名。。。如果找到了腳本文件則返迴文件名稱—如果沒找到也就是我們返回一個點 或者 至少一個點(三種情況 . test_case …..test_case 返回name可能存在的三種值) 在_test_find調用這個方法path=start_dir(我們傳的目錄)–這個返回一個點 或者 至少一個點 在 _fin_test_path調用這個方法是傳的我們傳的目錄下的文件路口–返回的name就是文件名

 1 name = self._get_name_from_path(start_dir)    #因為discover我們是支持我們傳目錄或者模塊尋找testcase的,所以這個方法
 2 
 3 def _get_name_from_path(self, path):
 4      #主要正確邏輯三個 比如我們的腳本目錄結構是  E://a/b/     b目錄下面有script.py    和  /c/script.py
 5      #第一次是我們自己傳的目錄--之前在discover他做了一個處理 就是第一次運行時會把我們傳的目錄賦值給頂層目錄---
 6      #第一個邏輯判斷我們傳的是不是  -和頂層一樣---一樣的話===_find_tests方法就直接從目錄下面找腳本-當如如果有目錄也會繼續走--他是在_find_tests_path判斷的--最終也是回到找腳本模塊上
 7      #如果不一樣--那就是找了 找到這個目錄了---那麼就從頂層開始找這個目錄的相對路徑--其實就是找最後那個目錄(必須是一個packge。上面說的目錄都是包)、。。然後返回一個name
 8      #如果還有子目錄  d---那就會返回  c.d
 9     if path == self._top_level_dir:  #首次運行pattern,top_level_dir=None):self._top_level_dir = top_level_dir = start_dir -第二次運行如果目錄沒有變,這裏也是直接返回的
10         return '.'
11         
12         
13     path = _jython_aware_splitext(os.path.normpath(path))  #如果我們當前傳的和上次傳的目錄不一致。。這裏得path我們當前傳的路徑
14     
15     _relpath = os.path.relpath(path, self._top_level_dir)    #從self._top_level_dir開始找path的相對路徑
16     #這裡是從我們傳的path開始找到self._top_level_dir上次傳的相對路徑 
17     #例如: path=path1="E:\\PyFiles\\Besettest\\besettest\\interface\\testFiles"   self._top_level_dir="E:\\PyFiles\\Besettest\\besettest\\interface\\result"
18     #那麼_relpath="..\testFiles"    -暫時還不清楚為什麼要找這個??????????????????????????
19     assert not os.path.isabs(_relpath), "Path must be within the project"
20     #↑↑斷言 不是絕對路徑-也就是說_relpath是否為相對路徑↑↑↑特么的 這裏肯定是一個相對路徑啊。。。上面都有relpath了。。。丟
21     #↓↓↓↓斷言以..開頭就失敗。。。↓↓↓--這兩處超出理解範圍了。。。。。。
22     assert not _relpath.startswith('..'), "Path must be within the project"
23     
24     name = _relpath.replace(os.path.sep, '.')  #然後這裏又把分隔符替換成.  返回 a.b  當然或許會有異常情況返回.....這是我意淫的
25     return name

View Code self._find_test_path #找測試的路徑 兩個主要邏輯: 傳到full_path 是一個文件 還是一個目錄

 1 def _find_test_path(self, full_path, pattern, namespace=False):  
 2  #_find_tests()調用這個方法 傳了一個我們傳的目錄下的a文件路徑、和需要找的文件pattern-namespace【傳的可能是true 也可能是false】,如果我的目錄是對的-namespace傳的就是false
 3     """Used by discovery.
 4 
 5     Loads tests from a single file, or a directories' __init__.py when
 6     passed the directory.
 7 
 8     Returns a tuple (None_or_tests_from_file, should_recurse).
 9     """
10     basename = os.path.basename(full_path)   #basename==文件名.py後續帶py的統稱文件--不帶後綴的統稱文件名。。。
11     if os.path.isfile(full_path):   #如果我們傳的full_path是一個文件---我們在discover傳的是一個腳本目錄-之前在_test_find是做了一個拼接得到的完整路徑full_path
12         if not VALID_MODULE_NAME.match(basename): #判斷他是不是一個py文件----
13             
14             # valid Python identifiers only
15             return None, False   #如果不是直接返回
16         if not self._match_path(basename, full_path, pattern):  #這裏雖然傳了三個值--但是實際上只有basename,pattern有用--
17          #_match_path調用fnmatch(文件, 我們傳的文件格式或文件)這個需要————from fnmatch import fnmatch他的主要作用是做此模塊的主要作用是文件名稱的匹配
18          #當此次傳入的文件名與我們的文件格式匹配一致self._match_path返回true
19             return None, False    #如果不一樣 就直接回到 _find_test繼續找
20         # if the test file matches, load it
21         name = self._get_name_from_path(full_path)   #然後這裏把文件路徑又傳到 self._get_name_from_path去返迴文件名-這個時候因為我們傳的是腳本目錄-full_path目錄下的文件路徑,所以返回的name 就是文件名
22        
23         #self._top_level_dir是當前文件目錄路徑,path是當前文件路徑--從目錄找文件--直接就是文件名--他返回的name就是文件名   
24         try:
25             module = self._get_module_from_name(name)    #_get_module_from_name  這個方法就是動態導入模塊名--然後返回一個所有導入的模塊的對象 moudel.__file__路徑、moudel.__name__名稱
26         except case.SkipTest as e:   #如果導入不成功 case.SkipTest 實際上case是繼承--Exception--所以把這個理解為Exception就可以了--
27             return _make_skipped_test(name, e, self.suiteClass), False
28         except:
29             error_case, error_message = \
30                 _make_failed_import_test(name, self.suiteClass)   
31             self.errors.append(error_message)
32             return error_case, False
33         else: #module 獲取到值之後走這裏。。
34             mod_file = os.path.abspath(
35                 getattr(module, '__file__', full_path))    #然後這裏取出我們導入模塊的 絕對路徑---如果反射找不到就返回該文件的路徑-其實差別不大-處理一下更嚴謹
36             realpath = _jython_aware_splitext(
37                 os.path.realpath(mod_file))   #os.path.realpath(mod_file)然後又返回真實路徑---然後又去掉路徑的.py,。,,,,,,,,丟
38             fullpath_noext = _jython_aware_splitext(
39                 os.path.realpath(full_path))   #然後full_path 找真實路徑去掉.py
40             if realpath.lower() != fullpath_noext.lower():  #如果動態導入的模塊的目錄路徑 不等於 傳進來(也就是pattern)的目錄路徑--實際上傳進來的路徑肯定是個絕對路徑--因為前面已經轉了好幾次絕對路徑了
41                 module_dir = os.path.dirname(realpath)   #不等於就找動態導入模塊所在的目錄----實際上上面處理的realpath已經是一個目錄了。。但是他防止realpath還是一個.py文件。所以又操作了一次
42                 mod_name = _jython_aware_splitext(    #full_path是文件的路徑.py的,然後這裏又先是basename取出文件(就是把路徑去掉,只留下xxx.py) 然後外面那個方法 把.py去掉--留下文件名
43                     os.path.basename(full_path))
44                 expected_dir = os.path.dirname(full_path)  
45                 #然後找到需要執行腳本所在的目錄。。。。。也就是說正常情況  假設expected_dir="e://a/b"  那麼 mod_file =realpathfullpath_noext="e://a/b/scripy" 
46                 #scripy是一個py文件---上面這個if是說的 正常情況。。。我想不到導入模塊和導入模塊的路徑不相等的情況--不過這個不重要-源碼這樣肯定是有道理的
47                   msg = ("%r module incorrectly imported from %r. Expected "
48                        "%r. Is this module globally installed?")
49                 raise ImportError(
50                     msg % (mod_name, module_dir, expected_dir))
51             return self.loadTestsFromModule(module, pattern=pattern), False   
52             #然後走 從模塊從加載測試s 這個方法---也就是說discover實際上是調用loadTestsFromMould這個方法的。。測試套件也是在這一步處理的
53     elif os.path.isdir(full_path):  #dicover裏面傳腳本目錄是走這裏。。
54         if (not namespace and    #namespace-默認是false   not namespace就是true  
55             not os.path.isfile(os.path.join(full_path, '__init__.py'))): #不是一個包。。-也就是說我們傳的目錄應該是一個包,下面包含__init__.py
56             return None, False
57         
58         load_tests = None  #這個load_tests是啥意思呢??????????後面繼續看----看了一遍--並且用unittest.main()試了一下-模塊下面是沒有這個屬性的。。只是unittest的初始化文件有這個方法--他也是通過discover找的。。
59         tests = None
60         name = self._get_name_from_path(full_path)   #這裏就是走子目錄的邏輯了
61         #get_name_from_path的邏輯在這裏就很清晰了 
62             #A.如果通過_find_test 調用self._get_name_from_path  是為了判斷兩次start_dir是否一致一致返回.  不一致返回從上次的start_dir1找到本次start_dir12的相對路徑--
63             #這裏又分兩種情況-A1正常情況-start_dir1是start_dir12的上級目錄。。。那麼返回的那麼就是-A.B這樣的了。。因為第一次的A是已經os.path.insert到環境變量了..所以A.B是可以直接用
64                                 #A2不正常情況 就是之前說的 最少返回一個點的...A這種返回---然後問題來了--他為什麼要這麼處理呢--原因是?????????
65                                 #我猜是與腳本同級存在另一個腳本目錄。。。後面驗證這一點。-----這裡在上面補充了--是因為子目錄中還存在腳本所有這麼走邏輯-完美的
66         try:
67             package = self._get_module_from_name(name)   #上面是導入的一個module--這裡是導入一個包-- 返回--
68         except case.SkipTest as e:
69             return _make_skipped_test(name, e, self.suiteClass), False
70         except:
71             error_case, error_message = \
72                 _make_failed_import_test(name, self.suiteClass)
73             self.errors.append(error_message)
74             return error_case, False
75         else:
76             load_tests = getattr(package, 'load_tests', None)  #然後判斷這個包裏面有沒有'load_tests'這個屬性---這裏我代碼一直看下來,我們是不知道這個lood_tests是什麼的,字面意思 加載測試集合
77             # Mark this package as being in load_tests (possibly ;))
78             self._loading_packages.add(name)  #然後把模塊名稱添加到set集合
79             try:
80                 tests = self.loadTestsFromModule(package, pattern=pattern)   
81                 #這裏傳了一個package模塊對象,和文件匹配規則合作或者文件。。--但是這裏導入一個包之後實際上是找不到testCase的-因為包下面的屬性肯定不是一個類-不會走loadTestsFromTestCase
82                 #所以這裏返回的tests是一個空列表
83                 if load_tests is not None: #貌似這個是棄用的,向後兼容-暫時沒看明白這個load_tests代表的意思
84                     # loadTestsFromModule(package) has loaded tests for us.
85                     return tests, False
86                 return tests, True  #  如果能走到這裏------就返回True_就是給_find_test判斷走遞歸的--_find_tests裏面就得到 should_recurse=True
87             finally:
88                 self._loading_packages.discard(name)  #然後這個刪掉set集合裏面之前導入的那個包
89         else:
90         return None, False

View Code

 1 def loadTestsFromModule(self, module, *args, pattern=None, **kws):
 2     """Return a suite of all test cases contained in the given module"""
 3     # This method used to take an undocumented and unofficial
 4     # use_load_tests argument.  For backward compatibility, we still
 5     # accept the argument (which can also be the first position) but we
 6     # ignore it and issue a deprecation warning if it's present.
 7     if len(args) > 0 or 'use_load_tests' in kws:    #args這個默認是一個空元組  長度默認為0  kws是一個空字典
 8         warnings.warn('use_load_tests is deprecated and ignored',
 9                       DeprecationWarning)
10         kws.pop('use_load_tests', None)
11     if len(args) > 1:
12         # Complain about the number of arguments, but don't forget the
13         # required `module` argument.
14         complaint = len(args) + 1
15         raise TypeError('loadTestsFromModule() takes 1 positional argument but {} were given'.format(complaint))
16     if len(kws) != 0:
17         # Since the keyword arguments are unsorted (see PEP 468), just
18         # pick the alphabetically sorted first argument to complain about,
19         # if multiple were given.  At least the error message will be
20         # predictable.
21         complaint = sorted(kws)[0]  #取出第一個Key-
22         raise TypeError("loadTestsFromModule() got an unexpected keyword argument '{}'".format(complaint))
23     tests = []
24     for name in dir(module):    #這裏得modul實際上使我們傳入的模塊對象---dir(object)  返回模塊下的所有屬性列表-
25         obj = getattr(module, name)   #然後反射返回name對象。。返回的是一個class對象
26         if isinstance(obj, type) and issubclass(obj, case.TestCase):    #這裏判斷obj是否是一個類--並且這個類是case.TestCase的子類,也就是說 是否寫在我們繼承unitest.testCase那個類的下面
27             tests.append(self.loadTestsFromTestCase(obj))  #可以看到最後走loadTestFromTestCase obj這裡是傳入的一個類名
28 
29     load_tests = getattr(module, 'load_tests', None)
30     tests = self.suiteClass(tests)
31     if load_tests is not None:
32         try:
33             return load_tests(self, tests, pattern)
34         except Exception as e:
35             error_case, error_message = _make_failed_load_tests(
36                 module.__name__, e, self.suiteClass)
37             self.errors.append(error_message)
38             return error_case
39     return tests  #返回集合

View Code loadTestsFromTestCase:這裏就是添加testcase到suit集合裏面的主要邏輯

 1 def loadTestsFromTestCase(self, testCaseClass):  #testCaseClass是我們傳的一個用例類
 2     """Return a suite of all test cases contained in testCaseClass"""
 3     if issubclass(testCaseClass, suite.TestSuite):  #這個類是不是suite.TestSuite的子類--如果是的就拋出異常==
 4         raise TypeError("Test cases should not be derived from "
 5                         "TestSuite. Maybe you meant to derive from "
 6                         "TestCase?")
 7     testCaseNames = self.getTestCaseNames(testCaseClass)  #getTestCaseNames  從類下面找到用例名稱--找到名稱返回的是一個列表
 8     if not testCaseNames and hasattr(testCaseClass, 'runTest'):  #這裏判斷testCaseNames是否為空-並且 是否存在"runTest"這個元素
 9         testCaseNames = ['runTest']
10     loaded_suite = self.suiteClass(map(testCaseClass, testCaseNames)) #是我的一個用例類--然後將testCaseNames類下面的測試方法--帶進去遍歷。。。高級用法---第一次見--這個方法很關鍵
11     return loaded_suite

View Code getTestCaseNames

 1 def getTestCaseNames(self, testCaseClass):
 2     """Return a sorted sequence of method names found within testCaseClass
 3     """
 4     def isTestMethod(attrname, testCaseClass=testCaseClass,   #定義一個內部方法
 5                      prefix=self.testMethodPrefix):     #self.testMethodPrefix="test"   這個在TestLoader下第一行就已經默認了,他是我們用例開頭的固定格式
 6         return attrname.startswith(prefix) and \  #這裏判斷了是否已test開頭以及 方法對象是否可用----getattr返回方法對象--callable()是檢測對象是否可用    返回一個布爾值
 7             callable(getattr(testCaseClass, attrname))
 8     testFnNames = list(filter(isTestMethod, dir(testCaseClass)))
 9      #dir(testCaseClass)返回該對象下面所有的屬性--包括變量test_1--所以上面需要檢測屬性時test開頭且是一個可以調用的對象--
10      #filter函數---前面是一個function -後面是一個可迭代對象-會遍歷可迭代對象-並傳入function-functions返回true則添加到列表--這樣就找到了所有的
11     if self.sortTestMethodsUsing:
12         testFnNames.sort(key=functools.cmp_to_key(self.sortTestMethodsUsing))     
13         #sortTestMethodsUsing = staticmethod(util.three_way_cmp) 轉為為靜態方法-內存地址指向self.sortTestMethodsUsing
14         #然後通過functools這個模塊排序==
15     return testFnNames  #然後返回用例方法名稱列表

View Code 所有的邏輯 就是在TestLoader找測試用例的時候–通過_find_tests這個方法從目錄開始找文件(子目錄)-模塊-類-方法名 然後將某個模塊下的類通過他下面的方法map返回多個對象,也就是說一個testClass下面存在五個test_method,他就會返回五個實例對象-並生成一個suite集合–然後加入到一個列表 如果一個模塊下有多個testClasee 同樣-實際上是一樣的–實際上他是先通過loadTestsFromModule這個方法找到所有的類對象之後在遍歷–然後才走上面那一步的,,,多個testClasss就存在多個suite集合– 也就是說 一個modul下面的 suite集合會添加到一個列表–[suite=[A-TestCase實例化對象1,A-TestCase實例化對象2],suite=[B-TestCase實例化對象1,B-TestCase實例化對象2]]—然後在將這個列表當做參數傳入TestSuite實例化一個新對象[suite=-[suite=[A-TestCase實例化對象1,A-TestCase實例化對象2],suite=[B-TestCase實例化對象1,B-TestCase實例化對象2]]]—-這樣就是一個模塊下用的結構 但是還沒有完–這裏只是一個modul下的—還有多個modul–到了大家估計也知道剩下的會幹什麼了— 沒錯–當我得到modul的全部suite集合之後—這個集合最終會返回給_find_tests方法–通過生成器返回給discover–也就是將這個suite集合又加入到了一個新的列表–然後discover又將這個list 帶入形成了一個—–最終的實例對象,最終返回的格式如下——- [suite= [suite1=-[suite=[A-TestCase實例化對象1,A-TestCase實例化對象2],suite=[B-TestCase實例化對象1,B-TestCase實例化對象2]]], [suite2=-[suite=[A-TestCase實例化對象1,A-TestCase實例化對象2],suite=[B-TestCase實例化對象1,B-TestCase實例化對象2]]] ]   –看過源碼的都知道—我們run的時候—就這個實例對象是可以接受參數的–而這個參數就是result—因為TestSuite繼承的BaseTestsSuite 有一個__call__這個
魔術方法:如果在類中實現了 __call__ 方法,那麼實例對象也將成為一個可調用對象,具體百度。這裏不做過多解釋—–所以最終的suite是可以接受參數的test(result)–接受參數之後直接走——call下面的邏輯了 本站聲明:網站內容來源於博客園,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

※廣告預算用在刀口上,台北網頁設計公司幫您達到更多曝光效益

※別再煩惱如何寫文案,掌握八大原則!

※教你寫出一流的銷售文案?

※超省錢租車方案

※FB行銷專家,教你從零開始的技巧

Optional 容器類

什麼是Optional容器類

Optional 類(java.util.Optional) 是一個容器類,代表一個值存在或不存在,原來用 null 表示一個值不存在,現在 Optional 可以更好的表達這個概念。並且可以避免空指針異常。

Optional類常用方法:

Optional.of(T t) : 創建一個 Optional 實例。

Optional.empty() : 創建一個空的 Optional 實例。

Optional.ofNullable(T t):若 t 不為 null,創建 Optional 實例,否則創建空實例。

isPresent() : 判斷是否包含值。

orElse(T t) : 如果調用對象包含值,返回該值,否則返回t。

orElseGet(Supplier s) :如果調用對象包含值,返回該值,否則返回 s 獲取的值。

orElseThrow(Supplier es) : 當遇到一個不存在的值的時候,並不返回一個默認值,而是拋出異常。

map(Function f): 如果有值對其處理,並返回處理后的Optional,否則返回 Optional.empty()。

flatMap(Function mapper):與 map 類似,要求返回值必須是Optional。

filter(Predicate p):接收一個函數式接口,當符合接口時,則返回一個Optional對象,否則返回一個空的Optional對象。

示例:

 1 import org.junit.Test;
 2 import java.util.Optional;
 3 /*
 4  * Optional 容器類:用於盡量避免空指針異常
 5  *     Optional.of(T t) : 創建一個 Optional 實例
 6  *     Optional.empty() : 創建一個空的 Optional 實例
 7  *     Optional.ofNullable(T t):若 t 不為 null,創建 Optional 實例,否則創建空實例
 8  *     isPresent() : 判斷是否包含值
 9  *     ifPresent(Consumer<? super T> consumer) 判斷是否包含值,再執行 consumer
10  *     orElse(T t) :  如果調用對象包含值,返回該值,否則返回t
11  *     orElseGet(Supplier s) :如果調用對象包含值,返回該值,否則返回 s 獲取的值
12  *     orElseThrow(Supplier<? extends X> exceptionSupplier) : 當遇到一個不存在的值的時候,並不返回一個默認值,而是拋出異常
13  *     map(Function f): 如果有值對其處理,並返回處理后的Optional,否則返回 Optional.empty()
14  *     flatMap(Function mapper):與 map 類似,要求返回值必須是Optional
15  *     filter(Predicate<? super T> predicate):接收一個函數式接口,當符合接口時,則返回一個Optional對象,否則返回一個空的Optional對象
16  *
17  *     map、flatMap 和 filter 的使用方法和 StreamAPI 中的一樣
18  */
19 public class TestOptional {
20 
21 
22     /**
23      * 創建 Optional 實例
24      */
25     @Test
26     public void test1(){
27 
28         // Optional.empty() : 創建一個空的 Optional 實例
29         Optional<String> empty = Optional.empty();
30         System.out.println(empty);// 輸出結果:Optional.empty
31         //System.out.println(empty.get());// 報錯:java.util.NoSuchElementException: No value present
32 
33         //    Optional.of(T t) : 創建一個 Optional 實例
34         Optional<Employee> eop = Optional.of(new Employee());
35         System.out.println(eop);// 輸出結果:Optional[Employee{name='null', age=null, gender=null, salary=null, status=null}]
36         System.out.println(eop.get());//輸出結果:Employee{name='null', age=null, gender=null, salary=null, status=null}
37 
38         //注意:Optional.of(T t) 中,傳遞給of()的值不可以為空,否則會拋出空指針異常
39         //Optional<Employee> eop1 = Optional.of(null);//這一行直接報錯:java.lang.NullPointerException
40 
41         //Optional.ofNullable(T t):若 t 不為 null,創建 Optional 實例,否則創建空實例
42         //所以,在創建Optional對象時,如果傳入的參數不確定是否會為Null時,就可以使用 Optional.ofNullable(T t) 方式創建實例。
43         Optional<Object> op = Optional.ofNullable(null);//這樣的效果和 Optional.empty() 一樣
44         System.out.println(op);//Optional.empty
45         op = Optional.ofNullable(new Employee());//
46         System.out.println(op);//Optional[Employee{name='null', age=null, gender=null, salary=null, status=null}]
47     }
48 
49 
50     /**
51      * isPresent() : 判斷是否包含值
52      * ifPresent(Consumer<? super T> consumer) 判斷是否包含值,再執行 consumer
53      */
54     @Test
55     public void test2(){
56         Optional<Employee> opt = Optional.of(new Employee());
57         System.out.println(opt.isPresent());//輸出結果:true
58         opt = Optional.ofNullable(null);
59         System.out.println(opt.isPresent());//輸出結果:false
60         opt.ifPresent(employee -> System.out.println(employee));// 如果 opt.isPresent() 為false ,這裏就不輸出,否則就輸出 employee
61     }
62 
63     /**
64      *     orElse(T t) :  如果調用對象包含值,返回該值,否則返回t
65      *     orElseGet(Supplier s) :如果調用對象包含值,返回該值,否則返回 s 獲取的值
66      */
67     @Test
68     public void test3(){
69         //Optional<Employee> opt = Optional.of(new Employee());
70         Optional<Employee> opt = Optional.ofNullable(null);
71         /*Employee emp = opt.orElse(new Employee("張三",20));
72         System.out.println(emp);*/
73 
74         int condition = 2;//模擬條件
75         Employee emp = opt.orElseGet(()-> {
76             if (condition == 1){
77                 return new Employee("李四");
78             }else if (condition == 2){
79                 return new Employee("王二麻子");
80             }else {
81                 return new Employee("趙六");
82             }
83         });
84         System.out.println(emp);
85     }
86     /**
87      *     orElseThrow(Supplier<? extends X> exceptionSupplier) : 當遇到一個不存在的值的時候,並不返回一個默認值,而是拋出異常
88      */
89     @Test
90     public void test4(){
91         Object obj = Optional.ofNullable(null).orElseThrow(IllegalArgumentException::new);//當參數為null,則拋出一個不合法的參數異常
92         System.out.println(obj);
93     }
94 
95 
96 }

備註:map、flatMap 和 filter 的使用方法和 StreamAPI 中的一樣,如需了解詳細使用方法,請參考:Stream API 詳解

 

本站聲明:網站內容來源於博客園,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

※別再煩惱如何寫文案,掌握八大原則!

※網頁設計一頭霧水該從何著手呢? 台北網頁設計公司幫您輕鬆架站!

※超省錢租車方案

※教你寫出一流的銷售文案?

※網頁設計最專業,超強功能平台可客製化

IDEA自定義類註釋和方法註釋(自定義groovyScript方法實現多行參數註釋)

一、類註釋

1、打開設置面板:file -> setting -> Editor -> file and code Templates

選擇其中的inclues選項卡,並選擇File header,如圖。不要選擇Files選項卡再設置Class,這樣比較麻煩,而且這樣設置以後沒新建一個類都要自己寫一次Date。

2、在右邊編輯面板插入自己想要的註釋即可。其中${}是變量,需要在變量基本都在編輯款下面的Description,往下拉即可看到。
/*
* @Classname ${NAME}
*
* @Date ${DATE}
*
* @userName
*/
3、新建一個類,看是否自動加了註釋

 

二、方法註釋

1、打開設置面板:file -> setting -> Editor -> Live Templates

 

 2、新建一個Template Group…,命名隨意,假設為bokeyuan,然後選擇該組,點擊新建一個模板Live Template

 

3、名稱建議設為*,文本框輸入自己想要設置的註釋格式,右下角要選擇enter(原本是tab)。

 

 4、留意註釋格式,其中參數要直接寫變量$param$,開頭只有一個*號。寫好之後點擊上圖框中的edit variables

 

其中返回值return使用系統自帶的,下拉可以找到methodReturnType()

 

 

 5、自定義多行參數註釋

IDEA自帶的參數函數methodParameters()產出的註釋格式是這樣的:

/**
      * 
      * @param [a,b,c]
      * @return void
      * @throws 
      */

我們可能需要的是多行參數註釋:

/**
      * 
      * @param a
      * @param b
      * @param c
      * @return void
      * @throws 
      */

這個時候就要使用裏面的groovyScript()函數來自定義格式:

groovyScript("def result=''; def params=\"${_1}\".replaceAll('[\\\\[|\\\\]|\\\\s]', '').split(',').toList(); for(i = 0; i < params.size(); i++) {if(i == 0) result += '* @param ' + params[i] + ' ' + ((i < params.size() - 1) ? '\\n' : '');else result+='  * @param ' + params[i] + ' ' + ((i < params.size() - 1) ? '\\n' : '')}; return result", methodParameters())

直接複製在Expression裏面即可。

 

6、選擇語言,點擊Define勾選Java

 

 

有其他問題可以評論問我哦

本站聲明:網站內容來源於博客園,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

※教你寫出一流的銷售文案?

※廣告預算用在刀口上,台北網頁設計公司幫您達到更多曝光效益

※回頭車貨運收費標準

※別再煩惱如何寫文案,掌握八大原則!

※超省錢租車方案

※產品缺大量曝光嗎?你需要的是一流包裝設計!