跳到主要內容

Python - varibale

Python - varibale#

第一次玩 python 是在 samba porting 時,用來做 auto-testing。 看中的是 python 比起 bash 豐富些的封裝能力,感覺 code 重用的效果會比較好, 比起 c++ 寫起來彈性也好一些。然而在一個比較大的 project 當中, 修正 pylint 的錯誤時,一邊 trace code,一邊浮起了很多疑問。

想像在看 code 時,看到這樣的一段:

    ...
    def func(self, ...):
        ...
        x.a = self.b()
        ...
  • 怎麼知道 x 是什麼?如果在檔案中有很多個 x 有關係的是哪些?
  • 怎麼知道 x.a 是什麼?找到了 x 後,怎麼找到 x.a?
  • self 是什麼?為什麼有的時候要傳,有的時候又不用?

Reference or value#

在 c/c++ 中,常會說 "call by reference" or "call by value"。其實只要有變數取用、 傳遞等行為,就需要問到底傳的是 reference 還是 value。

比方說

a = 1
b = a
a = 2
print(b)

執行結果 b == 1,看起來並不會因為 a 的值改變了而改變。 對於學 c/c++ 或 javascript 的人來說,好像都滿直覺的。 但這滿有趣的,並非所有語言都是如此,在 Makefile 當中,就不會是這樣的結果。

而如果換成是:

a = []
b = a
a.append(1)
print(b)

則會得到 b == [1] 的結果。怎麼好像又會隨著 a 一起改變呢?

這對於 javascript 也是滿直覺的, 所有型別分成兩大類, primitive type 在 b = a 食,會發生 value 的複製;而另一類都是 object 的子孫,在 b = a 時, 會發生的是 reference 的複製。因此未來作用在同一個 object 上的改變, 都會影響到擁有這個 reference 的所有人。 但在 javascript 中的 string 特別,雖然是複製 reference,但是因為他是 immutable 的,因此當所有人都可以 reference 到同一個 object,但沒有人能改它。 想要更新值只能將 a = '...' 這樣重新 assign 一個新的 reference。 因此,看起來的行為就會跟 int 時很像了。

如果 c++ 這邊很細節,除了 primitive type 外,要看這個型態的 copy assignment 是怎麼設計的。但一般來說,除非有特別需求,不然在 c++ 中,要 a、b reference 到同一塊記憶體時,會明確地使用 reference,不會在 b = a 後,還讓 a 與 b 糾纏不清。

對於 python 的話,則是全部都像是 javascript 的 object,全部都是 reference。 因此,只看到 b = a 時,兩個一定是指向同一個 object,會不會互相干擾得看 type 是否為 immutable。

The value of some objects can change. Objects whose value can change are said to be mutable; objects whose value is unchangeable once they are created are called immutable. (The value of an immutable container object that contains a reference to a mutable object can change when the latter’s value is changed; however the container is still considered immutable, because the collection of objects it contains cannot be changed. So, immutability is not strictly the same as having an unchangeable value, it is more subtle.) An object’s mutability is determined by its type; for instance, numbers, strings and tuples are immutable, while dictionaries and lists are mutable.

注意 immutable container 的部分,只是 container 不會改變,不代表裡面的東西也不會改變。 但因為 int 這些 type 都是 immutable 的,因此行為大致上也跟 javascript 一樣。 只是在 lauguage level 多了 immutable 的設計,也提供了 mutable 與 immutable container,豐富了一些語意,知道他不會變是很令人安心的事。

傳參數進 function 也是一樣的事,一切都是 reference,只有傳進去後會不會被修改。

p.s.

  • 在 python 中,如果要比對兩個 reference 是不指到同一個 instance 可以用 a is b 來判斷;要比對兩個是不是同樣的值的話要用 a == b。
  • 習慣 c/c++ 的,可以印印看 '%x' % id(a),在 cpython 中,這就會像是 virtual memory address 的東西。

Nested scope#

對於原本習慣於 c/c++ 的人,python 的 scope 並不太一樣;也比較像 javascript 一些。

參考 pytnon3 tutorial - scopes and namespaces 如果要找 x 是什麼,順序會是:

nested_scope

考慮這樣的一段 code,在一個 mod.py 當中:

x = 1
def foo():
    y = 2
    print([_ for _ in locals()])
print([_ for _ in globals()])

一個 .py 檔,可視為一個 module。module 中的東西都算在 global symbol table 當中。 x、foo 都會被塞到那邊。可以用 globals() 來 reference 到這個 global symbol table。

如果出現一個 def ...,則會出現另一個 scope,裡面的東西會放到該 function 的 local symbol table 當中。在 function 中可以用 locals() 來調用。 而回到了 module 下時 globals() is locals()。 在 def ... 當中,出現在參數列或是出現在等號左邊 z = ... 時,都會視為這個 symbol 是在 local symbol table。

x = 1
def foo():
    y = x
    def bar():
        z = y

在巢狀結構下,bar 中可以讀到外層 function foo 中的 symbol y。在 bar 中, z 所在的 scope 是 local,y 則稱 non-local,x 所在的區域則稱 global。

  1. 外層的 reference 並不會因為 y 與 bar 出現的順序而有影響。 就算把 y 寫到 def bar... 後面,結果會是一樣的。

    但如果在編譯期時,foo 中沒有找到 y,則會被編成是去 global 中找。 這時就不會管當下 global 中有沒有 y 了,要在什麼時候塞進去都可以。

     def foo():
         return x
    

    比方說這樣的 code 編譯期都會正常,只是在執行時會說沒有 x 這個 global variable。

  2. 在 bar 編譯完之後,其中的邏輯會被放在 bar.__code__ 當中, 而如果需要往外層查找 symbol,則會被編在 bar.__closure__ 當中。 找不到的部分就會去 bar.__globals__ 中找,這 global 就會直接的指向 module 的 symbol table,查找就比較像是在 lookup dictionary。 在編譯時,要放在哪邊就決定了。

    因為這樣的處理,因此之後 bar 在執行期間,不論被傳到哪邊去,local 與 non-local 的行為都不會再改變,與編譯時一模一樣。而 global 的部分,則會確保一直 reference 到同一個 global symbol table。

    例如有一個 mod.py 中寫著

     def foo():
         print('hello')
    

    另外一個 .py 檔,可以在 import mod.py 後,藉由修改掉他的 global,來 hidden 掉 print:

     import mod
     def _my_print(*args, **kwargs):
         print('not what you think')
     mod.print = _my_print
     mod.foo()
    

    這機制讓 mock 或是一些 monkey patch 的機制可以成立。

  3. 內層的 function 可以看到外層 function 的 symbol,而如過這個 symbol 的 type 是 mutable 的,可以直接修改它的內容。如果是 immutable,則須透過 nonlocal、 global 等關鍵字,來強制內層的 function 把某個 symbol 視為 non-local 或 global。

    mutable:

     x = {}
     def foo():
         x['a'] = 1
     foo()
     print(x)
    

    immutable, use global keyword

     x = 1
     def foo():
         global x
         x = 2
     foo()
     print(x)
    

    immutable, use nonlocal keyword (python3+).

     def foo():
         x = 1
         def bar():
             nonlocal x
             x = 2
         bar()
         print(x)
     foo()
    
  4. 雖然家了 global、nonlocal 才可以用 assign 來改,但是取得 reference 到的 object 這件事都是在執行期才發生的。編譯期比較像是存下 reference 的 reference。

    bar.__closure__ 會是一個 tuple of cell,這個 cell 把外層與內層的 symbol 綁在一起。

     def foo():
         x = 1
         def getter():
             print('x: %x' % id(x))
             return x
         def setter(v):
             nonlocal x
             x = v
         print('x: %x' % id(x))
         return getter, setter
     getter, setter = foo()
     setter(3)
     print(getter())
     y = (1,2,3)
     print('y: %x' % id(y))
     setter(y)
     print(getter())
     print(getter.__closure__[0])
     print(setter.__closure__[0])
    

    會印出

     x: a69e00
     x: a69e40
     3
     y: 7f8c804efd38
     x: 7f8c804efd38
     (1, 2, 3)
     <cell at 0x7f8c8065e438: tuple object at 0x7f8c804efd38>
     <cell at 0x7f8c8065e438: tuple object at 0x7f8c804efd38>
    

    bar.__globals__ 就比較像是綁定到一個 symbol table,其他的等執行時直接查找。 比方說在 mod.py 中:

     def foo():
         print('x: %x' % id(x))
         return x
     print('globals: %x' % id(globals()))
    

    並用另一個 .py 來 import mod

     import mod
    
     try:
         print(mod.foo())
     except NameError as e:
         print(e)
     mod.x = 1
     print(mod.foo())
     y = (1,2,3)
     print('y: %x' % id(y))
     mod.x = y
     print(mod.foo())
     print('mod.__dict__: %x' % id(mod.__dict__))
     print(mod.foo.__globals__ is mod.__dict__)
    

    則會得到

     x: a69e00
     x: a69e40
     3
     y: 7f8c804efd38
     x: 7f8c804efd38
     (1, 2, 3)
     <cell at 0x7f8c8065e438: tuple object at 0x7f8c804efd38>
     <cell at 0x7f8c8065e438: tuple object at 0x7f8c804efd38>
    

    雖然有些不同,但兩種都是會在執行時才去 de-reference,取得最新的 instance。

Class members#

如同 nested scope 一樣,a.x 的查找,一樣有一個順序。

class A(object):
    x = 1
    print('x: %x' % id(x))
    def __init__(self):
        print('self.x: %x' % id(self.x))
        self.x = 2
        print('self.x: %x' % id(self.x))
a = A()
print('a.x: %x' % id(a.x))
print('A.x: %x' % id(A.x))
print('A.__dict__["x"]: %x' % id(A.__dict__['x']))

output:

x: a69e00
self.x: a69e00
self.x: a69e20
a.x: a69e20
A.x: a69e00
A.__dict__["x"]: a69e00

由 code 可知在這段中會有兩個 x。一個在 class 當中,一個在 instance 當中。

attribute_lookup

ps.1. 這圖沒有考慮 __slot__,僅用 __dict__ 來表示

ps.2. 最後會回到 object,new style 的所有 class 都應該要是 object 的子孫。

如果考慮繼承,則這個 x 會一直往上找。而實際上,考慮多重繼承,是照著 MRO 的順序依序往上找。MRO 有點像是一個 DFS 的 topology sort,將所有繼承關係拉成一條直線。 可以印印看 A.__mro__ 來瞭解一下這個 chain 是什麼樣子。 圖中寫的 "super" 很微妙,並非是真的 parent class,而是 MRO 中的下一個。 事實上如果在 class 中使用 super() 也是一樣的概念。

class C(object):
    def get(self):
        print('C')
        return 1
class B(object):
    def get(self):
        print('B')
        return super().get()
class A(B, C):
    def get(self):
        print('A')
        return super().get()
a = A()
a.get()
b = B()
b.get()

比方說上段的 B 是繼承 object,但透過 a 時 super 在這邊會指到 C 去。 而如果是透過 b 的話,就會丟出 exception 說 object 中沒有 get。 考慮菱形的繼承關係的話,這邊就會更複雜,在此就先跳過了。

小結 a.x 的尋找過程,有點像這樣:

    def __getattribute__(self, key):
        if key in self.__dict__:
            return self.__dict__[key]
        for cls in type(self).__mro__:
            if key in cls.__dict__[key]:
                return cls.__dict__[key]
        raise AttributeError

這只是示意的 code,並不能跑的。至少,他如果是用來給出 a.x 的答案,那 function 中不應該出現任何 self.* 吧?不然就 recursion 了。事實上, __getattribute__ 中包含的邏輯也還不止於此。一般來說,不應該要去 overwrite 這個 function。

metaclass#

class ... 之後的 indent block,每一個都是一個 statement,有點像一個 function 一樣, 也符合上面的 nested scope,可以存取到 non-local、global。透過 locals() 可印出這 scope 的 symbol table。

def outer():
    x = 1
    class A(object):
        MY_CONST = 2
        def foo(self, value = x + MY_CONST):
            return value
        def bar(self):
            return x + self.MY_CONST
        print(locasl())
    return A

這解釋了為什麼 foo 的 default param 那邊,可以 access 到 MY_CONST。 但是在 bar 中,如果不是透過 self,是不能夠直接拿到 MY_CONST 的。 class 的 indent block 中,會有一個 symbol table,但不會發生 closure。發生 closure 的只有 outer 的 x。

這條編譯與執行期的界線滿重要的,這樣的設計,讓 inner class 可以有繼承關係; 可以在 class 中寫一些 decorator 給後面的 function 宣告用; 也可以在 class 定義中加入條件判斷。 但不會發生 closure,在 class 中找東西,都統一透過 self.* 的 chain。

這個 indent block 編譯完後,不像 function 會塞到 __code__ 當中, class 執行完 indent block 中的 statement 後,並把做好的 locals() 當成個 dict 餵給 class 的 constructor -- metaclass.__new__, 然後轉為 class instance A 中的 A.__dict__。滿像是 javascript 把東西塞進 prototype 中一樣。

metaclass 扮演的是一個產生 class instance 的 class class。 如果說 class 是用來 create instance 的話,metaclass 就是用來 create class 的。

大部分的 class creation 並不需要特別的邏輯, 因此大部分的 class 的 metaclass 都是 type。

是的,所以 A.x 有著與 a.x 一樣的是會有一個查找的順序。

metaclass

可由 type(A).__mro__ 來印出這個 chain,中有些什麼東西。這與上面 a.x 的圖很像 不過不要搞混了,是根本是一模一樣。兩個圖如果疊起來一起看,交集的部分是 A.__dict__ 在 a.x 中會去搜尋,A.x 也會。因此,不要在 metaclass 中放太多東西,這會混肴 A.x 到底是什麼的解讀,讓它在不同的情況下有不同的值,一般這個來說還是給 instance 用的而不會想到這是拿來 create class 的吧?

那這樣 metaclass 到底有什麼用呢?

比方說 google.appengine.ext.db:PropertiedClass 就是一個 metaclass,在看過 class 的 dict 後,用來幫忙建立一個 properties 列表, 以及把 class 加到一個 _kind_map 當中列管。

或像是 khanacademy 一樣,利用 metaclass 來確保每一個 webapp2.RequestHandler 的 method 都有加到 access control。

descriptor#

如果 A.foo 是一個 function 呢?

class A(object):
    def foo(self):
        print(self)
a = A()
a.foo()
A.foo(a)
print(a.foo is A.foo) # False

不像上面 a.x 的例子,這邊並沒有在 a.__dict__ 中塞入 foo 啊?為什麼 a.foo 與 A.foo 會不一樣呢?這代表我們關心的 a.x 的故事其實還沒有結束。

這樣的奇妙機制,在 python 當中叫做 descriptor。

只要一個 instance 有 implement __get__ or __set__ or __delete__ 就是一個 descriptor。這三個 protocol 分別代表在 a.x 被引用的三種時機時的 hook。

class X(object):
    def __get__(self, obj, cls):
        print(obj, cls)
class A(object):
    x = X()
a = A()
a.x
A.x

結果為:

<__main__.A object at 0x7ff1f25c1588> <class '__main__.A'>
None <class '__main__.A'>

在 a.x、A.x 時,就自動的呼叫到了 X.__get__,並且會給不同的參數。 如果是從 instance 過去的,obj 與 cls 都會給,但如果是由 class 過去的,就只會有 cls,此時 obj 就會是 None。這樣參數的給法,其實是在 __getattribute__ 當中發生的。還記得 a.x 與 A.x 查找時的 chain 嗎,一樣 a.__getattribute__ 與 A.__getattribute__' 也有不同的查找的 chain。a.x的部分,應該會查到object.getattribute(除非被 overwrite...),此時會把self, type(self)給傳進去給get使用;而A.x的部分,則會查到type.getattribute, 此時會把None, self` 傳進去。

透過這樣的機制,就可以很簡單的來模擬一下 class member function 的行為。

class Foo(object):
    def __get__(self, obj, cls):
        if obj:
            return lambda *args: self.__call__(obj, *args)
        return self
    def __call__(self, obj, *args):
        print('a[%x].foo%s' % (id(obj), args))
class A(object):
    foo = Foo()
a = A()
print('a: %x' % id(a))
a.foo(1, 2, 3)
A.foo(a, 4, 5)

descriptor protocol 不會發生在 instance 的 dict 當中,

class Foo(object): ...
class A(object): pass
a = A()
print('a: %x' % id(a))
a.foo = Foo()
a.foo(1, 2, 3)

這時 __get__ 的魔法會失效,此時傳入 __call__ 中的 obj 會是 1。 如果改成塞到 A.foo = Foo() 則是會成功的像是加入了一個 member function 一樣。 有興趣可以自己實作看看 @staticmethod、@classmethod 等 decorator。

以上講的只有一個 __get__ 時,這樣的 descriptor 叫做 non-data descriptor;而有 __get__、__set__ 的稱為 data descriptor,最直接的使用情境就是 property, 這個 decorator 會幫忙照著語意設定 __get__、__set__, 來實作 python 版的 getter、setter。

這機制很深入 python 內部,大部分 code 都是 c 的了。 詳細流程畫出來又還會更複雜。先參考 document 中的規則:

  • descriptors are invoked by the getattribute() method
  • overriding getattribute() prevents automatic descriptor calls
  • object.getattribute() and type.getattribute() make different calls to get().
  • data descriptors always override instance dictionaries.
  • non-data descriptors may be overridden by instance dictionaries.

前三個應該都好理解了,後兩個則要考慮 assign 時的情境:

class X(object):
    def __get__(self, obj, cls):
        ...
class A(object):
    x = X()
    def __init__(self):
        self.x = 3

當 A() create 出來後,self.x 就被蓋掉了,這樣就真的蓋掉了,跟沒有 descriptor 一饃一樣了。

但考慮

class X(object):
    def __get__(self, obj, cls):
        ...
    def __set__(self, value):
        ...
class A(object):
    x = X()
    def __init__(self):
        self.x = 3

這時我們也會希望 3 被 X.__set__ 處理,因此 instance dict 的優先序應該要比較小才是。 但照 descriptor 的設計,我們沒有辦法阻止這件事:

        self.__dict__['x'] = 3

因此感覺上一不做二不休,如果用 a.x 來存取時,發現 A.x 是一個 descriptor,則不管 a.__dict__ 中有沒有 x,一律不會去查它,好像不會看見一樣。 這樣才不會混淆後面對於 self.x 的想像。 但這段 spec 一下,就把流程搞得很複雜,可直接參考, understanding pyton metaclass 中的圖,我想這是記不起來的,不如記上面的規則:

不過可以拿來驗證對於這邊的想像是否一樣。

name mangling#

class B(object):
    x = 1
    def get(self):
        return self.x
class A(object):
    def __init__(self, val):
        self.x = val
a = A(3);
print(a.get())

有時,這是你想的邏輯,有時卻不是。沒有犯錯的原因在於寫 A 的人,知道 x 對於 parent class B 來說是什麼樣的意義。

這有點違背封裝的原則, 不應該所有的繼承都覺得要對 parent class 這麼的了解,每個用過的 self.* 都要注意, 這樣太瘋狂了。

除了盡量少在 self 當中塞些亂七八糟的東西,及好好 document 外,python 提供了一個 name mangling 的機制來避免發生問題。這基本上只是個 name trick,當在 class context 當中出現 __x 這樣的變數,但不是 __x__ 的形式,會自動變成 _{classname}__x 這樣的變數。

不過還是提一下 python 的哲學,

如果沒有打算要 close,那就 open 吧。如果 open 了造成了什麼問題,是 caller 要負責的。

因此寫 class 也不用像防賊一樣的把所有東西都加上 __ prefix。尤其在並不覺得這個 class 是要給別人繼承時。但如果在規劃一個繼承的結構, 而當這個 class 所要負的責任不會很直接的聯想到這個名字,那麼的確用 __ 開頭會是個避免錯誤的選擇。

Scope traps#

default values#

def foo(condition=True, values=[]):
    if condition:
        values.append(1)
    print(values)
foo()
foo()
foo()

由於 default value 在 function 中,是編譯期決定的。再加上 [] 是 mutable。 因此在這個 foo 當中,values 的 default value 永遠都會指向同一份。

def foo(condition=True, values=None):
    values = [] if values is None else values[:]
    if condition:
        values.append(1)
    print(values)
  • 在這種情況下用 None 來當 default value 會好些。
  • 傳進來的 values 如果是 mutable,不要修改他的值比較禮貌。 如果沒有 performance issue,可以用 deepcopy, 然後真的要傳出去用 return 的。

    如果有 performance issue,考慮使用 copy on write 的 object。

    或是乾脆把 values 包進個 class 中,把 foo 移成 member function。 讓他看起來像是 write function 一點。

class default values#

class A(object):
    values = []
    def __init__(self, condition=True):
        if True:
            self.values.append(1)

因 values 會在 class dict 當中,因此所有 instance 會共用一份。當 values 是 mutable 時,放在哪邊就很重要,也代表不同的語意。

data descriptor#

延伸上面的例子,data descriptor 一定要在 class dict 當中,因此在 data descriptor 當中,不要塞東西進 self,要注意這個 self 或是全部 create 出來的 instance 一起共用的。

missing global keyword#

_cache = {}

def get(key):
    if key in _cache:
        return _cache[key]
    ...

def clear_cache():
    _cache = {}

由於 clear_cache 中少了 global keyword,因此跑出了一個 local variable, 跑完了之後啥事都沒發生。

留言

這個網誌中的熱門文章

Resolve cycle

Resolve cycle # 之前用 astroid 寫的 tool pydependency , 將 dependency graph 匯出成 .dot 或是一些 .json 格式後, 就有幾個方便的 tool 可以來分析了。 以下的故事基本上都是在分析 junyi-academy 的 code,但大部分的壞味道,從 khan-academy 時代就有了。如果只看 dependency graph 沒感覺的話,也可以載 source code 來看看。 graph tools # graphviz # graphviz 為老牌的 graph 視覺化工具。 pylint 的內含的工具 pyreverse 就是拿 graphviz 來畫 UML 的圖的。好處就是簡單好上手,畫出第一張圖: echo "digraph { hello->world }" | dot -Tpng -o dot.png 強大之處在於其 layout 演算法,但是如果 cycle 很多,畫出來就會全部結成一團, 也不知道能怎麼調整。 python 部分套件可以安裝 pygraphviz 或 pydot,讀寫 dot 會方便些。 pygraphviz 包裝原生 graphviz library 的 C api 到 python 中,所以對於 agraph 的操作是直接對應到 C 的 data structure 的。 而 pydot 對於 graph 的操作都還是在 python 當中。直到要找 graphviz tool 時, 才利用其中的 parser 作資料的轉換。 兩個在 layout/draw 時,都還是直接用 subprocess.Popen 呼叫 graphviz 的 command line tools。pygraphviz 也沒有因為直接呼叫 C,而直接用像 gvToolTred 這樣的 function。還是多了一層 serialize、deserialize 及 pipe。 在 tred 或是 layout (只需要多標上 position?) 這樣的功能上,感覺滿慘的。 tred 是一個 grap...

Blogger tool

Blogger tool # 不懂為什麼,對於所見即所得的編輯器不太會用(少了 vim 加持??) 直接寫 html 又太痛苦。那要寫 markdown 嗎? 希望在文件中方便插入 code、流程圖等。 找其他 blog?就只是記記東西還要去找,有點懶。 如果透過 markdown tool,再貼到 blogger,html tag 很髒,有些東西也可能會跑掉。 目前 markdown 也不符合需求,需要小改。未來如果不滿意,還是可以隨時調整。 因此就決定自己 render 然後推上 blogger。 How to push posts # 1. 申請一個 app id/secret # 不知道有沒有別的申請方法,目前是直接登入 console.cloud.google.com 來申請。 然後隨便找一個 project (這樣對嗎?邏輯上這樣比較像是要把存取 blogger 的權限放到這個 web application 當中一樣。但其實暫時還只打算先跑 CLI)。 先把 blogger API 打開,然後到 "API 和服務" > "憑證" 去建立。 申請過程中會問一下是要幹什麼用的,還有填個 app 的名字, 然後就可以下載 client_id.json 裡面帶有 api id/secret 了。 如果弄丟了,或要修改,之後也可回到這個頁面當中做後續的維護動作。 2. install API client # 沒有其實也可以啦,手動打 REST API 而已。只是有好像比較方便一些。 python 版 pip install --upgrade google-api-python-client nodejs 版 (alpha) npm install googleapis --save 最後選用了 nodejs,因為 python-markdown 出來的結果不是很滿意 (另一個 misaka 沒試過) , marked 之前用起來感覺還不錯。只用同一種 language 之後在修改時應該比較方便擴充。 3. get access token # 因為需要操作 ...

Python - import

Python - import # 當 code 變多,需要更好的整理和封裝,因此 python 提供了 package、module 的功能, 及 import 的語法來調用這些包裝好的元件。 python 中,一個 .py 的檔案即為一個 module。而存放 module 的目錄,則稱為一個 package。 在 import 過程中,module 會被編譯、執行,並在 import 後, 被放入 caller 的 local symbol table 當中。 之後可透過 module.symbol 來調用這個 module 下的 symbol。 如此簡單、似是而非的描述,距離實際上實作細節還很遙遠。 在深入一個個機制之前,先列一下希望能回答的問題。 package 與 module 的定義是什麼?有什麼分別嗎?責任的界定是什麼呢? 傳說在 package 的目錄當中,必須要有一個 __init__.py 的存在?這是做什麼用的? 我的 python interpreter 去哪邊找 package 的,什麼是 sys.path ? 這些package、module 是一個物件嗎?是的話他們的生命週期是怎樣呢? 會不會有 circular import 的發生?有什麼限制? import 的基本語法 # 在 python3 - module (or python2 - module ) 中,介紹得滿清楚的。複習一下這些內容: import mod 最簡單的型式,找到 module 後,在 local symbol table 中建立 (bind) 名為 mod 的變數,reference 到 module mod 。 import pkg.mod 找到 package pkg 後,找到 module mod ,在 local symbol table 中建立 pkg 。之後可以透過 pkg.mod.X 去調用下面的東西。 import pkg.mod as mod 匯入後改變在 local symbol table 中的名稱。在 pkg.mod import 後,可用 mod 去存取。 from pkg import mod 、 fro...