本文最后更新于:14 天前

单例模式

类每次实例化的时候都会创建一个新的对象,如果要求类只能被实例化一次该怎么做呢?

class Earth:

    def __init__(self):
        self.name = 'earth'


e = Earth()
print(e, id(e))
a = Earth()
print(a, id(a))

按照我们上一节讲的,类可以多个实例化

<__main__.Earth object at 0x000001D54B28A978> 2015600617848
<__main__.Earth object at 0x000001D54B293EF0> 2015600656112

我们可以看出,多个实例化,每个实例化的地址都不相同。

class Earth:

    def __new__(cls, *args, **kwargs):
        if not hasattr(cls, 'instance'):
            cls.instance = super().__new__(cls)
        return cls.instance

    def __init__(self):
        self.name = 'earth'


e = Earth()
print(e, id(e))
a = Earth()
print(a, id(a))

类的实例化的时候,会在init前调用new方法。

<__main__.Earth object at 0x0000024EF2DBA940> 2538105186624
<__main__.Earth object at 0x0000024EF2DBA940> 2538105186624

可以看出两次创建对象,结果返回的是同一个对象实例

变量共享

class Earth:

    def __new__(cls, *args, **kwargs):
        if not hasattr(cls, 'instance'):
            cls.instance = super().__new__(cls)
        return cls.instance

    def __init__(self, name):
        self.name = name


e = Earth('china')
print(e, id(e))
a = Earth('others')
print(a, id(a))


print(e.name)
print(a.name)

输出

<__main__.Earth object at 0x000002077AA33E80> 2231145545344
<__main__.Earth object at 0x000002077AA33E80> 2231145545344
others
others

应用

  • Python的logger就是一个单例模式,用以日志记录
  • Windows的资源管理器是一个单例模式
  • 线程池,数据库连接池等资源池一般也用单例模式
  • 网站计数器

使用情况

当每个实例都会占用资源,而且实例初始化会影响性能,这个时候就可以考虑使用单例模式,它给我们带来的好处是只有一个实例占用资源,并且只需初始化一次;

当有同步需要的时候,可以通过一个实例来进行同步控制,比如对某个共享文件(如日志文件)的控制,对计数器的同步控制等,这种情况下由于只有一个实例,所以不用担心同步问题。

总结

初始化函数之前:new方法会在初始化函数init方法之前执行。

单例模式:利用这个new方法可以很方便的实现类的单例模式。

合理利用:new 方法合理利用可以带来方便,常应用在类的单例模式。

定制属性访问

如何判断一个实例里面有某个属性呢?

怎样删除实例属性呢?

同样的怎样删除变量呢?

class Rectangle:

    # 传入长和宽
    def __init__(self, length, width):
        self.length = length
        self.width = width

    def area(self):
        area = self.width * self.length
        return area

b = Rectangle(3,4)

接下来我们来对类的属性进行定制化

增加属性

setattr
setattr(b, 's', 12)
b.s
Out[4]: 12
setattr(b, 'h', 6)
b.h
Out[6]: 6

往类的属性里面添加方法并赋值。

b.__setattr__('s', 5)
b.__setattr__('h', 15)
b.s
Out[12]: 5
b.h
Out[13]: 15

等价于类的对应魔术方法

删除属性

delattr
delattr(b, 's')
delattr(b, 'h')

删除属性

b.__delattr__('s')
b.__delattr__('h')

等价于类的对应魔术方法

修改属性

setattr
b.s
Out[5]: 5
b.h
Out[6]: 15
setattr(b, 's', 20)
setattr(b, 'h', 20)
b.s
Out[9]: 20
b.h
Out[10]: 20

同样是使用setattr来修改属性

b.__setattr__('s', 20)
b.__setattr__('h', 20)
b.s
Out[12]: 20
b.h
Out[13]: 20

等价于类的对应魔术方法

查找属性

hasattr
hasattr(b, 's')
Out[11]: True
hasattr(b, 'h')
Out[12]: True
hasattr(b, 'x')
Out[13]: False

有对应属性就返回True,否则就返回Flase

getattr
getattr(b, 's')
Out[14]: 20
getattr(b, 'h')
Out[15]: 20
getattr(b, 'x')
Traceback (most recent call last):
  File "C:\Program Files\Python36\lib\site-packages\IPython\core\interactiveshell.py", line 3265, in run_code
    exec(code_obj, self.user_global_ns, self.user_ns)
  File "<ipython-input-16-ae1b4378a11a>", line 1, in <module>
    getattr(b, 'x')
AttributeError: 'Rectangle' object has no attribute 'x'

有就返回属性值,没有就报错。

b.__getattribute__('s')
Out[17]: 20
b.__getattribute__('x')
Traceback (most recent call last):
  File "C:\Program Files\Python36\lib\site-packages\IPython\core\interactiveshell.py", line 3265, in run_code
    exec(code_obj, self.user_global_ns, self.user_ns)
  File "<ipython-input-18-f65163239ef2>", line 1, in <module>
    b.__getattribute__('x')
AttributeError: 'Rectangle' object has no attribute 'x'

等价于类的对应魔术方法

扩展

我们在查询属性的时候,使用getattr,如果没有属性值,又不想报错怎么办呢?

class Rectangle:

    # 传入长和宽
    def __init__(self, length, width):
        self.length = length
        self.width = width

    def area(self):
        area = self.width * self.length
        return area

    def __getattribute__(self, item):
        print("没有这个属性!")

b = Rectangle(3,4)
getattr(b, 's')
没有这个属性!

当属性不存在时,如果定义了此方法,则调用方法

总结

属性访问

hasattr: 判断是否存在属性,如果属性存在则进行下一步操作。

getattr: 得到属性值。

setattr:设置属性。

描述符

如果在一个类中实例化另一个类,对这个属性进行访问的时候怎么做的?

class MyAtrribute:
    pass

class MyClass:
    m = MyAtrribute()

c = MyClass() 

c.m
<__main__.MyAtrribute object at 0x000001F4922CF2E8>

返回的是对象

class MyAtrribute:
    def __get__(self, instance, owner):
        print('get')
        print(instance)
        print(owner)

class MyClass:
    m = MyAtrribute()

c = MyClass()

print(c.m)
get
<__main__.MyClass object at 0x00000258C5703E80>
<class '__main__.MyClass'>
None

直接访问时,调用get方法

class MyAtrribute:
    def __get__(self, instance, owner):
        print('get')
        print(instance)
        print(owner)

    def __set__(self, instance, value):
        print(instance)
        print(value)
        print('set')

    def __delete__(self, instance):
        print(instance)
        print('delete')


class MyClass:
    m = MyAtrribute()

    def __del__(self):
        print('__del__')


c = MyClass()

# 调用 __set__
c.m = 1

# 调用 __deletel__
del c.m
delattr(c, 'm')
<__main__.MyClass object at 0x000002312DAEBF28>
1
set
<__main__.MyClass object at 0x000002312DAEBF28>
delete
<__main__.MyClass object at 0x000002312DAEBF28>
delete
__del__

根据访问时带使用不同的方式,调用不用的属性。

总结

描述符大家了解即可

魔术方法的作用其实是让开发人员能够更加灵活的控制类的表现形式

装饰器

之前我们讲了闭包,闭包中可以传入一个函数吗?

def fx(x):
    x += 1

    def fy(y):
        return x + y

    return fy


a = fx(1)
print(a(12))
12

这是我们前面所见过的闭包

def f1(func):
    def f2(y):
        print('f2 running')
        return func(y) + 1
    return f2

def f3(m):
    print('f3 running')
    return m * m

a = f1(f3)
print(a)
print(a(3))
<function f1.<locals>.f2 at 0x0000026D2F4BA488>
f2 running
f3 running
10

闭包传入函数

语法糖

def f1(func):
    def f2(y):
        print('f2 running')
        return func(y) + 1

    return f2


@f1  # 语法糖
def f3(m):
    print('f3 running')
    return m * m


print(f3(3))  # f3 = f1(f3)(3)
f2 running
f3 running
10

在Python中直接用语法糖,f3(3) = f1(f3)(3)

内置装饰器

@property
class Rectangle:

    # 传入长和宽
    def __init__(self, length, width):
        self.length = length
        self.width = width

    @property
    def area(self):
        area = self.width * self.length
        return area

    def area1(self):
        area = self.width * self.length
        return area

    def __getattr__(self, item):
        print('no attribute')

b = Rectangle(3,4)
print(b.area1())
print(b.area)
12
12

访问函数时,就像访问属性一样

@staticmethod
class Rectangle:

    # 传入长和宽
    def __init__(self, length, width):
        self.length = length
        self.width = width

    @property
    def area(self):
        area = self.width * self.length
        return area

    @staticmethod
    def func():
        print('func')

    def __getattr__(self, item):
        print('no attribute')

b = Rectangle(3,4)

Rectangle.func()
b.func()
func
func

静态方法

@classmethod
class Rectangle:

    # 传入长和宽
    def __init__(self, length, width):
        self.length = length
        self.width = width

    @property
    def area(self):
        area = self.width * self.length
        return area

    @staticmethod
    def func():
        print('func')

    @classmethod
    def show(cls):
        print(cls)
        print('show')

    def fun2(self):
        print(self)
        print('fun2')

    def __getattr__(self, item):
        print('no attribute')

b = Rectangle(3,4)
b.show()
b.fun2()
Rectangle.show()
Rectangle.fun2(b)
<class '__main__.Rectangle'>
show
<__main__.Rectangle object at 0x000001897E3519B0>
fun2
<class '__main__.Rectangle'>
show
<__main__.Rectangle object at 0x000001897E3519B0>
fun2

类方法:cls代表类本身,如果加上self,在调用时就要把实例传入。

类装饰器

class Test_Class:
    def __init__(self, func):
        self.func = func

    def __call__(self):
        print('类')
        print(self.func())
        return self.func


@Test_Class
def fun_test():
    print('这是个测试函数')
类
这是个测试函数
None
<function fun_test at 0x000001E024B28730>

类也可以做装饰器,但是需要定义call 方法

扩展

查看函数运行时间的装饰器

import time


def run_time(func):
    def new_fun(*args,**kwargs):
        t0 = time.time()
        print('star time: %s'%(time.strftime('%x',time.localtime())) )
        back = func(*args,**kwargs)
        print('end time: %s'%(time.strftime('%x',time.localtime())) )
        print('run time: %s'%(time.time() - t0))
        return back
    return new_fun


@run_time
def demo():
    print('1213')

demo()
star time: 12/19/18
1213
end time: 12/19/18
run time: 0.0

总结

装饰器本质是闭包,在不影响原函数使用的情况下,增加原函数功能。

内置装饰器:三个内置装饰器是需要掌握的,在项目中会经常使用。