前言
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對象,接收三個參數
- class的名稱
- 繼承的父類,用元組
- 屬性,字典形式
擁有了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是需要慎用的。