Python中類創建和實例化過程

来源:https://www.cnblogs.com/xxpythonxx/p/18215841
-Advertisement-
Play Games

目的:求多個集合之前的並集,例如:現有四個集合C1 = {11, 22, 13, 14}、C2 = {11, 32, 23, 14, 35}、C3 = {11, 22, 38}、C4 = {11, 22, 33, 14, 55, 66},則它們之間的並集應該為: C1 & C2 & C3 = {11 ...


一、 type()

1、創建類的兩種方式

方式一

class MyClass(object):
    def func(self,name):
        print(name)

myc = MyClass()

print(MyClass, type(MyClass))
print(myc, type(myc))

我們創建了一個名為MyClass的類,並實例化了這個類,得到其對象myc

上面代碼列印的結果為:

<class '__main__.MyClass'>    <class 'type'>
<__main__.MyClass object at 0x0288F8F0>   <class '__main__.MyClass'>

type()函數可以查看一個類型或變數的類型,MyClass是一個class,它的類型就是type,而myc是一個實例,它的類型就是class MyClass。

我們說class的定義是運行時動態創建的,而創建class的方法就是使用type()函數。

type()函數既可以返回一個對象的類型,又可以創建出新的類型,比如,我們可以通過type()函數創建出MyClass類,而無需通過Class MyClass(object)...的定義:

方式二

動態創建類
type(類名, 父類的元組(針對繼承的情況,可以為空),包含屬性的字典(名稱和值))

def fn(self, name='world'): # 先定義函數
    print('Hello, %s.' % name)

MyClass = type('MyClass', (object,), {'func':fn}) # 創建MyClass類,得到一個type的類對象
# MyClass = type('MyClass', (object,), {'func':lambda self,name:name}) # 創建MyClass類

myc=MyClass()

print(MyClass, type(MyClass))
print(myc, type(myc))

列印結果:

<class '__main__.MyClass'>   <class 'type'>
<__main__.MyClass object at 0x0364B830>   <class '__main__.MyClass'>

要創建一個class對象,type()函數依次傳入3個參數:

  • class的名稱;
  • 繼承的父類集合,註意Python支持多重繼承,如果只有一個父類,別忘了tuple的單元素寫法;
  • class的方法名稱與函數綁定,這裡我們把函數fn綁定到方法名func上。

通過type()函數創建的類和直接寫class是完全一樣的,因為Python解釋器遇到class定義時,僅僅是掃描一下class定義的語法,然後調用type()函數創建出class。

type就是創建類對象的類。你可以通過檢查__class__屬性來看到這一點。Python中所有的東西,註意,我是指所有的東西——都是對象。這包括整數、字元串、函數以及類。它們全部都是對象,而且它們都是從一個類(元類,預設為type,也可以自定製)創建而來。type也是由type創建。。

二、元類(metaclass)

除了使用type()動態創建類以外,要控制類的創建行為,還可以使用metaclass。

metaclass,直譯為元類,簡單的解釋就是:

當我們定義了類以後,就可以根據這個類創建出實例,所以:先定義類,然後創建實例。

但是如果我們想創建出類呢?那就必鬚根據metaclass創建出類,所以:先定義元類(不自定義時,預設用type),然後創建類。

連接起來就是:先定義metaclass,就可以創建類,最後創建實例。

所以,metaclass允許你創建類或者修改類。換句話說,你可以把類看成是元類創建出來的“實例”。

預設情況下,類是使用type()構造的。類主體在一個新的名稱空間中執行,類名在本地綁定到類型的結果(名稱、基、名稱空間)。

可以通過在類定義行中傳遞元類關鍵字參數來定製類創建過程,或者從包含此類參數的現有類繼承。在下麵的示例中,MyClass和MySubclass都是Meta的實例:

class Meta(type):
    pass

class MyClass(metaclass=Meta):
    pass

class MySubclass(MyClass):
    pass

使用metaclass的兩種方式

class MyType(type):  # 自定義一個type的派生類
    def __init__(self,*args,**kwargs):
    print('xx')
       super(MyType,self).__init__(*args,**kwargs)

    def __call__(cls, *args, **kwargs):
        obj = cls.__new__(cls,*args, **kwargs)
        cls.__init__(obj,*args, **kwargs)
        return obj

def with_metaclass(base):
    return MyType("MyType2",(base,),{})

# 方式一
class Foo(metaclass=MyType):  # metaclass=MyType,即指定了由MyType創建Foo類,當程式運行,用到class Foo時,即調用MyType的__init__方法,創建Foo類
    def __init__(self,name):
        self.name = name


#方式二    在Flask的wtform的源碼中用到過
# class Foo(with_metaclass(object)):
#     def __init__(self,name):
#         self.name = name


a=Foo('name')

方式一:即用類的形式

執行代碼後,當遇到class Foo時即聲明要創建一個Foo類,就會調用type的__init__方法創建類,由於此處(metaclass=MyType),即指定了Foo類的創建方式,所以會執行type的派生類MyType的__init__方法,創建Foo類,列印一次'xx'

  • 一般情況下, 如果你要用類來實現metaclass的話,該類需要繼承於type,而且通常會重寫type的__new__方法來控制創建過程。

  • 在metaclass裡面定義的方法會成為類的方法,可以直接通過類名來調用

方式二:用函數的形式

構建一個函數,返回一個type的派生類對象,例如叫type的派生類, 需要3個參數:name, bases, attrs

  • name: 類的名字
  • bases: 基類,通常是tuple類型
  • attrs: dict類型,就是類的屬性或者函數

metaclass 原理

1.基礎

metaclass的原理其實是這樣的:當定義好類之後,創建類的時候其實是調用了type的__new__方法為這個類分配記憶體空間,創建好了之後再調用type的__init__方法初始化(做一些賦值等)。所以metaclass的所有magic其實就在於這個__new__方法裡面了。

說說這個方法:__new__(cls, name, bases, attrs)

  • cls: 將要創建的類,類似與self,但是self指向的是instance,而這裡cls指向的是class

  • name: 類的名字,也就是我們通常用類名.__name__獲取的。

  • bases: 基類

  • attrs: 屬性的dict。dict的內容可以是變數(類屬性),也可以是函數(類方法)。

所以在創建類的過程,我們可以在這個函數裡面修改name,bases,attrs的值來自由的達到我們的功能。這裡常用的配合方法是

getattr和setattr(just an advice)

2.查找順序

元類是由以下優先規則決定的:

如果“元類”存在,它就被使用了。

否則,如果至少有一個基類,則使用它的元類(這首先查找類屬性,如果沒有找到,則使用它的類型)。

否則,如果一個名為元類的全局變數存在,就會使用它。

三、 __init____new____call__三個特殊方法

  • __new__: 對象的創建,是一個靜態方法,第一個參數是cls。(想想也是,不可能是self,對象還沒創建,哪來的self),其必須要有返回值,返回實例化出來的實例,需要註意的是,可以return父類__new__()出來的實例,也可以直接將object的__new__()出來的實例返回。

  • __init__ : 對象的初始化, 是一個實例方法,第一個參數是self,該self參數就是__new__()返回的實例,__init__()__new__()的基礎上可以完成一些其它初始化的動作,__init__()不需要返回值。

  • __call__ : 對象可call,註意不是類,是對象。

1.對於__new__

class Bar(object):
    pass

class Foo(object):
    def __new__(cls, *args, **kwargs):
        return Bar()

print(Foo()) 

列印結果為:

<__main__.Bar object at 0x0090F930>

可以看到,輸出來是一個Bar對象。

__new__方法在類定義中不是必須寫的,如果沒定義,預設會調用object.__new__去創建一個對象。如果定義了,就是會覆蓋,使用自定義的,這樣就可以自定製創建對象的行為。

2.對於__init__

class Person(object):

  def __init__(self, name, age):
    self.name = name
    self.age = age
    print('執行__init__')

  def __new__(cls, *args, **kwargs):
      obj = object.__new__(cls) # 創建對象
      print('執行__new__方法')
      return obj

p1 = Person('hc', 24)
print(p1)

列印結果:

執行__new__方法
執行__init__
<__main__.Person object at 0x028EB830>

__init__ 方法通常用在初始化一個類實例的時候,但__init__其實不是實例化一個類的時候第一個被調用 的方法。當使用 Persion(name, age) 這樣的表達式來實例化一個類時,最先被調用的方法 其實是 __new__ 方法。從列印結果就可以看出來

__new__()沒有正確返回當前類cls的實例,那__init__()將不會被調用,即使是父類的實例也不行。

3.對於__call__

  對象通過提供__call__(slef, *args ,**kwargs)方法可以模擬函數的行為,如果一個對象x提供了該方法,就可以像函數一樣使用它,也就是說x(arg1, arg2...) 等同於調用x.__call__(self, arg1, arg2)

class Foo(object): 
  def __call__(self): 
    pass 
#學習中遇到問題沒人解答?小編創建了一個Python學習交流群:153708845 
f = Foo()    #類(),即執行元類的__call__
f()    #對象(),即執行Foo的__call__ 

4、實例化對象的完整過程

class Foo(Bar):
    pass

當我們寫如這段代碼時,Python做瞭如下的操作:

Foo中有metaclass這個屬性嗎?如果是,Python會在記憶體中通過metaclass創建一個名字為Foo的類對象(我說的是類對象,請緊跟我的思路)。如果Python沒有找到metaclass,它會繼續在Bar(父類)中尋找metaclass屬性,並嘗試做和前面同樣的操作。如果Python在任何父類中都找不到metaclass,它就會在模塊層次中去尋找metaclass,並嘗試做同樣的操作。如果還是找不到metaclass,Python就會用內置的type來創建這個類對象。

把上面這段話反覆讀幾次,現在的問題就是,你可以在metaclass中放置些什麼代碼呢?

答案就是:可以創建一個類的東西。

那麼什麼可以用來創建一個類呢?

type,或者任何使用到type或者子類化type的東東都可以。

以上面的代碼為例,我們實例化一個對象obj=Foo()時,會先執行Foo類的__new__方法,沒寫時,用父類的__new__方法,創建一個對象,並返回,然後執行__init__方法(自己有就用自己的,沒有就用父類的),對創建的對象進行初始化。

obj()會執行Foo類的__call__方法,沒有則用父類的

我們現在已經知道,類也是對象,是元類的對象,即我們實例化一個類時,調用其元類的__call__方法。

元類處理過程:定義一個類時,使用聲明或者預設的元類對該類進行創建,對元類求type運算,得到父元類(該類聲明的元類的父元類),調用父元類的__call__函數,在父元類的__call__函數中, 調用該類聲明的元類的__new__函數來創建對象(該函數需要返回一個對象(指類)實例),然後再調用該元類的__init__初始化該對象(此處對象是指類,因為是元類創建的對象),最終返回該類

1.對象是類創建,創建對象時候類的__init__方法自動執行,對象()執行類的__call__方法
2.類是type創建,創建類時候type的__init__方法自動執行,類() 執行type的 __call__方法(類的__new__方法,類的__init__方法)

原始type的__call__應該是參數結構應該是:

metaname, clsname, baseclasses, attrs

原始type的__new__

metaname, clsname, baseclasses, attrs

原始type的__init__

class_obj, clsname, baseclasses, attrs

元類的__new____init__影響的是創建類對象的行為,父元類的__call__控制對子元類的 __new____init__的調用,就是說控制類對象的創建和初始化。父元類的__new____init__由更上層的控制,

一般來說,原始type是最初的父元類,其__new____init__是具有普遍意義的,即應該是分配記憶體、初始化相關信息等

元類__call__影響的是創建類的實例對象的行為,此時如果類自定義了__new____init__就可以控制類的對象實例的創建和初始化

__new____init__ 影響的是創建對象的行為,當這些函數在元類中時,影響創建的是類;同理,當這倆個函數在普通類中時,影響創建的是普通的對象實例。

__call__ 影響()調用行為, __call__是在創建類的時候調用,即: class Test(object): __metaclass__=type, 定義類時就是創建類,此時會調用元類的__call__,如果元類有繼承,子元類定義時執行的是父元類的__call__
如果是普通類實例化對象,調用的是普通類的__call__


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

-Advertisement-
Play Games
更多相關文章
  • 設計原則名稱 定義 使用頻率 單一職責原則 一個類只負責一個功能領域中的相應職責 四顆星 開閉原則 軟體實體應對擴展開發,而對修改關閉 五顆星 里氏代換原則 所有引用基類對象的地方能夠透明地使用其子類的對象 五顆星 依賴倒轉原則 抽象不應該依賴於細節,細節應該依賴於抽象 五顆星 介面隔離原則 使用多 ...
  • 中國程式員的特點 中國程式員的最大優點是非常勤奮。中國互聯網行業有句話叫:“they earn a lot of money but die early”(賺得多死得早)。由於工作強度大,經常有程式員突然去世的新聞報道。 996 工作制度:中國程式員通常實行“996”工作制度(即每天工作從早9點到晚 ...
  • 前言 大家好,我是老馬。很高興遇到你。 我們為 java 開發者實現了 java 版本的 nginx https://github.com/houbb/nginx4j 如果你想知道 servlet 如何處理的,可以參考我的另一個項目: 手寫從零實現簡易版 tomcat minicat 手寫 ngin ...
  • 二項分佈是描述固定次數獨立試驗中成功次數的概率分佈,常用於分析二元結果的事件,如拋硬幣。分佈由參數 n(試驗次數)、p(單次成功概率)和 k(成功次數)定義。概率質量函數 P(k) = C(n, k) * p^k * (1 - p)^(n - k)。NumPy 的 `random.binomial(... ...
  • Spring AI 初學 Spring AI 官方地址 ”spring 不生產 AI,只是 AI 工具的搬運工“ 項目可以查看gitee Open AI 前期準備 Open AI官方地址,需要使用魔法才能打開,同時購買很麻煩,建議淘寶進行購買,只需要購買 open ai 的 apikey 即可。 a ...
  • 在財稅工作中,處理髮票信息是一項繁瑣而重要的任務。然而,藉助先進的技術,我們可以將這個過程簡化並提高效率。今天,我將介紹一個API介面,它可以在秒級內識別發票信息,讓財稅工作變得更輕鬆。 這個API介面是由挖數平臺提供的,你可以在他們的網站上找到詳細的信息。它支持對多種類型的發票進行結構化識別,包括 ...
  • 一、爬取目標 小紅書是眾多客戶的流量藍海,可通過評論區數據高效引流獲客。我用python開發的爬蟲採集軟體,可自動抓取小紅書評論數據,並且含二級評論數據。 為什麼有了源碼還開發界面軟體呢?方便不懂編程代碼的小白用戶使用,無需安裝python,無需改代碼,雙擊打開即用! 1.1 效果截圖 軟體界面截圖 ...
  • 作者:l拉不拉米 鏈接:https://juejin.cn/post/7031445206152577061 一、前言 公司剛入職了一名中級Java開發,經過一個星期的適應學習,各方面表現還不錯,於是分配了一個小的迭代給新人做。 需求很簡單,把從第三方拉取的數據匹配到自身公司後臺設置的渠道後,聚合到 ...
一周排行
    -Advertisement-
    Play Games
  • 示例項目結構 在 Visual Studio 中創建一個 WinForms 應用程式後,項目結構如下所示: MyWinFormsApp/ │ ├───Properties/ │ └───Settings.settings │ ├───bin/ │ ├───Debug/ │ └───Release/ ...
  • [STAThread] 特性用於需要與 COM 組件交互的應用程式,尤其是依賴單線程模型(如 Windows Forms 應用程式)的組件。在 STA 模式下,線程擁有自己的消息迴圈,這對於處理用戶界面和某些 COM 組件是必要的。 [STAThread] static void Main(stri ...
  • 在WinForm中使用全局異常捕獲處理 在WinForm應用程式中,全局異常捕獲是確保程式穩定性的關鍵。通過在Program類的Main方法中設置全局異常處理,可以有效地捕獲並處理未預見的異常,從而避免程式崩潰。 註冊全局異常事件 [STAThread] static void Main() { / ...
  • 前言 給大家推薦一款開源的 Winform 控制項庫,可以幫助我們開發更加美觀、漂亮的 WinForm 界面。 項目介紹 SunnyUI.NET 是一個基於 .NET Framework 4.0+、.NET 6、.NET 7 和 .NET 8 的 WinForm 開源控制項庫,同時也提供了工具類庫、擴展 ...
  • 說明 該文章是屬於OverallAuth2.0系列文章,每周更新一篇該系列文章(從0到1完成系統開發)。 該系統文章,我會儘量說的非常詳細,做到不管新手、老手都能看懂。 說明:OverallAuth2.0 是一個簡單、易懂、功能強大的許可權+可視化流程管理系統。 有興趣的朋友,請關註我吧(*^▽^*) ...
  • 一、下載安裝 1.下載git 必須先下載並安裝git,再TortoiseGit下載安裝 git安裝參考教程:https://blog.csdn.net/mukes/article/details/115693833 2.TortoiseGit下載與安裝 TortoiseGit,Git客戶端,32/6 ...
  • 前言 在項目開發過程中,理解數據結構和演算法如同掌握蓋房子的秘訣。演算法不僅能幫助我們編寫高效、優質的代碼,還能解決項目中遇到的各種難題。 給大家推薦一個支持C#的開源免費、新手友好的數據結構與演算法入門教程:Hello演算法。 項目介紹 《Hello Algo》是一本開源免費、新手友好的數據結構與演算法入門 ...
  • 1.生成單個Proto.bat內容 @rem Copyright 2016, Google Inc. @rem All rights reserved. @rem @rem Redistribution and use in source and binary forms, with or with ...
  • 一:背景 1. 講故事 前段時間有位朋友找到我,說他的窗體程式在客戶這邊出現了卡死,讓我幫忙看下怎麼回事?dump也生成了,既然有dump了那就上 windbg 分析吧。 二:WinDbg 分析 1. 為什麼會卡死 窗體程式的卡死,入口門檻很低,後續往下分析就不一定了,不管怎麼說先用 !clrsta ...
  • 前言 人工智慧時代,人臉識別技術已成為安全驗證、身份識別和用戶交互的關鍵工具。 給大家推薦一款.NET 開源提供了強大的人臉識別 API,工具不僅易於集成,還具備高效處理能力。 本文將介紹一款如何利用這些API,為我們的項目添加智能識別的亮點。 項目介紹 GitHub 上擁有 1.2k 星標的 C# ...