剑指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的类型是,可以知道如果__new__中不返回本类的实例的话,是没法调用__init__方法的。想要返回本类的实例,只需要把以下代码中return Other()改成 return super(NoReturn, cls).__new__(cls, *args, **kwargs)即可。

# output:NoReturn __new__
#        other instance
#        

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 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))  # 
    print(type(b)) # 
    # 实例方法
    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)) # 
    print(type(b)) # 

最后来说用__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)

 

你可能感兴趣的:(Python剑指offer)