Python3 與 C# 擴展之~基礎拓展

 

上次知識回顧:http://www.javashuo.com/article/p-tmcacaan-d.htmlhtml

代碼褲子:https://github.com/lotapp/BaseCode前端

在線編程:https://mybinder.org/v2/gh/lotapp/BaseCode/masterpython

在線預覽http://github.lesschina.com/python/base/ext/基礎拓展.htmlgit

終於期末考試結束了,聰明的小明同窗如今固然是美滋滋的過暑假了,左手一隻瓜,右手一本書~正在給老鄉小張同窗拓展他研究多日的知識點程序員

1.NetCore裝飾器模式

裝飾器此次從C#開始引入,上次剛講迭代器模式,此次把裝飾器模式也帶一波(純Python方向的能夠選擇性跳過,也能夠當擴展)github

其實通俗講就是,給原有對象動態的添加一些額外的職責(畢竟動不動就改類你讓其餘調用的人咋辦?也不符合開放封閉原則是吧~)redis

舉個簡單的例子:(https://github.com/lotapp/BaseCode/tree/master/netcore/3_Ext/Decorators)算法

BaseComponent.cs數據庫

/// <summary>
/// 組件的抽象父類
/// </summary>
public abstract class BaseComponent
{
    /// <summary>
    /// 定義一個登陸的抽象方法
    /// 其餘方法,這邊省略
    /// </summary>
    public abstract string Login();
}

LoginComponent.cs編程

/// <summary>
/// 默認登陸組件(帳號+密碼)
/// 其餘方法省略
/// 友情提醒一下,抽象類裏面能夠定義非抽象方法
/// </summary>
public class LoginComponent : BaseComponent
{
    public override string Login()
    {
        return "默認帳號密碼登陸";
    }
}

默認調用:

static void Main(string[] args)
{
    var obj = new LoginComponent();
    var str = obj.Login();
    Console.WriteLine(str);
}

若是這時候平臺須要添加微信第三方登陸,怎麼辦?通常都是用繼承來解決,其實還能夠經過靈活的裝飾器來解決:(好處能夠本身體會)

先定義一個通用裝飾器(不必定針對登陸,註冊等等只要在BaseComponent中的都能用)

/// <summary>
/// 裝飾器
/// </summary>
public class BaseDecorator : BaseComponent
{
    protected BaseComponent _component;
    /// <summary>
    /// 構造函數
    /// </summary>
    /// <param name="obj">登陸組件對象</param>
    protected BaseDecorator(BaseComponent obj)
    {
        this._component = obj;
    }
    public override string Login()
    {
        string str = string.Empty;
        if (_component != null) str = _component.Login();
        return str;
    }
}

如今根據需求添加微信登陸:(符合開放封閉原則)

/// <summary>
/// 默認登陸組件(帳號+密碼)
/// 其餘方法省略
/// </summary>
public class WeChatLoginDecorator : BaseDecorator
{
    public WeChatLoginDecorator(BaseComponent obj) : base(obj)
    {
    }
    /// <summary>
    /// 添加微信第三方登陸
    /// </summary>
    /// <returns></returns>
    public string WeChatLogin()
    {
        return "add WeChatLogin";
    }
}

調用:(原有系統該怎麼用就怎麼用,新系統可使用裝飾器來添加新功能)

static void Main(string[] args)
{
    #region 登陸模塊V2
    // 實例化登陸裝飾器
    var loginDecorator = new WeChatLoginDecorator(new LoginComponent());
    // 原有的登陸方法
    var str1 = loginDecorator.Login();
    // 如今新增的登陸方法
    var str2 = loginDecorator.WeChatLogin();
    Console.WriteLine($"{str1}\n{str2}");
    #endregion
}

結果:

默認帳號密碼登陸
add WeChatLogin

若是再加入QQ和新浪登陸的功能就再添加一個V3版本的裝飾器,繼承當時V2版本的登陸便可(版本迭代特別方便)

/// <summary>
/// 默認登陸組件(帳號+密碼)
/// 其餘方法省略
/// </summary>
public class LoginDecoratorV3 : WeChatLoginDecorator
{
    public LoginDecoratorV3(BaseComponent obj) : base(obj)
    {
    }

    /// <summary>
    /// 添加QQ登陸
    /// </summary>
    /// <returns></returns>
    public string QQLogin()
    {
        return "add QQLogin";
    }

    /// <summary>
    /// 添加新浪登陸
    /// </summary>
    /// <returns></returns>
    public string SinaLogin()
    {
        return "add SinaLogin";
    }
}

調用:

static void Main(string[] args)
{
    #region 登陸模塊V3
    // 實例化登陸裝飾器
    var loginDecoratorV3 = new LoginDecoratorV3(new LoginComponent());
    // 原有的登陸方法
    var v1 = loginDecoratorV3.Login();
    // 第二個版本迭代中的微信登陸
    var v2 = loginDecoratorV3.WeChatLogin();
    // 新增的QQ和新浪登陸
    var qqLogin = loginDecoratorV3.QQLogin();
    var sinaLogin = loginDecoratorV3.SinaLogin();
    Console.WriteLine($"{v1}\n{v2}\n{qqLogin}\n{sinaLogin}");
    #endregion
}

結果:

默認帳號密碼登陸
add WeChatLogin
add QQLogin
add SinaLogin

其實還有不少用處,好比原有系統緩存這塊當時考慮不到,如今併發來了,已經上線了,原有代碼又不太敢大幅度修改,這時候裝飾器就很方便的給某些功能添加點緩存、測試、日記等等系列功能(AOP裏面不少這種概念)

實際場景說的已經很明白了,其餘的本身摸索一下吧

 

2.Python裝飾器

那Python怎麼實現裝飾器呢?小胖問道。

小明屁顛屁顛的跑過去說道,經過閉包咯~(閉包若是忘了,能夠回顧一下)

2.1.裝飾器引入

來看一個應用場景,之前老版本系統由於併發比較小,沒考慮到緩存

def get_data():
    print("直接數據庫讀取數據")

def main():
    get_data()

if __name__ == '__main__':
    main()

在不修改原有代碼的前提下咋辦?咱們參照C#和Java寫下以下代碼:

In [1]:
# 添加一個閉包
def cache(func):
    def decorator():
        print("給功能添加了緩存")
        if True:
            pass
        else:
            func()# 若是緩存失效則讀取數據庫獲取新的數據
    return decorator

def get_data():
    print("直接數據庫讀取數據")

def main():
    f1 = cache(get_data)
    f1()
    print(type(f1))

if __name__ == '__main__':
    main()
 
給功能添加了緩存
<class 'function'>
 

小張問道:「怎麼也這麼麻煩啊,C#的那個我就有點暈了,怎麼Python也這樣啊?」f1 = cache(get_data) f1()

小明哈哈一笑道:「人生苦短,我用Python~這句話可不是隨便說着玩的,來來來,看看Python的語法糖」:

In [2]:
def cache(func):
    def wrapper():
        print("給功能添加了緩存")
        if True:
            pass
        else:
            func()  # 若是緩存失效則讀取數據庫獲取新的數據
    return wrapper

@cache
def get_data():
    print("直接數據庫讀取數據")

def main():
    get_data()

if __name__ == '__main__':
    main()
 
給功能添加了緩存
 

其實

@cache
def get_data()

等價於

# 把f1改爲函數名字罷了。能夠這麼理解:get_data重寫指向了一個新函數
get_data = cache(get_data)

小張同窗瞪了瞪眼睛,努力回想着之前的知識點,而後脫口而出:「這不是咱們以前講的屬性裝飾器嗎?並且好方便啊,這徹底符合開放封閉原則啊!「

class Student(object):
    def __init__(self, name, age):
        # 通常須要用到的屬性都直接放在__init__裏面了
        self.name = name
        self.age = age

    @property
    def name(self):
        return self.__name

    @name.setter
    def name(self, name):
        self.__name = name

    @property
    def age(self):
        return self.__age

    @age.setter
    def age(self, age):
        if age > 0:
            self.__age = age
        else:
            print("age must > 0")

    def show(self):
        print("name:%s,age:%s" % (self.name, self.age))

小明也愣了愣,說道:」也對哦,你不說我都忘了,咱們學習面向對象三大特性的時候常常用呢,怪不得這麼熟悉呢「

隨後又嘀咕了一句:」我怎麼不知道開放封閉原則...「

小張嘲笑道:」這你都不知道?對擴展開放,對已經實現的代碼封閉嘛~「

In [3]:
# 須要注意一點
def cache(func):
    print("裝飾器開始裝飾")
    def wrapper():
            print("給功能添加了緩存")
            if True:
                pass
            else:
                func()  # 若是緩存失效則讀取數據庫獲取新的數據
    return wrapper

@cache # 當你寫這個的時候,裝飾器就開始裝飾了,閉包裏面的功能是你調用的時候執行
def get_data():
    print("直接數據庫讀取數據")
 
裝飾器開始裝飾
 

2.2.多個裝飾器

小明趕忙扯開話題,」咳咳,咱們接下來咱們接着講裝飾器"

小張問道,像上面那個第三方登陸的案例,想加多少加多少,Python怎麼辦呢?

小明一笑而過~

如今項目又升級了,要求每次調用都要打印一下日記信息,方便之後糾錯,小張先用本身的理解打下了這段代碼,而後像小明請教:

In [4]:
def log(func):
    def wrapper():
        print("輸出日記信息")
        cache(func)()
    return wrapper
    
def cache(func):
    def wrapper():
        print("給功能添加了緩存")
        if True:
            pass
        else:
            func()  # 若是緩存失效則讀取數據庫獲取新的數據
    return wrapper

@log
def get_data():
    print("直接數據庫讀取數據")

def main():
    get_data()

if __name__ == '__main__':
    main()
 
輸出日記信息
給功能添加了緩存
 

小明剛美滋滋的喝着口口可樂呢,看到代碼後一不當心噴了小張一臉,而後尷尬的說道:「Python又不是隻能裝飾一個裝飾器,來看看個人代碼」:

In [5]:
def log(func):
    print("開始裝飾Log模塊")
    def wrapper():
        print("輸出日記信息")
        func()
    return wrapper

def cache(func):
    print("開始裝飾Cache模塊")
    def wrapper():
        print("給功能添加了緩存")
        if True:
            pass
        else:
            func()  # 若是緩存失效則讀取數據庫獲取新的數據
    return wrapper

@log
@cache
def get_data():
    print("直接數據庫讀取數據")

def main():
    get_data()

if __name__ == '__main__':
    main()
 
開始裝飾Cache模塊
開始裝飾Log模塊
輸出日記信息
給功能添加了緩存
 

小張耐心的看完了代碼,而後說道:「咦,我發現它裝飾的時候是從下往上裝飾,執行的時候是從上往下啊?執行的時候程序原本就是從上往下,按照道理應該是從上往下裝飾啊?」

小明神祕的說道:「你猜啊~你能夠把它理解爲寄快遞和拆快遞

小張興奮的跳起來了:

裝飾器:裝快遞,先包裝裏面的物品,而後再加個盒子。執行裝飾器:拆快遞,先拆外面的包裝再拆裏面的~簡直妙趣橫生啊

2.3.帶參裝飾器

小明繼續講述他哥哥的血淚歷史:

需求時刻在變,系統使用範圍更廣了,爲了避免砸場子,摳門的老闆決定每一年多花5W在技術研發的硬件支持上,這下子技術部老開心了,想一想之前前端只能經過CDN和HTTP請求來緩存,後端只能依賴頁面緩存和數據庫緩存就心塞,因而趕忙新增長一臺Redis的雲服務器。爲了之後和如今緩存代碼得變一變了,須要支持指定的緩存數據庫:(若是不是維護別人搞的老項目,你這麼玩保證被打死,開發的時候老老實實的工廠模式搞起)

帶參數的裝飾器通常都是用來記錄logo日記比較多,本身開發知道debug模式,生產指定except模式等等

In [6]:
# 能夠理解爲,在原來的外面套了一層
def cache(cache_name):
    def decorator(func):
        def wrapper():
            if cache_name == "redis":
                print("給功能添加了Redis緩存")
            elif cache_name == "memcache":
                pass
            else:
                func()
        return wrapper
    return decorator

@cache("redis") # 至關因而:get_data = cache(」redis「)(get_data)
def get_data():
    print("直接數據庫讀取數據")

def main():
    get_data()

if __name__ == '__main__':
    main()
 
給功能添加了Redis緩存
 

小張很高興,而後練了練手,而後質問小明道:」你是否是藏了一手!「

代碼以下:

In [7]:
def log(func):
    def inner():
        print("%s log_info..." % func.__name__)
        func()
    return inner

@log
def login_in(name_str, pass_str):
    return "歡迎登陸:%s" % (name_str)

@log
def login_out():
    print("已經退出登陸")

@log
def get_data(id):
    print("%s:data xxx" % id)

def main():
    login_out()
    get_data(1)
    print(login_in("小明", "xxx"))

if __name__ == '__main__':
    main()
 
login_out log_info...
已經退出登陸
 
---------------------------------------------------------------------------
TypeError                                 Traceback (most recent call last)
<ipython-input-7-dcb695819107> in <module>()
     23 
     24 if __name__ == '__main__':
---> 25main()

<ipython-input-7-dcb695819107> in main()
     19 def main():
     20     login_out()
---> 21get_data(1)
     22     print(login_in("小明", "xxx"))
     23 

TypeError: inner() takes 0 positional arguments but 1 was given
 

2.4.通用裝飾器

小明尷尬的笑了下,而後趕忙傾囊相授,定義一個通用的裝飾器:(傳參數就在外面套一層)

def log(func):
    @functools.wraps(func) # 簽名下面一個案例就會講
    def wrapper(*args,**kv):
        """可變參 + 關鍵字參數"""
        print("%s log_info..." % func.__name__)
        return func(*args,**kv)
    return wrapper

這部分知識若是忘記了能夠回顧一下,咱們以前講的函數系列:http://www.javashuo.com/article/p-obgfrmjz-g.html

In [8]:
def log(func):
    # 可變參 + 關鍵字參數
    def wrapper(*args,**kv):
        print("%s log_info..." % func.__name__)
        return func(*args,**kv)
    return wrapper

@log
def login_in(name_str, pass_str):
    return "歡迎登陸:%s" % (name_str)

@log
def login_out():
    print("已經退出登陸")

@log
def get_data(id):
    print("%s:data xxx" % id)

def main():
    login_out()
    get_data(1)
    print(login_in("小明", "xxx"))

if __name__ == '__main__':
    main()
 
login_out log_info...
已經退出登陸
get_data log_info...
1:data xxx
login_in log_info...
歡迎登陸:小明
 

2.5.擴展補充

其實裝飾器能夠作不少事情,好比強制類型檢測等,先看幾個擴展:

1.裝飾器方法簽名的問題

成也裝飾器,敗也裝飾器,來個案例看看,裝飾器裝飾的函數真的就對原函數沒點影響?

In [9]:
# 添加一個閉包
def cache(func):
    def wrapper(*args,**kv):
        if True:
            print("緩存還沒有失效:直接返回緩存數據")
        else:
            func(*args,**kv)
    return wrapper

def get_data(id):
    """獲取數據"""
    print("經過%d直接數據庫讀取數據"%id)
In [10]:
# 進行裝飾
get_data = cache(get_data)
# 調用原有名稱的函數
get_data(110)
# 發現雖然函數調用時候的名字沒有變
# 可是內部簽名卻變成了閉包裏面的函數名了
print(get_data.__name__)
print(get_data.__doc__)
# print(get_data.__annotations__)
 
緩存還沒有失效:直接返回緩存數據
wrapper
None
 

發現雖然函數調用時候的名字沒有變,可是內部簽名卻變成了閉包裏面的函數名了!

玩過逆向的人都知道,像你修改了apk文件,它看似同樣,但簽名就變了,得再處理纔可能繞過原來的一些自效驗的驗證措施

這邊同樣的道理,你寫了一個裝飾器做用在某個函數上,可是這個函數的重要的元信息好比名字、文檔字符串、註解和參數簽名都丟失了。

functools裏面的wraps就幫咱們幹了這個事情(以前講模塊的時候引入了functools,隨後講衍生的時候用了裏面的偏函數,這邊講講wraps

上面代碼改改:

In [11]:
from functools import wraps

# 添加一個閉包
def cache(func):
    @wraps(func)
    def wrapper(*args,**kv):
        if True:
            print("緩存還沒有失效:直接返回緩存數據")
        else:
            func(*args,**kv)
    return wrapper

def get_data(id):
    """獲取數據"""
    print("經過%d直接數據庫讀取數據"%id)

# 進行裝飾
get_data = cache(get_data)
# 調用原有名稱的函數
get_data(110)
# 簽名已然一致
print(get_data.__name__)
print(get_data.__doc__)
# print(get_data.__annotations__)
 
緩存還沒有失效:直接返回緩存數據
get_data
獲取數據
 

另外:@wraps有一個重要特徵是它能讓你經過屬性 __wrapped__ 直接訪問被包裝函數,eg:

In [12]:
get_data.__wrapped__(100)
 
經過100直接數據庫讀取數據
 

2.裝飾器傳參的擴展(可傳可不傳)

In [13]:
import logging
from functools import wraps, partial

def logged(func=None, *, level=logging.DEBUG, name=None, message=None):
    if func is None:
        return partial(logged, level=level, name=name, message=message)

    logname = name if name else func.__module__
    log = logging.getLogger(logname)
    logmsg = message if message else func.__name__

    @wraps(func)
    def wrapper(*args, **kwargs):
        log.log(level, logmsg)
        return func(*args, **kwargs)
    return wrapper

@logged
def add(x, y):
    return x + y

@logged(level=logging.CRITICAL, name='測試')
def get_data():
    print("讀數據ing")

def main():
    add(1,2)
    get_data()

if __name__ == '__main__':
    main()
 
get_data
 
讀數據ing
 

3.類中定義裝飾器

在類裏面定義裝飾器很簡單,可是你首先要確認它的使用方式。好比究竟是做爲一個實例方法仍是類方法:(別忘記寫selfcls

In [14]:
from functools import wraps

class A(object):
    # 實例方法
    def decorator1(self, func):
        @wraps(func)
        def wrapper(*args, **kwargs):
            print("實例方法裝飾器")
            return func(*args, **kwargs)
        return wrapper

    # 類方法
    @classmethod
    def decorator2(cls, func):
        @wraps(func)
        def wrapper(*args, **kwargs):
            print("類方法裝飾器")
            return func(*args, **kwargs)
        return wrapper
In [15]:
# 裝飾方式不同
a = A()
@a.decorator1 # 實例方法調用
def test1():
    pass

@A.decorator2 # 類方法調用
def test2():
    pass
In [16]:
# 調用一下
test1()
test2()
 
實例方法裝飾器
類方法裝飾器
 

在涉及到繼承的時候。 例如,假設你想讓在A中定義的裝飾器做用在子類B中。你須要像下面這樣寫:

class B(A):
    @A.decorator2
    def test(self):
        pass

也就是說,裝飾器要被定義成類方法而且你必須顯式的使用父類名去調用它。

你不能使用 @B.decorator2 ,由於在方法定義時,這個類B尚未被建立。

4.類裝飾器

看這個以前,咱們先來看看怎麼把類當函數同樣使用:

In [17]:
class A(object):
    def __call__(self):
        print("讓類對象能像函數同樣調用的~魔法方法")

def main():
    a = A()
    a()

if __name__ == '__main__':
    main()
 
讓類對象能像函數同樣調用的~魔法方法
 

重載這些魔法方法通常會改變對象的內部行爲。上面這個例子就讓一個類對象擁有了被調用的行爲。

裝飾器函數實際上是這樣一個接口約束,它必須接受一個callable對象做爲參數,而後返回一個callable對象。

在Python中通常callable對象都是函數,但也有例外。只要某個對象重寫了 __call__() 方法,那麼這個對象就是callable的

用類來實現呢?咱們可讓類的構造函數__init__()接受一個函數,而後重載__call__()並返回一個函數,也能夠達到裝飾器函數的效果

咱們拿以前說的通用裝飾器的例子繼續說:(通常來講裝飾器就定義成方法,而後給須要添加的函數或者類方法添加就基本夠用了

In [18]:
from functools import wraps

class Log(object):
    def __init__(self, func):
        wraps(func)(self)  # @wraps(func) 訪問不到,因此用這種方式
        self.__func = func

    def __call__(self, *args, **kvs):
        print("%s log_info..." % self.__func.__name__)
        return self.__func(*args, **kvs)

@Log # 至關於 login_in=Log(login_in)
def login_in(name_str, pass_str):
    return "歡迎登陸:%s" % (name_str)

@Log
def login_out():
    print("已經退出登陸")

@Log
def get_data(id):
    print("%s:data xxx" % id)

def main():
    login_out()
    get_data(1)
    print(login_in("小明", "xxx"))

if __name__ == '__main__':
    main()
 
login_out log_info...
已經退出登陸
get_data log_info...
1:data xxx
login_in log_info...
歡迎登陸:小明
 

對類進行裝飾的測試:(以上一個案例爲例)

裝飾實例方法的時候容易出現莫名其妙的錯誤,因此通常加上get方法(反射系列的稍後會講)

eg:show() missing 1 required positional argument: 'self'

完整寫法:(你能夠去除__get__試試)

In [19]:
import types
from functools import wraps

class Log(object):
    def __init__(self, func):
        wraps(func)(self)  # @wraps(func) 訪問不到,因此用這種方式
        self.__func = func

    def __call__(self, *args, **kvs):
        print("%s log_info..." % self.__func.__name__)
        return self.__func(*args, **kvs)

    # 裝飾實例方法的時候容易出現莫名其妙的錯誤,因此通常加上get方法
    # eg:show() missing 1 required positional argument: 'self'
    def __get__(self, instance, cls):
        if instance is None:
            return self
        else:
            return types.MethodType(self, instance)

class LoginComponent(object):
    def __init__(self, name):
        self.__name = name

    @Log
    def show(self):
        """實例方法"""
        print("歡迎你:%s" % self.__name)

    @classmethod
    @Log  # 寫在下面("從下往上裝,從上往下拆")
    def login_in(cls):
        """類方法"""
        print("登陸ing")

    @staticmethod
    @Log
    def show_news():
        """靜態方法"""
        print("今天的新聞是...")

def main():
    LoginComponent.login_in()
    LoginComponent.show_news()
    login = LoginComponent("小明")
    login.show()

if __name__ == '__main__':
    main()
 
login_in log_info...
登陸ing
show_news log_info...
今天的新聞是...
show log_info...
歡迎你:小明
 

更多的能夠參考以下連接:

詳解Python裝飾器

將裝飾器定義爲類

Python中的__init__()和__call__()函數

python中裝飾器的使用和類裝飾器在類中方法的使用


3.面向對象系列擴展

看着小張準備回家換衣服了,小明有點失落,又有點孤單,因而說道:「逗逼張,你還要聽嗎?我準備講類相關的知識了,這些但是我課後自學的哦~」

小張轉了轉身,一念間就留了下來~

3.1.動態添加屬性和方法

類相關的基礎知識若是忘記,能夠查看以前的文章:http://www.javashuo.com/article/p-purcxavn-d.html

當咱們定義了一個class,建立了一個class的實例後,咱們能夠給該實例綁定任何屬性和方法,這就是動態語言的靈活性:

In [20]:
# 定義一個類
class Person(object):
    def __init__(self, name):
        self.__name = name

    def show(self):
        print("中國歡迎你~", self.__name)
In [21]:
xiaoming = Person("小明")
xiaoming.show() # 正常調用

# 給實例動態添加一個屬性
xiaoming.age = 22
print(xiaoming.age)
 
中國歡迎你~ 小明
22
In [22]:
# 其餘實例是訪問不到這個屬性的
xiaopan = Person("小潘")
xiaopan.age
 
---------------------------------------------------------------------------
AttributeError                            Traceback (most recent call last)
<ipython-input-22-efcec543fe3f> in <module>()
      1 # 其餘實例是訪問不到這個屬性的
      2 xiaopan = Person("小潘")
----> 3xiaopan.age

AttributeError: 'Person' object has no attribute 'age'
 

"這個之前不是講過嘛,動態添加屬性,還有沒有啥我不知道的知識了?"小張不屑的說道.

小明故做懸疑,擡頭看着小張說道:「你知道怎麼添加類屬性嗎?知道怎麼添加方法嗎?」

小張沉默不語,默默的看着小明講課,隨後內心想到:「這個坑貨,話也不說全,還好如今是夏天,否則我早着涼了」

要想添加其餘實例均可以訪問的屬性,能夠給類添加一個類屬性,用法和上面差很少,只是把對象改爲類。

來看個案例:

In [23]:
# 給類動態添加一個屬性
Person.age = 22

xiaoming = Person("小明")
print(xiaoming.age)

xiaopan = Person("小潘")
print(xiaopan.age)
 
22
22
 
1.添加實例方法

小張,還記得講裝飾器的時候有這麼一句代碼嗎?

types.MethodType(self, instance)

小張:"記得當時用類裝飾實例方法的時候出現了問題,而後才加的?"

對頭,以上面Person類爲例,來一塊兒看怎麼動態添加方法

In [24]:
import types

class Person(object):
    def __init__(self, name):
        self.__name = name

def test(self):
    print("測試一下")

def main():
    xiaoming = Person("小明")
    xiaoming.test = types.MethodType(test, xiaoming)
    xiaoming.test()

if __name__ == '__main__':
    main()
 
測試一下
 

你能夠思考一下,爲何必須經過types.MethodType才行?(提示:self

注意一點,當你在新方法中調用類中私有方法時就會出問題

其實這個本質至關於經過實例對象調用裏面公開屬性

In [25]:
import types

class Person(object):
    def __init__(self, name):
        self.__name = name

# 同樣的代碼,只是調用了私有屬性
def test(self):
    print("中國歡迎你,%s" % self.__name)

def main():
    xiaoming = Person("小明")
    xiaoming.test = types.MethodType(test, xiaoming)
    xiaoming.test() # 其實這個本質至關於經過實例對象調用裏面公開屬性

if __name__ == '__main__':
    main()
 
---------------------------------------------------------------------------
AttributeError                            Traceback (most recent call last)
<ipython-input-25-2bf92b457fc8> in <module>()
     15 
     16 if __name__ == '__main__':
---> 17main()

<ipython-input-25-2bf92b457fc8> in main()
     12     xiaoming = Person("小明")
     13     xiaoming.test = types.MethodType(test, xiaoming)
---> 14xiaoming.test() # 其實這個本質至關於經過實例對象調用裏面公開屬性
     15 
     16 if __name__ == '__main__':

<ipython-input-25-2bf92b457fc8> in test(self)
      7 # 同樣的代碼,只是調用了私有屬性
      8 def test(self):
----> 9print("中國歡迎你,%s" % self.__name)
     10 
     11 def main():

AttributeError: 'Person' object has no attribute '__name'
 
2.添加類方法和靜態方法

看一下類方法和靜態方法的案例:

In [26]:
# 類方法案例
class Person(object):
    pass

@classmethod
def test(cls):
    print(cls)

def main():
    Person.test = test # 直接賦值便可
    xiaoming = Person()
    xiaoming.test()

if __name__ == '__main__':
    main()
 
<class '__main__.Person'>
In [27]:
# 靜態方法案例
class Person(object):
    pass

@staticmethod
def test():
    print("test")

def main():
    Person.test = test
    xiaoming = Person()
    xiaoming.test()

if __name__ == '__main__':
    main()
 
test
 

3.2.__slots__

這下小張急了,怎麼又和上次講得模塊同樣,沒法無天了啊?有沒有辦法限制一下呢?

小明哈哈一笑,娓娓道來:

1.指定實例屬性

若是咱們想要限制實例的屬性怎麼辦?好比,只容許添加指定屬性和方法?

In [28]:
# 定義一個類
class Person(object):
    __slots__ = ("age", "name")  # 用tuple定義容許綁定的屬性名稱

    def show(self):
        print("中國歡迎你~")

xiaoming = Person()
xiaoming.name="小明"
xiaoming.age = 22
xiaoming.qq = 110 # 不容許的屬性就添加不了
 
---------------------------------------------------------------------------
AttributeError                            Traceback (most recent call last)
<ipython-input-28-2f9e13cdc435> in <module>()
      9 xiaoming.name="小明"
     10 xiaoming.age = 22
---> 11xiaoming.qq = 110 # 不容許的屬性就添加不了

AttributeError: 'Person' object has no attribute 'qq'
 

說幾個測試後的結論:

  1. __slots__不必定是元組,你用列表也同樣(推薦和官方一致)
  2. 若是你定義的私有屬性不在元組內,也會報錯
In [29]:
# 列表定義__slots__不會報錯
class Person(object):
    __slots__ = ["__name", "age", "gender"]

    def __init__(self, name):
        self.__name = name

    def show(self):
        print("中國歡迎你~")


xiaoming = Person("小明")
xiaoming.age = 22
xiaoming.gender = "男"
In [30]:
# 注意一個東西,若是你定義的私有屬性不在元組內,也會報錯
class Person(object):
    __slots__ = ("age")

    def __init__(self, name):
        self.__name = name

    def show(self):
        print("中國歡迎你~")

xiaoming = Person("小明")
xiaoming.age = 22
 
---------------------------------------------------------------------------
AttributeError                            Traceback (most recent call last)
<ipython-input-30-0b85ac9c18af> in <module>()
      9         print("中國歡迎你~")
     10 
---> 11xiaoming = Person("小明")
     12 xiaoming.age = 22

<ipython-input-30-0b85ac9c18af> in __init__(self, name)
      4 
      5     def __init__(self, name):
----> 6self.__name = name
      7 
      8     def show(self):

AttributeError: 'Person' object has no attribute '_Person__name'
 

2.指定實例「方法」

這個限制對實例方法同樣有效,再複習下給實例對象添加方法:

import types

class Person(object):
    __slots__ = ("__name", "age", "test")

    def __init__(self, name):
        self.__name = name
    def show(self):
        print("中國歡迎你~")

def test(self):
    print("test")

xiaoming = Person("小明")
xiaoming.age = 22
xiaoming.test = types.MethodType(test, xiaoming)
xiaoming.test()

看看被限制以後:(Python中定義的方法至關於定義了一個屬性,而後指向了定義的函數)

In [31]:
# 這個限制對實例方法同樣有效
import types

class Person(object):
    __slots__ = ("__name", "age")

    def __init__(self, name):
        self.__name = name
    def show(self):
        print("中國歡迎你~")

def test(self):
    print("test")

xiaoming = Person("小明")
xiaoming.age = 22
xiaoming.test = types.MethodType(test, xiaoming)
xiaoming.test()
 
---------------------------------------------------------------------------
AttributeError                            Traceback (most recent call last)
<ipython-input-31-d1bab7d57b40> in <module>()
     15 xiaoming = Person("小明")
     16 xiaoming.age = 22
---> 17xiaoming.test = types.MethodType(test, xiaoming)
     18 xiaoming.test()

AttributeError: 'Person' object has no attribute 'test'
 

小明講得唾沫橫飛,而後故做神祕的和小張說道:

3.擴展:看看對類有啥影響

測試結果:不影響

In [32]:
# 類方法案例
class Person(object):
    __slots__ = ("name", "age")
    pass

@classmethod
def test1(cls):
    print("類方法")

@staticmethod
def test2():
    print("靜態方法")

def main():
    Person.qq = 110
    Person.test1 = test1  # 類方法
    Person.test2 = test2  # 靜態方法
    xiaoming = Person()
    print(xiaoming.qq)
    xiaoming.test1()
    xiaoming.test2()

if __name__ == '__main__':
    main()
 
110
類方法
靜態方法
 

擴展:__getattribute__屬性攔截器

有點像C#裏面的Attribute標籤,AOP其實就是這類的思想

更多能夠參考以下連接:

動態添加屬性和方法

反射以及魔法方法相關內容

制定類以及魔法方法相關內容

In [33]:
class Person(object):
    def __init__(self, name):
        self.__name = name

    def show(self):
        print(self.__name)

    # 屬性攔截器裏面不要調用self.方法 or self.屬性
    def __getattribute__(self, obj):
        print("obj:", obj)
        if obj == "show":
            print("do something")
        elif obj == "_Person__name":  # 注意這種狀況,若是你想要訪問私有屬性,須要寫出類名.屬性
            print("Log info : xxx")
        return object.__getattribute__(self, obj) # 你重寫了屬性、方法獲取的方式,別忘記返回對應的屬性

def main():
    p = Person("小明")
    p.show()

if __name__ == '__main__':
    main()
 
obj: show
do something
obj: _Person__name
Log info : xxx
小明
 

3.3.元類系列

小張一臉懵逼的看着小明,而後說道:」就沒有相似於C#裏面的反射機制?「

小明揹着手,緩緩的繞着小張走了一圈,那眼神彷彿是在看一件工藝藝術品同樣,而後隨口說道:

3.3.1.type動態建立類

前面咱們講過了type()函數能夠查看一個類型或變量的類型。好比說:

Person是一個class,它的類型就是type,而xiaoming是一個實例,它的類型就是class Person

看個例子:

In [34]:
class Person(object):
    pass

def main():
    xiaoming = Person()
    print(type(Person))
    print(type(xiaoming))

if __name__ == '__main__':
    main()
 
<class 'type'>
<class '__main__.Person'>
 

其實還能夠經過 __class__ 來查看建立對象的是誰:

In [35]:
class Person(object):
    pass

def main():
    xiaoming = Person()
    print(Person.__class__)
    print(xiaoming.__class__)

if __name__ == '__main__':
    main()
 
<class 'type'>
<class '__main__.Person'>
 

小張被小明看的發毛,而後趕忙扯開話題說道:」怎麼都是type?難道這個就是接下來準備講的內容?「

小明點頭說道:」是滴~「

咱們說class的定義是運行時動態建立的,而建立class的方法就是使用type()函數

那怎麼建立呢?以上面那個案例爲摸版,來個案例:

類名 = type("類名", 父類們的Tuple, Dict)

In [36]:
def main():
    Person = type("Person", (object, ), {})
    xiaoming = Person()
    print(Person.__class__)
    print(xiaoming.__class__)

if __name__ == '__main__':
    main()
 
<class 'type'>
<class '__main__.Person'>
 

小張感嘆道:」Python的這種‘反射’太過簡單了吧,我直接均可以寫案例了「

好比,實現以下內容:

In [37]:
class Person(object):
    def show(self):
        print("父類方法:mmd")

class Student(Person):
    gender = "男"

    def __init__(self, name):
        self.__name = name

    def eat(self):
        print("%s實例方法:大口吃飯" % self.__name)

    @classmethod
    def run(cls):
        print("我是類方法:跑着上課")

    @staticmethod
    def sleep():
        print("靜態方法:晚安")

def main():
    print(Student.gender)
    xiaoming = Student("小明")
    xiaoming.show()
    xiaoming.eat()
    xiaoming.run()
    xiaoming.sleep()

if __name__ == '__main__':
    main()
 
男
父類方法:mmd
小明實例方法:大口吃飯
我是類方法:跑着上課
靜態方法:晚安
In [38]:
def show(self):
    print("父類方法:mmd")

def __init__(self, name):
    self.__name = name

def eat(self):
    print("%s實例方法:大口吃飯" % self.__name)

@classmethod
def run(cls):
    print("我是類方法:跑着上課")

@staticmethod
def sleep():
    print("靜態方法:晚安")

def main():
    Person = type("Person", (object, ), {"show": show})
    Student = type(
        "Student", (Person, ), {
            "gender": "男",
            "__init__": __init__,
            "eat": eat,
            "run": run,
            "sleep": sleep
        })
    
    print(Student.gender)
    xiaoming = Student("小明")
    xiaoming.show()
    xiaoming.eat()
    xiaoming.run()
    xiaoming.sleep()

if __name__ == '__main__':
    main()
 
男
父類方法:mmd
小明實例方法:大口吃飯
我是類方法:跑着上課
靜態方法:晚安
 

3.3.2.元類~metaclass

小明又仔細端詳了小張一次,而後繼續講到:

當咱們定義了類之後,就能夠根據這個類建立出實例,因此:先定義類,而後建立實例。

可是若是咱們想建立出類呢?那就必須根據metaclass建立出類,因此:先定義metaclass,而後建立類。

總的流程就是:先定義metaclass,再建立類,最後建立實例

type就是Python在背後用來建立全部類的那個元類


小張有點恐慌的看了一眼小明,而後繼續聽講

Python2是看看類裏面有沒有__metaclass__這個屬性,有就經過它指向的函數或者方法來建立類

Python3簡化了一下,在Class定義的時候就能夠指定了,eg:class Person(object, metaclass=type)

In [39]:
# 這三個參數其實就是type對應的三個參數
def create_class(name, bases, attrs):
    attrs["name"] = "小明"
    return type(name, bases, attrs)

class Person(object, metaclass=create_class):
    pass

def main():
    # 判斷一個對象有沒有某個屬性
    hasattr(Person, "name")
    print(Person.name)

if __name__ == '__main__':
    main()
 
小明
 

其實原類有點像剛剛講的屬性攔截器了,大概流程以下:

  1. 攔截類的建立
  2. 修改類
  3. 返回修改以後的類

來一個正規化的寫法,eg:給MyList添加一個add方法(list是append方法,別混淆了)

In [40]:
# metaclass是類的模板,因此必須從`type`類型派生:
class ListMetaclass(type):
    def __new__(cls, name, bases, attrs):
        attrs['add'] = lambda self, value: self.append(value)
        return type.__new__(cls, name, bases, attrs)

class MyList(list, metaclass=ListMetaclass):
    pass

def main():
    mylist = MyList()
    mylist.add("mmd")
    print(mylist)

if __name__ == '__main__':
    main()
 
['mmd']
 

元類通常ORM用的比較多(映射),若是你不編寫ORM框架的話,基本上用不到

這方面能夠參考這篇文章:嘗試編寫一個ORM框架


3.4.枚舉類

枚舉類常常用,代碼也很簡單,繼承一下Enum類就能夠了,unique用來防止重複的(重複會提示你)

In [41]:
from enum import Enum, unique

@unique
class StatusEnum(Enum):
    # 待審覈狀態(0)默認
    Pendding = 0

    # 審覈已經過(1)正常
    Normal = 1

    # 審覈不經過(2)未刪
    Cancel = 2

    # 已刪除狀態(99)假刪
    Delete = 99

# 調用:
StatusEnum.Delete
Out[41]:
<StatusEnum.Delete: 99>
In [42]:
# 重複項測試
from enum import Enum, unique

@unique
class StatusEnum(Enum):
    # 審覈已經過(1)正常
    Normal = 1
    # 已刪除狀態(99)假刪
    Delete = 99
    # 重複測試
    Test = 99

# 調用:
StatusEnum.Delete
 
---------------------------------------------------------------------------
ValueError                                Traceback (most recent call last)
<ipython-input-42-6a79f45cf1d9> in <module>()
      3 
      4 @unique
----> 5class StatusEnum(Enum):
      6     # 審覈已經過(1)正常
      7     Normal = 1

~/anaconda3/lib/python3.6/enum.py in unique(enumeration)
    832                 ["%s -> %s" % (alias, name) for (alias, name) in duplicates])
    833         raise ValueError('duplicate values found in %r: %s' %
--> 834                 (enumeration, alias_details))
    835     return enumeration
    836 

ValueError: duplicate values found in <enum 'StatusEnum'>: Test -> Delete
 

3.5.垃圾回收

以前寫的文章裏面有提到過,能夠簡單回顧一下:(可變類型和不可變類型 引用數的引入)

其實程序員基本上關注,實在要關注的就是怎麼顯示回收:

import gc # 須要導入gc模塊

print(gc.collect()) # 顯式垃圾回收
print(gc.garbage)   # 看回收了哪些

先看看以前講可變類型和不可變類型說的一句話:

Python對int類型和較短的字符串進行了緩存,不管聲明多少個值相同的變量,實際上都指向同個內存地址

看個案例:

In [2]:
a=10
b=10
c=10
print(id(a))
print(id(b))
print(id(c))
 
94747627400000
94747627400000
94747627400000
 

上面的ID都同樣,那較短究竟是多短呢?

先貼一下逆天的測試結果:(不要在編輯器裏面測試,建議進入官方的python3交互模式,用vscode測試的結果不許)

  1. 小整數[-5,257)共用對象,常駐內存不在這個範圍內的均建立一個新的對象
  2. 單個字符共用對象,常駐內存
  3. 字符串:
    • 英文單詞,共用對象,引用計數爲0就刪除
    • 英文中有空格(英文句子、詞組),不共用,引用計數爲0的時候就刪掉
    • 中文字符串:不共用,引用計數爲0的時候就刪掉

其實也很好理解,第一個範圍是程序員常常用的範圍,字符串系列嘛就更正常了,老外確定無論中文什麼的,要是中國人發明的能夠經常使用漢字常駐內存^_^ 而後一篇文章裏面單詞出現頻率確定比詞組和句子高,因此都能解釋通了

來簡單驗證一下:

圖片

In [1]:
# 257的時候就取不到了,這時候都是不一樣的ID
# 這個就是所謂的大整數了(每個大整數,均建立一個新的對象)
a=257
b=257
c=257
print(id(a))
print(id(b))
print(id(c))
 
140602139583728
140602139584112
140602139583792
In [2]:
# 單個字符
d='a'
e='a'
f='a'
print(id(d))
print(id(e))
print(id(f))
 
140602366927792
140602366927792
140602366927792
In [3]:
# 英文單詞
str1 = "dog"
str2 = "dog"
str3 = "dog"
print(id(str1))
print(id(str2))
print(id(str3))
 
140602139175376
140602139175376
140602139175376
In [4]:
# 英文中有空格(句子,詞組)
str4 = "big dog"
str5 = "big dog"
str6 = "big dog"
print(id(str4))
print(id(str5))
print(id(str6))
 
140602139174984
140602139174816
140602139175544
In [5]:
# 不共享對象,計數爲0就刪除
str7 = "明"
str8 = "明"
str9 = "明"
print(id(str7))
print(id(str8))
print(id(str9))
 
140602139296272
140602139296352
140602139296192
In [6]:
str10 = "小明"
str11 = "小明"
str12 = "小明"
print(id(str10))
print(id(str11))
print(id(str12))
 
140602139147320
140602139146616
140602139146792
In [7]:
str13 = "小 明"
str14 = "小 明"
str15 = "小 明"
print(id(str10))
print(id(str11))
print(id(str12))
 
140602139147320
140602139146616
140602139146792
 

再說說查看引用的時候注意一下:sys.getrefcount的參數object也會佔1個引用計數(sys.getrefcount(a)能夠查看a對象的引用計數,可是比正常計數大1,由於調用函數的時候傳入a,這會讓a的引用計數+1)

這個是Python主要的一種垃圾回收方式(計數引用),看看源碼:

參考連接:https://github.com/python/cpython/blob/master/Include/object.h

// 實際上沒有任何東西被聲明爲PyObject,可是每一個指向Python對象的指針均可以強制轉換爲PyObject(這是手工製做的繼承)
typedef struct _object {
    _PyObject_HEAD_EXTRA
    Py_ssize_t ob_refcnt; /* 引用計數 */
    struct _typeobject *ob_type;
} PyObject;

// 相似地,每一個指向可變大小Python對象的指針均可以轉換爲PyVarObject
typedef struct {
    PyObject ob_base;
    Py_ssize_t ob_size; /* 可變變量引用計數 */
} PyVarObject;
In [1]:
# 引用計數
import sys


# 定義一個臨時類
class Temp(object):
    def __del__(self):
        print("你被幹掉了")


t1 = Temp()
print(sys.getrefcount(t1))  #(結果比實際引用大1)【object也會佔1個引用計數】

t2 = t1
print(sys.getrefcount(t1))
print(sys.getrefcount(t2))

del t1
print(sys.getrefcount(t2))
# sys.getrefcount(t1)#被刪掉天然沒有了

del t2
print("-" * 10)
 
2
3
3
2
你被幹掉了
----------
 

引用計數基本上能夠解決大部分的問題,用起來比較簡單,並且實時性比較高(一旦沒有引用,內存就直接釋放了。不用像其餘機制等到特定時機。實時性還帶來一個好處:處理回收內存的時間分攤到了平時)

但對於循環引用,或者對於像雙向鏈表這樣的方式,就算引用對象刪除了,它的計數仍是1(相互引用嘛)

因此Python解釋器用了另外一種方法解決這個:

分代回收(隔代回收)

Python解釋器設置了某些閥值,當達到了閥值就進行第一輪迴收(大概是有循環引用的-1,而後看兩個相互引用的對象如今的引用結果是否是都是0,若是都是0說明沒有外部引用,那就是垃圾了),不是垃圾的移到第二個鏈表裏面,當第二輪達到閥值的時候,進行第二輪迴收(一輪的也回收下),不是垃圾的"老對象"移到第三個鏈表裏面,當第三輪達到閥值的時候通通回收一波)

gc.get_count() 獲取當前自動執行垃圾回收的計數器

gc.get_threshold() 獲取的gc模塊中自動執行垃圾回收的頻率(能夠本身設置)默認是:(700, 10, 10)

來看看閥值狀況:

In [1]:
import gc

print(gc.get_count())
print(gc.get_threshold())
 
(234, 8, 1)
(700, 10, 10)
 

好比你新建立了1000個對象,才釋放20個,就已經超過默認的700閥值,Python第一代檢測就上場了(以此類推)

通常能活到最後的都不大多是垃圾了,好比配置文件之類的,基本上不太改動的(越老越成精嘛)

小張如有所思的說道:

  1. 當計數器從(699,3,0)增長到(700,3,0),gc模塊就會執行gc.collect(0),即檢查一代對象的垃圾,並重置計數器爲(0,4,0)
  2. 當計數器從(699,9,0)增長到(700,9,0),gc模塊就會執行gc.collect(1),即檢查1、二代對象的垃圾,並重置計數器爲(0,0,1)
  3. 當計數器從(699,9,9)增長到(700,9,9),gc模塊就會執行gc.collect(2),即檢查1、2、三代對象的垃圾,並重置計數器爲(0,0,0)

小明左右端詳小張,終於忍不住說出了那句話:「小張,你能不能..."

話沒說完就被小張打斷了:」我是男的,不搞基!就是搞基也只喜歡咱們班的培哥!「

小明吃驚的說道:」你想啥呢?我只是看你骨骼清奇,想要收你爲徒罷了...「

(完)


經典引用:(參考1 參考2

在Python中,每一個對象都保存了一個稱爲引用計數的整數值,來追蹤到底有多少引用指向了這個對象。不管什麼時候,若是咱們程序中的一個變量或其餘對象引用了目標對象,Python將會增長這個計數值,而當程序中止使用這個對象,則Python會減小這個計數值。一旦計數值被減到零,Python將會釋放這個對象以及回收相關內存空間。

從六十年代開始,計算機科學界就面臨了一個嚴重的理論問題,那就是針對引用計數這種算法來講,若是一個數據結構引用了它自身,即若是這個數據結構是一個循環數據結構,那麼某些引用計數值是確定沒法變成零的。

剛剛說到的例子中,咱們以一個不是很常見的狀況結尾:咱們有一個「孤島」或是一組未使用的、互相指向的對象,可是誰都沒有外部引用。換句話說,咱們的程序再也不使用這些節點對象了,因此咱們但願Python的垃圾回收機制可以足夠智能去釋放這些對象並回收它們佔用的內存空間。可是這不可能,由於全部的引用計數都是1而不是0。Python的引用計數算法不可以處理互相指向本身的對象。

這就是爲何Python要引入Generational GC算法的緣由!

Python使用一種不一樣的鏈表來持續追蹤活躍的對象。而不將其稱之爲「活躍列表」,Python的內部C代碼將其稱爲零代(Generation Zero)。每次當你建立一個對象或其餘什麼值的時候,Python會將其加入零代鏈表。

由於循環引用的緣由,而且由於你的程序使用了一些比其餘對象存在時間更長的對象,從而被分配對象的計數值與被釋放對象的計數值之間的差別在逐漸增加。一旦這個差別累計超過某個閾值,則Python的收集機制就啓動了,而且觸發上邊所說到的零代算法,釋放「浮動的垃圾」,而且將剩下的對象移動到一代列表。

隨着時間的推移,程序所使用的對象逐漸從零代列表移動到一代列表。而Python對於一代列表中對象的處理遵循一樣的方法,一旦被分配計數值與被釋放計數值累計到達必定閾值,Python會將剩下的活躍對象移動到二代列表。

經過這種方法,你的代碼所長期使用的對象,那些你的代碼持續訪問的活躍對象,會從零代鏈表轉移到一代再轉移到二代。經過不一樣的閾值設置,Python能夠在不一樣的時間間隔處理這些對象。Python處理零代最爲頻繁,其次是一代而後纔是二代。

參考連接:

Python垃圾回收機制詳解

經典之~畫說 Ruby 與 Python 垃圾回收

使用 GC、Objgraph 幹掉 Python 內存泄露與循環引用

相關文章
相關標籤/搜索