1. 程式人生 > >劍指offer:面試題2

劍指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)