Python Cookbook學習筆記 ch9_03超程式設計
9.15 定義有可選引數的元類
- 問題:想要定義一個元類,它的引數是可選的,這樣可以控制或者配置型別的建立過程
- 方案:使用“metaclass”關鍵字引數指定特定的元類
from abc import ABCMeta,abstractmethod
class IStream(metaclass=ABCMeta):
@abstractmethod
def read(self,maxsize=None):
pass
@abstractmethod
def write(self,data):
pass
- 也可以使用其它關鍵字,但是必須在__prepare__()和__new__()、__init__()中都是用強制關鍵字引數:
class MyMeta(type):
@classmethod
def __prepare__(cls, name, bases, *, debug=False, synchronize=False):
pass
return super().__prepare__(name,bases)
def __new__(cls, name, bases, ns, *, debug=False, synchronize=False):
pass
return super().__new__(cls, name, bases, ns)
def __init__(cls, name, bases, *, debug=False, synchronize=False):
pass
super().__init__(name, bases, ns)
- __prepare__():在所有類定義開始執行之前首先被呼叫,用來建立類名稱空間
- __new__():用來例項化類物件,他在類的主體被執行完後開始執行
- __init__():最後被呼叫,用來給物件進行初始化
- 當我們要定義元類時,只需要定義new()或者init()方法,但並不是都定義。但是當要接受其它關鍵字引數的時候,就需要兩個都定義並提供對應的引數簽名。預設的 prepare
9.16 * args 和** kwargs的強制引數簽名
- 問題:有一個函式或者方法,它的引數為 * args和** kwargs,這樣使得它比較通用,但是想要對傳進來的引數進行型別檢查
- 方案:對於任何涉及到操作函式呼叫簽名的問題,都應該使用inspect模組中的簽名特性
from inspect import Signature, Parameter
parms = [Parameter('x',Parameter.POSITIONAL_OR_KEYWORD),
Parameter('y',Parameter.POSITIONAL_OR_KEYWORD,default=42),
Parameter('z',Parameter.KEYWORD_ONLY,default=None)]
sig = Signature(parms)
print(sig)
(x, y=42, *, z=None)
- 一旦有了簽名物件,就可以使用它的bind()方法將它繫結到* args和** kwargs上去
def func(*args, **kwargs):
bound_values = sig.bind(*args, **kwargs)
for name, value in bound_values.arguments.items():
print(name,value)
func(1,2,z=3)
x 1
y 2
z 3
func(1)
x 1
func(1,z=3)
x 1
z 3
func(y=2,x=1)
x 1
y 2
- 下面的例子更加具體,首先在基類中定義一個通用的初始化方法,然後強制所有的子類必須提供一個特定的引數簽名。
from inspect import Signature,Parameter
def make_sig(*names):
parms = [Parameter(name, Parameter.POSITIONAL_OR_KEYWORD) for name in names]
return Signature(parms)
class Structure:
__signature__ = make_sig()
def __init__(self, *args, **kwargs):
bound_values = self.__signature__.bind(*args, **kwargs)
for name, value in bound_values.arguments.items():
setattr(self,name,value)
class Stock(Structure):
__signature__ = make_sig('name','shares','price')
class Point(Structure):
__signature__ = make_sig('x','y')
import inspect
print(inspect.signature(Stock))
(name, shares, price)
s1 = Stock('ACME', 100, 389.1)
s2 = Stock('ACME', 100)
---------------------------------------------------------------------------
TypeError Traceback (most recent call last)
<ipython-input-24-39f0ea31793d> in <module>()
----> 1 s2 = Stock('ACME', 100)
<ipython-input-20-8adc4a21dcf0> in __init__(self, *args, **kwargs)
7 __signature__ = make_sig()
8 def __init__(self, *args, **kwargs):
----> 9 bound_values = self.__signature__.bind(*args, **kwargs)
10 for name, value in bound_values.arguments.items():
11 setattr(self,name,value)
d:\program filles\python\lib\inspect.py in bind(*args, **kwargs)
2966 if the passed arguments can not be bound.
2967 """
-> 2968 return args[0]._bind(args[1:], kwargs)
2969
2970 def bind_partial(*args, **kwargs):
d:\program filles\python\lib\inspect.py in _bind(self, args, kwargs, partial)
2881 msg = 'missing a required argument: {arg!r}'
2882 msg = msg.format(arg=param.name)
-> 2883 raise TypeError(msg) from None
2884 else:
2885 # We have a positional argument to process
TypeError: missing a required argument: 'price'
9.17在類上強制使用程式設計規約
- 問題:程式中包含很大的類繼承體系,希望強制執行某些程式設計規約
- 方案:入股想要監控類的定義,通常可以定義一個元類,一個基本元類通常是繼承自type並重新定義new方法或者init方法
class MyMeta(type):
def __new__(self, clsname, bases, clsdict):
return super().__new__(cls, clsname, bases, clsdict)
# 另一種是定義__ini__()
class MyMeta(type):
def __init__(self, clsname, bases, clsdict):
super().__init__(clsname,bases, clsdict)
# 為了使用上面的元類,通常把他放到一個頂級父類定義之中,然後其他的類繼承這個頂級父類
class Root(metaclass=MyMeta):
pass
class A(Root):
pass
class B(Root):
pass
- 元類的一個關鍵特點是它允許在定義的時候檢查類的內容。下面的例子定義了一個元類,它會拒絕任何有混合大小寫名字作為方法的類定義
class NoMixCaseMeta(type):
def __new__(cls, clsname, bases, clsdict):
for name in clsdict:
if name.lower() != name:
raise TypeError(" Bad attribute name: ", name)
return super().__new__(cls, clsname, bases,clsdict)
class Root(metaclass=NoMixCaseMeta):
pass
class A(Root):
def foo_bar(self):
pass
class B(Root):
def fooBar(self):
pass
---------------------------------------------------------------------------
TypeError Traceback (most recent call last)
<ipython-input-30-bc6fbee49cec> in <module>()
----> 1 class B(Root):
2 def fooBar(self):
3 pass
<ipython-input-29-e74c44098507> in __new__(cls, clsname, bases, clsdict)
3 for name in clsdict:
4 if name.lower() != name:
----> 5 raise TypeError(" Bad attribute name: ", name)
6 return super().__new__(cls, clsname, bases,clsdict)
7 class Root(metaclass=NoMixCaseMeta):
TypeError: (' Bad attribute name: ', 'fooBar')
- 作為一個更加高階的例子,下面的元類用來檢測過載方法,確保他呼叫的引數和父類中原始方法有著相同的引數簽名。
from inspect import signature
import logging
class MatchSignatureMeta(type):
def __init__(self,clsname, bases, clsdict):
super().__init__(clsname, bases, clsdict)
sup = super(self, self)
for name, value in clsdict.items():
if name.startswith('_') or not callable(value):
continue
pre_dfn = getattr(sup, name, None)
if pre_dfn:
prev_sig = signature(pre_dfn)
val_sig = signature(value)
if prev_sig != val_sig:
logging.warning("Signature mismatch in %s. %s != %s", value.__qualname__, prev_sig, val_sig)
class Root(metaclass=MatchSignatureMeta):
pass
class A(Root):
def foo(self,x, y):
pass
def spam(self,x,*,z):
pass
class B(A):
def foo(self, a, b):
pass
def spam(self,x,z):
pass
WARNING:root:Signature mismatch in B.foo. (self, x, y) != (self, a, b)
WARNING:root:Signature mismatch in B.spam. (self, x, *, z) != (self, x, z)
-
在元類中選擇重新定義 __new__() 方法還是 __init__() 方法取決於你想怎樣使
用結果類。 __new__() 方法在類建立之前被呼叫,通常用於通過某種方式(比如通過改
變類字典的內容)修改類的定義。而 __init__() 方法是在類被建立之後被呼叫,當你
需要完整構建類物件的時候會很有用。 -
程式碼中有一行使用了 super(self, self) ,當使用元類的時候,我們要時刻記住一點就是 self 實際上是一個類物件。因此,這條語句其實
就是用來尋找位於繼承體系中構建 self 父類的定義。
9.18 以程式設計的方式定義類
- 問題:寫了一段程式碼,需要建立一個新的類物件。想將類的定義原始碼以字串的形式釋出出去。並且使用函式比如 exec() 來執行它,但是你想尋找一個更加優雅的解決方案。
- 方案:使用函式 types.new_class() 來初始化新的類物件。你需要做的只是提供
類的名字、父類元組、關鍵字引數,以及一個用成員變數填充類字典的回撥函式。
def __init__(self, name, shares, price):
self.name = name
self.shares = shares
self.price = price
def cost(self):
return self.shares * self.price
cls_dict = {
'__init__':__init__,
'cost':cost,
}
import types
Stock = types.new_class('Stock',(),{},lambda ns:ns.update(cls_dict))
Stock.__module__ = __name__
s = Stock("ACME",50,91.1)
s
<__main__.Stock at 0xbfdeb0>
s.cost()
4555.0
- 如果你建立的類需要一個不同的元類,可以通過設定types.new_class()的第三個引數
import abc
Stock = types.new_class('Stock',(),{'metaclass': abc.ABCMeta},lambda ns: ns.update(cls_dict))
Stock.__module__ = __name__
Stock
__main__.Stock
type(Stock)
abc.ABCMeta
- 第三個引數還可以包含其他的關鍵字引數:
# class Spam(Base,debug=True, typecheck=False):
# pass
# 上面的程式碼相當於
# Spam = types.new_class('Spam',Base,{'debug'= True, 'typecheck' = False},lambda ns:ns.update(cls_dict))
-
new_class() 第四個引數是一個用來接受類名稱空間的對映物件的函
數。通常這是一個普通的字典,但是它實際上是 __prepare__() 方法返回的任意物件, -
也可以呼叫collections.namedtuple()
import collections
Stock = collections.namedtuple('Stock',['name', 'shares','price'])
Stock
__main__.Stock
import operator
import types
import sys
def named_tuple(classname, fieldnames):
cls_dict = {name:property(operator.itemgetter(n)) for n, name in enumerate(fieldnames)}
def __new__(cls, *args):
if len(args) != len(fieldnames):
raise TypeError("Excepted {} arguments".format(len(fieldnames)))
return tuple.__new__(cls,args)
cls = types.new_class(classname, (tuple,),{},lambda ns:ns.update(cls_dict))
cls.__module__ = sys._getframe(1).f_globals['__name__']
return cls
- 上面的程式碼中使用了一個所謂的“框架魔法”,通過sys._getframe() 來獲取呼叫者的模組名
Point = named_tuple("point", ['x','y'])
Point
__main__.point
p = Point((4,5))
len(p)
2
p.x
4
p.y
5
p.x = 2
---------------------------------------------------------------------------
AttributeError Traceback (most recent call last)
<ipython-input-12-6bf7f157f4bc> in <module>()
----> 1 p.x = 2
AttributeError: can't set attribute
print('%s %s'%p )
4 5
相關推薦
Python Cookbook學習筆記 ch9_03超程式設計
9.15 定義有可選引數的元類 問題:想要定義一個元類,它的引數是可選的,這樣可以控制或者配置型別的建立過程 方案:使用“metaclass”關鍵字引數指定特定的元類 from abc impor
Python Cookbook學習筆記ch8_02
8.8子類中擴充套件property 問題:在子類中想要擴充套件在父類中的property功能 方案:見下述程式碼 class Person: def __init__(self,name)
python cookbook 學習筆記 第三章 數字日期和時間(9) 大型資料運算
大型資料運算 -問題: 需要在大資料集(比如陣列或網路)上面執行計算。 解決方案: 涉及到陣列的重量級運算,可以使用 Numpy 庫。Numpy 的一個主要特徵是他會給 Python 提 供一個數組物件,相比標準的 Python 列表更適合用來做數學運
python cookbook學習筆記十六:類和物件(1)
我們經常會對列印一個物件來得到物件的某些資訊。 class pair: def __init__(self,x,y): self.x=x self.y=y if __name__=='__main__': p=pair
流暢的python和cookbook學習筆記(一)
構造函數 推導 笛卡爾 expr 列表推導 叠代 建立 笛卡兒 imp 1.數據結構 1.1 內置序列類型 四種序列類型: 1.容器序列:list、tuple和collections.deque 2.扁平序列:str、bytes、bytearray、memory
流暢的python和cookbook學習筆記(五)
pytho col () 學習 util 學習筆記 取出 minute python 1.隨機選擇 python中生成隨機數使用random模塊。 1.從序列中隨機挑選元素,使用random.choice() >>> import random
流暢的python和cookbook學習筆記(八)
不可變 pri 列表 改變 如果 book 影響 color print 1.函數的默認參數必須不可變 如果函數的默認參數為可變的對象,那麽默認參數在函數外被修改也會影響到函數本身的。 >>> def spam(a, b=None): # b要為不
Python學習筆記(四) IO程式設計
1.讀檔案 使用open()函式開啟檔案,返回一個檔案物件,可選擇傳參模式和緩衝區,預設是讀模式,緩衝區是無 利用open()函式可以開啟檔案, 如下程式碼,open()的第二個引數是’r’,表示的是讀檔案,第三個引數encoding指定的是檔案的編碼格式. filePath =
python學習筆記(44) 網路程式設計
統一入口:微信小程式和公眾號 b/s架構是c/s架構的一種 網絡卡上有全球唯一的mac地址 4個點分十進位制(ip v4協議) 4個8位2進位制數: 0.0.0.0--255.255.255.255 通過ip地址找mac地址--arp協議 廣播——單播 (廣播風暴) 閘道器:區域網中的機器訪
面向物件的程式設計(1)——簡明python教程學習筆記
本文大量內容來源於沈老師的簡明python教程,其中夾雜部分個人的理解如有偏頗之處還望海涵。 一.簡介 到目前為止,在我們的程式中,我們都是根據操作資料的函式或語句塊來設計程式的。這被稱為面向過程的程式設計。 還有一種把資料和功能結合起來,用稱為物
【Python個人學習筆記】---《Python遊戲程式設計入門》第二章小結挑戰習題(二)
問題:選取一個示列,例如,繪製線條示例,修改它以便用隨機的值繪製1000個線條。瞭解一下random庫和random.randint()函式。 最開始想法是,用pygame.draw.line(Surface, color, start_pos, end_pos, width) 來
【Python個人學習筆記】---《Python遊戲程式設計入門》第二章小結挑戰習題(三)
問題:繪製矩形示列是一個圍繞螢幕移動形狀的示列,任何時候,矩形碰到螢幕邊界時,矩形都會改變顏色。 把 每次碰撞時改變的顏色用列表來歸納並計算反彈次數作為索引是個不錯的思路。 程式碼如下: import sys import pygame from pygame.locals i
Python學習筆記整理併發程式設計
多併發程式設計之程序 0x0程序的概念 0x0什麼是程序? 正在進行的過程或任務,而執行該任務的是CPU。 0x1程序與程式的區別? 程式只是單純的程式碼集,而程式一旦執行則該程式執行的過程則一個程序,在同一個程式執行多次的情況下出現的是多個程序,而不是一個程序,由此得出結論,每執行一次程式就會產生
【Python】學習筆記——-10、IO程式設計
一、什麼是IO程式設計 IO在計算機中指Input/Output,也就是輸入和輸出。由於程式和執行時資料是在記憶體中駐留,由CPU這個超快的計算核心來執行,涉及到資料交換的地方,通常是磁碟、網路等,就需要IO介面。 比如你開啟瀏覽器,訪問新浪首頁,瀏覽器這個程式就需要
python完全學習筆記
tee lsp 般的 posix adding efi 屬性 路徑 block dir(__builtins__) help(input) ‘let\‘s go‘ #轉義字符 \ r‘c:\now‘ #字符串前加r 自動轉義 str= ‘‘‘
python基礎學習筆記
好的 留下 path false 找到 __name__ ems 單詞 ups 1 #!/usr/bin/env python 2 #coding=utf-8 3 4 def login(username): 5 if username==‘
Python Click 學習筆記(轉)
col 輸出 小工具 方法 chm 好的 put name 回調 原文鏈接:Python Click 學習筆記 Click 是 Flask 的團隊 pallets 開發的優秀開源項目,它為命令行工具的開發封裝了大量方法,使開發者只需要專註於功能實現。恰好我最近在開發的一個小
Android學習筆記:超能RecyclerView組件使用總結
popu bin view設置 and col cas mda rac data 個人認為 RecyclerView組件確實值得學習並用到我們的項目中去,前面學了相關的內容。今天再補充一些相關的東東。 1,實現對RecyclerView中的數據進行加入和刪除操作。
python 學習筆記
pythonpython 學習筆記1 import : (1)import module (2)from module import argv (3)from module import *2 item : item :把字典中的每一對 key 和 value 組成一個元組,
python 學習筆記 (核心)
python 學習筆記 (核心)python 學習筆記 (核心)Python解釋器從頭到尾一行接一行執行腳本# -*- coding: UTF-8 -*- //字符編碼不區分單引號和雙引號,x=’hello’,x[0],x[-1]指最後一個字符,x[2:4]取子串,‘’’hello’’’ #