python 魔術(shù)方法
前言
在做python開發(fā)的過程中,我們大家都會(huì)遇到在class(類)中使用雙下劃線的方法,這些都是我們經(jīng)常所說的"魔法"方法.這些方法可以對(duì)類添加特殊的功能,使用恰當(dāng)可以很大的提升我們?cè)陂_發(fā)過程中的便捷性,方便的進(jìn)行擴(kuò)展.
概覽
目前我們常見的魔法方法大致可分為以下幾類:
- 構(gòu)造與初始化
- 類的表示
- 訪問控制
- 比較操作
- 容器類操作
- 可調(diào)用對(duì)象
- Pickling序列化
我們這次主要介紹這幾類常用魔法方法:
1.構(gòu)造與初始化
__init__
構(gòu)造方法是我們使用頻率最高的魔法方法了劣砍,幾乎在我們定義類的時(shí)候容握,都會(huì)去定義構(gòu)造方法梢杭,它的主要作用就是在初始化一個(gè)對(duì)象時(shí)吐葱,定義這個(gè)對(duì)象的初始值。
class Person(object):
def __init__(self, name, age):
self.name = name
self.age = age
p1 = Person('Jack', 25)
p2 = Person('shuke', 20)
__new__
- 事實(shí)上,當(dāng)我們理解了new方法后淤翔,我們還可以利用它來做一些其他有趣的事情蕴茴,比如實(shí)現(xiàn) 設(shè)計(jì)模式中的 單例模式(singleton)
- 依照Python官方文檔的說法,new方法主要是當(dāng)你繼承一些不可變的class時(shí)(比如int, str, tuple)因惭, 提供給你一個(gè)自定義這些類的實(shí)例化過程的途徑。還有就是實(shí)現(xiàn)自定義的metaclass
- 這個(gè)方法我們一般很少定義绩衷,不過我們?cè)谝恍╅_源框架中偶爾會(huì)遇到定義這個(gè)方法的類蹦魔。實(shí)際上激率,這才是"真正的構(gòu)造方法",它會(huì)在對(duì)象實(shí)例化時(shí)第一個(gè)被調(diào)用勿决,然后再調(diào)用init乒躺,它們的區(qū)別主要如下:
- new的第一個(gè)參數(shù)是cls,而init的第一個(gè)參數(shù)是self
- new返回值是一個(gè)實(shí)例低缩,而init沒有任何返回值嘉冒,只做初始化操作
- new由于是返回一個(gè)實(shí)例對(duì)象,所以它可以給所有實(shí)例進(jìn)行統(tǒng)一的初始化操作
- 由于new優(yōu)先于init調(diào)用咆繁,且返回一個(gè)實(shí)例讳推,所以我們可以利用這種特性,每次返回同一個(gè)實(shí)例來實(shí)現(xiàn)一個(gè)單例類:
__new__的作用:
class PositiveInteger(int):
def __init__(self, value):
super(PositiveInteger, self).__init__(self, abs(value))
i = PositiveInteger(-3)
print(i)
但運(yùn)行后會(huì)發(fā)現(xiàn)玩般,結(jié)果根本不是我們想的那樣娜遵,我們?nèi)匀坏玫搅?3。這是因?yàn)閷?duì)于int這種不可變的對(duì)象壤短,我們只有重載它的new方法才能起到自定義的作用设拟。
修改后的代碼如下:
class PositiveInteger(int):
def __new__(cls, value):
return super(PositiveInteger, cls).__new__(cls, abs(value))
i = PositiveInteger(-3)
print(i)
通過重載new方法,我們實(shí)現(xiàn)了需要的功能.
class g(float):
"""千克轉(zhuǎn)克"""
def __new__(cls, kg):
return float.__new__(cls, kg * 2)
# 50千克轉(zhuǎn)為克
a = g(50)
print(a) # 100.0
print(a + 100) # 200.0 由于繼承了float久脯,所以可以直接運(yùn)算纳胧,非常方便!
用new來實(shí)現(xiàn)單例
因?yàn)轭惷恳淮螌?shí)例化后產(chǎn)生的過程都是通過new來控制的帘撰,所以通過重載new方法跑慕,我們 可以很簡單的實(shí)現(xiàn)單例模式。
# 寫法一
class Singleton(object):
def __new__(cls):
# 關(guān)鍵在于這摧找,每一次實(shí)例化的時(shí)候核行,我們都只會(huì)返回這同一個(gè)instance對(duì)象
if not hasattr(cls, 'instance'):
cls.instance = super(Singleton, cls).__new__(cls)
return cls.instance
obj1 = Singleton()
obj2 = Singleton()
obj1.attr1 = 'value1'
print(obj1.attr1, obj2.attr1)
print(obj1 is obj2)
"""
>>>
value1 value1
True
"""
可以看到obj1和obj2是同一個(gè)實(shí)例。
# 寫法二
class Singleton(object):
"""單例"""
_instance = None
def __new__(cls, *args, **kwargs):
if not cls._instance:
cls._instance = super(Singleton, cls).__new__(cls, *args, **kwargs)
return cls._instance
class MySingleton(Singleton):
pass
a = MySingleton()
b = MySingleton()
print(a is b)
"""
>>>
True
"""
2. del析構(gòu)方法
這個(gè)方法代表析構(gòu)方法蹬耘,也就是在對(duì)象被垃圾回收時(shí)被調(diào)用芝雪。但是請(qǐng)注意,執(zhí)行del x不一定會(huì)執(zhí)行此方法综苔。
由于Python是通過引用計(jì)數(shù)來進(jìn)行垃圾回收的惩系,也就是說,如果這個(gè)實(shí)例還是有被引用到如筛,即使執(zhí)行del銷毀這個(gè)對(duì)象堡牡,但其引用計(jì)數(shù)還是大于0,所以不會(huì)觸發(fā)執(zhí)行del杨刨。
例子:
此時(shí)我們沒有對(duì)實(shí)例進(jìn)行任何操作時(shí)晤柄,del在程序退出后被調(diào)用。
class Person(object):
def __del__(self):
print('__del__')
a = Person()
print('exit')
"""
exit
__del__
"""
由于此實(shí)例沒有被其他對(duì)象所引用妖胀,當(dāng)我們手動(dòng)銷毀這個(gè)實(shí)例時(shí)芥颈,del被調(diào)用后程序正常退出惠勒。
class Person(object):
def __del__(self):
print('__del__')
a = Person()
b = a # b引用a
del a # 手動(dòng)銷毀,不觸發(fā)__del__
print('exit')
"""
exit
__del__
"""
此時(shí)實(shí)例有被其他對(duì)象引用,盡管我們手動(dòng)銷毀這個(gè)實(shí)例浇借,但依然不會(huì)觸發(fā)del方法,而是在程序正常退出后被調(diào)用執(zhí)行怕品。
為了保險(xiǎn)起見妇垢,當(dāng)我們?cè)趯?duì)文件、socket進(jìn)行操作時(shí)肉康,要想安全地關(guān)閉和銷毀這些對(duì)象闯估,最好是在try異常塊后的finally中進(jìn)行關(guān)閉和釋放操作!
3. 類的表示
str/repr
這兩個(gè)魔法方法一般會(huì)放到一起進(jìn)行講解吼和,它們的主要差別為:
str強(qiáng)調(diào)可讀性涨薪,而repr強(qiáng)調(diào)準(zhǔn)確性/標(biāo)準(zhǔn)性
str的目標(biāo)人群是用戶,而repr的目標(biāo)人群是機(jī)器炫乓,它的結(jié)果是可以被執(zhí)行的
%s調(diào)用str方法刚夺,而%r調(diào)用repr方法
來看幾個(gè)例子,了解內(nèi)置類實(shí)現(xiàn)這2個(gè)方法的效果:
>>> a = 'hello'
>>> str(a)
'hello'
>>> '%s' % a # 調(diào)用__str__
'hello'
>>>
>>> repr(a) # 對(duì)象a的標(biāo)準(zhǔn)表示末捣,也就是a是如何創(chuàng)建的
"'hello'"
>>> '%r' % a # 調(diào)用__repr__
"'hello'"
>>>
>>>
>>> import datetime
>>> b = datetime.datetime.now()
>>> str(b)
'2018-05-03 19:08:45.921879'
>>> print(b) # 等同于print str(b)
2018-05-03 19:08:45.921879
>>>
>>>
>>> repr(b) # 展示對(duì)象b的標(biāo)準(zhǔn)創(chuàng)建方式(如何創(chuàng)建的
'datetime.datetime(2018, 5, 3, 19, 8, 45, 921879)'
>>> b
datetime.datetime(2018, 5, 3, 19, 8, 45, 921879)
>>>
>>> c = eval(repr(b))
>>> c
datetime.datetime(2018, 5, 3, 19, 8, 45, 921879)
從上面的例子可以看出這兩個(gè)方法的主要區(qū)別侠姑,在實(shí)際中我們定義類時(shí),一般這樣定義即可:
class Person(object):
def __init__(self, name, age):
self.name = name
self.age = age
def __str__(self):
# 格式化箩做,友好對(duì)用戶展示
return 'name: %s, age: %s' % (self.name, self.age)
def __repr__(self):
# 標(biāo)準(zhǔn)化展示
return "Person('%s', %s)" % (self.name, self.age)
person = Person('zhangsan', 20)
print(str(person)) # name: zhangsan, age: 20
print('%s' % person) # name: zhangsan, age: 20
print(repr(person)) # Person('zhangsan', 20)
print('%r' % person) # Person('zhangsan', 20)
"""
name: zhangsan, age: 20
name: zhangsan, age: 20
Person('zhangsan', 20)
Person('zhangsan', 20)
"""
這里值得注意的是莽红,如果只定義了str或repr其中一個(gè),那會(huì)是什么結(jié)果邦邦?
如果只定義了str_安吁,那么repr(person)輸出<main.Person object at 0x10783b400>
如果只定義了repr,那么str(person)與repr(person)結(jié)果是相同的
也就是說燃辖,repr在表示類時(shí)鬼店,是一級(jí)的,如果只定義它黔龟,那么str = repr薪韩。
而str展示類時(shí)是次級(jí)的,用戶可自定義類的展示格式捌锭,如果沒有定義repr俘陷,那么repr(person)將會(huì)展示缺省的定義。
4. 對(duì)象判斷
hash/eq
hash方法返回一個(gè)整數(shù)观谦,用來表示該對(duì)象的唯一標(biāo)識(shí)拉盾,配合eq方法判斷兩個(gè)對(duì)象是否相等(==):
class Person(object):
def __init__(self, uid):
self.uid = uid
def __repr__(self):
return 'Person(%s)' % self.uid
def __hash__(self):
return self.uid
def __eq__(self, other):
return self.uid == other.uid
p1 = Person(1)
p2 = Person(1)
print(p1 == p2)
p3 = Person(2)
print(set([p1, p2, p3])) # 根據(jù)唯一標(biāo)識(shí)去重輸出 set([Person(1), Person(2)])
"""
True
{Person(1), Person(2)}
"""
如果我們需要判斷兩個(gè)對(duì)象是否相等,只要我們重寫hash和eq方法就可以完成此功能豁状。此外使用set存放這些對(duì)象時(shí)捉偏,會(huì)根據(jù)這兩個(gè)方法進(jìn)行去重操作倒得。
5. 對(duì)象布爾判斷
bool
當(dāng)調(diào)用bool(obj)時(shí),會(huì)調(diào)用bool方法夭禽,返回True/False霞掺。
class Person(object):
def __init__(self, uid):
self.uid = uid
def __bool__(self):
return self.uid > 10
p1 = Person(1)
p2 = Person(15)
print(bool(p1)) # False
print(bool(p2)) # True
"""
False
True
"""
??: 在Python3中,nonzero被重命名bool
6. 訪問控制
訪問控制相關(guān)的魔法方法讹躯,主要涉及以下幾個(gè):
setattr:通過.設(shè)置屬性或setattr(key, value)
getattr:訪問不存在的屬性
delattr:刪除某個(gè)屬性
getattribute:訪問任意屬性或方法
來看一個(gè)完整的例子:
class Person(object):
def __setattr__(self, key, value):
"""屬性賦值"""
if key not in ('name', 'age'):
return
if key == 'age' and value < 0:
raise ValueError()
super(Person, self).__setattr__(key, value)
def __getattr__(self, key):
"""訪問某個(gè)不存在的屬性"""
return 'unknown'
def __delattr__(self, key):
"""刪除某個(gè)屬性"""
if key == 'name':
raise AttributeError()
super(Person, self).__delattr__(key)
def __getattribute__(self, key):
"""所有屬性/方法調(diào)用都經(jīng)過這里"""
if key == 'money':
return 100
if key == 'hello':
return self.say
return super(Person, self).__getattribute__(key)
def say(self):
return 'hello'
p1 = Person()
p1.name = 'zhangsan' # 調(diào)用__setattr__
p1.age = 20 # 調(diào)用__setattr__
print(p1.name) # zhangsan
print(p1.age) # 20
setattr(p1, 'name', 'lisi') # 調(diào)用__setattr__
setattr(p1, 'age', 30) # 調(diào)用__setattr__
print(p1.name) # lisi
print(p1.age) # 30
p1.gender = 'male' # __setattr__中忽略對(duì)gender賦值
print(p1.gender) # gender不存在,調(diào)用__getattr__返回:unknown
print(p1.money) # money不存在,在__getattribute__中返回100
print(p1.say()) # hello
print(p1.hello()) # hello,調(diào)用__getattribute__菩彬,間接調(diào)用say方法
del p1.name # __delattr__中引發(fā)AttributeError
p2 = Person()
p2.age = -1 # __setattr__中引發(fā)ValueError
-
setattr
通過此方法,對(duì)象可在在對(duì)屬性進(jìn)行賦值時(shí)進(jìn)行控制潮梯,所有的屬性賦值都會(huì)經(jīng)過它骗灶。
一般常用于對(duì)某些屬性賦值的檢查校驗(yàn)邏輯,例如age不能小于0秉馏,否則認(rèn)為是非法數(shù)據(jù)等等耙旦。 -
getattr
很多同學(xué)以為此方法是和setattr完全對(duì)立的,其實(shí)不然萝究!
這個(gè)方法只有在訪問某個(gè)不存在的屬性時(shí)才會(huì)被調(diào)用免都,看上面的例子,由于gender屬性在賦值時(shí)帆竹,忽略了此字段的賦值操作琴昆,所以此屬性是沒有被成功賦值給對(duì)象的。當(dāng)訪問這個(gè)屬性時(shí)馆揉,getattr被調(diào)用业舍,返回unknown。 -
del
刪除對(duì)象的某個(gè)屬性時(shí)升酣,此方法被調(diào)用舷暮。一般常用于某個(gè)屬性必須存在,否則無法進(jìn)行后續(xù)的邏輯操作噩茄,會(huì)重寫此方法下面,對(duì)刪除屬性邏輯進(jìn)行檢查和校驗(yàn)。 -
getattribute
這個(gè)方法我們很少用到绩聘,它與getattr很容易混淆沥割。它與前者的區(qū)別在于:
getattr訪問某個(gè)不存在的屬性被調(diào)用,getattribute訪問任意屬性被調(diào)用
getattr只針對(duì)屬性訪問凿菩,getattribute不僅針對(duì)所有屬性訪問机杜,還包括方法調(diào)用
7. Python的類下面的item系列
xxxitem:使用 [''] 的方式操作屬性時(shí)被調(diào)用
setitem:每當(dāng)屬性被賦值的時(shí)候都會(huì)調(diào)用該方法,因此不能再該方法內(nèi)賦值 self.name = value 會(huì)死循環(huán)
getitem:當(dāng)訪問不存在的屬性時(shí)會(huì)調(diào)用該方法
delitem:當(dāng)刪除屬性時(shí)調(diào)用該方法
#! /usr/bin/env python
# -*- coding: utf-8 -*-
# __author__ = "shuke"
# Date: 2018/5/2
class A(object):
def __init__(self):
self['B'] = "BB"
self['D'] = "DD"
del self['B']
def __setitem__(self, name, value):
'''''
@summary: 每當(dāng)屬性被賦值的時(shí)候都會(huì)調(diào)用該方法衅谷,因此不能再該方法內(nèi)賦值 self.name = value 會(huì)死循環(huán)
'''
print("__setitem__:Set %s Value %s" % (name, value))
self.__dict__[name] = value
def __getitem__(self, name):
'''''
@summary: 當(dāng)訪問不存在的屬性時(shí)會(huì)調(diào)用該方法
'''
print("__getitem__:No attribute named '%s'" % name)
return 123
def __delitem__(self, name):
'''''
@summary: 當(dāng)刪除屬性時(shí)調(diào)用該方法
'''
print("__delitem__:Delect attribute '%s'" % name)
del self.__dict__[name]
print(self.__dict__)
if __name__ == "__main__":
X = A()
X['bb'] = "BB"
print(X.__dict__)
"""
>>>
__setitem__:Set B Value BB
__setitem__:Set D Value DD
__delitem__:Delect attribute 'B'
{'D': 'DD'}
__setitem__:Set bb Value BB
{'D': 'DD', 'bb': 'BB'}
"""
越是強(qiáng)大的魔法方法椒拗,責(zé)任越大,如果你不能正確使用它,最好還是不用為好蚀苛,否則在出現(xiàn)問題時(shí)很難排查!