本文目的是由浅入深地介绍python装饰器原理
装饰器(Decorators)是 Python 的一个重要部分
其功能是, 在不修改原函数(类)定义代码的情况下,增加新的功能
在这个例子中,函数hi的形参name,默认为'world'
所以呢,在函数内部调用howdoyoudo()时,将以调用hi时的实参为默认值,但也可以给howdoyoudo输入其他参数.
上面的例子运行后输出结果为:
这里新定义的howdoyoudo可以称作一个"闭包".不少关于装饰器的blog都提到了这个概念,但其实没必要给它取一个多专业的名字.我们知道闭包是 函数内的函数 就可以了
当我们进行 def 的时候,我们在做什么?
这时,hi函数,打印一个字符串,同时返回一个字符串.
但hi函数本身也是一个对象,一个可以执行的对象.执行的方式是hi().
这里hi和hi()有本质区别,
hi 代表了这个函数对象本身
hi() 则是运行了函数,得到函数的返回值.
作为对比,可以想象以下代码
此时也是b存在,可以正常使用.
在调用double_exec时,可以将函数作为输入传进来
输出结果就是
同样,也可以将函数作为输出
输出结果为
①.0
有了以上两个核心操作,我们可以尝试构造装饰器了.
装饰器的目的: 在不修改原函数(类)定义代码的情况下,增加新的功能
试想一下,现在有一个原函数
在不修改原函数定义代码的情况下,如果想进行函数内容的添加,可以将这个函数作为一个整体,添加到这样的包裹中:
我们定义了一个my_decorator函数,这个函数进行了一种操作:
对传入的f,添加操作(运行前后增加打印),并把添加操作后的内容连同运行原函数的内容,一起传出
这个my_decorator,定义了一种增加前后打印内容的行为
调用my_decorator时,对这个行为进行了操作.
所以呢,new_function是一个在original_function上增加了前后打印行为的新函数
这个过程被可以被称作装饰.
这里已经可以发现,装饰器本身对于被装饰的函数是什么,是不需要考虑的.装饰器本身只定义了一种装饰行为,这个行为是通过装饰器内部的闭包函数()进行定义的.
运行装饰前后的函数,可以清晰看到装饰的效果
我们复现一下实际要用装饰器的情况,我们往往有一种装饰器,想应用于很多个函数,比如
实际调用的时候,就需要调用添加装饰器的函数名了
当然,也可以赋值给原函数名
这样至少不需要管理一系列装饰前后的函数.
同时,在不需要进行装饰的时候,需要把
全部删掉.
事实上,这样并不方便,尤其对于更复杂的装饰器来说
为此,python提供了一种简写方式
这个定义print1函数前的@my_decorator,相当于在定义完print1后,自动直接运行了
不论采用@my_decorator放在新函数前,还是显示地重写print1 = my_decorator(print1),都会存在一个问题:
装饰后的函数,名字改变了(其实不止名字,一系列的索引都改变了)
输出结果为:
这个现象的原因是,装饰行为本身,是通过构造了一个新的函数(例子中是wrap_func函数)来实现装饰这个行为的,然后把这个修改后的函数赋给了原函数名.
这样,会导致我们预期的被装饰函数的一些系统变量(比如__name__)发生了变化.
对此,python提供了解决方案:
经过这个行为后,被装饰函数的系统变量问题被解决了
刚才的例子都比较简单,被装饰的函数是没有参数的.如果被装饰的函数有参数,只需要在定义装饰行为时(事实上,这个才更通用),增加(*args, **kwargs)描述即可
之前的描述中可以感受到,对于例子中的装饰行为(前后加打印),函数被装饰后,本质上是调用了新的装饰函数wrap_func.
所以呢,如果原函数需要有输入参数传递,只需要在wrap_func(或其他任意名字的装饰函数)定义时,也增加参数输入(*args, **kwargs),并将这些参数,原封不动地传给待装饰函数f.
这种定义装饰行为的方式更具有普遍性,忘记之前的定义方式吧
我们试一下
输出
这里需要注意的是,如果按照以下的方式定义装饰器
那么以下语句将不会执行
因为装饰后实际的函数wrap_func(虽然名字被改成了原函数,系统参数也改成了原函数),运行到return f(*args, **kwargs) 的时候已经结束了
因为装饰器my_decorator本身也是可以输入的,所以呢,只需要在定义装饰器时,增加参数,并在后续函数中使用就可以了,比如
此时装饰器已经可以有输入参数了
你可能发现,为什么不用简写版的方法了
因为以上代码会报错!!
究其原因,虽然
等价于
但是,
并不等价于
这本身和@语法有关,使用@my_decorator时,是系统在应用一个以单个函数作为参数的闭包函数.即,@是不能带参数的.
但是你应该发现了,之前的@wraps(f)不是带参数了吗?请仔细观察以下代码
通过一层嵌套,my_decorator_with_parma本质上是返回了一个参数仅为一个函数的函数(my_decorator),但因为my_decorator对my_decorator_with_parma来说是一个闭包,my_decorator_with_parma是可以带参数的.(这句话真绕)
通过以上的定义,我们再来看
可以这么理解,my_decorator_with_parma(msg='yusheng')的结果是原来的my_decorator函数,同时,因为my_decorator_with_parma可以传参,参数实际上是参与了my_decorator的(因为my_decorator对my_decorator_with_parma是闭包), my_decorator_with_parma(msg='yusheng') 全等于 一个有参数参加的my_decorator
所以呢,以上代码等价于有参数msg传递的
比较绕,需要理解一下,或者干脆强记这种范式:
以上范式包含函数的输入输出、装饰器的输入,可以应对大部分情况了.
实验一下:
好了,全部的一个log装饰器,利用datetime统计了函数的耗时,
并且,装饰器可以进行输出文件操作,如果给出了文件路径,则输出文件,否则就打印.
利用这个装饰器,可以灵活地进行耗时统计
不设置输出文件地址,则打印.运行结果为:
也可以输出到文件
同时在当前目录生成了一个test.log 文件,内容为:
以上的装饰器都是以函数形式出现的,但我们可以稍做改写,将装饰器以类的形式实现.
这个装饰器类Log 上个例子里的装饰器函数log功能是一样的,同时,这个装饰器类还可以作为基类被其他继承,进一步增加功能.
原文
单例模式
$_instance必须声明为静态的私有变量
构造函数和析构函数必须声明为私有,防止外部程序new 类从而失去单例模式的意义
getInstance()方法必须设置为公有的,必须调用此方法 以返回实例的一个引用
::操作符只能访问静态变量和静态函数
new对象都会消耗内存
使用场景:最常用的地方是数据库连接.
使用单例模式生成一个对象后, 该对象可以被其它众多对象所使用.
私有的__clone()方法防止克隆对象
单例模式,使某个类的对象仅允许创建一个.构造函数private修饰,?
申明一个static getInstance方法,在该方法里创建该对象的实例.如果该实例已经存在,则不创建.比如只需要创建一个数据库连接.
工厂模式
工厂模式,工厂方法或者类生成对象,而不是在代码中直接new.?
使用工厂模式,可以避免当改变某个类的名字或者方法之后,在调用这个类的所有的代码中都修改它的名字或者参数.
* 如果某个类在很多的文件中都new ClassName(),那么万一这个类的名字
* 发生变更或者参数发生变化,如果不使用工厂模式,就需要修改每一个PHP
* 代码,使用了工厂模式之后,只需要修改工厂类或者方法就可以了.
注册模式
注册模式,解决全局共享和交换对象.已经创建好的对象,挂在到某个全局可以使用的数组上,在需要使用的时候,直接从该数组上获取即可.将对象注册到全局的树上.任何地方直接去访问.
策略模式
策略模式,将一组特定的行为和算法封装成类,以适应某些特定的上下文环境.?
eg:假如有一个电商网站系统,针对男性女性用户要各自跳转到不同的商品类目,并且所有的广告位展示不同的广告.在传统的代码中,都是在系统中加入各种if else的判断,硬编码的方式.如果有一天增加了一种用户,就需要改写代码.使用策略模式,如果新增加一种用户类型,只需要增加一种策略就可以.其他所有的地方只需要使用不同的策略就可以.?
首先声明策略的接口文件,约定了策略的包含的行为.然后,定义各个具体的策略实现类.
执行结果图:?
总结:?
通过以上方式,可以发现,在不同用户登录时显示不同的内容,但是解决了在显示时的硬编码的问题.如果要增加一种策略,只需要增加一种策略实现类,然后在入口文件中执行判断,传入这个类即可.实现了解耦.?
实现依赖倒置和控制反转?(有待理解)?
通过接口的方式,使得类和类之间不直接依赖.在使用该类的时候,才动态的传入该接口的一个实现类.如果要替换某个类,只需要提供一个实现了该接口的实现类,通过修改一行代码即可完成替换.
观察者模式
①.:观察者模式(Observer),当一个对象状态发生变化时,依赖它的对象全部会收到通知,并自动更新.?
定义一个事件触发抽象类.
当某个事件发生后,需要执行的逻辑增多时,可以以松耦合的方式去增删逻辑.也就是代码中的红色部分,只需要定义一个实现了观察者接口的类,实现复杂的逻辑,然后在红色的部分加上一行代码即可.这样实现了低耦合.
装饰器模式
①.:装饰器模式,可以动态的添加修改类的功能?
①可以用地址传值的方式获得你的链接的值,也可以直接弄一个静态的
例:
php
$q=mysql_query("select * from news");
while($rs=mysql_fetch_array($q)){ //$rs是一个数组
......
}