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 Framework 4.8 開發的深度學習模型部署測試平臺,提供了YOLO框架的主流系列模型,包括YOLOv8~v9,以及其系列下的Det、Seg、Pose、Obb、Cls等應用場景,同時支持圖像與視頻檢測。模型部署引擎使用的是OpenVINO™、TensorRT、ONNX runti... ...
  • 十年沉澱,重啟開發之路 十年前,我沉浸在開發的海洋中,每日與代碼為伍,與演算法共舞。那時的我,滿懷激情,對技術的追求近乎狂熱。然而,隨著歲月的流逝,生活的忙碌逐漸占據了我的大部分時間,讓我無暇顧及技術的沉澱與積累。 十年間,我經歷了職業生涯的起伏和變遷。從初出茅廬的菜鳥到逐漸嶄露頭角的開發者,我見證了 ...
  • C# 是一種簡單、現代、面向對象和類型安全的編程語言。.NET 是由 Microsoft 創建的開發平臺,平臺包含了語言規範、工具、運行,支持開發各種應用,如Web、移動、桌面等。.NET框架有多個實現,如.NET Framework、.NET Core(及後續的.NET 5+版本),以及社區版本M... ...
  • 前言 本文介紹瞭如何使用三菱提供的MX Component插件實現對三菱PLC軟元件數據的讀寫,記錄了使用電腦模擬,模擬PLC,直至完成測試的詳細流程,並重點介紹了在這個過程中的易錯點,供參考。 用到的軟體: 1. PLC開發編程環境GX Works2,GX Works2下載鏈接 https:// ...
  • 前言 整理這個官方翻譯的系列,原因是網上大部分的 tomcat 版本比較舊,此版本為 v11 最新的版本。 開源項目 從零手寫實現 tomcat minicat 別稱【嗅虎】心有猛虎,輕嗅薔薇。 系列文章 web server apache tomcat11-01-官方文檔入門介紹 web serv ...
  • 1、jQuery介紹 jQuery是什麼 jQuery是一個快速、簡潔的JavaScript框架,是繼Prototype之後又一個優秀的JavaScript代碼庫(或JavaScript框架)。jQuery設計的宗旨是“write Less,Do More”,即倡導寫更少的代碼,做更多的事情。它封裝 ...
  • 前言 之前的文章把js引擎(aardio封裝庫) 微軟開源的js引擎(ChakraCore))寫好了,這篇文章整點js代碼來測一下bug。測試網站:https://fanyi.youdao.com/index.html#/ 逆向思路 逆向思路可以看有道翻譯js逆向(MD5加密,AES加密)附完整源碼 ...
  • 引言 現代的操作系統(Windows,Linux,Mac OS)等都可以同時打開多個軟體(任務),這些軟體在我們的感知上是同時運行的,例如我們可以一邊瀏覽網頁,一邊聽音樂。而CPU執行代碼同一時間只能執行一條,但即使我們的電腦是單核CPU也可以同時運行多個任務,如下圖所示,這是因為我們的 CPU 的 ...
  • 掌握使用Python進行文本英文統計的基本方法,並瞭解如何進一步優化和擴展這些方法,以應對更複雜的文本分析任務。 ...
  • 背景 Redis多數據源常見的場景: 分區數據處理:當數據量增長時,單個Redis實例可能無法處理所有的數據。通過使用多個Redis數據源,可以將數據分區存儲在不同的實例中,使得數據處理更加高效。 多租戶應用程式:對於多租戶應用程式,每個租戶可以擁有自己的Redis數據源,以確保數據隔離和安全性。 ...