帶你學夠浪:Go語言基礎系列 – 8分鐘學控制流語句

文章每周持續更新,原創不易,「三連」讓更多人看到是對我最大的肯定。可以微信搜索公眾號「 後端技術學堂 」第一時間閱讀(一般比博客早更新一到兩篇)

對於一般的語言使用者來說 ,20% 的語言特性就能夠滿足 80% 的使用需求,剩下在使用中掌握。基於這一理論,Go 基礎系列的文章不會刻意追求面面俱到,但該有知識點都會覆蓋,目的是帶你快跑趕上 Golang 這趟新車。

Hurry up , Let’s go !

控制語句是程序的靈魂,有了它們程序才能完成各種邏輯,今天我們就來學習 Go 中的各種控制語句。

通過本文的學習你將掌握以下知識:

  • if 條件語句
  • for 循環語句
  • switch 語句
  • defer 延遲調用

if 條件語句

與大多數編程語言一樣,if 用於條件判斷,當條件表達式 exprtrue 執行 {} 包裹的消息體語句,否則不執行。

語法是這樣的:

if expr {
    // some code
}

**注意:**語法上和 c 語言不同的是不用在條件表達式 expr 外帶括號,和 python 的語法類似。

當然,如果想在條件不滿足的時候做點啥,就可以 if 后帶 else 語句。語法:

if expr {
    // some code
} else {
    // another code
}

不僅僅是 if

除了可以在 if 中做條件判斷之外,在 Golang 中你甚至可以在 if 的條件表達式前執行一個簡單的語句。

舉個例子:

if x2 := 1; x2 > 10 { 
    fmt.Println("x2 great than 10")
} else {
    fmt.Println("x2 less than 10", x2)
}

上面的例子在 if 語句中先聲明並賦值了 x2,之後對 x2 做條件判斷。

注意:此處在 if 內聲明的變量 x2 作用域僅限於 if 和else 語句。

for循環語句

當需要重複執行的時候需要用到循環語句,Go 中只有 for 這一種循環語句。

標準的for循環語法:

for 初始化語句; 條件表達式; 後置語句 {
    // some code
}

這種語法形式和 C 語言中 for 循環寫法還是很像的,不同的是不用把這三個部分用 () 括起來。循環執行邏輯:

  • 初始化語句:初始循環時執行一次,做一些初始化工作,一般是循環變量的聲明和賦值。
  • 條件表達式:在每次循環前對條件表達式求值操作,若求值結果是
    true 則執行循環體內語句,否則不執行。
  • 後置語句:在每次循環的結尾執行,一般是做循環變量的自增操作。

舉個例子:

sum := 0
for i := 0; i < 10; i++ {
    sum += i // i作用域只在for語句內
    fmt.Println(i, sum)
}

注意:循環變量i 的作用域只在 for 語句內,超出這個範圍就不能使用了。

while循環怎麼寫?

前面說了,Golang 中只有 for 這一種循環語法,那有沒有類似 C 語言中 while 循環的寫法呢?答案是有的:把 for 語句的前後兩部分省略,只留中間的「條件表達式」的 for 語句等價於 while 循環。

像下面這樣:

sum1 := 0
for ;sum1 < 10; { // 可以省略初始化語句和後置語句
    sum1++
    fmt.Println(sum1)
}

上面的示例沒有初始化語句和後置語句,會循環執行 10 次後退出。

當然你要是覺得前後的分號也不想寫了,也可以省略不寫,上面的代碼和下面是等效的:

sum1 := 0
for sum1 < 10 { // 可以省略初始化語句和後置語句,分號也能省略
    sum1++
    fmt.Println(sum1)
}

在 Golang 中死循環可以這樣寫,相當於 C 語言中的 while(true)

 for { // 死循環
  // your code
 }

switch 語句

switch 語句可以簡化多個 if-else 條件判斷寫法,避免代碼看起來雜亂。

可以先定義變量,然後在 switch 中使用這個變量。

 a := 1
 switch a {
 case 1: 
  fmt.Println("case 1") // 不用寫break 執行到這自動跳出
 case 2:
  fmt.Println("case 2")
 default:
  fmt.Printf("unexpect case")
 }
輸出:case 1

從 C 語言過來的朋友一定有這樣的經歷:經常會在 case 語句中漏掉 break 導致程序繼續往下執行,從而產生奇奇怪怪的 bug ,這種問題在 Golang 中不復存在了。

Golang 在每個 case 後面隱式提供 break 語句。 除非以 fallthrough 語句結束,否則分支會自動終止。

 switch a := 1; a { //這裡有分號
 case 1: // case 無需為常量,且取值不必為整數。
  fmt.Println("case 1") // 不用寫break 執行到自動跳出 除非以 fallthrough 語句結束
  fallthrough
 case 2:
  fmt.Println("case 2")
 default:
  fmt.Printf("unexpect case")
 }
輸出:
case 1
case 2

還可以直接在 switch 中定義變量后使用,但是要注意變量定義之後又分號,比如下面這樣:

 switch b :=1; b { //注意這裡有分號
 case 1: 
  fmt.Println("case 1") 
 case 2:
  fmt.Println("case 2")
 default:
  fmt.Printf("unexpect case")
 }

沒有條件的switch

沒有條件的 switch 同 switch true 一樣,只有當 case 中的表達式值為「真」時才執行,這種形式能簡化複雜的 if-else-if else 語法。

下面是用 if 來寫多重條件判斷,這裏寫的比較簡單若是再多幾個 else if 代碼結構看起來會更糟糕。

    a := 1
    if a > 0 {
        fmt.Println("case 1") 
    } else if a < 0 {
        fmt.Println("case 2")   
    } else {
        fmt.Printf("unexpect case")   
    }

如果用上不帶條件的 switch 語句,寫出來就會簡潔很多,像下面這樣。

 a := 1
 switch {    // 相當於switch true
 case a > 0: // 若表達式為「真」則執行 
  fmt.Println("case 1") 
 case a < 0:
  fmt.Println("case 2")
 default:
  fmt.Printf("unexpect case")
 }

defer 語句

defer 語句有延遲調用的效果。具體來說defer後面的函數調用會被壓入堆棧,當外層函數返回才會對壓棧的函數按後進先出順序調用。說起來有點抽象,舉個例子:

package main

import "fmt"

func main() {
 fmt.Println("entry main")
 for i := 0; i < 6; i++ {
  defer fmt.Println(i)
 }
 fmt.Println("exit main")
}

fmt.Println(i) 不會每次立即執行,而是在 main 函數返回之後才依次調用,編譯運行上述程序的輸出:

entry main
exit main  //外層函數返回
5
4
3
2
1
0

上面是簡單的使用示例,實際使用中defer 通常用來釋放函數內部變量,因為它可以在外層函數 return 之後繼續執行一些清理動作。

這在文件類操作異常處理中非常實用,比如用於釋放文件描述符,我們以後會講解這塊應用,總之先記住 defer 延遲調用的特點。

總結

通過本文的學習,我們掌握了 Golang 中基本的控制流語句,利用這些控制語句加上一節介紹的變量等基礎知識,可以構成豐富的程序邏輯,就能用 Golang 來做一些有意思的事情了。

感謝各位的閱讀,文章的目的是分享對知識的理解,技術類文章我都會反覆求證以求最大程度保證準確性,若文中出現明顯紕漏也歡迎指出,我們一起在探討中學習.

今天的技術分享就到這裏,我們下期再見。

創作不易,白票不是好習慣,如果在我這有收穫,動動手指「點贊」「關注」是對我持續創作的最大支持。

微信搜索公眾號「 後端技術學堂 」回復「資料」「1024」有我給你準備的各種編程學習資料。文章每周持續更新,我們下期見!

本文使用 mdnice 排版

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

【其他文章推薦】

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

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

※想知道購買電動車哪裡補助最多?台中電動車補助資訊懶人包彙整

南投搬家公司費用,距離,噸數怎麼算?達人教你簡易估價知識!

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

※超省錢租車方案

※回頭車貨運收費標準

異步函數async await在wpf都做了什麼?

首先我們來看一段控制台應用代碼:

 class Program
 {
     static async Task Main(string[] args)
     {
        System.Console.WriteLine($"Thread Id is Thread:{Thread.CurrentThread.ManagedThreadId},Is Thread Pool:{Thread.CurrentThread.IsThreadPoolThread}");
        var result = await ExampleTask(2);
        System.Console.WriteLine($"Thread Id is Thread:{Thread.CurrentThread.ManagedThreadId},Is Thread Pool:{Thread.CurrentThread.IsThreadPoolThread}");
        System.Console.WriteLine(result);
        Console.WriteLine("Async Completed");
     }

     private static async Task<string> ExampleTask(int Second)
     {
        await Task.Delay(TimeSpan.FromSeconds(Second));
        return $"It's Async Completed in {Second} seconds";
     }
 }

輸出結果

Thread Id is Thread:1,Is Thread Pool:False
Thread Id is Thread:4,Is Thread Pool:True
It's Async Completed in 2 seconds
Async Completed

如果這段代碼在WPF運行,猜猜會輸出啥?

      private async void Async_Click(object sender, RoutedEventArgs e)
      {
          Debug.WriteLine($"Thread Id is Thread:{Thread.CurrentThread.ManagedThreadId},Is Thread Pool:{Thread.CurrentThread.IsThreadPoolThread}");
          var result= await ExampleTask(2);
          Debug.WriteLine($"Thread Id is Thread:{Thread.CurrentThread.ManagedThreadId},Is Thread Pool:{Thread.CurrentThread.IsThreadPoolThread}");
          Debug.WriteLine(result);
          Debug.WriteLine("Async Completed");   
      }

      private async Task<string> ExampleTask(int Second)
      {
          await Task.Delay(TimeSpan.FromSeconds(Second));
          return $"It's Async Completed in {Second} seconds";
      }

輸出結果:

Thread Id is Thread:1,Is Thread Pool:False
Thread Id is Thread:1,Is Thread Pool:False
It's Async Completed in 2 seconds
Async Completed

這時候你肯定是想說,小朋友,你是否有很多問號????,我們接下看下去

一.SynchronizationContext(同步上下文)

首先我們知道async await 異步函數本質是狀態機,我們通過反編譯工具dnspy,看看反編譯的兩段代碼是否有不同之處:

控制台應用:

internal class Program
{
    [DebuggerStepThrough]
	private static Task Main(string[] args)
	{
		Program.<Main>d__0 <Main>d__ = new Program.<Main>d__0();
		<Main>d__.args = args;
		<Main>d__.<>t__builder = AsyncTaskMethodBuilder.Create();
		<Main>d__.<>1__state = -1;
		<Main>d__.<>t__builder.Start<Program.<Main>d__0>(ref <Main>d__);
		return <Main>d__.<>t__builder.Task;
	}
    
	[DebuggerStepThrough]
	private static Task<string> ExampleTask(int Second)
	{
		Program.<ExampleTask>d__1 <ExampleTask>d__ = new Program.<ExampleTask>d__1();
		<ExampleTask>d__.Second = Second;
		<ExampleTask>d__.<>t__builder = AsyncTaskMethodBuilder<string>.Create();
		<ExampleTask>d__.<>1__state = -1;
		<ExampleTask>d__.<>t__builder.Start<Program.<ExampleTask>d__1>(ref <ExampleTask>d__);
		return <ExampleTask>d__.<>t__builder.Task;
	}

	[DebuggerStepThrough]
	private static void <Main>(string[] args)
	{
	        Program.Main(args).GetAwaiter().GetResult();
	}
}

WPF:

public class MainWindow : Window, IComponentConnector
{

	public MainWindow()
	{
	       this.InitializeComponent();
	}

	[DebuggerStepThrough]
	private void Async_Click(object sender, RoutedEventArgs e)
	{
		MainWindow.<Async_Click>d__1 <Async_Click>d__ = new MainWindow.<Async_Click>d__1();
		<Async_Click>d__.<>4__this = this;
		<Async_Click>d__.sender = sender;
		<Async_Click>d__.e = e;
		<Async_Click>d__.<>t__builder = AsyncVoidMethodBuilder.Create();
		<Async_Click>d__.<>1__state = -1;
		<Async_Click>d__.<>t__builder.Start<MainWindow.<Async_Click>d__1>(ref <Async_Click>d__);
	}

	[DebuggerStepThrough]
	private Task<string> ExampleTask(int Second)
	{
	        MainWindow.<ExampleTask>d__3 <ExampleTask>d__ = new MainWindow.<ExampleTask>d__3();
		<ExampleTask>d__.<>4__this = this;
		<ExampleTask>d__.Second = Second;
		<ExampleTask>d__.<>t__builder = AsyncTaskMethodBuilder<string>.Create();
		<ExampleTask>d__.<>1__state = -1;
		<ExampleTask>d__.<>t__builder.Start<MainWindow.<ExampleTask>d__3>(ref <ExampleTask>d__);
		return <ExampleTask>d__.<>t__builder.Task;
	}

	[DebuggerNonUserCode]
	[GeneratedCode("PresentationBuildTasks", "4.8.1.0")]
	public void InitializeComponent()
	{
		bool contentLoaded = this._contentLoaded;
		if (!contentLoaded)
		{
		     this._contentLoaded = true;
		     Uri resourceLocater = new Uri("/WpfApp1;component/mainwindow.xaml", UriKind.Relative);
		     Application.LoadComponent(this, resourceLocater);
		}
	}
	private bool _contentLoaded;
}

我們可以看到完全是一致的,沒有任何區別,為什麼編譯器生成的代碼是一致的,卻會產生不一樣的結果,我們看看創建和啟動狀態機代碼部分的實現:

public static AsyncVoidMethodBuilder Create()
{
	SynchronizationContext synchronizationContext = SynchronizationContext.Current;
	if (synchronizationContext != null)
	{
		synchronizationContext.OperationStarted();
	}
	return new AsyncVoidMethodBuilder
	{
		_synchronizationContext = synchronizationContext
	};
}

[DebuggerStepThrough]
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void Start<[Nullable(0)] TStateMachine>(ref TStateMachine stateMachine) where TStateMachine : IAsyncStateMachine
{
	AsyncMethodBuilderCore.Start<TStateMachine>(ref stateMachine);
}

[DebuggerStepThrough]
public static void Start<TStateMachine>(ref TStateMachine stateMachine) where TStateMachine : IAsyncStateMachine
{
	if (stateMachine == null)
	{
		ThrowHelper.ThrowArgumentNullException(ExceptionArgument.stateMachine);
	}
	Thread currentThread = Thread.CurrentThread;
	Thread thread = currentThread;
	ExecutionContext executionContext = currentThread._executionContext;
	ExecutionContext executionContext2 = executionContext;
	SynchronizationContext synchronizationContext = currentThread._synchronizationContext;
	try
	{
	     stateMachine.MoveNext();//狀態機執行代碼
	}
	finally
	{
	     SynchronizationContext synchronizationContext2 = synchronizationContext;
	     Thread thread2 = thread;
	     if (synchronizationContext2 != thread2._synchronizationContext)
	     {
		  thread2._synchronizationContext = synchronizationContext2;
	     }
	     ExecutionContext executionContext3 = executionContext2;
	     ExecutionContext executionContext4 = thread2._executionContext;
	     if (executionContext3 != executionContext4)
	     {
		 ExecutionContext.RestoreChangedContextToThread(thread2, executionContext3, executionContext4);
	     }
	}
}

在這裏總結下:

  • 創建狀態機的Create函數通過SynchronizationContext.Current獲取到當前同步執行上下文
  • 啟動狀態機的Start函數之後通過MoveNext函數執行我們的異步方法
  • 這裏還有一個小提示,不管async函數裏面有沒有await,都會生成狀態機,只是MoveNext函數執行同步方法,因此沒await的情況下避免將函數標記為async,會損耗性能

同樣的這裏貌似沒能獲取到原因,但是有個很關鍵的地方,就是Create函數為啥要獲取當前同步執行上下文,之後我從MSDN找到關於SynchronizationContext
的介紹,有興趣的朋友可以去閱讀以下,以下是各個.NET框架使用的SynchronizationContext:

SynchronizationContext 默認
WindowsFormsSynchronizationContext WindowsForm
DispatcherSynchronizationContext WPF/Silverlight
AspNetSynchronizationContext ASP.NET

我們貌似已經一步步接近真相了,接下來我們來看看DispatcherSynchronizationContext

二.DispatcherSynchronizationContext

首先來看看DispatcherSynchronizationContext類的比較關鍵的幾個函數實現:

public DispatcherSynchronizationContext(Dispatcher dispatcher, DispatcherPriority priority)
{
     if (dispatcher == null)
     {
         throw new ArgumentNullException("dispatcher");
     }
     Dispatcher.ValidatePriority(priority, "priority");
     _dispatcher = dispatcher;
     _priority = priority;
     SetWaitNotificationRequired();
 }

//同步執行
public override void Send(SendOrPostCallback d, object state)
{
     if (BaseCompatibilityPreferences.GetInlineDispatcherSynchronizationContextSend() && _dispatcher.CheckAccess())
     {
         _dispatcher.Invoke(DispatcherPriority.Send, d, state);
     }
     else
     {
          _dispatcher.Invoke(_priority, d, state);
     }
}

//異步執行
public override void Post(SendOrPostCallback d, object state)
{
     _dispatcher.BeginInvoke(_priority, d, state);
}

我們貌似看到了熟悉的東西了,Send函數調用Dispatcher的Invoke函數,Post函數調用Dispatcher的BeginInvoke函數,那麼是否WPF執行異步函數之後會調用這裏的函數嗎?我用dnspy進行了調試:

我通過調試之後發現,當等待執行完整個狀態機的之後,也就是兩秒后跳轉到該Post函數,那麼,我們可以將之前的WPF那段代碼大概可以改寫成如此:

private async void Async_Click(object sender, RoutedEventArgs e)
{
    //async生成狀態機的Create函數。獲取到UI主線程的同步執行上下文
    DispatcherSynchronizationContext synchronizationContext = (DispatcherSynchronizationContext)SynchronizationContext.Current;
    
    //UI主線程執行
    Debug.WriteLine($"Thread Id is Thread:{Thread.CurrentThread.ManagedThreadId},Is Thread Pool:{Thread.CurrentThread.IsThreadPoolThread}");
    
    //開始在狀態機的MoveNext執行該異步操作
    var result= await ExampleTask(2);
    
    //等待兩秒,異步執行完成,再在同步上下文異步執行
    synchronizationContext.Post((state) =>
    {
         //模仿_dispatcher.BeginInvoke
         Debug.WriteLine($"Thread Id is Thread:{Thread.CurrentThread.ManagedThreadId},Is Thread Pool:{Thread.CurrentThread.IsThreadPoolThread}");
         Debug.WriteLine(result);
         Debug.WriteLine("Async Completed");  
     },"Post");           
 }

輸出結果:

Thread Id is Thread:1,Is Thread Pool:False
Thread Id is Thread:1,Is Thread Pool:False
It's Async Completed in 2 seconds
Async Completed

也就是asyn負責生成狀態機和執行狀態機,await將代碼分為兩部分,一部分是異步執行狀態機部分,一部分是異步執行完之後,通過之前拿到的DispatcherSynchronizationContext,再去異步執行接下來的部分。我們可以通過dnspy調試DispatcherSynchronizationContext的 _dispatcher字段的Thread屬性,知道Thread為UI主線程,而同步界面UI控件的時候,也就是通過Dispatcher的BeginInvoke函數去執行同步的

三.Task.ConfigureAwait

Task有個ConfigureAwait方法,是可以設置是否對Task的awaiter的延續任務執行原始上下文,也就是為true時,是以一開始那個UI主線程的DispatcherSynchronizationContext執行Post方法,而為false,則以await那個Task裏面的DispatcherSynchronizationContext執行Post方法,我們來驗證下:

我們將代碼改為以下:

private async void Async_Click(object sender, RoutedEventArgs e)
{
    Debug.WriteLine($"Thread Id is Thread:{Thread.CurrentThread.ManagedThreadId},Is Thread Pool:{Thread.CurrentThread.IsThreadPoolThread}");
    var result= await ExampleTask(2).ConfigureAwait(false);
    Debug.WriteLine($"Thread Id is Thread:{Thread.CurrentThread.ManagedThreadId},Is Thread Pool:{Thread.CurrentThread.IsThreadPoolThread}");
    Debug.WriteLine(result);
    Debug.WriteLine($"Async Completed");
}

輸出:

Thread Id is Thread:1,Is Thread Pool:False
Thread Id is Thread:4,Is Thread Pool:True
It's Async Completed in 2 seconds
Async Completed

結果和控制台輸出的一模一樣,且通過dnspy斷點調試依舊進入到DispatcherSynchronizationContext的Post方法,因此我們也可以證明我們上面的猜想,而且默認ConfigureAwait的參數是為true的,我們還可以將異步結果賦值給UI界面的Text block:

private async void Async_Click(object sender, RoutedEventArgs e)
{
    Debug.WriteLine($"Thread Id is Thread:{Thread.CurrentThread.ManagedThreadId},Is Thread Pool:{Thread.CurrentThread.IsThreadPoolThread}");
    var result= await ExampleTask(2).ConfigureAwait(false);
    Debug.WriteLine($"Thread Id is Thread:{Thread.CurrentThread.ManagedThreadId},Is Thread Pool:{Thread.CurrentThread.IsThreadPoolThread}");
    this.txt.Text = result;//修改部分
    Debug.WriteLine($"Async Completed");
}

拋出異常:

調用線程無法訪問此對象,因為另一個線程擁有該對象

補充
推薦林大佬的一篇文章,也講的也簡潔透徹C# dotnet 自己實現一個線程同步上下文

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

【其他文章推薦】

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

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

※想知道購買電動車哪裡補助最多?台中電動車補助資訊懶人包彙整

南投搬家公司費用,距離,噸數怎麼算?達人教你簡易估價知識!

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

※超省錢租車方案

※回頭車貨運收費標準

自己動手實現深度學習框架-7 RNN層–GRU, LSTM

目標

        這個階段會給cute-dl添加循環層,使之能夠支持RNN–循環神經網絡. 具體目標包括:

  1. 添加激活函數sigmoid, tanh.
  2. 添加GRU(Gate Recurrent Unit)實現.
  3. 添加LSTM(Long Short-term Memory)實現.
  4. 使用基於GRU和LSTM的RNN模型擬合一個正餘弦疊加函數.

RNN原理

原始的RNN

        RNN模型用來捕捉序列數據的特徵. 給定一個長度為T的輸入系列\(X=(x_1, x_2, .., X_T)\), RNN層輸出一個長度為T的序列\(H=(h_1, h_2, …, H_T)\), 對於任意時間步t, 可以表示為:

\[H_t = δ(X_tW_x + H_{t-1}W_h + b), \quad t = 2, 3, .., T \]

        函數δ是sigmoid函數:

\[δ = \frac{1}{1 + e^{-x}} \]

        \(H_t\)包含了前面第1到t-1步的所有信息。 和CNN層類似, CNN層在空間上共享參數, RNN層在時間步上共享參數\(W_x, W_h, b\).

        RNN層中隱藏層的數量為T-2, 如果T較大(超過10), 反向傳播是很容易出現梯度爆炸. GRU和LSTM就是為了解決這個問題而誕生, 這兩種模型,可以讓RNN能夠支持長度超過1000的輸入序列。

GRU

        GRU使用了不同功能的門控單元, 分別捕捉序列上不同時間跨度的的依賴關係。每個門控單元都會都有獨立的參數, 這些參數在時間步上共享。

        GRU的門控單元有:

        \(R_t = δ(X_tW^r_x + H_{t-1}W^r_h + b^r)\), 重置門用於捕捉短期依賴關係.

        \(U_t = δ(X_tW^u_x + H_{t-1}W^u_h + b^u)\), 更新門用於捕捉長期依賴關係

        \(\bar{H}_t = tanh(X_t\bar{W}_x + (R_t * H_{t-1})\bar{W}_h + \bar{b})\)

        除此之外, 還有一個輸出單元:

        \(H_t = U_t * H_{t-1} + (1-U_t)*\bar{H}_t\)

LSTM

        LSTM的設計思路和GRU類似, 同樣使用了多個門控單元:

        \(I_t = δ(X_tW^i_x + H_{t-1}W^i_h + b^i)\), 輸入門,過濾記憶門的輸出.

        \(F_t = δ(X_tW^f_x + H_{t-1}W^f_h + b^f)\), 遺忘門, 過濾前面時間步的記憶.

        \(O_t = δ(X_tW^o_x + H_{t-1}W^o_h + b^o)\), 輸出門, 過濾當前時間步的記憶.

        \(M_t = tanh(X_tW^m_x + H_{t-1}W^m_h + b^m)\), 記憶門.

        它還有自己獨有的記憶單元和輸出單元:

        \(\bar{M}_t = F_t * \bar{M}_{t-1} + I_t * M_t\)

        \(H_t = O_t * tanh(\bar{M}_t)\)

RNN實現

        設計要求:

  1. RNN層中的隱藏層的數量是基於序列長度的,輸入序列有多長, RNN層應生成對應數量的隱藏層。
  2. RNN層在時間步上共享參數, 從前面的描述可以看出, 只有門控單元有參數,因此門控單元應獨立實現。
  3. 任意一個時間步上的層都依賴上一個時間步的輸出,在正向傳播和反向傳播過程中都需要上一個時間步的輸出, 每個門控單元都使用棧保存上一個時間步的輸出.
  4. 默認情況下RNN層輸出所有時間步的輸出。但有時只需要最後一個時間步的輸出, 這種情況下使用過濾層, 只向下一層傳播最後一個時間步的輸出。
  5. 使用門控單元實現GRU和LSTM

RNN基礎類的實現

RNN類

        文件: cutedl/rnn_layers.py, 類名: RNN

        這個類是RNN層基類, 它主要功能是控制向前傳播和向後傳播的主流程.

        初始化參數:

  '''
  out_units 輸出單元數
  in_units 輸入單元數
  stateful 保留當前批次的最後一個時間步的狀態作為下一個批次的輸入狀態, 默認False不保留

  RNN 的輸入形狀是(m, t, in_units)
  m: batch_size
  t: 輸入系列的長度
  in_units: 輸入單元數頁是輸入向量的維數

  輸出形狀是(m, t, out_units)
  '''
  def __init__(self, out_units, in_units=None, stateful=False, activation='linear'):

        向前傳播

def forward(self, in_batch, training):
    m, T, n = in_batch.shape
    out_units = self.__out_units
    #所有時間步的輸出
    hstatus = np.zeros((m, T, out_units))
    #上一步的輸出
    pre_hs = self.__pre_hs
    if pre_hs is None:
        pre_hs = np.zeros((m, out_units))

    #隱藏層循環過程, 沿時間步執行
    for t in range(T):
        hstatus[:, t, :] = self.hiden_forward(in_batch[:,t,:], pre_hs, training)
        pre_hs = hstatus[:, t, :]

    self.__pre_hs = pre_hs
    #pdb.set_trace()
    if not self.stateful:
        self.__pre_hs = None

    return hstatus

        反向傳播

def backward(self, gradient):
      m, T, n = gradient.shape

      in_units = self.__in_units
      grad_x = np.zeros((m, T, in_units))
      #pdb.set_trace()
      #從最後一個梯度開始反向執行.
      for t in range(T-1, -1, -1):
          grad_x[:,t,:], grad_hs = self.hiden_backward(gradient[:,t,:])
          #pdb.set_trace()
          if t - 1 >= 0:
              gradient[:,t-1,:] = gradient[:,t-1,:] + grad_hs

      #pdb.set_trace()
      return grad_x

sigmoid和tanh激活函數

sigmoid及其導數

\[sigmoid = \frac{1}{1+e^{-x}} \]

\[\frac{d}{dx}sigmoid = sigmoid(1-sigmoid) \]

tanh及其導數

\[tanh = \frac{e^x – e^{-x}}{e^x + e^{-x}} \]

\[\frac{d}{dx}tanh = 1 – tanh^2 \]

門控單元實現

        文件: cutedl/rnn_layers.py, 類名: GateUint

        門控單元是RNN層基礎的參數單元. 和Dense層類似,它是Layer的子類,負責學習和使用參數。但在學習和使用參數的方式上有很大的不同:

  • Dense有兩個參數矩陣, GateUnit有3個參數矩陣.
  • Dense在一次反向傳播過程中只使用當前的梯度學習參數,而GateUnit會累積每個時間步的梯度。

        下面我們會主要看一下GateUnit特別之處的代碼.

        在__ init__方法中定義參數和棧:

    #3個參數
    self.__W = None #當前時間步in_batch權重參數
    self.__Wh = None #上一步輸出的權重參數
    self.__b = None #偏置量參數

    #輸入棧
    self.__hs = []  #上一步輸出
    self.__in_batchs = [] #當前時間步的in_batch

        正向傳播:

  def forward(self, in_batch, hs, training):
      W = self.__W.value
      b = self.__b.value
      Wh = self.__Wh.value

      out = in_batch @ W + hs @ Wh + b

      if training:
          #向前傳播訓練時把上一個時間步的輸出和當前時間步的in_batch壓棧
          self.__hs.append(hs)
          self.__in_batchs.append(in_batch)

          #確保反向傳播開始時參數的梯度為空
          self.__W.gradient = None
          self.__Wh.gradient = None
          self.__b.gradient = None

      return self.activation(out)

        反向傳播:

def backward(self, gradient):
    grad = self.activation.grad(gradient)

    W = self.__W.value
    Wh = self.__Wh.value
    pre_hs = self.__hs.pop()
    in_batch = self.__in_batchs.pop()

    grad_in_batch = grad @ W.T
    grad_W = in_batch.T @ grad
    grad_hs = grad @ Wh.T
    grad_Wh = pre_hs.T @ grad
    grad_b = grad.sum(axis=0)

    #反向傳播計算
    if self.__W.gradient is None:
        #當前批次第一次
        self.__W.gradient = grad_W
    else:
        #累積當前批次的所有梯度
        self.__W.gradient = self.__W.gradient + grad_W

    if self.__Wh.gradient is None:
        self.__Wh.gradient = grad_Wh
    else:
        self.__Wh.gradient = self.__Wh.gradient +  grad_Wh

    if self.__b.gradient is None:
        self.__b.gradient = grad_b
    else:
        self.__b.gradient = self.__b.gradient + grad_b

    return grad_in_batch, grad_hs

GRU實現

        文件: cutedl/rnn_layers.py, 類名: GRU

        隱藏單初始化:

def set_parent(self, parent):
    super().set_parent(parent)

    out_units = self.out_units
    in_units = self.in_units

    #pdb.set_trace()
    #重置門
    self.__g_reset = GateUnit(out_units, in_units)
    #更新門
    self.__g_update = GateUnit(out_units, in_units)
    #候選輸出門
    self.__g_cddout = GateUnit(out_units, in_units, activation='tanh')

    self.__g_reset.set_parent(self)
    self.__g_update.set_parent(self)
    self.__g_cddout.set_parent(self)

    #重置門乘法單元
    self.__u_gr = MultiplyUnit()
    #輸出單元
    self.__u_out = GRUOutUnit()

        向前傳播:

  def hiden_forward(self, in_batch, pre_hs, training):
      gr = self.__g_reset.forward(in_batch, pre_hs, training)
      gu = self.__g_update.forward(in_batch, pre_hs, training)
      ugr = self.__u_gr.forward(gr, pre_hs, training)
      cddo = self.__g_cddout.forward(in_batch, ugr, training)

      hs = self.__u_out.forward(gu, pre_hs, cddo, training)

      return hs

        反向傳播:

def hiden_backward(self, gradient):

    grad_gu, grad_pre_hs, grad_cddo = self.__u_out.backward(gradient)
    #pdb.set_trace()
    grad_in_batch, grad_ugr = self.__g_cddout.backward(grad_cddo)

    #計算梯度的過程中需要累積上一層輸出的梯度
    grad_gr, g_pre_hs = self.__u_gr.backward(grad_ugr)
    grad_pre_hs = grad_pre_hs + g_pre_hs

    g_in_batch, g_pre_hs = self.__g_update.backward(grad_gu)
    grad_in_batch = grad_in_batch + g_in_batch
    grad_pre_hs = grad_pre_hs + g_pre_hs

    g_in_batch, g_pre_hs = self.__g_reset.backward(grad_gr)
    grad_in_batch = grad_in_batch + g_in_batch
    grad_pre_hs = grad_pre_hs + g_pre_hs

    #pdb.set_trace()
    return grad_in_batch, grad_pre_hs    

LSTM實現

        文件: cutedl/rnn_layers.py, 類名: LSTM

        隱藏單元初始化:

def set_parent(self, layer):
    super().set_parent(layer)

    in_units = self.in_units
    out_units = self.out_units

    #輸入門
    self.__g_in = GateUnit(out_units, in_units)
    #遺忘門
    self.__g_forget = GateUnit(out_units, in_units)
    #輸出門
    self.__g_out = GateUnit(out_units, in_units)
    #記憶門
    self.__g_memory = GateUnit(out_units, in_units, activation='tanh')

    self.__g_in.set_parent(self)
    self.__g_forget.set_parent(self)
    self.__g_out.set_parent(self)
    self.__g_memory.set_parent(self)

    #記憶單元
    self.__memory_unit =LSTMMemoryUnit()
    #輸出單元
    self.__out_unit = LSTMOutUnit()

        向前傳播:

def hiden_forward(self, in_batch, hs, training):
    g_in = self.__g_in.forward(in_batch, hs, training)
    #pdb.set_trace()
    g_forget = self.__g_forget.forward(in_batch, hs, training)
    g_out = self.__g_out.forward(in_batch, hs, training)
    g_memory = self.__g_memory.forward(in_batch, hs, training)

    memory = self.__memory_unit.forward(g_forget, g_in, g_memory, training)
    cur_hs = self.__out_unit.forward(g_out, memory, training)

    return cur_hs

        反向傳播:

def hiden_backward(self, gradient):
    #pdb.set_trace()
    grad_out, grad_memory = self.__out_unit.backward(gradient)
    grad_forget, grad_in, grad_gm = self.__memory_unit.backward(grad_memory)

    grad_in_batch, grad_hs = self.__g_memory.backward(grad_gm)
    tmp1, tmp2 = self.__g_out.backward(grad_out)
    grad_in_batch += tmp1
    grad_hs += tmp2

    tmp1, tmp2 = self.__g_forget.backward(grad_forget)
    grad_in_batch += tmp1
    grad_hs += tmp2

    tmp1, tmp2 = self.__g_in.backward(grad_in)
    grad_in_batch += tmp1
    grad_hs += tmp2

    return grad_in_batch, grad_hs

驗證

        接下來, 驗證示例將會構建一個簡單的RNN模型, 使用該模型擬合一個正餘弦疊加函數:

#採樣函數
def sample_function(x):
    y = 3*np.sin(2 * x * np.pi) + np.cos(x * np.pi) + np.random.uniform(-0.05,0.05,len(x))
    return y

        訓練數據集和測試數據集在這個函數的不同定義域區間內樣. 訓練數據集的採樣區間為[1, 200.01), 測試數據集的採樣區間為[200.02, 240.002). 模型任務是預測這個函數值的序列.

        示例代碼在examples/rnn/fit_function.py文件中.

使用GRU構建的模型

def fit_gru():
    model = Model([
                rnn.GRU(32, 1),
                nn.Filter(),
                nn.Dense(32),
                nn.Dense(1, activation='linear')
            ])
    model.assemble()
    fit('gru', model)

訓練報告:

使用LSTM構建的模型

def fit_lstm():
    model = Model([
                rnn.LSTM(32, 1),
                nn.Filter(),
                nn.Dense(2),
                nn.Dense(1, activation='linear')
            ])
    model.assemble()
    fit('lstm', model)

訓練報告:

總結

        這個階段,框架新增了RNN的兩個最常見的實現:GRU和LSTM, 相應地增加了它需要的激活函數. cute-dl已經具備了構建最基礎RNN模型的能力。通過驗證發現, GRU模型和LSTM模型在簡單任務上都表現出了很好的性能。會添加嵌入層,使框架能夠構建文本分類任務的模型,然後在imdb-review(電影評價)數據集上進行驗證.

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

【其他文章推薦】

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

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

※想知道購買電動車哪裡補助最多?台中電動車補助資訊懶人包彙整

南投搬家公司費用,距離,噸數怎麼算?達人教你簡易估價知識!

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

※超省錢租車方案

※回頭車貨運收費標準

博斯普魯斯海峽變色 出現正港土耳其藍

摘錄自2020年5月27日中央社報導

「每日晨報」(Daily Sabah)報導,博斯普魯斯海峽(Bosporus)水色自26日起轉變成「土耳其藍」。伊斯坦堡科技大學(Istanbul Technical University)教授托羅斯(Huseyin Toros)認為,東北風是導致「海水變色」主要原因。

托羅斯指出:「單細胞生物被東北風曳引進入博斯普魯斯海峽,海水表面經過折射,轉變成土耳其藍色。在此一大氣環境下的氣流、海平面下的活動、不同微生物、白天陽光變化等因素也可能導致海水顏色產生變化。」他表示,海水將於幾天內恢復「本色」。

美國國家航空暨太空總署(NASA)的衛星於當年5月29日首度補捉到黑海浮游生物激增的圖像。漁夫們相信,海中出現大量浮游生物意味當年鯷魚產量將會大增。但是浮游生物也會消耗水中大量氧氣,從而對其他海洋生物造成傷害。

土地水文
土地利用
國際新聞
土耳其
海水
港口
浮游生物

本站聲明:網站內容來源環境資訊中心https://e-info.org.tw/,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

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

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

※想知道購買電動車哪裡補助最多?台中電動車補助資訊懶人包彙整

南投搬家公司費用,距離,噸數怎麼算?達人教你簡易估價知識!

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

※超省錢租車方案

※回頭車貨運收費標準

掌握SpringBoot-2.3的容器探針:基礎篇

歡迎訪問我的GitHub

  • 地址:https://github.com/zq2599/blog_demos
  • 內容:原創文章分類匯總,及配套源碼,涉及Java、Docker、K8S、DevOPS等

關於《SpringBoot-2.3容器化技術》系列

《SpringBoot-2.3容器化技術》系列,旨在和大家一起學習實踐2.3版本帶來的最新容器化技術,讓咱們的Java應用更加適應容器化環境,在雲計算時代依舊緊跟主流,保持競爭力;
全系列文章分為主題和輔助兩部分,主題部分如下:

  1. 《體驗SpringBoot(2.3)應用製作Docker鏡像(官方方案)》;
  2. 《詳解SpringBoot(2.3)應用製作Docker鏡像(官方方案)》;
  3. 《掌握SpringBoot-2.3的容器探針:基礎篇》;
  4. 《掌握SpringBoot-2.3的容器探針:深入篇》;
  5. 《掌握SpringBoot-2.3的容器探針:實戰篇》;
  • 輔助部分是一些參考資料和備忘總結,如下:
  1. 《SpringBoot-2.3鏡像方案為什麼要做多個layer》;
  2. 《設置非root賬號不用sudo直接執行docker命令》;
  3. 《開發階段,將SpringBoot應用快速部署到K8S》;

SpringBoot容器探針系列文章簡介

為了讓應用更適應容器化環境,SpringBoot2.3版本推出了新的探針技術,《掌握SpringBoot-2.3的容器探針》系列旨在與您一起學習和實踐這些新技術,分為三個階段:

  1. 基礎篇:即本文,對容器探針的相關知識點進行梳理和學習;
  2. 深入篇:繼續深入學習探針相關的知識點;
  3. 實戰篇:將springboot應用部署在kubernetes環境,並使用最新的探針技術;

探針特性的官方信息

  • 如下圖紅框所示,2.3版本的容器探針特性早在預覽版(v2.3.0.M4)就已經發布:

  • 如今v2.3.0.RELEASE已發布,可以放心的學習和使用該特性了,首先把基礎知識點列出來,確保準備工作OK;

知識點整理

下面是掌握探針技術所需的基礎知識,也是本文的主要內容:

  1. kubernetes的存活探針livenessProbe
  2. kubernetes的就緒探針readinessProbe
  3. SpringBoot的actuator

接下來逐個學習,有了這些知識積累,我們才能更好的閱讀官方資料,開發適合自己業務場景的探針;

kubernetes的存活探針livenessProbe

  1. kubernetes的探針涉及的內容是很多的,這裏只提和SpringBoot相關的部分;
  2. kubelet 使用存活探針livenessProbe來知道什麼時候要重啟容器;
  3. 下圖是kubernetes官網的存活探針示例,幾個關鍵參數已經做了詳細說明:
  1. 可見如果我們的SpringBoot應用發布到kubernetes環境,只要應用還健康,livenessProbe對應的地址就要能響應200-400的返回碼;

kubernetes的就緒探針readinessProbe

  1. 有時候,應用程序會暫時性的不能提供通信服務。例如,應用程序在啟動時可能需要加載很大的數據或配置文件,或是啟動后要依賴等待外部服務。在這種情況下,既不想殺死應用程序,也不想給它發送請求。Kubernetes 提供了就緒探測器來發現並緩解這些情況。容器所在 Pod 上報還未就緒的信息,並且不接受通過 Kubernetes Service 的流量。
  2. 就緒探測器的配置和存活探測器的配置相似,唯一區別就是要使用 readinessProbe字段,而不是 livenessProbe 字段;
  3. 簡單的說,就緒探針正常的容器,k8s就認為是可以對外提供服務的,相應的請求也會被調度到該容器上來;

SpringBoot的actuator

  1. 簡單來說,actuator是用來幫助用戶監控和操作SprinBoot應用的,這些監控和操作都可以通過http請求實現,如下圖,http://localhost:8080/actuator/health 地址返回的是應用的健康狀態:
  1. 下面是常用的actuator地址,訪問不同的地址可以得到不同的信息:
  1. 在SpringBoot-2.3版本中,actuator新增了兩個地址:/actuator/health/liveness/actuator/health/readiness,前者用作kubernetes的存活探針,後者用作kubernetes的就緒探針

畫外音:SpringBoot的探針技術就這點東西?

  1. 文章看到這裏,您可能覺得索然無味:所謂的容器探針特性如此簡單,新增兩個actuator地址留給kubernetes的存活和就緒探針用,只要這兩個地址響應正常,kubernetes就判定該容器正常;
  2. 大多數時候,上述結論並無不妥,SpringBoot官方給出的推薦配置如下圖,我們只要照搬即可:
  1. 冷靜下來仔細思考,有三個問題似乎沒有解決:
  • 首先,SpringBoot為kubernetes提供了兩個actuator項,但是那些並未部署在kubernetes的SringBoot應用呢?用不上這兩項也要對外暴露這兩個服務地址嗎?

  • 其次,就緒探針是什麼時候開始返回200返回碼的?應用啟動階段,業務服務可能需要一段時間才能正常工作,就緒探針要是提前返回了200,那k8s就認為容器可以正常工作了,這時候把外部請求調度過來是無法正常響應的,所以搞清楚就緒探針的狀態變化邏輯很重要;

  • 最後,也是最重要的一點:有的場景下,例如外部依賴服務異常、本地全局異常等情況下,業務不想對外提供服務,等到問題解決后業務又可以對外提供服務了,如果此時我們能自己寫代碼控制就緒探針的返回碼,那就做到了控制kubernetes是否將外部請求調度到此容器上,這可是個很實用的功能!

還需要繼續深入

面對上述三個問題您是否會感慨:看似簡單的容器探針技術,想要用好還需掌握更多知識,接下來的文章中咱們一起努力吧,從知識覆蓋到實戰操練,終究會掌握這門實用技術;

歡迎關注我的公眾號:程序員欣宸

https://github.com/zq2599/blog_demos

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

【其他文章推薦】

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

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

※想知道購買電動車哪裡補助最多?台中電動車補助資訊懶人包彙整

南投搬家公司費用,距離,噸數怎麼算?達人教你簡易估價知識!

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

※超省錢租車方案

※回頭車貨運收費標準

同叫X7的自主SUV,誰才是更強的後起之秀

動力總成兩者都是1。5L自吸發動機+5擋手動的動力組合。漢騰X7最大可輸出149匹馬力和215牛米,在2000轉才爆發最大扭矩到4000轉就開始衰減。而斯威X7最大可輸出156馬力和230牛米,在1750轉便爆發最大扭矩,可持續到4500轉。從賬面來看,斯威X7的動力要優於漢騰X7,出力也更早,同時還是直噴發動機,能更好地減少燃油損失。

前言

自主品牌有不少車型都喜歡用X7來命名,所以一說起X7總能提起一堆車型。最近上市了兩款X7,分別屬於漢騰和斯威的,這兩個牌子也是新近建立的汽車廠商,兩款車型的對碰到底會擦出什麼火花,讓我們拭目以待。

為了公平起見,我會拿漢騰X7的手動豪華型與斯威X7的手動舒適型來對比,兩者價差為1100元。

漢騰汽車-漢騰X7

2016款 1.5T 手動豪華型

指導價:8.98萬

華晨鑫源-斯威X7

2016款 1.5T 手動舒適型

指導價:9.09萬

外觀設計

漢騰X7的前臉看起來比較扁平,整個設計拉得比較寬,給人一種大車感。尾部則更為簡約,雙邊兩出的排氣口顯得較為大體。

斯威X7從前面看,會覺得有一點點像寶馬X3,特別是那兩組圓形大燈,看起來與寶馬的天使之瞳更為相像。大面積的鍍鉻格柵也很符合國人的口味。側面看,斯威X7像一台升高版的MpV。尾部設計則比較緊湊,最具亮點是那個M標識,像極了寶馬M系的標識,只是那幾條斜線的顏色有所不同。

內飾設計

漢騰X7的中控會稍向駕駛員一側傾斜,木紋裝飾板也顯得很高級,用料方面對得起這個價格。只是那塊中控屏幕有點汽配城的feel,拖累整個內飾觀感。

斯威X7的中控設計則看起來先進許多,與榮威RX5的類似,也是一整塊觸控屏,乾淨整潔,會比較耐看。

動力總成

兩者都是1.5L自吸發動機+5擋手動的動力組合。漢騰X7最大可輸出149匹馬力和215牛米,在2000轉才爆發最大扭矩到4000轉就開始衰減。而斯威X7最大可輸出156馬力和230牛米,在1750轉便爆發最大扭矩,可持續到4500轉。從賬面來看,斯威X7的動力要優於漢騰X7,出力也更早,同時還是直噴發動機,能更好地減少燃油損失。

底盤表現

漢騰X7的后懸架用的是多連桿獨立懸架,濾震動作會比較富有韌性,迎接衝擊時,有點點硬。不過整體還是偏軟,過彎時的側傾較大。

斯威X7的后懸架用的是扭力梁,不過調校得還是很不錯。在過一些減速帶時,對車子的小彈跳抑製得很到位。

空間表現

↑漢騰X7的後座↑

↑漢騰X7的後排中央↑

↑斯威X7的後排↑

兩者的空間都屬於中規中矩那種,但是漢騰X7的後排中央會有一點凸起,而斯威X7則幾乎全平。關鍵是,斯威X7是一款7座車型,第三排短途坐一下乘客是完全沒有問題的。不過斯威X7的第二排座椅不可前後調節,即便如此空間也很可觀。

配置對比

從配置的對比上來看,兩者各有千秋,不過漢騰X7上到豪華型也沒有車身穩定系統,這點很說不過去。具體的對比,可以看下圖。

編者總結

兩輛X7的對比上,斯威X7會更為佔優,具體表現在動力、空間表現與配置都要優於漢騰X7。作為兩個後起之秀,這樣的定價確實也是足夠親民。雖然許多方面都不佔優勢,但是漢騰X7較為傳統的內飾設計,也許可以俘獲不少大叔的芳心。本站聲明:網站內容來源於http://www.auto6s.com/,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

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

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

※想知道購買電動車哪裡補助最多?台中電動車補助資訊懶人包彙整

南投搬家公司費用,距離,噸數怎麼算?達人教你簡易估價知識!

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

※超省錢租車方案

※回頭車貨運收費標準

這輛只要8萬起的車 頂級建築師和低調富二代都買

5L地球夢科技發動機,可以爆發出131匹強勁馬力,無論是起步加速還是中後段加速,全新鋒范的動力響應都表現的非常迅速,百公里10。2秒的加速時間更是在同級中名列前茅,滿足了年輕人的駕駛慾望。這樣的動力總成和強勁的加速性能和無論是對男主角時光,還是對很多追求速度的年輕人來說,都是很有吸引力的。

你難以靠近難以不再想念

我難以抗拒你容顏

把心畫在寫給你的信中

希望偶爾能夠見到你微笑的容顏

……

當這首熟悉的《難以抗拒你容顏》在電視里響起時,應該有無數人跟着哼唱了吧。沒錯,小編說的就是最近火爆的電視劇《咱們相愛吧》里女主之一蔡春娜在KTV里唱這首歌以至潸然淚下的劇情。這首歌勾起了她與劇中前男友的回憶,又何嘗沒有勾起大家對青春的回憶呢,簡直經典啊!所以這部劇紅也不是沒有道理,不僅身邊上至80歲跳廣場舞的阿姨下至18歲青春活潑的姑娘都沉迷其中,各大視頻網站也都佔據熱搜。

只見地鐵哪怕擠成狗,都有不少人堅決舉起手機在看;公司中午休息,女同事沉迷於劇情飯都顧不上吃;就連晚上跳廣場舞的大媽都提前結束回家追劇了。在好奇心的驅使下,小編也跟着看了起來。

而八點檔電視劇總有一種迷の魅力,一旦靠近就不可自拔。結果你們也猜到了,基本上是這樣的↓↓↓

但是!小編當然不是一個沉迷追劇不務正業的人,生活在哪裡汽車就在哪裡,電視看着看着就自然對裏面出現的汽車更關心一點,今天就來給大家八一八這部劇里出現頻率頗高的轎車——全新鋒范。

在劇里,它是男主角時光以及大毛的座駕,而兩位車主一個是著名的建築設計師,一個則是深藏不露的富二代,都不是“一般人”。 生活中小編身邊不少全新鋒范車主,也都是優質青年,可見這輛車對高品位人群的吸引力。

男主角時光作為一位建築設計師,對外觀的要求絕不會低。全新鋒范的外觀設計,恰恰符合了他的要求。飛翼式前臉突出凌厲與飽滿,車身上下雙腰線簡潔幹練,輪廓鮮明。翹起的尾部採用寬大設計,搭配鑽石切割的后尾燈組合,顯得動感又不失時尚。這種簡約不簡單的風格,與很多現代建築也是交相呼應的。

內飾未來感十足,布局層次分明。簡潔大氣的儀錶盤與中央的中控大屏、觸控式智能空調營造出科技感十足的駕駛氛圍,恍惚間會讓你覺得是在開一輛“未來之車”。

當然,全新鋒范的動力應該也是男主角時光選擇它的重要原因。這樣一個在生活中方方面面都追求完美的霸道總裁對動力也不會妥協。好在全新鋒范搭載的是本田1.5L地球夢科技發動機,可以爆發出131匹強勁馬力,無論是起步加速還是中後段加速,全新鋒范的動力響應都表現的非常迅速,百公里10.2秒的加速時間更是在同級中名列前茅,滿足了年輕人的駕駛慾望。這樣的動力總成和強勁的加速性能和無論是對男主角時光,還是對很多追求速度的年輕人來說,都是很有吸引力的。

而劇中另外一位開全新鋒范的主角大毛,一開始只是一個默默喜歡女主角芝芝的上班族,沒想到最後才知道,他的父親其實是有名的商人。

但大毛對此顯得不以為意,依舊每天開着一輛紅色全新鋒范靠自己的努力打拚一番事業。其實除掉富二代這個身份后,大毛和現在眾多打拚的年輕人並沒有區別,相比華而不實的超跑,一輛十萬級別,經濟適用的轎車反而才是事業上最大的助力。

與全新鋒范1.5L地球夢科技發動機相匹配的是支持自動啟停功能的CVT無級變速箱,平順、有力的換擋過程為駕駛者帶來暢快的加速體驗以及低至5.4L的百公里油耗表現。如此經濟的表現對於年輕人來說更是大大減輕了用車壓力。並且全新鋒范搭載了時下先進的科技配置,中控7英寸多功能互聯显示系統使車型氛圍充滿了未來感,除了可與多種主流手機型號連接並同步操作以外,屏幕內還集成了先進的三模式倒車影像,為駕駛者提供了一個清晰、直觀的後方視角,使車輛在倒車時更有安全保障。全新鋒范強大的功能配置以及突出的性價比,堪稱生活好幫手、創業好夥伴,難怪能成為了大毛的選擇。

綜合來看,決意東山再起的時光與憑藉自身努力創業的大毛選擇全新鋒范作為座駕再適合不過,外觀內飾極具現代感,高科技配置豐富,低油耗高動力,一切都恰好滿足年輕人的需求。並且不久前的廣州車展上,廣汽本田又推出了2017款鋒范1.5L CVT精英版,新增多達7項配置,使得外觀造型與實用性上都得到了更好的提升。在外觀上加入了15寸鋁質輪轂與後置短天線。在實用性方面加入了對於如今年輕人來說不可或缺的無鑰匙進入/一鍵啟動功能,靠近車門自動解鎖,出發前的系列動作一氣呵成,說不出的瀟洒與帥氣。而後排出風口的出現,在讓所有坐上你車的人都能得到享受的同時,更是使得整輛車達到了越級的表現。

2017款鋒范1.5L CVT精英版的官方指導價為9.58萬元。如此豐富的配置,售價卻未超出10萬,再加上即將結束的購置稅減半政策,性價比在同級車型中非常突出,可見廣汽本田的十足誠意。所以十萬級合資轎車中,2017款鋒范不失為一個上上之選。

追劇的同時不忘給大家推薦車型,小編感覺自己今天一米八!本站聲明:網站內容來源於http://www.auto6s.com/,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

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

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

※想知道購買電動車哪裡補助最多?台中電動車補助資訊懶人包彙整

南投搬家公司費用,距離,噸數怎麼算?達人教你簡易估價知識!

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

※超省錢租車方案

※回頭車貨運收費標準

Pytest單元測試框架——Pytest+Allure+Jenkins的應用

一、簡介

  pytest+allure+jenkins進行接口測試、生成測試報告、結合jenkins進行集成。

  pytest是python的一種單元測試框架,與python自帶的unittest測試框架類似,但是比unittest框架使用起來更簡潔,效率更高

  allure-pytest是python的一個第三方庫。用於連接pytest和allure,使它們可以配合在一起使用。

  allure-pytest基於pytest的原始執行結果生成適用於allure的json格式結果。該json格式結果可以用於後續適用allure生成html結果。

二、安裝  

  1、安裝pytest,命令行或終端中輸入

1 pip install pytest

  2、安裝allure-pytest,安裝成功

1 pip install allure-pytest

  allure-pytest安裝成功后截圖如下。

  3、下載安裝JDK

  官方下載:https://www.oracle.com/java/technologies/javase-jdk11-downloads.html

  安裝與配置不作闡述請諒解  

  4、下載安裝Jenkins

  官方下載:https://www.jenkins.io/

  安裝與配置不作闡述請諒解

三、下載Allure並配置

  下載allure並配置

  1、allure官網下載:https://github.com/allure-framework/allure2/releases

  如下圖所示

  2、allure2下載下來是一個zip的壓縮包,我們要解壓至自己的文件目錄下(可解壓放至項目的測試用例下或python安裝目錄下),自己可找到文件即可。

  3、打開allure2目錄,找到bin目錄,複製bin文件目錄, 然後進行環境變量的配置,設置環境變量的目的就是讓系統無論在哪個目錄下都可以運行allure2。

  4、環境變量設置:(桌面——我的電腦——右鍵屬性——高級系統配置——環境變量——系統變量——Path——編輯環境變量——把我們上面複製的目錄路徑新增至環境變量中即可)

  設置環境變量,如下圖所示。

 

  5、配置好后,打開cmd終端,輸入allure,出現以下幫助文檔,就說明配置成功了。

 四、Allure裝飾器描述

  Allure裝飾器

 五、Pytest+Allure的應用

  上述我們講了一些理論的知識,下面我們就來實戰練習一下吧。進一步理解Pytest+allure如何結合應用的。

  1、新建testcase文件夾,用來存放測試用例,新建test_Demo.py文件,作為pytest的具體測試用例文件。在test_Demo.py文件中輸入以下代碼。

 1 # test_Demo.py
 2 # Creator:wuwei
 3 # Date:2020-06-09
 4 
 5 import pytest
 6 import requests
 7 import allure
 8 import sys
 9 sys.dont_write_bytecode = True
10 
11 @allure.epic('測試描述'.center(30, '*'))
12 @allure.feature('測試模塊')
13 @allure.suite('測試套件')
14 class TestPytestOne():
15     @allure.story('用戶故事描述:用例一')
16     @allure.title('測試標題:用例一')
17     @allure.description('測試用例描述:用例一')
18     @allure.testcase('測試用例地址:https://www.baidu.com/')
19     @allure.tag('測試用例標籤:用例一')
20     def test_one(self):
21         print('執行第一個用例')
22         assert 1 == 1
23 
24     @allure.story('用戶故事描述:用例二')
25     @allure.title('測試標題:用例二')
26     @allure.description('測試用例描述:用例二')
27     @allure.testcase('測試用例地址:https://www.sogou.com/')
28     @allure.tag('測試用例標籤:用例二')
29     def test_two(self,action):
30         print('執行第二個用例')
31         assert True == True
32 
33 # pytest運行
34 if __name__ == "__main__":
35     pytest.main(['-s', '-v', 'test_Demo.py', '-q', '--alluredir', '../reports'])

  2、我們再來創建一個conftest.py,conftest用來共享數據及不同層次之間共享使用的文件,測試用例的前置和後置中一般都可以用到的。

 1 # conftest.py
 2 # Creator:wuwei
 3 # Date:2020-06-09
 4 
 5 import pytest
 6 import sys
 7 sys.dont_write_bytecode = True
 8 
 9 @pytest.fixture()
10 def action():
11     print("測試用例開始".center(30, '*'))
12     yield
13     print("測試用例結束".center(30, '*'))

  3、運行test_Demo.py文件,test_Demo文件中已經pytest+allure的結合,可查看allure的運行結果,可看出在根目錄中生成了一個reports文件夾,其中生成了測試報告的json文件,這裏面的json文件可通過allure生成html的測試報告。
  運行test_Demo.py,終端显示如下圖所示。

   生成的Json格式的測試報告,如下圖所示。

   4、使用allure將json文件生成html的測試報告,定位至項目文件根目錄下,運行以下命令,會在項目根目錄下生成一個名為allure_reports的文件夾,用來存放html測試報告。命令下如所示。

1 allure generate reports -o allure_reports/

  成功運行allure,結果如下圖所示。

  項目根目錄下的allure_reports文件,存放的是allure生成的測試報告。可看出文件下有一個HTML文件,可通過Python的編輯器Pycharm來打開該HTML文件(測試報告),或可通過allure命令來打開該HTML,展示HTML測試報告。如下所示。

  測試報告文件,HTML測試報告如下。

  allure命令打開HTML測試報告。命令如下所示。

1 allure open allure_reports/

  如下圖所示。

   打開生成的HTML測試報告如下圖所示。

 六、Pytest+Allure+Jenkins的應用

  1、Jenkins插件網站上下載allure插件最新版本:

    http://mirrors.jenkins-ci.org/plugins/allure-jenkins-plugin/

  2、Jenkins的安裝我已經在Postman+Newman+Git+Jenkins的篇章中講過了,沒看小夥伴可以看一下那篇文章。確認Jenkins服務是否開啟。確認開啟后,在瀏覽器中輸入:http://localhost:8080/,進入Jenkins配置頁面。

  3、http://localhost:8080/,登錄Jenkins的頁面,在管理Jenkins——插件管理——高級中找到上傳插件。將(1)步驟中下載的.hpi的文件上傳至jenkins上。

  上傳安裝好的allure-jenkins-plugin的插件,安裝完成並成功,是藍色圓點显示,因我已經安裝過一次,會提示已經安裝,重啟Jenkins即可生效。(注意:不是關閉瀏覽器重新打開,而是重啟Jenkins服務

  4、全局變量中配置allure路徑與JDK的路徑,

  配置JDK安裝的路徑,如下圖所示。

  配置allure安裝的路徑,如下圖所示。

  5、新建Item,配置構建后的allure測試報告生成。這裏配置Pytest執行完成之後,生成的allure文件所在的目錄位置。

  項目中生成allure的json測試報告的位置。需與下面構建后操作中的Results的Path文件一致。

  構建后操作的allure生成測試報告的配置,如下圖所示

  6、配置構建命令。就是上述在cmd中運行項目時的命令。如下圖所示。

注意:運行后發現有報錯。“Build step ‘Execute Windows batch command’ marked build as failure”,解決方案,在運行項目的命令后添加exit 0。如下圖所示。

  7、修改運行命令后我們再來運行一下。我們可發現運行后,allure裏面沒任務數據。因為我們還沒設置運行的項目路徑。設置工作空間,打開工作空間目錄,將我們的項目複製到jenkins的工作目錄中。

  我們可將代碼傳至GitHub上,在Jenkins中設置相關Github項目的配置,也可進行Jenkins部署。我在Postman+Newman+Git+Jenkins這篇博客里就應用到了。有興趣的可參考看看這篇Jenkins如何Git項目。在這裏我們使用本地項目來部署。

  測試報告無數據因為工作空間裏面沒有項目配置。

  複製項目至Jenkins工作空間的目錄中。

  8、添加項目后,我們再運行一下,藍點則為運行成功,可看到後面已經生成了allure的測試報告了。可直接點擊後面的alluree圖標跳轉至HTML的測試報告。如下圖所示。

  allure生成的HTML測試報告

八、總結

  上述我們聊了下pytest+allure+jenkins如何結合集成一起使用的,本地啟動jenkins,運行項目,調用allure生成測試報告。也簡單的做了一個小Demo。後期我將結合Requests接口測試和seleniumWeb測試應用至具體項目中。

 

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

【其他文章推薦】

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

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

※想知道購買電動車哪裡補助最多?台中電動車補助資訊懶人包彙整

南投搬家公司費用,距離,噸數怎麼算?達人教你簡易估價知識!

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

※超省錢租車方案

※回頭車貨運收費標準

想要成家的90后,12萬元究竟能買到什麼好車?

以上車型都是適合年輕人的,即使是使用貸款的方式購買也不會造成太大的壓力,關鍵的一點是這些車型基本可以陪伴你度過初初成家、孩子長大的過程。

以上車型都是適合年輕人的,即使是使用貸款的方式購買也不會造成太大的壓力,關鍵的一點是這些車型基本可以陪伴你度過初初成家、孩子長大的過程。本站聲明:網站內容來源於http://www.auto6s.com/,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

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

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

※想知道購買電動車哪裡補助最多?台中電動車補助資訊懶人包彙整

南投搬家公司費用,距離,噸數怎麼算?達人教你簡易估價知識!

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

※超省錢租車方案

※回頭車貨運收費標準

冬至南吃湯圓北吃餃,原來車也分南派北派,你是哪派?

最典型如座椅加熱/方向盤加熱和座椅通風。座椅加熱對於冬至還在穿短袖的廣州人民是一個雞肋的配置,通風功能則要實用得多。相反加熱功能對北方的小夥伴來說簡直是一個“雪中送炭”的配置。大家都知道如今霧霾已經成為威脅人類健康的頭等“殺手”,因此不少主機廠都會在優化車內空氣質量上花心思,比如裝配pM2。

要不是主編大人提醒,叫獸我都忘了昨天居然是冬至。冬至,意味着一年中最寒冷的節氣來臨。此時此刻,你在北方的霧霾里醉生夢死,我在南方的艷陽里穿着短袖···

每逢佳節倍思親,作為一隻“南漂單身狗”,每每到了過節的時候就會感到格外凄涼。而辦公室的另一頭,同事們還在為冬至吃湯圓還是水餃這個問題爭論的時候,叫獸我已經默默提筆,於是有了今天這篇文章。

到底湯圓還是水餃,說白了就是南北的習俗差異。只能說咱們祖國地大物博,不光是過節的習俗有很大不同,就連買車和用車都有很大差異。今天我就化思緒為動力,來和大家探討這個問題。

一.購車習慣差異

都說北方人粗獷大氣,不拘小節,選車習慣也非常符合他們的脾性。相比而言,他們往往對車內的做工和配置等方面不會過於在意,但發動機動力性能要強,車輛高速行駛時的穩定性要好。這裏以德系和美系車最具代表性。

南方人的消費觀念則比較精打細算,平日生活里追求“小而精”。如廣州茶餐廳里精緻的點心一樣,嘗過一回廣式早茶便能體會到其中的精髓。因此他們在選車時會對車輛的做工和油耗等方面比較在意;除此外,性價比也是他們比較看重的部分。這裏則以日系車為代表。

其實從國內幾大主機廠的分佈情況也能看出一二,北方多德系(一汽大眾、奧迪),南方則幾乎成了日系的“大本營”(廣汽旗下的本田、豐田、謳歌)。

二.配置需求不盡相同

這一點很好理解,由於地理環境的影響,南北方消費者對車輛配置方面的要求也有挺大差別。最典型如座椅加熱/方向盤加熱和座椅通風。座椅加熱對於冬至還在穿短袖的廣州人民是一個雞肋的配置,通風功能則要實用得多;相反加熱功能對北方的小夥伴來說簡直是一個“雪中送炭”的配置。

大家都知道如今霧霾已經成為威脅人類健康的頭等“殺手”,因此不少主機廠都會在優化車內空氣質量上花心思,比如裝配pM2.5凈化系統等配置。在這樣的氣候環境下,以往這個看起來毫不起眼的配置變得令不少消費者都重視了起來,北方朋友們則更情有獨鍾。

三.消費觀念不同

*提醒一點,這部分是叫獸以個人經驗得出看法,沒有實際數據作支撐,僅供參考。

同為國內相對富裕地區,廣東與江浙土豪們在汽車消費觀方面也有明顯差異。去過杭州、寧波等地朋友們一定會發現,那裡的超豪車尤其多,繁華地區幾乎隨處可見法拉利、蘭博基尼;相比下廣深兩地的超豪華車的數量則要“理性”得多。

叫獸斗膽以為,廣東人是先富起來的那批人,這裏擁有大量的實業經濟(東莞曾被成為世界工廠),除了政策方面的激勵,他們更多的是靠勤勞和汗水打出來的天下,財富是一點一滴積累起來的;而江浙滬地帶則多金融行業,財富來得有些“大起大落”,因此土豪們花起錢來也要大手大腳一些,畢竟坐一台勞斯萊斯、賓利去談生意可比坐奔馳要容易得多。

四.用車養車的區別

冬季北方氣溫低、雪量大,因此不少注重車輛保養和行車安全的車主都會在冬季來臨前給愛車做一次“大保健”,換粘度更低的機油和凝結點更低的純防凍液;還有最重要的一點,一套雪地胎是必須的。關乎安全,一點也不能放鬆。

夏季南方則是暴雨和暴晒交替進行,所以南方車主對車輛的漆面保養尤為注意,漆面鍍膜則是個不錯的辦法;另外要注意的是,雨季時務必將汽車停在安全地帶防止泡水。據保險公司的朋友介紹,由於天氣的多變性,已經有越來越多的車主會主動購買車輛自燃險和涉水險。

以上是叫獸總結出幾點南方和北方朋友們在選車和用車時的不同之處,如果大家還有更多的看法,歡迎大家留言與叫獸互動。本站聲明:網站內容來源於http://www.auto6s.com/,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

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

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

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

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

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

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