目錄
1.1 namespace (命名空間)
定義:是一個從名字到對象的映射。(一般不用關注)
對象的屬性集合也是一種命名空間的形式。
1.2 屬性
定義:任何跟在一個點號之後的名稱都稱爲 屬性 --- 例如,在表達式 z.real 中,real 是對象 z 的一個屬性。
在表達式 modname.funcname 中,modname 是一個模塊對象而 funcname 是它的一個屬性。在此情況下在 模塊的屬性 和 模塊 中定義的全局名稱之間正好存在一個直觀的映射:它們共享相同的命名空間!
1.3 命名空間的生存期
不同時刻創建的命名空間擁有不同的生存期。
- 包含內置名稱的命名空間是在 Python 解釋器啓動時創建的,永遠不會被刪除。((內置名稱實際上也存在於一個模塊中;這個模塊稱作 builtins 。))
- 模塊的全局命名空間在模塊定義被讀入時創建,也會持續到解釋器退出。(被解釋器的頂層調用執行的語句,從一個腳本文件讀取或交互式地讀取,被認爲是 __main__ 模塊調用的一部分,因此它們擁有自己的全局命名空間。)
- 一個函數的本地命名空間在這個函數被調用時創建,並在函數返回或拋出一個不在函數內部處理的錯誤時被刪除。
1.4 作用域
定義:一個 作用域 是一個命名空間可直接訪問的 Python 程序的文本區域。
“可直接訪問” 意味着對名稱的 非限定引用 會在 命名空間 中查找名稱。
- 最先搜索的最內部作用域包含局部名稱(函數內部)
- 從最近的封閉作用域開始搜索的任何封閉函數的範圍包含非局部名稱,也包括全局名稱
- 倒數第二個作用域包含當前模塊的全局名稱
- 最外面的範圍(最後搜索)是包含內置名稱的命名空間
1.4.1 全局變量
如果一個名稱被聲明爲全局變量,則所有引用和賦值將直接指向包含該模塊的全局名稱的中間作用域。
1.4.2 局部變量
要重新綁定在最內層作用域以外找到的變量,可以使用 nonlocal 語句聲明爲非本地變量。如果沒有被聲明爲非本地變量,這些變量將是隻讀的(嘗試寫入這樣的變量只會在最內層作用域中創建一個 新的 局部變量,而同名的外部變量保持不變)。( 類似於函數內部的變量)
通常,當前局部作爲域將引用當前函數的局部名稱。 在函數以外,局部作用域將引用與全局作用域相一致的命名空間:模塊的命名空間。 類 定義將在局部命名空間內再放置另一個命名空間。(在一個模塊內定義的函數的全局作用域就是該模塊的命名空間,無論該函數從什麼地方或以什麼別名被調用。)
如果不存在生效的 global 語句 -- 對名稱的賦值總是進入最內層作用域。賦值不會複製數據 --- 它們只是將名稱綁定到對象(類似於指針)。
global 語句可被用來表明特定變量生存於全局作用域並且應當在其中被重新綁定;nonlocal 語句表明特定變量生存於外層作用域中並且應當在其中被重新綁定。
1.5 作用域和命名空間示例
def scope_test():
def do_local():
spam = "local spam"
def do_nonlocal():
nonlocal spam
spam = "nonlocal spam"
def do_global():
global spam
spam = "global spam"
spam = "test spam"
do_local()
print("After local assignment:", spam)
do_nonlocal()
print("After nonlocal assignment:", spam)
do_global()
print("After global assignment:", spam)
scope_test()
print("In global scope:", spam)
輸出
After local assignment: test spam
After nonlocal assignment: nonlocal spam
After global assignment: nonlocal spam
In global scope: global spam
將 spam 名稱(標籤)(可寫)掛到 "test spam" 對象(物品)(只讀)上,通過輸出發現只改變了最內層作用域的標籤。(意思大概是函數內部發生的事只會影響函數內部的作用域以及作用域內的對象)
局部 賦值(這是默認狀態)不會改變 scope_test 對 spam 的綁定。 nonlocal 賦值會改變 scope_test 對 spam 的綁定,而 global 賦值會改變模塊層級的綁定。
2 類
2.1.1 類定義
- 類定義與函數定義 (def 語句) 一樣必須 被執行 纔會起作用。
- 在實踐中,類定義內的語句通常都是 函數定義 ,但也允許有其他語句
- 類內部的函數定義通常具有一種 特別形式的參數列表,這是方法調用的約定規範所指明的
- 進行類定義時,將創建一個新的命名空間,並將其用作局部作用域-- 因此,所有對局部變量的賦值都是在這個新命名空間之內。 特別的,函數定義會綁定到這裏的新函數名稱。
- 離開類定義時,將創建一個 類對象(也就是類的名稱)。 這基本上是一個包圍在類定義所創建命名空間內容周圍的包裝器;
2.1.2 類方法(可以先看後面)
Python中3種方式定義類方法, 常規方式 (self), @classmethod修飾方式, @staticmethod修飾方式。
- 普通的類方法,需要通過 self參數 隱式傳遞 當前類的實例對象。
- @classmethod修飾的方法,需要傳遞當前類對象 參數cls(調用時可以不寫)。
- @staticmethod修飾的方法,定義與普通函數是一樣的,不需要傳實例對象和類對象。
@classmethod,@staticmethod修飾方式區別:
- @staticmethod不需要表示自身對象的self和自身類的cls參數,就跟使用函數一樣。
- @classmethod也不需要self參數,但第一個參數需要表示自身類的cls參數。
- 如果在@staticmethod中要調用到這個類的一些屬性方法,只能直接類名.屬性名或類名.方法名。
- 而@classmethod因爲持有cls參數,可以來調用類的屬性,類的方法,實例化對象等,避免硬編碼。
簡單示例:
class A(object):
bar = 1
def foo(self):
print('foo')
@staticmethod
def static_foo():
print('static_foo')
print(A.bar)
@classmethod
def class_foo(cls):
print('class_foo')
print(cls.bar)
cls().foo()
A.static_foo()
A.class_foo()
# 輸出結果
static_foo
1
class_foo
1
foo
2.2 類對象
類對象支持兩種操作:屬性引用 和 實例化 。
2.2.1 屬性引用
屬性引用 使用 Python 中所有屬性引用所使用的標準語法: obj.name。
class MyClass:
"""A simple example class"""
i = 12345
def f(self):
return 'hello world'
MyClass.i , MyClass.f , MyClass.__doc__ 都是有效的屬性引用。
MyClass.i 返回 12345 (一個int對象)
MyClass.f 返回 <function __main__.MyClass.f(self)>(一個函數對象)
MyClass.__doc__ 返回 “A simple example class” (一個字符串對象)
2.2.2 實例化
類的 實例化 使用 函數表示法 。 可以把類對象視爲是返回 該類的一個新實例的 不帶參數的函數。
x = MyClass()
創建類的新 實例 並將此對象分配給局部變量 x 。實例化操作(“調用”類對象)會創建一個 空對象 。
如果需要創建一個帶有初始狀態的實例(對象),需要在類定義時包含一個名爲 __init__() 的特殊方法。
def __init__(self):
self.data = []
__init__() 方法
當一個類定義了 __init__() 方法時,類的實例化操作會自動爲新創建的類實例發起調用 __init__()。
class Complex:
def __init__(self, realpart, imagpart):
self.r = realpart
self.i = imagpart
x = Complex(3.0, -4.5)
x.r, x.i
x,r 返回 3.0
x.i 返回 -4.5
2.3 實例對象
實例對象理解的唯一操作是 屬性引用 。有兩種有效的屬性名稱,數據屬性 和 方法 。
2.3.1 數據屬性
x.counter = 1
while x.counter < 10:
x.counter = x.counter * 2
print(x.counter)
del x.counter
print 輸出 16,通過 x.counter 創建 count 數據屬性,並通過 del 刪除。
2.3.2 方法
另一類實例屬性引用稱爲 方法 。 方法是“從屬於”對象的 函數 。函數一般與對象無關,像一些lambda函數、python內置函數等。
作用域:方法是通過實例化的對象進行方法的調用,調用後開闢的空間不會釋放,而函數則不同,函數執行完後,爲其開闢的內存空間立即釋放(存儲到了棧裏)。(參考命名空間和作用域的生存期)
實例對象的有效方法名稱依賴於其所屬的類。一個類中所有是函數對象的屬性都是定義了其實例的相應方法。
在示例中,x.f 是有效的方法引用,因爲 MyClass.f 是一個函數,而 x.i 不是方法,因爲 MyClass.i 不是一個函數。 但是 x.f 與 MyClass.f 並不是一回事 --- 它是一個 方法對象,不是函數對象。(即類在實例化後,類中的函數對象會變成實例的方法對象)
2.4 方法對象
2.4.1 調用
函數是通過“函數名()”的方式調用,方法通過“對象.方法名()”的方式進行調用。
立即調用
x.f()
保存調用
xf = x.f
while True:
print(xf())
上面調用 x.f() 時並沒有帶參數,但是類定義中 f() 的函數定義指定了一個參數。
方法的特殊之處就在於 實例對象 會作爲函數的第一個參數被傳入(即調用 x.f() 其實就相當於 MyClass.f(x))
總之,調用一個具有 n 個參數的方法就相當於調用 n+1 個參數的對應函數,實例對象爲第一個參數值。
2.5 類變量和實例變量
一般來說,實例變量用於每個實例的唯一數據,而類變量用於類的所有實例共享的屬性和方法:
class Dog:
kind = 'canine' # 類變量
def __init__(self, name):
self.name = name # 實例變量
>>> d = Dog('Fido')
>>> e = Dog('Buddy')
>>> d.kind # d,e共享
'canine'
>>> e.kind
'canine'
>>> d.name # d私有
'Fido'
>>> e.name # e私有
'Buddy'
2.5.1 類變量共享(避免使用)
class Dog:
tricks = [] # 類變量
def __init__(self, name):
self.name = name
def add_trick(self, trick):
self.tricks.append(trick)
>>> d = Dog('Fido')
>>> e = Dog('Buddy')
>>> d.add_trick('roll over')
>>> e.add_trick('play dead')
>>> d.tricks # tricks[]被所有實例對象共享改動
['roll over', 'play dead']
2.5.2 實例變量私有(通常用法)
class Dog:
def __init__(self, name):
self.name = name
self.tricks = [] # 在類實例化後創建一個實例變量
def add_trick(self, trick):
self.tricks.append(trick)
>>> d = Dog('Fido')
>>> e = Dog('Buddy')
>>> d.add_trick('roll over')
>>> e.add_trick('play dead')
>>> d.tricks # d私有一個tricks列表
['roll over']
>>> e.tricks # e私有一個tricks列表
['play dead']
方法可以通過使用 self 參數的方法屬性調用其他方法:(通過self隱形傳遞,參見上2.1.2 類方法)
class Bag:
def __init__(self):
self.data = []
def add(self, x):
self.data.append(x)
def addtwice(self, x):
self.add(x)
self.add(x)
每個值都是一個對象,因此具有 類 (也稱爲 類型),並存儲爲 object.__class__ 。(類似用type(object))
3 繼承
class DerivedClassName(BaseClassName):
或者 該基類來源自其他模塊
class DerivedClassName(modname.BaseClassName):
如果請求的屬性在 派生類 中找不到,搜索將轉往 基類 中進行查找。 如果基類本身也派生自其他某個類,則此規則將被遞歸地應用。
派生類的實例化(與類實例化無區別): DerivedClassName() 會創建該類的一個新實例。
Python有兩個內置函數可被用於繼承機制:
- 使用 isinstance() 來檢查一個實例的類型: isinstance(obj, int) 僅會在 obj.__class__ 爲 int 或某個派生自 int 的類時爲 True。
- 使用 issubclass() 來檢查類的繼承關係: issubclass(bool, int) 爲 True,因爲 bool 是 int 的子類。 但是,issubclass(float, int) 爲 False,因爲 float 不是 int 的子類。
4 多重繼承
class DerivedClassName(Base1, Base2, Base3):
<statement-1>
.
.
.
<statement-N>
按照深度優先,從左至右的 靜態 假設下:如果某一屬性在 DerivedClassName 中未找到,則會到 Base1 中搜索它,然後(遞歸地)到 Base1 的基類中搜索,如果在那裏未找到,再到 Base2 中搜索,依此類推。
4.1 父類調用
super() 函數是用於調用父類(超類)的一個方法。super 是用來解決多重繼承問題的,直接用類名調用父類方法在使用單繼承的時候沒問題,但是如果使用多繼承,會涉及到查找順序(MRO)、重複調用(鑽石繼承)等種種問題。(MRO 就是類的方法解析順序表, 其實也就是繼承父類方法時的順序表。)
4.2 super() 功能
-
方法擴展
-
隔離更改
內置類的 方法擴展 示例:
class LoggingDict(dict):
def __setitem__(self, key, value):
logging.info('Settingto %r' % (key, value))
super().__setitem__(key, value)
此類具有與其父項(dic)相同的功能,但它擴展了 [setitem] 方法,以在更新密鑰時創建日誌條目。創建日誌條目後,該方法使用 super() 來委派使用鍵/值對實際更新字典的工作。
在 super() 之前,我們將用dict._setitem_(self, key, value)硬連接。但是,super() 更好,因爲它是間接引用。間接的一個好處是 隔離更改 ,我們不必按名稱指定 父類 。如果需要變動基類,則 super() 引用將自動跟隨不用改動。
class LoggingDict(SomeOtherMapping): # 新的基類
def __setitem__(self, key, value):
logging.info('Settingto %r' % (key, value))
super().__setitem__(key, value) # 不用改動
實例研究:
#!/usr/bin/python
# -*- coding: UTF-8 -*-
class FooParent(object):
def __init__(self):
self.parent = 'I\'m the parent.'
print ('Parent')
def bar(self,message):
print ("%s from Parent" % message)
class FooChild(FooParent):
def __init__(self):
super(FooChild,self).__init__()
#等同super().__init__()
print ('Child')
def bar(self,message):
super(FooChild, self).bar(message)
#等同super().bar(message)
print ('Child bar fuction')
print (self.parent)
if __name__ == '__main__':
fooparent = FooParent()
fooChild = FooChild()
fooChild.bar('HelloWorld')
fooparent = FooParent()實例化
Parent
一個__init__函數初始化後的實例對象,沒有__init__函數則爲空的實例對象
fooChild = FooChild() 實例化
Parent
Child
類似上面dict.__setitem__方法的擴展,該super(FooChild,self).__init__() 是對FooParent.__init__()的擴展,因此輸出兩個值。
fooChild.bar('HelloWorld') 屬性引用
HelloWorld from Parent
Child bar fuction
I’m the parent.
同樣的使用super().bar(message) 對FooParent.bar()方法的擴展。
super() 擴展:
函數的裝飾器(由方法到函數)
裝飾器仍然是一個 Python 函數,實現由 閉包 (不用改動)支撐,裝飾器的返回值也是一個 函數對象。讓函數在無需修改任何代碼的前提下給其增加功能(類似super())。
def debug(func):
#定義外層 debug(func),return wrapper,然後定義內層wrapper(),return func
def wrapper():
#增加一些額外的功能
#……
print("[DEBUG]: enter {}()".format(func.__name__))
#……
return func()
#執行額外的功能,再執行func----相當於被裝飾func多了這些額外的功能
return wrapper
@debug
def say_hello():
print("hello!")
常見功能
- 計算函數的運行時間
- 計算函數的運行次數
- 給函數插入運行日誌
- 讓函數實現事務一致性:讓函數要麼一起運行成功,要麼一起運行失敗
- 實現緩存處理
- 權限校驗:在函數外層套上權限校驗的代碼,實現權限校驗
計算函數運行時間示例:
def timer(func):
def wrapper(*args, **kwargs): #因爲被裝飾sum()有多個參數
start_time = time.time()
res = func(*args, **kwargs)
print("[Time out]: %.4f s" % (time.time() - start_time))
return res
return wrapper
#timer裝飾器,在運行sum()時,同時計算其運行時間,與sum()函數無關。
@timer
def sum(a, b):
time.sleep(2)
return a + b
print("計算結果:", sum(1, 2))
# 運行結果:
[Time out]: 2.0019 s
計算結果: 3
計算函數運行次數示例:
import logging
counts= 0
def count(f):
def wrapper(*args,**kwargs):
global counts
counts += 1
result = f(*args,**kwargs)
logging.warning("%s processed %s times!"%(f.__name__,counts))
return result
return wrapper
@count
def hello(s):
print(s)
hello("well")
hello("Hello!")
hello("Word!")
#輸出
WARNING:root:hello processed 1 times!
WARNING:root:hello processed 2 times!
WARNING:root:hello processed 3 times!
well
Hello!
Word!
運行日誌裝飾器:
def trace_func(func):
def tmp(*args, **kargs):
#*args 可以傳入元組和列表,**kargs可以傳入字典,兩者都是可選參數
print('Start %s(%s, %s)...' % (func.__name__, args, kargs) )
return func(*args, **kargs)
return tmp
@trace_func
def log_test_with_empty_parameter():
pass
@trace_func
def log_test_with_many_parameter(a_int, b_string, c_list, d_dict):
pass
@trace_func
def log_test_with_key_parameter(a = 'www', b = 1, c = [1,2]):
pass
if __name__ == '__main__':
log_test_with_empty_parameter()
log_test_with_many_parameter(1, 'wwww', [1,2,'c'], {1: 'a', 2 : 'ww'})
log_test_with_key_parameter(1, 'wwww', c = [3, 4])
#運行結果
Start log_test_with_empty_parameter((), {})...
Start log_test_with_many_parameter((1, 'wwww', [1, 2, 'c'], {1: 'a', 2: 'ww'}), {})...
Start log_test_with_key_parameter((1, 'wwww'), {'c': [3, 4]})...
權限校驗裝飾器:
user_list = [
{'name':'user1','passwd':'123'},
{'name':'user2','passwd':'123'},
{'name':'user3','passwd':'123'},
]
#初始狀態,用來保存登陸的用戶,
client_dic = {'username':None,'login':False}
#添加新功能
def auth_func(func):
def wrapper(*args,**kwargs):
#參數檢查,判斷是否有用戶登錄,如果有不用驗證,直接執行函數的功能
if client_dic['username'] and client_dic['login']:
res = func(*args,**kwargs)
return res
username = input('用戶名:').strip()
passwd = input('passwd:').strip()
#對比列表,檢查用戶名和密碼是否正確
for user_dic in user_list:
if username == user_dic['name'] and passwd == user_dic['passwd']:
client_dic['username'] = user_dic['name']
client_dic['login'] = True
res = func(*args,**kwargs)
return res
else:
print('用戶名或者密碼錯誤!')
return wrapper
@auth_func
def index():
print("歡迎來到主頁")
@auth_func
def home(name):
print("歡迎回家:%s"%name)
@auth_func
def shoppping_car():
print('購物車裏有[%s,%s,%s]'%('麪包','可樂','薯片'))
index()
home('root')
shoppping_car()
參考資料:https://rhettinger.wordpress.com/
https://docs.python.org/zh-cn/3/tutorial/classes.html
https://www.runoob.com/python/python-func-super.html
http://www.python88.com/topic/148/
https://www.cnblogs.com/junneyang/p/5332307.html
https://blog.csdn.net/qq_33531400/article/details/79324551