Python元程式設計 控制你想控制的一切

2021-08-20 20:24:39 字數 4920 閱讀 4222

很多人不理解「元程式設計」是個什麼東西,關於它也沒有乙個十分準確的定義。這篇文章要說的是python裡的元程式設計,實際上也不一定就真的符合「元程式設計」的定義。只不過我無法找到乙個更準確的名字來代表這篇文章的主題,所以就借了這麼乙個名號。

副標題是控制你想控制的一切,實際上這篇文章講的都是乙個東西,利用python提供給我們的特性,盡可能的使**優雅簡潔。具體而言,通過程式設計的方法,在更高的抽象層次上對一種層次的抽象的特性進行修改。

首先說,python中一切皆物件,老生常談。還有,python提供了許多特殊方法、元類等等這樣的「元程式設計」機制。像給物件動態新增屬性方法之類的,在python中根本談不上是「元程式設計」,但在某些靜態語言中卻是需要一定技巧的東西。我們來談些python程式設計師也容易被搞糊塗的東西。

我們先來把物件分分層次,通常我們知道乙個物件有它的型別,老早以前python就將型別也實現為物件。這樣我們就有了例項物件和類物件。這是兩個層次。稍有基礎的讀者就會知道還有元類這個東西的存在,簡言之,元類就是「類」的「類」,也就是比類更高層次的東西。這又有了乙個層次。還有嗎?

如果我們換個角度,不用非得和之前的三個層次使用同樣的標準。我們再來區分兩個東西:importtime和runtime,它們之間也並非界限分明,顧名思義,就是兩個時刻,匯入時和執行時。

當乙個模組被匯入時,會發生什麼?在全域性作用域的語句(非定義性語句)被執行。函式定義呢?乙個函式物件被建立,但其中的**不會被執行。類定義呢?乙個類物件被建立,類定義域的**被執行,類的方法中的**自然也不會被執行。

執行時呢?函式和方法中的**會被執行。當然你要先呼叫它們。

所以我們可以說元類和類是屬於importtime的,import乙個模組之後,它們就會被建立。例項物件屬於runtime,單import是不會建立例項物件的。不過話不能說的太絕對,因為如果你要是在模組作用域例項化類,例項物件也是會被建立的。只不過我們通常把它們寫在函式裡面,所以這樣劃分。

如果你想控制產生的例項物件的特性該怎麼做?太簡單了,在類定義中重寫__init__方法。那麼我們要控制類的一些性質呢?有這種需求嗎?當然有!

經典的單例模式,大家都知道有很多種實現方式。要求就是,乙個類只能有乙個例項。

最簡單的實現方法是這樣的

class _spam:

def __init__(self):

print("spam!!!")

_spam_singleton =none

def spam():

global _spam_singleton

if _spam_singleton is not none:

return _spam_singleton

else:

_spam_singleton = _spam()

return _spam_singleton

工廠模式,不太優雅。我們再來審視一下需求,要乙個類只能有乙個例項。我們在類中定義的方法都是例項物件的行為,那麼要想改變類的行為,就需要更高層次的東西。元類在這個時候登場在合適不過了。前面說過,元類是類的類。也就是說,元類的__init__方法就是類的初始化方法。 我們知道還有__call__這個東西,它能讓例項像函式那樣被呼叫,那麼元類的這個方法就是類在被例項化時呼叫的方法。

**就可以寫出來了:

class singleton(type):

def __init__(self, *args, **kwargs):

self._instance = none

super().__init__(*args, **kwargs)

def __call__(self, *args, **kwargs):

if self._instance is none:

self._instance = super().__call__(*args, **kwargs)

return self._instance

else:

return self._instance

class spam(metaclass=singleton):

def __init__(self):

print("spam!!!")

主要有兩個地方和一般的類定義不同,一是singleton的基類是type,一是spam定義的地方有乙個metaclass=singleton。type是什麼?它是object的子類,object是它的例項。也就是說,type是所有類的類,也就是最基本的元類,它規定了一些所有類在產生時需要的一些操作。所以我們的自定義元類需要子類化type。同時type也是乙個物件,所以它又是object的子類。有點不太好理解,大概知道就可以了。

我們再來說說裝飾器。大多數人認為裝飾器是python裡面最難理解的概念之一。其實它不過就是乙個語法糖,理解了函式也是物件之後。就可以很輕易的寫出自己的裝飾器了。

from functools import wraps

def print_result(func):

@wraps(func)

result = func(*args, **kwargs)

print(result)

return result

@print_result

def add(x, y):

return x + y

#相當於:

#add = print_result(add)

add(1, 3)

我在注釋裡寫了,@decorator這樣的形式等價於func=decorator(func),理解了這一點,我們就可以寫出更多種類的裝飾器。比如類裝飾器,以及將裝飾器寫成乙個類。

def attr_upper(cls):

for attrname,value in cls.__dict__.items():

if isinstance(value,str):

if not value.startswith('__'):

setattr(cls,attrname,bytes.decode(str.encode(value).upper()))

return cls

@attr_upper

class person:

*** = 'man'

print(person.***) # man

注意普通的裝飾器和類裝飾器實現的不同點。

如果我們想讓某一些類擁有某些相同的特性,或者說可以實現在類定義對其的控制,我們可以自定義乙個元類,然後讓它成為這些類的元類。如果我們想讓某一些函式擁有某些相同的功能,又不想把**複製貼上一遍,我們可以定義乙個裝飾器。那麼,假如我們想讓例項的屬性擁有某些共同的特點呢?有人可能會說可以用property,當然可以。但是這些邏輯必須在每個類定義的時候都寫一遍。如果我們想讓這些類的例項的某些屬性都有相同的特點的話,就可以自定義乙個描述符類。

關於描述符,這篇文章講得很好,同時它還講解了描述符是怎麼隱藏在函式的背後,實現函式、方法的統一和不同的。這裡我們給出一些例子。

class typedfield:

def __init__(self, _type):

self._type = _type

def __get__(self, instance, cls):

if instance is none:

return self

else:

return getattr(instance, self.name)

def __set_name__(self, cls, name):

self.name = name

def __set__(self, instance, value):

if not isinstance(value, self._type):

raise typeerror('expected' + str(self._type))

instance.__dict__[self.name] = value

class person:

age = typedfield(int)

name = typedfield(str)

def __init__(self, age, name):

self.age = age

self.name = name

jack = person(15, 'jack')

jack.age = '15'

# 會報錯

在這裡面有幾個角色,typedfield是乙個描述符類,person的屬性是描述符類的例項,看似描述符是作為person,也就是類的屬性而不是例項屬性存在的。但實際上,一旦person的例項訪問了同名的屬性,描述符就會起作用。需要注意的是,在python3.5及之前的版本中,是沒有__set_name__這個特殊方法的,這意味著如果你想要知道在類定義中描述符被起了乙個什麼樣的名字,是需要在描述符例項化時顯式傳遞給它的,也就是需要多乙個引數。不過在python3.6中,這個問題得到了解決,只需要在描述符類定義中重寫__set_name__這個方法就好了。還需要注意的是__get__的寫法,基本上對instance的判斷是必需的,不然會報錯。原因也不難理解,就不細說了。

在python3.6中,我們可以通過實現__init_subclass__特殊方法,來自定義子類的建立,這樣我們就可以在某些情況下擺脫元類這個討厭的東西。

class pluginbase:

subclasses =

def __init_subclass__(cls, **kwargs):

super().__init_subclass__(**kwargs)

class plugin1(pluginbase):

pass

class plugin2(pluginbase):

pass

諸如元類等元程式設計對於大多數人來說有些晦澀難懂,大多數時候也無需用到它們。但是大多數框架背後的實現都使用到了這些技巧,這樣才能讓使用者寫出來的**簡潔易懂。

程序控制程式設計

q 為何需要多程序 或者多執行緒 為何需要併發?a 併發技術,就是可以在同一時間同時執行多條任務的技術,使用者進行的任務往往不止乙個,單 cpu計算機實際上只能在乙個時間片段內執行一條指令。linux 使用 程序排程 實現併發,為每個程序指派一定的執行時間,這個時間通常很短,然後依照某種規則,依次單...

程序控制程式設計

1.多程序與併發的關係 當有多個執行緒在操作時,如果系統只有乙個cpu,則它根本不可能同時真正進行乙個以上的執行緒,它只能把 cpu執行時間劃分成若干個時間段,再把時間段分配給各個執行緒執行,在乙個時間段的執行緒 執行時,其他執行緒處於掛起狀態。這種方式我們成為併發。2.kill,pkill,xki...

程序控制程式設計

一.程序的分類 互動程序,批處理程序,守護程序。二.程序的屬性 1.程序id pid 是唯一的數值,用來區分程序 2.父程序和父程序的id ppid 3.啟動程序的使用者id uid 和所歸屬的組 gid 4.程序狀態 狀態分為執行r,休眠s,殭屍z 5.程序執行的優先順序 6.程序所連線的終端名 ...