new 和 init
创建一个类的实例对象的时候,程序先访问__new__方法,该方法返回一个这个类的实例,然后才会运行__init__初始化变量。通常情况下,我们继承自object的__new__方法,所以就不需要重新在子类中实现。
__new__的用处:
- 重构一些不可变方法,例如,int, str, tuple
- 实现单例模式(Singleton Pattern)
单例模式
参考
这种类型的设计模式属于创建型模式,它提供了一种创建对象的最佳方式。有时候我们需要严格的限制一个类只有一个实例存在,一个系统只有一个全局对象,这样有利于协调系统的整体行为。
class NewInt(object):
_singleton = None
def __new__(cls, *args, **kwargs):
if not cls._singleton:
cls._singleton = object.__new__(cls, *args, **kwargs)
return cls._singleton
new1 = NewInt()
new2 = NewInt()
print(new1)
print(new2)
# 输出
<__main__.NewInt object at 0x000002BB02FF6080>
<__main__.NewInt object at 0x000002BB02FF6080>
注意__new__返回的实例是由object.__new__创建的,不能调用cls.__new__这样会造成死循环。
如果__new__()返回其他类(新式类或经典类均可)的实例,那么只会调用被返回的那个类的构造方法。
我们一般创建一个类的对象的时候,其实是调用的这个类的父类,即继承object的new方法来创建一个对象。
如果私有变量__instance有值 我则直接返回刚开始创建的那个对象。如果没有创建,则调用父类new方法创建一个对象。
以后凡是继承了NewInt的子类都属于单例模式
装饰器
参考
简单来说就是操作函数的函数,通过某种方式来增强函数的功能。
def run_time(func):
def wrapper():
start = time()
func() # 函数在这里运行
end = time()
cost_time = end - start
print("func three run time {}".format(cost_time))
return wrapper
@run_time
def fun_one():
sleep(1)
@run_time
def fun_two():
sleep(1)
@run_time
def fun_three():
sleep(1)
通过闭包来实现装饰器,函数作为外层函数的传入参数,然后在内层函数中运行、附加功能,随后把内层函数作为结果返回。
带参数的装饰器
def logger(msg=None):
def run_time(func):
def wrapper(*args, **kwargs):
start = time()
func() # 函数在这里运行
end = time()
cost_time = end - start
print("[{}] func three run time {}".format(msg, cost_time))
return wrapper
return run_time
@logger(msg="One")
def fun_one():
sleep(1)
@logger(msg="Two")
def fun_two():
sleep(1)
@logger(msg="Three")
def fun_three():
sleep(1)
fun_one()
fun_two()
fun_three()
在示例基本用法里编写的装饰器外层又嵌套了一层函数用来接收参数msg,这样的话在每个函数(func_one、func_two、func_three)
前面调用时可以给装饰器传入参数,这样的输出结果是,
[One] func three run time 1.0013229846954346
[Two] func three run time 1.000720500946045
[Three] func three run time 1.0001459121704102
自定义属性的装饰器
上述介绍的几种用法中其实有一个问题,就是装饰器不够灵活,我们预先定义了装饰器run_time,它就会按照我们定义的流程去工作,只具备这固定的一种功能,当然,我们前面介绍的通过带参数的装饰器让它具备了一定的灵活性,但是依然不够灵活。其实,我们还可以对装饰器添加一些属性,就如同给一个类定义实现不同功能的方法那样。
以输出日志为例,初学Python的同学都习惯用print打印输出信息,其实这不是一个好习惯,当开发商业工程时,你很用意把一些信息暴露给用户。在开发过程中,我更加鼓励使用日志进行输出,通过定义WARNING、DEBUG、INFO等不同等级来控制信息的输出,比如INFO是可以给用户看到的,让用户直到当前程序跑到哪一个阶段了。DEBUG是用于开发人员调试和定位问题时使用。WARING是用于告警和提示。
那么问题来了,如果我们预先定义一个打印日志的装饰器,
def logger_info(func):
logmsg = func.__name__
def wrapper():
func()
log.log(logging.INFO, "{} if over.".format(logmsg))
return wrapper
logging.INFO
是打印日志的等级,如果我们仅仅写一个基本的日志装饰器logger_info
,那么它的灵活度太差了,因为如果我们要输出DEBUG、WARING
等级的日志,还需要重新写一个装饰器。
解决这个问题,有两个解决方法:
- 利用前面所讲的带参数装饰器,把日志等级传入装饰器
- 利用自定义属性来修改日志等级
由于第一种已经以统计函数运行时间的方式进行讲解,这里主要讲解第二种方法。
先看一下代码,
import logging
from functools import partial
def wrapper_property(obj, func=None):
if func is None:
return partial(attach_wrapper, obj)
setattr(obj, func.__name__, func)
return func
def logger_info(level, name=None, message=None):
def decorate(func):
logmsg = message if message else func.__name__
def wrapper(*args, **kwargs):
log.log(level, logmsg)
return func(*args, **kwargs)
@wrapper_property(wrapper)
def set_level(newlevel):
nonlocal level
level = newlevel
@wrapper_property(wrapper)
def set_message(newmsg):
nonlocal logmsg
logmsg = newmsg
return wrapper
return decorate
@logger_info(logging.WARNING)
def main(x, y):
return x + y
这里面最重要的是wrapper_property
这个函数,它的功能是把一个函数func编成一个对象obj的属性,然后通过调用wrapper_property
,给装饰器添加了两个属性set_message
和set_level
,分别用于改变输出日志的内容和改变输出日志的等级。