iT邦幫忙

第 11 屆 iThome 鐵人賽

DAY 15
1

今天我們來聊聊 Scope 這件事吧!不要小看這件事情,有很多東西還是對進階的 Developer 有所幫助呢!

Python 3

  • 在 Python 的世界,在 Function 外指定的變數屬於 Global scope (例如下面的 global_variable),而 Function 內的變數包含參數則是屬於 Local scope,也就是當 Function 結束之後,這些變數也就被消滅了。然而跟其他語言有點不同的地方是,我們並沒有辦法直接在 Function 內對 Global 變數做修改 (純粹存取是可以的),例如下面的情況會在執行 print(global_variable)的時候報錯 UnboundLocalError: local variable 'global_variable' referenced before assignment ,咦?為什麼這邊報錯說的是 local variable 呢?明明 global_variable 是宣告成 global variable 呀!我們先來說,如果我們要能夠改變 global_variable 的值,必須要 Function 內用 global 的關鍵字,例如global global_variable ,表示這是 global scope 的變數,才能做修改。因此如果在 Python 的 Function 中沒有宣告 global 的話,一律是為 Local 變數,和其他語言不大相同。所以為什麼剛才會是UnboundLocalError: local variable 'global_variable' referenced before assignment 呢?因為這裡的 global_variable 其實在 function 內被宣告後 (global_variable = 'ABC')就會被認定是 local variable (因為在 Function 內沒有用 global 關鍵字),而我們又試圖在所謂 local variable 還沒被宣告之前,就試圖去印出它 (print(global_variable)),所以就會報這樣的錯啦!
global_variable = 'abc'
def a()
    # global global_variable
    print(global_variable) # "abc"
    global_variable = 'ABC' # UnboundLocalError: local variable 'global_variable' referenced before assignment
a()
  • 所以基本上比較好的做法是,讓 global variable 可以以 Function 參數的方式被引入使用,而且也盡量不要去修改 global variable 囉! (因為這也是所謂的 Side effect)

Scala

  • 對於 Scala 的變數來說,在不同的地方被宣告會變成不同的 Scope。這裡有三種 Scope 的 Type:Fields、Method Parameters 以及 Local Variables。第一種是 Field variable:當我們定義了一個 Class 或是 Object 時,在裡頭用 varval 所宣告的變數便是 Field variable,他們是可以被其中 (Class 或是 Object) 的 Method 所存取的,至於能否被外面所存取要看其 access modifier 的狀況,例如在宣告的時候,加上了像是 protected (只能被繼承的子類別使用)、或 private (只能在內部被使用)。而預設都是 public。例如下面定義的 Shape ,外界是可以在 new 了一個 object 之後來存取 height , width 而內部方法也可以 access (如果是 private 那麼外界就不能存取了)。第二種是 Method Parameters,指的就是 method 的參數,其在 Method 中被使用,而其總是 immutable (也就是 val)。第三種是 Local Variables,也就是在 method 內所定義的變數,其只能在 Method 中被使用,可以是 var 也可以是 val
class Shape { 
  val height = 3
  val width = 15  
  def area() { 
    println(height * width) 
  } 
}
  • 講完變數的 Scope,那我們來講 Method 的 Scope。Scala 一共提供了這些選項 Object-private scope、Private、Package、Package-specific、Public。Object-private scope 是在 Method 的前頭加上 private[this] 的關鍵字,表示說這個方法是沒有辦法被其他同 Class 的 Object 所存取的,例如下面的情況是無法 Compile 的 (other.isFoo 是無法的)。但如果把 private[this] 改成只有 private 那就可以囉!表示只要是同一個 Class 的 object 都可以存取,但如果是在外部則無法直接存取,另外子類別也無法存取父類別的 private Method (必須要改成 protected)。
class Foo {
    private[this] def isFoo = true
    def doFoo(other: Foo) {
        if (other.isFoo) { 
            // ...
        }
    }
}
  • Package scope 則是說,在同一個 Package 下,即使是不同的 Class,只要 Method 定義時加上 private[<package name>] 就可以被同一個 Package 下的其他 Class 存取,例如下面 Bar 就可以存取 Foo 的 X method。再更細的是,因為 package 有層次,所以我們可以不只定義例如 pp 這層,
    可以定義 private[ryan] ,這樣只要是 com.ryan 之下的都可以存取。而最後 public 就是預設的情況,只要透過 object 都可以在外部直接存取囉!
package com.ryan.pp {
    class Foo {
        private[pp] def X {}
    }
    class Bar {
        val f = new Foo
        f.X  
    }
}

Golang

  • Golang 是所謂的 lexically scoped using blocks,也就是在變數存在於與這個變數最接近的大括號 (區塊) 內,而在此區塊內的其他區塊都可以存取到這個變數。看個簡單的例子,應該不難去推論為何最下面的 fmt.Println(a) 會找不到 a 。而假使我們在內層區塊去修改 a 的值,那麼 a 的值就也會真的被改變,在之後被存取的時候看到改變後的結果。此外 Golang 也允許我們在內層區塊去重新宣告一個同名的變數 ( 又稱 shadowing),例如在第一個 fmt.Println(a)之上加入 a := 2 ,這時候就會看到第一個 fmt.Println(a) 印出來是 2 但是之後的還是印出 1 囉!我們可以發現只要遵循這個規則,其實在 Golang 是很好去做判斷的。
func main() {
    {
        a := 1
        {
            fmt.Println(a)
        }
        fmt.Println(a)
    }
    fmt.Println(a) // "undefined: a" compilation error
}
  • 至於我們會在一個檔案裡頭去 import 一些第三方或標準 package,而這些 package 只會在同一個 File 裡面是可視的,也就是說,如果有另一個 File 即使屬於同一個我們自定義的 package,也要自己去做 import。但如果是同一個 package,是可以用其他檔案所定義的 Variables、constants、types、functions 的唷!也就是說他們的 Scope 是 Package block。

Rust

  • 和 Golang 一樣,Rust 也是用區塊界定出 Scope,也就是在某區塊內所定義的變數在其內層的其他區塊也都可以存取到這個變數,而在 Rust 中變數是可以重新綁定的,這在之前也有提到過。
  • 如同以前有提到過的,Rust 的誕生一部分是為了達到更安全的記憶體管理,所以在於資料的存取和處理上有比較嚴格的規則,不過因為 Rust 把這些再做了一些抽象化,所以其實了解過後也並不會太複雜。首先在 Rust,default 變數都是 immutable,這讓你能夠去思考你所宣告的變數是否真的在未來會被去修改。另一個就是先前也有提過的所有權 (ownership),當一個變數綁定了某個值 (resource),而當該變數已經不在某個 Scope 之中了,他所綁定的值 (resource) 就會被 free 掉。而為了避免 race condition,對於某個 resource 只能有一個 mutable reference。假使某個變數和值綁定後,需要讓別的變數也能夠去使用的話,像是下面的 let mut x = 1; let y = &mut x; 我們先綁定x1let y = &mut x; 是把 reference 從 x借給了 y這樣才能夠執行 *y += 1; ,但是這時候 println!("{}", x); 就會失敗啦!因為現在 1y 借走了。
fn main() {
    let mut x = 1;
    let y = &mut x;    // borrowing
    *y += 1;           
    println!("{}", x); // Error
}
  • 如果要讓 Error 消失就要如下,也就用如下的做法,讓 y 自己存在一個 Scope 之中,並且在 Scope 結束時,將所有權歸還囉!
fn main() {
    let mut x = 1;
    {
        let y = &mut x;    // borrowing
        *y += 1;           
    }                   
    println!("{}", x);     // ok!
}

結語

今天講了一些比較瑣碎,關於 Scope 在各語言的狀況,但是有時候這往往會是關鍵的細節,沒有處理好就可能會造成錯誤。明天見囉!


上一篇
[Day 13] 談談抽象這件事
下一篇
[Day 15] 手牽手心連心!
系列文
30 天把自己榨好榨滿的四週四語言大挑戰!30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言