劍指offer:面試題2
面試題2:實現Singleton模式
編譯器:python3.5.2
程式設計環境:pycharm2018.1.2x64
方法一、__new__方法來實現單例模式
用__new__方法實現的單例模式,比如下面的MyClass類,會對類的初始化有影響嗎?會對類的例項方法、類方法、靜態方法有影響嗎?下面會說下我對這些概念的理解,如有錯誤,歡迎交流指出,在此表示感謝。
__new__()是在新式類中新出現的方法,在Python2.7以前的版本在定義類時,都要顯示的繼承object才能使用。
object將__new__()方法定義為類的靜態方法,即使沒有被加上靜態方法裝飾器。並且至少需要傳遞一個位置引數cls,cls表示需要例項化的類,此引數在例項化時由Python直譯器自動提供。
__new__方法接受的引數雖然和__init__一樣,但__init__是在類例項建立之後呼叫,而__new__()方法是在類準備將自身例項化時呼叫, __new__方法正是建立這個類例項的方法。
先看下object類中對__new__()方法的定義:
class object: @staticmethod # known case of __new__ def __new__(cls, *more): # known special case of object.__new__ """ T.__new__(S, ...) -> a new object with type S, a subtype of T """ pass
object將__new__()方法定義為靜態方法。下面類中對__new__()方法的實現:
class juli(object): def __init__(self): print("__init__() called...") def __new__(cls, *args, **kwargs): print('__new__()') return object.__new__(cls, *args,**kwargs) ##注意這兩個引數 if __name__=='__main__': a=juli() #output: #__new__() #__init__() called...
發現例項化物件的時候,呼叫__init__()初始化之前,先呼叫了__new__()方法
__new__()必須要有返回值,返回例項化出來的例項,需要注意的是,可以return父類__new__()出來的例項,也可以直接將object的__new__()出來的例項返回。
__init__()有一個引數self,該self引數就是__new__()返回的例項,__init__()在__new__()的基礎上可以完成一些其它初始化的動作,__init__()不需要返回值。
若__new__()沒有正確返回當前類cls的例項,那__init__()將不會被呼叫,即使是父類的例項也不行。
我們可以將類比作製造商,__new__()方法就是前期的原材料購買環節,__init__()方法就是在有原材料的基礎上,加工,初始化商品環節。先看一段程式碼:
class doubleFloat(float):
def __new__(cls, *args, **kwargs):
return float.__new__(cls, *args,**kwargs)
def __init__(self, *args):
print('=======')
a = doubleFloat()
print(a)
b = doubleFloat(1.9)
print(b)
舉個例項來說明它的用途,比如說要定義一個Person類,在例項化一個物件時對初始化引數進行檢查,如果合法就建立例項,如果不合法就不建立例項返回。
class Person(object):
def __new__(cls, name, age):
if 0 < age < 150:
return object.__new__(cls)
# return super(Person, cls).__new__(cls)
else:
return None
def __init__(self, name, age):
self.name = name
self.age = age
def __str__(self):
return '{0}({1})'.format(self.__class__.__name__, self.__dict__)
print(Person('Tom', 10))
print(Person('Mike', 200))
#Person({'name': 'Tom', 'age': 10})
#None
通過上面的例子,總結: 在Python中__new__方法與__init__方法類似,但是如果兩個都存在那麼__new__先執行。
接下來總結一下__new__與__init__的異同點:
1,兩個功能相似,但是如果都存在__new__先執行;
2,__new__方法必須要返回一個例項化的物件;
3,__init__方法沒有返回值;
4,__new__有一個引數cls,__init__有一個引數self即為__new__返回的例項物件。
日常編寫Python程式碼的過程中,特別是Python新手,經常會遇到這樣的錯誤:
TypeError: object() takes no parameters
對於上面這個錯誤,很容易迷惑我們,因為這個錯誤資訊沒有很明確的指出,到底是哪段程式碼除了問題。那這個錯誤是怎麼產生的呢?
在python中,方法是一個屬性,也就是說,當我們呼叫一個方法時,python需要所屬方法名對應的屬性,比如說:
o.m()
python會現在物件o中搜索m屬性,如果物件o有m屬性(判斷物件o有沒有m屬性,可以用hasattr函式呼叫它.)
然而,python的方法是定義在一個class裡的,而不是object裡。也就是說如果m是o的方法,那就不可能是它的屬性。正常情況下,python會先搜尋物件的屬性,如果沒有,再去搜索類的屬性,如果屬性存在,則可以呼叫。(這地方可能大家會被類和物件兩個概念搞混,不太準確的來說,類就是class,物件就是例項,具體大家可以檢視文章笨辦法學Python)
在python中,大多數的類都繼承自object,在Python3中,如果你沒有指定繼承object,直譯器會自動給你加上,而Python如果你沒有指定,則為old-style class。大家在平時編寫類時,建議大家都最好加上繼承object,這樣一個是程式碼相容性號,一個是比較優雅。
這個錯誤是我在建立物件例項時報的錯誤,例如:
class Foo(object):
pass
如果我這樣:
f = Foo()
就不會有任何問題,但是如果我這樣:
f = Foo(10)
然後我就會得到上面的錯誤,這究竟是為什麼?
這是因為Python在建立物件時,分為兩個階段:第一個階段,物件是通過呼叫__new__方法來建立的,這個方法的細節我們基本上不用關心。__new__方法並不會立即返回一個物件例項,__new__方法之後,會呼叫__init__方法來給物件增加新的屬性。對於上面的物件o,呼叫的就是
o.__init__()
Python首先查詢o的__init__方法,但是沒找到,然後查詢父類的__init__方法,假設父類是上面的Foo,如果__init__方法依然不存在,所以最後會找到object的__init__屬性。object的__init__是存在的,並且是個方法,然後呼叫這個方法,傳入相應的引數,但是object.__init__方法沒有引數,然後我們就得到下面的錯誤。
TypeError: object() takes no parameters
整個流程下來,最讓人迷惑的地方是,Python沒有這樣報錯:
“object.__init__()” takes no parameters
於是我們沒法定為這個問題出在哪。
總結下來,在實現一個python的類時,最後寫上__init__方法,這樣就可以避免這樣的迷惑性的錯誤。
下面說下單例模式,單例模式是確保一個類只有一個例項,並且這個例項是自己創造的,在系統中用到的都是這個例項。單例模式是設計模式的一種,關於設計模式和單例模式更具體的內容,可以檢視相關的書本。
通過過載__new__實現單例(引例)
class SingleTon(object):
_instance = {}
def __new__(cls, *args, **kwargs):
if cls not in cls._instance:
cls._instance[cls] = super(SingleTon, cls).__new__(cls, *args, **kwargs)
print(cls._instance)
return cls._instance[cls]
class MyClass(SingleTon):
class_val = 22
該類中的__new__()方法的使用,就是再進行初始化之前,檢查快取中是否存在該物件,如果存在則將快取存放物件直接返回,如果不存在,則將物件放至快取中,供下次使用。
在Python中,__new__是用來創造一個類的例項的,而__init__是用來初始化這個例項的。既然__new__用來創造例項,也就需要最後返回相應類的例項,那麼如果返回的是其他類的例項,結果如何呢?見下面的程式碼。以下程式碼執行後,首先打印出NoReturn __new__然後打印出other instance,最後通過type(t)可以看到t的型別是<class '__main__.Other'>,可以知道如果__new__中不返回本類的例項的話,是沒法呼叫__init__方法的。想要返回本類的例項,只需要把以下程式碼中return Other()改成 return super(NoReturn, cls).__new__(cls, *args, **kwargs)即可。
# output:NoReturn __new__
# other instance
# <class '__main__.Other'>
class Other(object):
val = 111
def __init__(self):
print('other instance')
class NoReturn(object):
def __new__(cls, *args, **kwargs):
print('NoReturn __new__')
return Other()
def __init__(self, a):
print(a)
print('NoReturn __init__')
t = NoReturn(66)
print(type(t))
# 完善方法
# output:NoReturn __new__
# 66
# NoReturn __init__
# <class '__main__.NoReturn'>
class Other(object):
val = 111
def __init__(self):
print('other instance')
class NoReturn(object):
def __new__(cls, *args, **kwargs):
print('NoReturn __new__')
return super(NoReturn, cls).__new__(cls)
def __init__(self, a):
print(a)
print('NoReturn __init__')
t = NoReturn(66)
print(type(t))
# 進一步完善方法
class SingleTon(object):
_instance = {}
def __new__(cls, *args, **kwargs):
if cls not in cls._instance:
cls._instance[cls] = cls._instance[cls] = super(SingleTon, cls).__new__(cls)
print(cls._instance)
return cls._instance[cls]
class MyClass(SingleTon):
class_val = 22
def __init__(self, val):
self.val = val
def obj_fun(self):
print(self.val, 'obj_fun')
@staticmethod
def static_fun():
print('staticmethod')
@classmethod
def class_fun(cls):
print(cls.class_val, 'classmethod')
if __name__ == '__main__':
a = MyClass(1)
b = MyClass(2)
print(a is b) # True
print(id(a), id(b)) # 4367665424 4367665424
# 型別驗證
print(type(a)) # <class '__main__.MyClass'>
print(type(b)) # <class '__main__.MyClass'>
# 例項方法
a.obj_fun() # 2 obj_fun
b.obj_fun() # 2 obj_fun
# 類方法
MyClass.class_fun() # 22 classmethod
a.class_fun() # 22 classmethod
b.class_fun() # 22 classmethod
# 靜態方法
MyClass.static_fun() # staticmethod
a.static_fun() # staticmethod
b.static_fun() # staticmethod
# 類變數
a.class_val = 33
print(MyClass.class_val) # 22
print(a.class_val) # 33
print(b.class_val) # 33
# 例項變數
print(b.val) # 2
print(a.val) # 2
__new__方法來實現單例模式最終版
class SingleTon(object):
_instance = {}
def __new__(cls, *args, **kwargs):
if cls not in cls._instance:
cls._instance[cls] = super(SingleTon, cls).__new__(cls)
# cls._instance[cls] = super(SingleTon, cls).__new__(cls, *args, **kwargs)
# print cls._instance
return cls._instance[cls]
class MyClass(SingleTon):
class_val = 22
def __init__(self, val):
self.val = val
def obj_fun(self):
print(self.val, 'obj_fun')
@staticmethod
def static_fun():
print('staticmethod')
@classmethod
def class_fun(cls):
print(cls.class_val, 'classmethod')
if __name__ == '__main__':
a = MyClass(11)
b = MyClass(22)
print(a is b) # True
print(id(a), id(b)) # 4367665424 4367665424
# 型別驗證
print(type(a)) # <class '__main__.MyClass'>
print(type(b)) # <class '__main__.MyClass'>
最後來說用__new__方法實現的單例模式,會對例項方法,類方法,靜態方法,例項變數和類變數有影響嗎?答案是對相應的方法是沒有影響的,但是如果用不同的變數都初始化了這個例項,在後面的變數中修改例項變數和類變數的話,前面的也會相應修改,而這也正好符合單例,無論多少個變數指向了這個例項,他們指向的是同一個。在__new__中產生完例項後,每次初始化例項物件,都是產生的同一個例項,而這個例項中相關的方法、變數還是和普通的例項一樣使用。
方法二、 使用裝飾器實現單例模式
from functools import wraps
def single_ton(cls):
_instance = {}
@wraps(cls)
def single(*args, **kwargs):
if cls not in _instance:
_instance[cls] = cls(*args, **kwargs)
return _instance[cls]
return single
@single_ton
class SingleTon(object):
val = 123
def __init__(self, a):
self.a = a
if __name__ == '__main__':
s = SingleTon(1)
t = SingleTon(2)
print(s is t)
print(s.a, t.a)
print(s.val, t.val)