django中的惰性求值
前言
- 软件开发中遇到的所有问题,都可以通过增加一层抽象而得以解决。
- 代理模式,为其他对象提供一种代理以控制对这个对象的访问。
django
中的LazyObject
django.utils.functional.py
|
|
增加一个LazyObject
类封装需要延迟实例化的对象,通过改写__getattr__
、__setattr__
和__delattr__
进行代理和按需实例化。(代理模式ProxyPattern)。
django中的SimpleLazyObject
如LazyObject
中所说,当只是简单的代理而不需要改变封装的_wrapped
时,只需使用SimpleLazyObject
。 这个代理类需要一个func
参数,当需要实例化时调用。这个func参数可以是被封装的类、延迟求值的函数等,任何需要代理的对象。为了实际使用需要处理一些特殊方法,基本的实现框架如下:
|
|
真实的代理类
- 重写
_setup
,被封装的类为self._wrapped = Setting(settings_module)
- 通过提供
configure
接口,可以由用户修改内部被封装的配置类·
django.conf.settings(django.conf.init.py)
|
|
总结
1.通过增加一个代理类,封装了延迟求值(实例化)的对象。通过增加一个层解决问题。
2.再通过改写__setattr__
、__getattr__
和__delattr__
方法,将对代理类的操作,直接委托给被代理的类。代理模式。
python中object/type/metaclass
从object
关键词说起
官方文档上这样定义object
:
|
|
具有状态(属性和值)以及定义了行为(方法)的数据就是object。同时object是所有新式类的最根本的基类。利用object
这样定义(新式)类:
|
|
MyClass
即为静态定义(运行前就定义好了)的类,它继承于object
,也因此获得了object
的一些属性和方法,包括魔术方法。python中的类的最大特色在于其也是一个对象(实例)。每个类存在于内存中,是object
的子类,type
的实例。
实例通过__init__
函数进行初始化,一般将此作用的方法称为构造函数。但是python中的__init__
还不能完全称为构造函数。很明显,在__init__
运行前,实例就存在了,即传入的self
参数。实例这个对象在__init__
运行前就被解释器按照object
的内存模型分配好了内存,进行了一番装饰。
|
|
通过上面这个例子看到self
这个参数就是python解释器已经初步实例化了的实例,也可以看到在__init__
函数被调用前就已经存在了。因此python有另外一个魔术方法__new__
,可以真正的定制实例的内存模型(创建一个什么样的实例)。下面的例子更能说明这一点:
|
|
MyClass
与之前的定义没有区别。不过通过自定义的__new__
函数可以在实例存在之前介入,直接操纵实现什么样的实例,并且还能够给实例赋予一些属性和方法。MyInt
的定义就更加明显,虽然没有继承于int
,但是在__new__
函数中直接操纵其实例按照int
的模型构造,并且赋予了实例一个方法to_str
。结果MyInt
类就和直接继承int
没有明显的区别。python中的单例模式可以更好的说明:
|
|
可以看到,通过__new__
可以从一开始就定制如何实现实例(其内存模型)。
type的传说
python中一切皆对象。一切都是其基类的实例。
|
|
object
是新式类的根本基类;type
则是所有超类的根本基类,甚至是它自己的基类。MyClass
是type
的实例,当MyClass
被定义的时候(python解释器编译这段代码时)就是实例化的过程。可以理解为,python解释器定义了一个变量为MyClass
,它是type
的实例。而type
这种能够用来定义类的类则称为超类metaclass
。
前面说过,MyClass
是静态定义的类,在运行前就定义好了属性和方法。python提供了type
关键字用于动态定义类。按照python文档的说明,下面两种定义类的方式是等价的。
|
|
通过实例化type
,python提供了一种动态创建类的方式。type(name, bases, attrs),第一个name参数给定类的名字MyClass.__name__
属性,第二个参数bases给定继承的多个基类,第三个参数attrs确定MyClass
的一些属性和方法。由于type
能够定义类,那么所有继承type
的类,也就有了type
这种神奇的定义类的功能,从而成为超类。
普通类可以在定义时通过__new__
函数操纵生成的实例,所有继承type
的超类也可以通过__new__
来操纵生成的类(超类的实例)。
|
|
MetaClass
也具有了type
的能力。但是,这样通过超类动态实现MyClass
需要额外的手动实例化,非常的不协调。python提供了另一种方法,通过定义__metaclass__
属性,让解释器实现这一过程。
|
|
当类或者基类的属性中有__metclass__
时,会调用__metaclass__
进行类本身的生成,而不是默认的类生成过程。类自己的内存模型通过定义__metaclass__
可以动态定制,其实例可以通过定义__new__
函数实现定制。进行类定制的超类继承于type
,通过定义__new__
函数实现。
可以简单总结为:超类继承于type
,通过__new__
定制超类的实例,即普通类;普通类继承于object
,通过__new__
定制普通类的实例。
Python绝对导入
absolute_import 绝对导入
绝对导入,指从顶层用import
直接导入模块,即(只)从sys.path
中导入模块。 例如:
|
|
import
关键词的歧义?
之所以引入绝对导入
,是因为python中的import
关键词存在歧义。当import foo
被定义时,用户根本无法得知foo
这个模块是内置模块,第三方安装模块,还是用户当前包下的自定义模块。目前python2.7.x默认情况下是优先导入当前目录下的自定义模块,那么在有冲突的情况下,内置(第三方)模块foo
怎么导入?
如果相反,默认情况下导入的是内置或者第三方安装模块,那么本地的foo
模块可以通过间接导入
以from . import foo
方式导入,从而解决这个问题。
因此,绝对导入
的概念被引入。python2.7.x可以通过from __future__ import absolute_import
改变import
行为,强制import foo
导入的是sys.path
下的模块,并且本地模块(非sys.path
下的模块)必须通过相对导入
的方式进行导入。
举例说明
目录结构如下:
|
|
当前的文件package.subpackage1.moduleX.py
|
|
ValueError: Attempted relative import in non-package
在上面的例子中,当试图直接运行python moduleX.py
时会报如上错误。
当直接运行moduleX.py
时,此文件被视为一个顶层的脚本而不是包。 而在一个顶层脚本里是不能进行相对引用的。
可以在package层添加test.py
脚本进行测试,内容如下。
|
|
此时,package整体作为一个包,包内就可以进行相对导入
了。 最终目录结构如下:
|
|
参考
Python多重继承
关于多重继承
我比较赞成继承的本质是逐步细化,即逐步的补充原来抽象层次所欠缺的,不断补充具体的应用场景,最终达到应用层次。对于继承的原因,一般分为两种:第一种是对接口(界面约定)的实现,另一种是重载(override)。就像java语言的设计,对于接口(interface)的继承叫做实现
(implements),对于普通类的继承叫做扩展
(extends)。并且规定接口不能实例化,而多重继承必须是一个抽象类和多个接口类。这也是使用多重继承的一般性原则。
python中的新类和旧类
python中的继承比较麻烦。由于一开始的设计问题,python中分为新类和旧类。新类指所有继承了object对象的类,而旧类指所有没有继承object的类。
|
|
python旧类的方法继承顺序
对于旧类,多重继承中方法采用深度优先
算法。按顺序从一个父类一直深度搜索,然后再搜索第二个父类,一直到找到方法为止。
- 当只有一层继承时:
|
|
可以看到c.method
方法实际为 A.method
方法。
将 Child(A, B)
改变继承顺序为 Child(B, A)
再观察。
|
|
这时继承c.method
方法实际为B.method
方法。
按照继承参数列表中的顺序由前向后查找, C(A, B) => C -> A -> B
- 有多层继承时
|
|
可以看到, c.method
方法实际为 A.method
, 查找顺序为 C -> A -> AA
。
按照继承参数列表中的顺序由前向后,深度优先查找。
python旧类中的属性继承顺序
如果存在初始化函数,旧类只能通过显示调用父类的初始化函数。
|
|
类属性查找没有像类方法那样,采用深度优先顺序。我们知道,在python中属性是存放在 __dict__
中,而初始化就是给这个字典设置值。所以,后初始化中设置的属性值会冲掉先初始化设置的值。
因此,子类的属性由最后一个初始化的父类决定。
|
|
可见,初始化顺序决定了属性的初值。
python旧类继承的问题
- 祖类的多次初始化
python旧类有个很严重的问题, 就是共同祖类
的多次初始化。如果继承的多个父类他们有共同祖类(父类们有相同的父类),会导致祖类的多次初始化。
|
|
python新类的方法继承
为了解决祖类的多次初始化,以及属性与初始化顺序相关的问题(还有其他问题)。python设计了新类,并且用super
隐式进行父类的初始化。
但是新类有一个问题就是,任何多重继承的子类都有一个共同的祖类object
,都是菱形继承。
如果再按照深度优先,那么一些方法最终调用的都是object
的方法,而不是其他父类中定义的方法。
所以新类的方法是按照 klass.__mro__
中储存的类的顺序查找的。
|
|
可见是按照Child.__mro__
的顺序查找,而且object
永远是最后一个父类。
python新类的属性继承
在新类中,通过调用super
函数进行隐式的父类初始化,保持与方法继承同样的顺序,并且避免祖类的多次初始化。
|
|
不仅避免了祖类A的多次初始化,又保持与方法的查找都是按照 Child.__mro__
进行的。
新类的问题
问题一
super
函数不能兼容旧类。因此当继承旧类时不能使用super
初始化。
|
|
有两个办法解决这个问题。
办法一: 利用多重继承object
。
|
|
办法二: 将is-a
修改为has-a
,并且利用__getattr__
动态获取属性和方法。将继承修改为拥有关系。
|
|
问题二
当继承多个拥有初始化函数__init__
时,无法同时初始化。(强烈不建议这种情况下的多重继承)
|
|
按照Child.__mro
,直接调用 A.__init__
初始化函数,而不会调用B.__init__
初始化函数。
改变继承顺序Child(A, B)
->Child(B, A)
观察其变化。
|
|
此时初始化需要3个参数,可见调用的是 B.__init__
。
有一个办法解决这个问题。
像旧类那样,显式的分别初始化父类
|
|
python多重继承的一般性原则
1. 永远使用新类, 继承object; 不继承旧类。
2. 多重继承只在父类中只有一个有初始化函数的情况下。
简单来说,就是像java那样,只采用一个类为基类,其他为接口的情况下
Python Decorator 装饰器
何为装饰器?
装饰器(decorator)是一种语法糖,需要编程语言支持闭包和first-class函数闭包(提供局部变量支持,first-class函数允许将函数作为参数和任意地方创建函数)。装饰器简单来说就是装饰一个函数(对象),在调用之前或者之后附加点操作逻辑,类似下面的逻辑。
|
|
如果没有装饰器语法如何实现相同功能?
如果没有装饰器语法的支持,我们可以用现有的语法实现。例如:
|
|
上面的逻辑比较简单。以wrapped
为参数调用decorator
,返回一个wrapper
函数,再赋值给wrapped
名字。表面看wrapped
没有改变,其实际内容已经被替换成wrapper
这个闭包。有了装饰器语法糖的支持,我们可以简写成:
|
|
python解释器负责调用decorator(wrapped)
再赋值给wrapped
名字的过程。当然上面的例子比较简单,并没有做一些额外的工作,可以稍微修改一下。
|
|
函数如何使用装饰器?
上面的例子已经给出如何使用装饰器的方式,即在被装饰函数的前面加上@decorator
。
|
|
如何使用多装饰器?
python解释器负责从底向上调用装饰器赋值,因此可以写为
|
|
即按照装饰的顺序由下向上调用。类似func = decorator_2(decorator_1(decorator_0(func)))
如何给装饰器转入参数
从前面看,装饰器要求最后能够返回一个函数。这个函数接受唯一的参数func即可。因此实现装饰器传入参数有几种办法。第一种,多层嵌套,装饰器本身接受参数后,再返回一个装饰器即可。当然,为了利用转入装饰器的参数,需要把参数再继续传递下去。
|
|
第二种,利用类实现参数。
|
|
利用类实现实际上是手工编写了一个闭包,再利用__call__
魔术方法将类当做函数运行。
第三种,利用functools.partial 将嵌套扁平化。
|
|
partial叫做函数的柯西化,即将多参数分阶段传入,等待传入最后一个参数才运行的技术。
|
|
装饰器的优点?
可以不改变函数名称和参数的情况下实现额外的功能。因为可以在不改变原有逻辑和代码的情况下附加功能,可以实现相同前置/后置逻辑的抽象。
python本身利用装饰器语法实现了很多功能。例如: @classmethod 实现类方法,@staticmethod实现类的静态方法。
|
|
还有例如用 @property 装饰器将类方法转成类属性(如上所示)等。编程中可以将所有函数均要做的逻辑抽象为一个装饰器,以此避免代码的重复。例如django中的@login_required
函数用来在调用view之前先验证当前请求的用户是否登录。
装饰器的缺点?
装饰器也有缺点,例如上例中的wrapped.__name__
返回的并不是wrapped
这个名字而是wrapper
。这是因为wrapped
本身被替换成了一个wrapper
闭包,所以返回的是这个闭包的各种属性,可以通过wrapped.func_closure
等func_xx函数查看。这叫做装饰器的副作用(side affect)。因此如果原来调用wrapped的逻辑,需要用到这些属性(包括__doc__
,__name__
,__module__
等),那么这些逻辑就会出错。
如何解决装饰器的副作用?
明白了装饰器的副作用的原因,我们就可以手动解决一下。将闭包的属性赋值为被装饰的函数的属性即可。
|
|
在装饰器中直接将装饰器的属性改为被装饰的对象的属性即可。
仔细看可以见到这种技巧的本质,是在装饰器中的wrapper
函数之后干了点事情,当然可以写一个通用的装饰wrapper
的装饰器来代替手工赋值。最后实现的类似这样:
|
|
利用一个装饰器来完成wrapper.__name__ = func.__name__
的工作。因为需要func的属性值,因此这个装饰器需要传入func参数。
|
|
可以看见最终wraps
函数实现了functools.wraps
的功能。
当然wraps
有两点不够好:第一点是两层的_inner
和inner
看起来比较难于理解;第二点,如果被装饰对象是一个类,那么类的属性将丢失。对于第一点,通过利用functools.partial将多层嵌套扁平化。对于第二点比较好解决,将类的__dict__
属性同样赋值给闭包即可,因此增加updated参数实现要将哪些属性更新。
|
|
这个wraps
就是函数functools.wraps
的实现,与传统的写法的不同点是将内部的update_wrapper
写在了外面。wraps
函数返回了一个这样的函数update_wrapper
,这个函数的一些参数都已经给定,只剩下wrapper
等待传入。
另外需要说明的是,partial函数是由C语言低层实现,不会修改update_wrapper的各种属性。
python中的多态
何为多态?
按照松本行弘的说法,多态就是把不同种类的东西当做相同的东西来处理。多态(面向对象)的本质是消息传递。由编译器向实例传递不同的消息来使之进行不同的操作。消息体为传递的方法名、参数个数以及类型等。在没有多态的支持下编程类似这样:
|
|
如果存在多态,编程类似这样
|
|
由编译器来自动将参数arg分派给正确的方法进行调用。
python中的多态?
从上面的例子可以看到,要想支持多态需要python解释器能够区分不同类型的参数(参数的个数比较好容易区别)。
而动态语言要想做到这一点比较困难(如果做到这一点就不是动态语言了)。所以,python是不支持完整的多态的(可以利用语法部分实现多态的功能)。
如何实现类似多态的支持?
python支持duck typing和多参数,所以部分补偿了不支持多态的问题(按照python老爹的说法,多态不是程序员真正需要的)。当然,多态是语法糖果,即使没有多态也能完成程序。不过在这里只讨论实现类似多态的办法,即方法名相同,不同类型和参数个数的调用。
- 方法之一,使用
if...elif...else
和*args
|
|
即利用 *args
不定参数来实现 或者
|
|
显式进行类型判断再分派(不是好的办法)。
- 方法二, 使用设计模式。将对不同对象(类型)的操作隐藏在操作对象中。
|
|
将操作隐含在对象本身中。 因为类似策略模式
所以使用了Context来表明类名称。但是,这种方法不支持不定参数个数的多态。
总结
在python中使用多态不是一种好的编程方法。
Python-Threading-Module
threading.Lock()
|
|
总结
<table>
<tr>
<td> wait参数</td> <td> lock处在unlocked状态 </td> <td> lock处在locked状态 </td>
</tr><tr>
<td> 不设置或者 True </td><td> True </td> <td> 等待直到unlocked,返回True</td>
</tr><tr>
<td> False </td><td> True </td> <td> 立即返回 False </td>
</tr>
</table>
|
|
threading.RLock(verbose=None) #可重入锁(reentrant lock),
|
|
Lock和RLock的区别
- Lock能够由非持有锁的线程release,但是RLock不可以。
- Lock不能够重入(wait=True则死锁,wait=False则返回False), 但是RLock可以重入多次。
- Lock的acquire只返回 True 或者 False; Rlock重复获取锁时返回 1。
- 对一个unlocked状态的lock调用 release 引起thread.error异常; RLock 则引起RuntimeError异常。
threading.Condition([lock])
|
|
令人感兴趣的是,由于Lock与RLock的不同,那么传入一个Lock与使用一个RLock作为Condition的锁时, 其表现究竟如何。
threading.Semaphore(value=1)
|
|
threading.Event
|
|
Python惯用法和技巧等
字符串格式化
format格式化
支持位置和字典参数格式化。(不支持同时位置和字典参数形式)
|
|
%百分号格式化
支持类型、位置和字典参数格式化。
|
|
()括号的作用
|
|
iter函数
- iter(collections) -> iterator
- iter(callable, stop) -> iterator
返回一个迭代器,反复调用callabe函数直到其返回stop
|
|
类的私有方法
__method
以双下划线开头的方法
|
|
类的私有属性,不能通过实例修改。
类似全局变量和私有变量的关系
|
|
修改类的类变量有两种方式
第一种方式通过类直接修改
|
|
第二种方式通过实例的class 修改。
|
|
当通过实例为 类属性 赋新值时,实际上是创建了一个新的实例属性,不会修改类属性。类似在函数内直接为全局变量赋值,不能修改全局变量,而是创建同名的局部变量
|
|
当然,如果类属性是一个可变变量,例如 [], {} 可以调用对应的方法修改内部元素,而不会创建实例变量
|
|
类属性的最佳使用方式是,创建所有实例共享的变量,并且不会通过实例修改;或者通过 isntance.__class.var的方式显式的进行修改
|
|
函数参数在定义时实例化,可变参数被共享
|
|
- 参数numbers在定义时候实例化,并且默认绑定,等于每次传入相同的实例化对象。
- 更好的实践方式,定义默认的不可变参数,然后处理
|
|
list列表
list 支持+
;不支持-
操作符
|
|
输出格式化的文本的第x行的第y列
类似 grep ‘cpu0’ input_file |cut -d” “ -f2 命令, 选取cpu0行的第2个
|
|
__slots__
减少实例内存使用,
__slots__
会阻止class建立 __dict__
属性,即阻止实例动态的添加属性,从而减少创建 __dict__
的内存消耗。
|
|
一颗树结构
|
|
context manager上下文管理器
with
与as
关键词是自动上下文管理
的语法支持。
|
|
file
类文件对象均支持 Context Manager
。一个类实现 Context Manager的方法是利用 __enter__
和 __exit__
特殊方法。实例:
|
|
需要注意的点:
- a)如果在
__enter__
内发生异常,则程序不进入context manager机制,直接抛出异常。
|
|
- b)如果在block内发生异常,
__exit__
将捕获异常。如果不返回True,则异常向上层传递。如果返回True,则异常被忽略。
|
|
如何对函数进行 context manager
利用 contexlib
模块, try...finally...
和 yield
语法。
|
|
同时有多个对象需要context manager
例如复制一个文件内容到另一个文件。
|
|
descriptor描述符
描述符是将一个方法转为属性的便捷方式。descriptor类必须包含至少一个 __get__
, __set__
, __delete__
特殊方法。 __get__
描述一个属性取值时的表现; __set__
描述一个属性赋值时的表现, __delete__
描述一个属性被删除时的表现。
|
|
更常见的情况下,是将两个相互联系的属性通过descriptor连接起来。
|
|
或者作为装饰器修饰类的一个方法,改变其默认行为。
|
|
利用__getattr__
和setattr
缓存
|
|
第二次获取属性的时候没有通过 __getattr__
方法获取,直接获取的属性。
iter迭代器的妙用
iter迭代器中的每个元素只会被迭代一次,可以通过in
、next
消耗。
|
|
通过slice函数对迭代器进行组合
|
|
如果使用map函数,可以产生所有组合,不足的使用None补充
|
|
再结合s[start:] = s[start:None]
的特性就可以输出给定序列的片段。
|
|
flock超时函数(stackoverflow)
利用linux中IO堵塞模型中遇到信号返回EINTR,利用Python IO模型中不成功返回IOError
|
|
这段代码逻辑清楚,而且对获取锁、超时、其他异常的错误处理都很完美,并且体现了对linux和python都有很深的掌握