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

来源:https://www.cnblogs.com/dotnetcrazy/archive/2018/07/19/9333792.html
-Advertisement-
Play Games

上次知識回顧:https://www.cnblogs.com/dotnetcrazy/p/9278573.html 代碼褲子:https://github.com/lotapp/BaseCode 線上編程:https://mybinder.org/v2/gh/lotapp/BaseCode/mast ...


 

上次知識回顧:https://www.cnblogs.com/dotnetcrazy/p/9278573.html

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

線上編程:https://mybinder.org/v2/gh/lotapp/BaseCode/master

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

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

1.NetCore裝飾器模式

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

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

舉個簡單的例子:(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

這部分知識如果忘記了可以回顧一下,我們之前講的函數系列:https://www.cnblogs.com/dotnetcrazy/p/9175950.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)
        
              
您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • JS中常用的內置對象:Array對象、Date對象、正則表達式對象、string對象、Global對象 Array對象中常用方法: concat() 表示把幾個數組合併成一個數組join() 設置分隔符連接數組元素為一個字元串 pop() 移除數組最後一個元素shift() 移除數組中第一個元素 s ...
  • MDN中FileReader的詳細介紹: https://developer.mozilla.org/zh-CN/docs/Web/API/FileReader 用FileReader獲取圖片base64,併在頁面預覽: ...
  • 效果如下:點擊發送驗證碼按鈕,按鈕背景變色,不可點擊,顯示倒計時文字 首先js文件的data裡面 聲明一個變數用於表示當前是否可以點擊,codeIsCanClick = true, 預設是可以點擊的 寫下界面代碼: wxml文件中 對應樣式 wxss文件: 以上構成頁面靜態效果。 註意button有 ...
  • 一、關於樣式(CSS) 1、Input 1)Input可編輯可下拉 2)Input下拉 3)Input邊線點擊不顯示 input點擊邊框樣式無效 4)提示文字:placeholder="手機號" input修改提示文字顏色 5)input出現背景是黃色 這種點擊框也不會出現黃色了 還有一種就是關閉自 ...
  • 前言 秒殺架構到後期,我們採用了消息隊列的形式實現搶購邏輯,那麼之前拋出過這樣一個問題:消息隊列非同步處理完每個用戶請求後,如何通知給相應用戶秒殺成功? 場景映射 首先,我們舉一個生活中比較常見的例子:我們去銀行辦理業務,一般會選擇相關業務列印一個排號紙,然後就可以坐在小板凳上玩著手機,等待被小喇叭報 ...
  • fork/join作為一個併發框架在jdk7的時候就加入到了我們的java併發包java.util.concurrent中,並且在java 8 的lambda並行流中充當著底層框架的角色。這樣一個優秀的框架設計,我自己想瞭解一下它的底層代碼是如何實現的,所以我嘗試的去閱讀了JDK相關的源碼。下麵我打 ...
  • 所有的異常都有一個超類throwable; throwable有兩個子類:Exception和error(一般在重大錯誤,不能夠自行恢復); Exception有兩個子類:checked和runtime exception異常; checked:檢查時異常,就是程式代碼有的錯誤會有紅色波浪線的異常, ...
  • Description Mato同學最近正在研究一種矩陣,這種矩陣有n行n列第i行第j列的數為gcd(i,j)。 例如n=5時,矩陣如下: 1 1 1 1 1 1 2 1 2 1 1 1 3 1 1 1 2 1 4 1 1 1 1 1 5 Mato想知道這個矩陣的行列式的值,你能求出來嗎? Mato ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...