python - OOP進階

前言

python的class有很多高級特性,除了OOP的三大特性,還在設計模式,自定制類和內存優化等都下了很多功夫。記載一些常用的類特性。

給實例動態綁定方法

不是一定要在定義的時候就要給class寫出所有的方法,可以在程序中動態的為實例綁定新的方法。

def func():
    print('hahaha')
class A(object):
    pass
a = A()
a.value = 1
from types import MethodType
a.hahaha = MethodType(func,a)

給類綁定方法的話比較簡單,直接綁定就可以,沒必要使MethodType去綁定

slots

我們知道,創建一個類時,為這個類創建了一個對象,這個類在實例化后會產生一個 __dict__,這個字典的目的是為了存儲實例中的各種屬性,你為實例添加變量或者函數都會記錄在這個字典中。但是如果在類中,聲明了__slots__ ,實例化時不會去創建字典,只會創建一個給定大小的數組用來存儲__slots__中指定的屬性。

class Person(object):
    __slots__ = ('name','age')

p = Person()
p.name = "Allenware"
p.age = 22

p.country = 'China' #報錯

__slots__ 只對當前類有效,對繼承的子類沒有作用。子類也做此聲明,則父類和子類本身的__slot__中聲明的名稱同時有效。

當然,__slots__ 雖然有著訪問限制的作用,但是python中的訪問限制全靠自覺,這個特性最主要的用法是在為一個類創建大量實例時,事先知道屬性范圍,使用__slots__可以節省大量的內存。原因就是開始的原因,定長數組是要比字典省內存的多的。

@property

這個語法糖,設計出來是為了讓封裝更加的間接。面向對象基礎中有這個例子

class Student(object):
    kind = 'school'

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

    def output(self):
        print(self.name)
        print(self.age)
    
    def getage(self):
        return self.age
    
    def setage(self,age):
        if age is int:
            self.age = age
        else: 
            raise ValueError('Not int')

Allen = Student('Allen','22')
Peter = Student('Peter','23')

print(Allen.kind)
print(Peter.kind)

print(Allen.name,Allen.age)
print(Peter.name,Peter.age)

在這個例子中,想要給實例設置年齡的話,必須使用 instance.setage(),雖然可以通過這個方法來檢查參數或者添加其他功能,但是不如 instance.age = age 直接綁定來的簡單粗暴。而且有時候會導致代碼可讀性降低。

這時候可以利用@property來改善代碼。

class Student(object):
    kind = 'school'

    def __init__(self,name,age):
        self.name = name
        self.age = age  #這里使用self._age會避開裝飾器的作用

    @property
    def age(self):
        return self._age
    
    
    @age.setter
    def age(self,age):
        if type(age) is int:
            self._age = age
        else: 
            raise ValueError('Not int')

Allen = Student('Allen',22)
Peter = Student('Peter',23)

Allen.age = 23
print(Allen.age)
Allen.age = 'a'

最后一句的綁定是會報錯的,這就是@property 的作用,給屬性綁定值的時候自動調用了age的set函數。在你原來的setage()函數上加上@age.setter語法糖,初次之外,還有@age.deleter這個用于刪除時的語法糖。不過這三個語法糖緊跟的函數必須是同名的。默認的@property其實就起著get的作用。

上面的例子可以換一種寫法

class Student(object):
    kind = 'school'

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

    def get_age(self):
        return self._age
    
    def set_age(self,age):
        if type(age) is int:
            self._age = age
        else: 
            raise ValueError('Not int')
     
    def del_age(self):
        raise AttributeError('Cannot delete age!')

    age = property(get_age,set_age,del_age)

這個作用是跟之前的寫法一樣的,通過這個寫法就能看出一點@property的原理了。property相當于一個類,為你指定的attribute實例化一個property對象,為get、set、del綁定好對應的三個方法,在你對attribute進行這三個操作時,會觸發事先綁定好的方法,這牽扯到了裝飾器的原理用法。所以用第一種寫法可讀性更高。

在用@property 要注意使用時的情景,并不是所有情況都適用,如果你有想要管理的屬性,那就是使用 @property 的時機。

__str__

class Person(object):
    def __str__(self):
        return 'Person Object'
    
    __repr__ = __str__
    
pan = Person()
print(pan)

這樣,print(instance) 的輸出就是和 __str__ 函數中指定的輸出了。看起來很簡單的小功能,其實是很實用的。可以在 __str__ 中加入你想要的屬性信息,很方便調試。

上下文管理

之前提到過with語句,讓一個類支持with特性的話,需要自定義__enter__()__exit__()方法

from socket import socket, AF_INET, SOCK_STREAM

class LazyConnection:
    def __init__(self, address, family=AF_INET, type=SOCK_STREAM):
        self.address = address
        self.family = family
        self.type = type
        self.sock = None

    def __enter__(self):
        if self.sock is not None:
            raise RuntimeError('Already connected')
        self.sock = socket(self.family, self.type)
        self.sock.connect(self.address)
        return self.sock

    def __exit__(self, exc_ty, exc_val, tb):    #異常值、異常類型、回溯信息
        self.sock.close()
        self.sock = None

這個類在實例化的時候并不會去創建連接,只有在使用with時,連接的連接和關閉都是自動完成的,所以調用完with之后,可以直接send或者recv進行數據交互了。

上下文管理在資源管理中用的非常廣,比較文件、網絡socket,還有上次說的線程鎖??梢栽?__exit__ 中release來保證鎖的釋放。

枚舉類

from eunm import Enum
Animal = Enum('Animal','cat dog ant')

class Animal(Enum):
    cat = 1
    dog = 2 
    ant = 3

__type__

type() 不僅僅用于檢測對象的類型,也可以用于創建class對象,接收三個參數

  1. class的名稱
  2. 繼承的父類,用元組
  3. 屬性,字典形式

擁有了type() ,我們可以動態的去創建類,讓一個函數返回新建的類。結合上面的枚舉類,自己用type()實現一個枚舉類

def enum(**enums):
    return type('Enum', (), enums)
 
Numbers = enum(ONE=1, TWO=2, THREE='three')

#人性化一點,可以接受列表的輸入
def enum(*sequential, **named):
    enums = dict(zip(sequential, range(len(sequential))), **named)
    return type('Enum', (), enums)
 
Numbers = enum('ZERO', 'ONE', 'TWO')

super()

super用來調用父類的方法,一般是用來保證在本身初始化的時候,父類也被正確的初始化。

class A:
    def __init__(self):
        self.x = 0

class B(A):
    def __init__(self):
        super().__init__()
        self.y = 1

這個可以結合訪問限制的 __ 來使用,因為之前說過,雙下劃線的命名方式的目的就是為了防止父類的屬性被子類不小心覆蓋了。我們可以在子類中找不到對應屬性的時候,使用super()去父類中尋找。

class Test(Father):
    def __init__(self, obj):
        self._obj = obj

    def __getattr__(self, name):
        return getattr(self._obj, name)

    def __setattr__(self, name, value):
        if name.startswith('__'):
            super().__setattr__(name, value) 
        else:
            setattr(self._obj, name, value)

super() 用于多繼承時,會避免重復調用某一個父類的初始化方法。而如果直接使用父類的類名來調用初始化方法,則會重復調用。當然super是需要慎用的。

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容

  • Spring Cloud為開發人員提供了快速構建分布式系統中一些常見模式的工具(例如配置管理,服務發現,斷路器,智...
    卡卡羅2017閱讀 134,932評論 18 139
  • 定義類并創建實例 在Python中,類通過 class 關鍵字定義。以 Person 為例,定義一個Person類...
    績重KF閱讀 3,971評論 0 13
  • 是否需要在存儲過程中寫commit主要要依據需求: (1) 如果是不需要在存儲過程中進行提交,而是由調用程序負責...
    GuangHui閱讀 3,567評論 0 1
  • 這句話,有多少種意味? 1、過去的事情,有時候會對我們產生極大的影響,有著巨大的代價,甚至擴展蔓延到生活各個方面。...
    莊魚閱讀 3,242評論 1 1
  • 逛街,遇到一位阿姨,立馬連珠炮的發問,在哪工作呢,工資多少了,結婚了嗎…… 我笑著,目前無業游民,兩袖清風,孑然一...
    木小醒閱讀 534評論 3 4