python3-cookbook筆記:第九章 元編程

来源:https://www.cnblogs.com/guyuyun/archive/2020/02/26/12364807.html
-Advertisement-
Play Games

python3-cookbook中每個小節以問題、解決方案和討論三個部分探討了Python3在某類問題中的最優解決方式,或者說是探討Python3本身的數據結構、函數、類等特性在某類問題上如何更好地使用。這本書對於加深Python3的理解和提升Python編程能力的都有顯著幫助,特別是對怎麼提高Py ...


python3-cookbook中每個小節以問題、解決方案和討論三個部分探討了Python3在某類問題中的最優解決方式,或者說是探討Python3本身的數據結構、函數、類等特性在某類問題上如何更好地使用。這本書對於加深Python3的理解和提升Python編程能力的都有顯著幫助,特別是對怎麼提高Python程式的性能會有很好的幫助,如果有時間的話強烈建議看一下。
本文為學習筆記,文中的內容只是根據自己的工作需要和平時使用寫了書中的部分內容,並且文中的示例代碼大多直接貼的原文代碼,當然,代碼多數都在Python3.6的環境上都驗證過了的。不同領域的編程關註點也會有所不同,有興趣的可以去看全文。
python3-cookbook:https://python3-cookbook.readthedocs.io/zh_CN/latest/index.html

 

 9.2 創建裝飾器時保留函數元信息

通常,在定義裝飾器函數時,總是應該記得使用functools.wraps來註解被包裝的函數,雖然在平時不加這個wraps裝飾器感覺也工作的很好,但其實不加的話被裝飾函數的元信息是被丟失了的,比如__name__、__doc__等信息,為了被裝飾函數的元信息不被丟失,就需要加上這個wraps裝飾器。

需要註意的是wraps應該放在包裝原始函數的那個函數定義之上,即參數為func的函數的返回值函數定義之上,特別是帶參數的裝飾器定義之中更要註意。

import time
from functools import wraps


def timethis(func):
    """普通的裝飾器"""

    def wrapper(*args, **kwargs):
        start = time.time()
        result = func(*args, **kwargs)
        end = time.time()
        print(func.__name__, end - start)
        return result

    return wrapper


def timethis_wraps(func):
    """有wraps的裝飾器"""

    @wraps(func)
    def wrapper(*args, **kwargs):
        start = time.time()
        result = func(*args, **kwargs)
        end = time.time()
        print(func.__name__, end - start)
        return result

    return wrapper


@timethis
def countdown(n):
    """timethis裝飾函數"""
    while n > 0:
        n -= 1


@timethis_wraps
def countdown_wraps(n):
    """timethis_wraps裝飾函數"""
    while n > 0:
        n -= 1


countdown(1000)
print(countdown.__name__)
print(countdown.__doc__)

countdown_wraps(1000)
print(countdown_wraps.__name__)
print(countdown_wraps.__doc__)
countdown 0.0
wrapper
None
countdown_wraps 0.0
countdown_wraps
timethis_wraps裝飾函數

 

9.6 帶可選參數的裝飾器

你想定義一個裝飾器,但使用的時候既可以傳遞參數給它,也可以不傳任何參數給它,那麼可以參考以下示例,利用functools.partial來實現。

import logging
from functools import wraps, partial


# 星號*的作用是迫使其後面的參數,即level,name,message三個參數,都必須使用關鍵字參數的形式傳參
def logged(func=None, *, level=logging.DEBUG, name=None, message=None):
    if func is None:
        # 此處partial的作用為返回一個函數,但它的level,name,message三個參數是已經指定好了的
        # 使用的時候只需要傳入剩下的參數即可,相當於:def logged(func=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函數的元信息是正確完整的
    @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='example')
def spam():
    print('Spam!')


print(add(2, 3))  # 輸出:5
spam()  # 輸出:Spam!

 

 

9.8 將裝飾器定義為類的一部分

如果需要在裝飾器中記錄信息或綁定信息時,那麼可以考慮將裝飾器定義在類中,需要註意的是裝飾器方法為類方法和實例方法時使用的也是對應的類或者實例,而且functools.wraps包裝的函數是不用傳遞額外的cls或self參數的。

from functools import wraps


class A:
    # 實例方法
    def decorator1(self, func):
        # wrapper函數不用定義參數self
        @wraps(func)
        def wrapper(*args, **kwargs):
            print('Decorator 1')
            return func(*args, **kwargs)

        return wrapper

    # 類方法
    @classmethod
    def decorator2(cls, func):
        # wrapper函數不用定義參數cls
        @wraps(func)
        def wrapper(*args, **kwargs):
            print('Decorator 2')
            return func(*args, **kwargs)

        return wrapper


a = A()


# 使用實例進行調用
@a.decorator1
def spam():
    pass


# 使用類進行調用
@A.decorator2
def grok():
    pass

 

 

9.21 避免重覆的屬性方法

如果在類中定義許多重覆的對於屬性的邏輯代碼,可以考慮如下示例進行簡化代碼:需要檢查name屬性是否為str類型,檢查age屬性是否為int類型。

普通方法定義屬性的檢查:

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

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

    @name.setter
    def name(self, value):
        if not isinstance(value, str):
            raise TypeError('name must be a string')
        self._name = value

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

    @age.setter
    def age(self, value):
        if not isinstance(value, int):
            raise TypeError('age must be an int')
        self._age = value

簡化後的代碼:

# 此函數返回一個屬性對象,用於檢查賦的值是否為指定的類型
# 在類中使用這個函數時,跟把這個函數中的代碼放到類中定義是一樣的,作用就只是把重覆使用的代碼提出來了
def typed_property(name, expected_type):
    storage_name = '_' + name

    @property
    def prop(self):
        return getattr(self, storage_name)

    @prop.setter
    def prop(self, value):
        if not isinstance(value, expected_type):
            raise TypeError('{} must be a {}'.format(name, expected_type))
        setattr(self, storage_name, value)

    return prop


class Person:
    name = typed_property('name', str)
    age = typed_property('age', int)

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

使用functools.partial改良的代碼:

from functools import partial

String = partial(typed_property, expected_type=str)
Integer = partial(typed_property, expected_type=int)


class Person:
    name = String('name')
    age = Integer('age')

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

 

 

9.22 定義上下文管理器的簡單方法

自定義上下文管理器有兩種方式,一種是使用contextlib.contextmanager裝飾器,但是這種方式應該只用來定義函數的上下文管理器,如果是對象,比如文件、網路連接或者鎖等,就應該使用另一種方式,即給對應的類實現__enter__()方法和__exit__()方法,在進入with語句時會調用__enter__()方法,退出時調用__exit__()方法,對象的方式容易懂一點,所以這裡就只給出函數方式的示例。

from contextlib import contextmanager


@contextmanager
def list_transaction(orig_list):
    working = list(orig_list)
    yield working
    # 如果沒有報錯,對列表orig_list的任何修改才會生效
    orig_list[:] = working


items = [1, 2, 3]
with list_transaction(items) as working:
    working.append(4)
    working.append(5)
print(items)

items = [1, 2, 3]
with list_transaction(items) as working:
    working.append(4)
    working.append(5)
    raise RuntimeError('oops!')
print(items)
[1, 2, 3, 4, 5]
Traceback (most recent call last):
  File "Z:/Projects/Daily Test/test.py", line 120, in <module>
    raise RuntimeError('oops!')
RuntimeError: oops!

 

 

9.23 在局部變數域中執行代碼

在使用exec()時,都應該想一下是否有更好的方案或者可以替代的方案。當然,如果是必須要使用exec(),那麼需要註意exec()執行時,它使用的局部變數域是拷貝自真實局部變數域,而不是直接使用的真實局部變數域,如果需要獲取exec局部變數域中的值,可以使用locals(),locals()返回一個真實局部變數域的拷貝,也是指向的exec()拷貝的局部變數域。

參考以下測試代碼和示例:

>>> a = 13
>>> exec('b = a + 1')
>>> b
14
>>> def test():
    a = 13
    exec('b = a + 1')
    print(b)

    
>>> # b並沒有在真實局部變數域中,所以會報錯
>>> test()
Traceback (most recent call last):
  File "<pyshell#3>", line 1, in <module>
    test()
  File "<pyshell#1>", line 4, in test
    print(b)
NameError: name 'b' is not defined
>>> 
>>> def test1():
    x = 0
    exec('x += 1')
    print(x)

    
>>> test1()  # x的值並沒有被修改
0
>>> 
>>> def test2():
    x = 0
    loc = locals()
    print('before:', loc)
    exec('x += 1')
    print('after:', loc)
    print('x =', x)
    # 想要獲取修改後的值,可以從locals()中獲取
    x = loc['x']
    print('x =', x)
    # 註意,再次運行locals()時,原來的值會被覆蓋掉
    x = 444
    loc = locals()
    print('new:', loc)

    
>>> test2()
before: {'x': 0}
after: {'x': 1, 'loc': {...}}
x = 0
x = 1
new: {'x': 444, 'loc': {...}}
>>> 

 


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 學習筆記(Python繼承) 有幾種叫法(父類-子類、基類-派生類)先拿代碼演示一下: 1 class father: 2 def work(self): 3 print("work>>>>>") 4 5 def car(self): 6 print("car>>>>>>>>>") 7 8 clas ...
  • 什麼是正則? 正則表達式也稱為正則,是一個特殊的字元序列,能幫助檢查一個字元串是否與某種模式匹配。可以用來進行驗證:郵箱、手機號、qq號、密碼、url = 網站地址、ip等。正則不是python語言獨有的技術,python語言直到1.5版本才將正則表達式完成的整理/納入進re模塊中,我們只需要導入r ...
  • IDB package com.bjpowernode.proxy; /** * 代理類和目標類都必須使用同一個介面。 */ public interface IDB { int insert(); int delete(); int update(); } OracleDB package com ...
  • IDB package com.bjpowernode.proxy; /** * 代理類和目標類都必須使用同一個介面。 */ public interface IDB { int insert(); int delete(); int update(); } OracleDB package com ...
  • Reader.class package com.bjpowernode.decorator; public interface Reader { void close(); } FileReader package com.bjpowernode.decorator; /** * 裝飾者模式中的被 ...
  • 一、 SortedSet集合直接舉例 package com.bjpowernode.java_learning; import java.util.*; /** * java.util.Set * java.util.SortedSet;無序不可以重覆,但是存進去的元素可以按照元素大小順序自動進行 ...
  • 手冊上關於這塊的解釋感覺不是很詳細清晰,經過幾個示例自己總結了下這塊的用法。 方法表達式:說簡單點,其實就是方法對象賦值給變數。 這裡有兩種使用方式: 1)方法值:隱式調用, struct實例獲取方法對象 2) 方法表達式:顯示調用, struct類型獲取方法對象, 需要傳遞struct實例對象作為 ...
  • 鑒於python3目前已成流行之勢,而各發行版Linux依然是自帶python2.x,筆者嘗試在centos7下,部署Python3.x與2.x共存環境 本文參考博主良哥95網址https://blog.csdn.net/qq_39091354/article/details/86584046內容。 ...
一周排行
    -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.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...