Blog My Minds


  • 首页

  • 分类

  • 归档

django模板系统-源码分析-1

发表于 2014-11-04   |   分类于 python   |  
## django模板简单实例 ---------------------- 简单的hellowold 示例。 undefined **- Template: 模板类,通过render方法求值** **- Context:环境类,保存上下文环境,变量到值的映射** **- variable: 通过{{ }}引用变量** ## django.template.Lexer --------------------------- django模板系统就是实现了一个小型的解释型语言,其中Template是编译器实例,Context是环境变量。源码中的其他几个类的作用。 **- Lexer: 词法分析类,将模板原始字符串转换成token** **- Parser: 语法分析类,将给定的token转换成node,并生成抽象语法树** django模板系统中的Lexer比较简单,并不是完全的将所有原始字符串都转成token,它只负责处理4种标识转成4种token。至于每个token内字符串的解析稍后分析,Lexer解析成的4种token分别是: **- TOKEN_TEXT = 0 文本类型,模板里面的字面字符串** **- TOKEN_VAR = 1 变量类型, 用{{ }}包围的字符串** **- TOKEN_BLOCK = 2 块类型,以{% %\}包围的字符串** **- TOKEN_COMMENT = 3 注释类型,以\{\# \#\}包围的字符串** Lexer会将原始字符串通过正则表达式解析为token,但不包括标识。如下所示。 undefined 简单匹配每个标识的开头和结尾。通过这里可以看到,如果一个语言语法中有块的结尾标识,解析起来是比较方便的。 undefined 因为Token类默认只有`__str__`方法,这里用`map`方法调用以显示token内的字符串。目前的调用路径为`Tempalte -> Lexer() => lexer -> lexer.tokensize => tokens`(->代表调用,=>代表返回值)。 ## django.base.Parser -------------------------- django模板的Parser负责解析Lexer生成的tokens生成语法树。Parser类在初始化函数中首先做的是加载内置库builtins,将django自带的一些tag极其filter加载进来,类似于初始化运行时环境。另外全局变量builtins在import时完成。具体如下 undefined Parser的逻辑分为几个部分: 第一部分,Parser将给定的tokens解析为对应的语法node。node大概分为3种: **- TextNode: 存储text,模板原始字符的字面值,字符串** **- VariableNode: 存储作为变量的字符,也可能是一个类似a.b.c的表达式** **- complied_result: block node中表达式的编译结果** #### TextNode TextNode比较简单,就是存储字面值,并且在render调用的时候返回该字面值。 ### VariableNode VariableNode比较复杂,本身存储的是一个叫做`FilterExpression`的实例,这个实例中存储的才是TOKEN\_VAR类型的token的内容(字符串)。简单的存储(依赖)关系是`token.contents -> FilterExpression -> VariableNode`。而这个`FilterExpression`就是处理VariableNode中的字符串,通过resolve求值。调用关系`Parser.parse -> Parser.compile_filter(token.contents) => filter_expression -> VariableNode(filter_expression) -> VariableNode.render -> filter_expression.resolve`。 ### complied_result complied\_result更为复杂,本身是一个叫做`complied_func(self,token)`的结果,其中`self`是Parser实例本身,token是TOKEN\_BLOCK类型的token。简单的调用关系`parser.parse -> token.contents.split()[0] => command -> parser.tags[command] => compiled_func => compiled_result`。BLOCK\_TOKEN存储的字符串的第一个单词(例如 if,with)作为key查找实际的处理函数complied\_func,调用compiled\_func会返回一个compiled\_result。这里可以猜到compiled\_result实际也是一个类node实例,通过resolve求值。 **每个node实现resolve求值方法,也就是具体BLOCK的语法逻辑。`template.render -> nodelist.render -> 遍历for node in nodelist --> node.render --> filter_expression.resolve(用户定义的方法,例如do_if) => string -> join(strings)返回字符串` ** 第二部分, Parser创建一个NodeList实例。nodelist作为语法树的载体,Parser.crate\_nodelist -> NodeList()。 ### NodeList nodelist继承于list存储同一个语法域(作用域?)的node。`render`方法会遍历当前的node,调用node.render返回一个完整的求值后的字符串。调用关系`nodelist.render -> for node in nodelist --> node.render => string -> 组合成一个字符串`。 ## django.template.Template ---------------------------------------- Template是整个django模板系统的入口函数,等同于一个解释器类interpreter。Template通过调用compile\_string返回nodelist语法树,通过render方法求值。调用关系`Template -> compile_string -> lexer => tokens -> parser => nodelist;Tempaltee.render -> nodelist.render最终求值`。 ## django.context.Context ---------------------------------------- Context是堆栈式的环境类,存储全局环境和上下文环境。Context内的dicts变量按照顺序存储每个作用域的环境变量的集合,这个集合是ContextDict的实例。Context.dicts的第一个元素dicts[0]就是一些内置变量集合{'True': True, 'False': False, 'None': None}。 **put Context的put方法存入当前ContextDict的实例,该实例存储了当前作用域定义的变量值。如 {% with 1 as a %};** **pop Context的pop方法弹出最深作用域中变量值集合的ContextDict的实例;** **get Context的get方法获取给定变量的值。** undefined ### 几个不同Context类的关系和区别 BaseContext是几个类的基类,几个类的继承关系如下。 undefined Context类中的render\_context属性为RenderContext的实例,该属性会作为解析模板的上下文环境实例。增加render\_context作为模板中设置的变量的上下文环境,以免出现冲突。也就是说,Context.dicts保存django设置的模板环境变量,Context.render\_context保存模板中用户设置的变量。 RenderContext类中的`get`方法不会遍历dicts属性中的所有context实例,只会查找当前作用域(dicts中最后一个上下文环境实例)。 RequestContext类继承Context类,增加一个request参数,会调用所有的processors对request进行处理。 ## 模板中变量(TOKEN_VAR)的求值 ----------------------- TOKEN\_TEXT的求值方法比较简单,直接返回字面值就好。接下来探讨下模板中变量的求值,即以双大括号包围的变量的求值。 django模板中双大括号包围的字符首先经Lexer转为TOKEN\_VAR类型的token,然后传入Parser解析为VariableNode,然后通过调用其resolve方法求值。简单的求值顺序`variable|default:"Default value"|date:"Y-m-d"' -> Lexer => Token(TOKEN_VAR) -> Parser => VariableNode => VariableNode.resolve`求值。下面是源码中的示例。 undefined 前面说过Parser将TOKEN\_VAR类型token中的字符串token.contents传入FilterExpression,再将这个表达式传入VariableNode。当字符串转入FilterExpression后,就会被解析为两种值。一种是数字,所以在变量表达式双大括号中写数字是语法允许的;另一种是Variable实例,该实例负责在环境context中求变量本身的值。同时FilterExpression还将字符串中的filter以及参数提取出来,例如上面这个例子中的两个过滤器。求值时,FilterExpression会将Variable得到的变量值再依次传入filter中计算。过程为`Parser.compiler_filter(token.contents) => filter_expression -> VariableNode解析出variable实例以及filters;VariableNode.render -> FilterExpression.resolve -> Variable.resolve => 值 -> 依次将值带入各个filter求解最后的值`。可以简单叙述各类的功能。 **- VariableNode 负责提供方法,对外提供一个render方法。很类似适配器模式** **- FilterExpression 负责解析原始字符串(变量表达式),创建Variable实例以及filters,提供resolve方法** **- Variable 负责解析变量,在上下文环境中求值** ## 模板中块(TOKEN_BLOCK)的求值 --------------------------- Django模板系统中的BLOCK可以提供多种功能,逻辑判断(if)、循环(for)、赋值(with)等等。类比上面处理变量的过程,BLOCK的处理过程类似,唯一不同的是具体返回的node和解析BLOCK表达式交由用户定制,每个具体的node的调用通过表达的第一个单词区分,`command=token.contens.split()[0] -> compile_func=parser.tags[command] -> compile_func(parser, token)`,这个compile\_func返回的是一个用户自定义的node。而每个BLOCK剩余表达式的解析也由用户定义函数实现(类似FilterExpression),通过compile\_func调用。 简单总结的说, 用户自定义的node实现了BLOCK的逻辑部分;自定义的解析函数实现了parser部分。

django中的惰性求值

发表于 2014-11-02   |   分类于 python   |  

前言


  1. 软件开发中遇到的所有问题,都可以通过增加一层抽象而得以解决。
  2. 代理模式,为其他对象提供一种代理以控制对这个对象的访问。

django中的LazyObject


django.utils.functional.py

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
empty = object()
def new_method_proxy(func):
def inner(self, *args):
if self._wrapped is empty:
self._setup()
return func(self._wrapped, *args)
return inner
class LazyObject(object):
"""
A wrapper for another class that can be used to delay instantiation of the
wrapped class.
# 一个封装类,被封装的对象能够延迟实例化的时间
By subclassing, you have the opportunity to intercept and alter the
instantiation. If you don't need to do that, use SimpleLazyObject.
# 通过继承此类,可以介入和改变被封装的实例。如果不需要这样做,可以使用SimpleLazyObject
"""
def __init__(self):
self._wrapped = empty
__getattr__ = new_method_proxy(getattr)
def __setattr__(self, name, value):
if name == "_wrapped":
# Assign to __dict__ to avoid infinite __setattr__ loops.
# self._wrapped = value 的写法会导致无限循环调用 __setattr__
self.__dict__["_wrapped"] = value
else:
# 进行赋值时,再通过 _setup 进行实例化
if self._wrapped is empty:
self._setup()
setattr(self._wrapped, name, value)
def __delattr__(self, name):
# 进行取值时, 再通过 _setup 进行实例化
if name == "_wrapped":
raise TypeError("can't delete _wrapped.")
if self._wrapped is empty:
self._setup()
delattr(self._wrapped, name)
def _setup(self):
"""
Must be implemented by subclasses to initialise the wrapped object.
"""
raise NotImplementedError
# introspection support:
__dir__ = new_method_proxy(dir)

增加一个LazyObject类封装需要延迟实例化的对象,通过改写__getattr__、__setattr__和__delattr__进行代理和按需实例化。(代理模式ProxyPattern)。

django中的SimpleLazyObject


如LazyObject中所说,当只是简单的代理而不需要改变封装的_wrapped时,只需使用SimpleLazyObject。 这个代理类需要一个func参数,当需要实例化时调用。这个func参数可以是被封装的类、延迟求值的函数等,任何需要代理的对象。为了实际使用需要处理一些特殊方法,基本的实现框架如下:

1
2
3
4
5
6
class SimpleLazyObject(LazyObject):
def __init__(self, func):
self.__dict__['_setupfunc'] = func
def _setup(self):
self._wrapped = self._setupfunc()

真实的代理类


  1. 重写_setup,被封装的类为 self._wrapped = Setting(settings_module)
  2. 通过提供configure接口,可以由用户修改内部被封装的配置类·

django.conf.settings(django.conf.init.py)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
class LazySettings(LazyObject):
"""
A lazy proxy for either global Django settings or a custom settings object.
The user can manually configure settings prior to using them. Otherwise,
Django uses the settings module pointed to by DJANGO_SETTINGS_MODULE.
"""
def _setup(self, name=None):
"""
Load the settings module pointed to by the environment variable. This
is used the first time we need any settings at all, if the user has not
previously configured the settings manually.
"""
try:
settings_module = os.environ[ENVIRONMENT_VARIABLE]
if not settings_module: # If it's set but is an empty string.
raise KeyError
except KeyError:
desc = ("setting %s" % name) if name else "settings"
raise ImproperlyConfigured(
"Requested %s, but settings are not configured. "
"You must either define the environment variable %s "
"or call settings.configure() before accessing settings."
% (desc, ENVIRONMENT_VARIABLE))
self._wrapped = Settings(settings_module)
self._configure_logging()
def __getattr__(self, name):
if self._wrapped is empty:
self._setup(name)
return getattr(self._wrapped, name)
def _configure_logging(self):
"""
Setup logging from LOGGING_CONFIG and LOGGING settings.
"""
if not sys.warnoptions:
try:
# Route warnings through python logging
logging.captureWarnings(True)
# Allow DeprecationWarnings through the warnings filters
warnings.simplefilter("default", DeprecationWarning)
except AttributeError:
# No captureWarnings on Python 2.6, DeprecationWarnings are on anyway
pass
if self.LOGGING_CONFIG:
from django.utils.log import DEFAULT_LOGGING
# First find the logging configuration function ...
logging_config_path, logging_config_func_name = self.LOGGING_CONFIG.rsplit('.', 1)
logging_config_module = importlib.import_module(logging_config_path)
logging_config_func = getattr(logging_config_module, logging_config_func_name)
logging_config_func(DEFAULT_LOGGING)
if self.LOGGING:
# Backwards-compatibility shim for #16288 fix
compat_patch_logging_config(self.LOGGING)
# ... then invoke it with the logging settings
logging_config_func(self.LOGGING)
def configure(self, default_settings=global_settings, **options):
"""
Called to manually configure the settings. The 'default_settings'
parameter sets where to retrieve any unspecified values from (its
argument must support attribute access (__getattr__)).
"""
if self._wrapped is not empty:
raise RuntimeError('Settings already configured.')
holder = UserSettingsHolder(default_settings)
for name, value in options.items():
setattr(holder, name, value)
self._wrapped = holder
self._configure_logging()
@property
def configured(self):
"""
Returns True if the settings have already been configured.
"""
return self._wrapped is not empty

总结


1.通过增加一个代理类,封装了延迟求值(实例化)的对象。通过增加一个层解决问题。
2.再通过改写__setattr__、__getattr__和__delattr__方法,将对代理类的操作,直接委托给被代理的类。代理模式。

python中object/type/metaclass

发表于 2014-10-11   |   分类于 python   |  

从object关键词说起


官方文档上这样定义object:

1
2
Any data with state (attributes or value) and defined behavior (methods).
Also the ultimate base class of any new-style class.

具有状态(属性和值)以及定义了行为(方法)的数据就是object。同时object是所有新式类的最根本的基类。利用object这样定义(新式)类:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
class MyClass(object):
def __init__(self, *args, **kwargs):
print 'new self id:', id(self)
id(MyClass):
>>> 48438744L
id(object)
>>> 506057936L
type(MyClass)
>>> <type 'type'>
isinstance(MyClass, type)
>>> True

MyClass即为静态定义(运行前就定义好了)的类,它继承于object,也因此获得了object的一些属性和方法,包括魔术方法。python中的类的最大特色在于其也是一个对象(实例)。每个类存在于内存中,是object的子类,type的实例。

实例通过__init__函数进行初始化,一般将此作用的方法称为构造函数。但是python中的__init__还不能完全称为构造函数。很明显,在__init__运行前,实例就存在了,即传入的self参数。实例这个对象在__init__运行前就被解释器按照object的内存模型分配好了内存,进行了一番装饰。

1
2
3
4
5
6
7
8
9
i = MyClass()
>>> new self id: 42531472L
id(i)
>>> 42531472L
ii = MyClass()
>>> new self id: 34971944
id(ii)
>>> 34971944L

通过上面这个例子看到self这个参数就是python解释器已经初步实例化了的实例,也可以看到在__init__函数被调用前就已经存在了。因此python有另外一个魔术方法__new__,可以真正的定制实例的内存模型(创建一个什么样的实例)。下面的例子更能说明这一点:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
class MyClass(object):
def __new__(cls, *args, **kwargs):
instance = super(MyClass, cls).__new__(cls)
#或者写成
#instance = object.__new__(cls)
return instance
def __init__(self, *args, **kwargs):
print 'new self id:', id(self)
i = MyClass()
>>> new self id: 69858136
id(i)
>>> 69858136L
class MyInt(object):
def __new__(cls, *args, **kwargs):
instance = int.__new__(cls) # 用int的模型构造实例
instance.to_str = lambda self: str(self)
return instance
m = MyInt(1)
m
>>> 1
m.to_str()
>>> '1'

MyClass与之前的定义没有区别。不过通过自定义的__new__函数可以在实例存在之前介入,直接操纵实现什么样的实例,并且还能够给实例赋予一些属性和方法。
MyInt的定义就更加明显,虽然没有继承于int,但是在__new__函数中直接操纵其实例按照int的模型构造,并且赋予了实例一个方法to_str。结果MyInt类就和直接继承int没有明显的区别。python中的单例模式可以更好的说明:

1
2
3
4
5
6
7
8
9
10
11
class Singleton(object):
instance = None
def __new__(cls, *args, **kwargs): # __init__不能够做到这一点,因为在调用之前,实例就存在了
if not cls.instance:
cls.instance = super(Singleton, cls).__new__(cls)
return cls.instnace
s1 = Singleton()
s2 = Singleton()
s1 is s2 # 同一个实例
>>> True

可以看到,通过__new__可以从一开始就定制如何实现实例(其内存模型)。

type的传说


python中一切皆对象。一切都是其基类的实例。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
isinstance(1, int) -> True
isinstance(int, type) -> True
isinstance(type, type) -> True
isinstance('abc', str) -> True
issubclass(str, basestring) -> True
isinstance(basestring, type) -> True
isinstance(type, type) -> True
isinstance(object, type) ->True
class MyClass(object):
pass
i = MyClass()
isinstance(i, MyClass) -> True
issubclass(MyClass, object) -> True
isinstance(MyClass, type) -> True

object是新式类的根本基类;type则是所有超类的根本基类,甚至是它自己的基类。MyClass是type的实例,当MyClass被定义的时候(python解释器编译这段代码时)就是实例化的过程。可以理解为,python解释器定义了一个变量为MyClass,它是type的实例。而type这种能够用来定义类的类则称为超类metaclass。

前面说过,MyClass是静态定义的类,在运行前就定义好了属性和方法。python提供了type关键字用于动态定义类。按照python文档的说明,下面两种定义类的方式是等价的。

1
2
3
4
class MyClass(object):
pass
MyClass = type('MyClass', (object,), {}) # 第二个参数必须是 tuple类型

通过实例化type,python提供了一种动态创建类的方式。type(name, bases, attrs),第一个name参数给定类的名字MyClass.__name__属性,第二个参数bases给定继承的多个基类,第三个参数attrs确定MyClass的一些属性和方法。由于type能够定义类,那么所有继承type的类,也就有了type这种神奇的定义类的功能,从而成为超类。

普通类可以在定义时通过__new__函数操纵生成的实例,所有继承type的超类也可以通过__new__来操纵生成的类(超类的实例)。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
class MetaClass(type): # 超类继承type,普通类继承object
def __new__(cls, name, bases, attrs):
print 'name:', name
print 'bases:', bases
print 'attrs:', attrs
new_class = super(MetaClass, cls).__new__(cls, name, bases, attrs)
return new_class
MyClass = MetaClass('MyClass', (object,), {}) # 手动实例化实现MyClass
>>> name: MyClass
>>> bases: (object,)
>>> attrs: {}
i = MyClass()
print type(i)
>>> <class '__main__.MyClass'>
isinstance(i, MyClass) -> True
isinstance(MyClass, MetaClass) -> True

MetaClass也具有了type的能力。但是,这样通过超类动态实现MyClass需要额外的手动实例化,非常的不协调。python提供了另一种方法,通过定义__metaclass__属性,让解释器实现这一过程。

1
2
3
4
5
6
7
8
9
10
class MyClass(object):
__metaclass__ = MetaClass
cls_var = 'var'
def method(self): pass
>>> name: MyClass
>>> bases: (object,)
>>> attrs: {'cls_var': 'var', 'method': <function m at 0x000000000437E588>, \
'__module__': '__main__', '__metaclass__': <class '__main__.MetaClass'>}

当类或者基类的属性中有__metclass__时,会调用__metaclass__进行类本身的生成,而不是默认的类生成过程。类自己的内存模型通过定义__metaclass__可以动态定制,其实例可以通过定义__new__函数实现定制。进行类定制的超类继承于type,通过定义__new__函数实现。

可以简单总结为:超类继承于type,通过__new__定制超类的实例,即普通类;普通类继承于object,通过__new__定制普通类的实例。

Python绝对导入

发表于 2014-09-24   |  

absolute_import 绝对导入


绝对导入,指从顶层用import直接导入模块,即(只)从sys.path中导入模块。 例如:

1
2
3
4
import sys
import os
import sys.path
import sys.path.join as path_join

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下的模块)必须通过相对导入的方式进行导入。

举例说明


目录结构如下:

1
2
3
4
5
6
7
8
9
10
package/
__init__.py
subpackage1/
__init__.py
moduleX.py
moduleY.py
subpackage2/
__init__.py
moduleZ.py
moduleA.py

当前的文件package.subpackage1.moduleX.py

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
from __future__ import absolute_import # __future__必须在代码的第一行
#绝对导入
import os
import sys
#相对导入
from . import moduleY
from .moduleY import xxx
from ..subpackage2 import moduleZ
#错误的导入, 绝对导入只能导入sys.path中的模块
#moduleY只能以相对导入的方式进行导入
#import moduleY
def main():
print 'ok'
if __name__ == '__main__:
main()

ValueError: Attempted relative import in non-package


在上面的例子中,当试图直接运行python moduleX.py时会报如上错误。
当直接运行moduleX.py时,此文件被视为一个顶层的脚本而不是包。 而在一个顶层脚本里是不能进行相对引用的。

可以在package层添加test.py脚本进行测试,内容如下。

1
2
3
from package.subpackage1 import moduleY
moduleY.main()

此时,package整体作为一个包,包内就可以进行相对导入了。 最终目录结构如下:

1
2
3
4
5
6
7
8
9
10
11
12
project/
test.py
package/
__init__.py
subpackage1/
__init__.py
moduleX.py
moduleY.py
subpackage2/
__init__.py
moduleZ.py
moduleA.py

参考


PEP328

Python多重继承

发表于 2014-08-13   |   分类于 python   |  

关于多重继承


我比较赞成继承的本质是逐步细化,即逐步的补充原来抽象层次所欠缺的,不断补充具体的应用场景,最终达到应用层次。对于继承的原因,一般分为两种:第一种是对接口(界面约定)的实现,另一种是重载(override)。就像java语言的设计,对于接口(interface)的继承叫做实现(implements),对于普通类的继承叫做扩展(extends)。并且规定接口不能实例化,而多重继承必须是一个抽象类和多个接口类。这也是使用多重继承的一般性原则。

python中的新类和旧类


python中的继承比较麻烦。由于一开始的设计问题,python中分为新类和旧类。新类指所有继承了object对象的类,而旧类指所有没有继承object的类。

1
2
3
4
5
def NewClass(object):
pass
def OldClass:
pass

python旧类的方法继承顺序


对于旧类,多重继承中方法采用深度优先算法。按顺序从一个父类一直深度搜索,然后再搜索第二个父类,一直到找到方法为止。

  1. 当只有一层继承时:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
class A:
def __init__(self):
pass
def method(self):
print 'A'
class B:
def __init__(self):
pass
def method(self):
print 'B'
class Child(A, B):
def __init__(self):
A.__init__(self)
B.__init__(self)
c = Child()
c.method()
>>>A

可以看到c.method方法实际为 A.method方法。
将 Child(A, B) 改变继承顺序为 Child(B, A)再观察。

1
2
3
4
5
6
7
8
class Child(B, A):
def __init__(self):
A.__init__(self)
B.__init__(self)
c = Child()
c.method()
>>>B

这时继承c.method方法实际为B.method方法。
按照继承参数列表中的顺序由前向后查找, C(A, B) => C -> A -> B

  1. 有多层继承时
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
class A:
def method(self):
print 'A'
class AA(A):
pass
class B:
def method(self):
print 'B'
class BB(B):
pass
class Child(AA, BB):
pass
c = Child()
c.method()
>>>A

可以看到, c.method 方法实际为 A.method, 查找顺序为 C -> A -> AA。
按照继承参数列表中的顺序由前向后,深度优先查找。

python旧类中的属性继承顺序


如果存在初始化函数,旧类只能通过显示调用父类的初始化函数。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
class A:
def __init__(self):
self.var = 'A'
class B:
def __init__(self):
self.var = 'B"
class Child(A, B):
def __init__(self):
A.__init__(self)
B.__init__(self)
c = Child()
print c.var
>>> B

类属性查找没有像类方法那样,采用深度优先顺序。我们知道,在python中属性是存放在 __dict__中,而初始化就是给这个字典设置值。所以,后初始化中设置的属性值会冲掉先初始化设置的值。
因此,子类的属性由最后一个初始化的父类决定。

1
2
3
4
5
6
7
8
class Child(A, B):
def __init__(self):
B.__init__(self) #修改初始化顺序
A.__init__(self)
c = Child()
print c.var
>>> A

可见,初始化顺序决定了属性的初值。

python旧类继承的问题


  1. 祖类的多次初始化
    python旧类有个很严重的问题, 就是共同祖类的多次初始化。如果继承的多个父类他们有共同祖类(父类们有相同的父类),会导致祖类的多次初始化。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
class A:
INITIAL = 0
def __init__(self):
self.__class__.INITIAL += 1
self.var = self.INITIAL
class AA(A):
def __init__(self):
A.__init__(self)
class BB(A):
def __init__(self):
A.__init__(self)
class Child(AA, BB):
def __init__(self):
AA.__init__(self)
BB.__init__(self)
c = Child()
print c.var
>>> 2

python新类的方法继承


为了解决祖类的多次初始化,以及属性与初始化顺序相关的问题(还有其他问题)。python设计了新类,并且用super隐式进行父类的初始化。

但是新类有一个问题就是,任何多重继承的子类都有一个共同的祖类object,都是菱形继承。
如果再按照深度优先,那么一些方法最终调用的都是object的方法,而不是其他父类中定义的方法。

所以新类的方法是按照 klass.__mro__中储存的类的顺序查找的。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
class A(object):
def method(self):
print 'A'
class B(object):
def method(self):
print 'B'
class AA(A):
pass
def Child(AA, B):
pass
c = Child()
print Child.__mro__
>>>(<class '__main__.Child'>, <class '__main__.AA'>, <class '__main__.A'>, <class '__main__.B'>, <type 'object'>)
c.method()
>> A

可见是按照Child.__mro__的顺序查找,而且object永远是最后一个父类。

python新类的属性继承


在新类中,通过调用super函数进行隐式的父类初始化,保持与方法继承同样的顺序,并且避免祖类的多次初始化。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
class A(object):
INITIAL = 0
def __init__(self):
self.__class__.INITIAL += 1
self.var = self.INITIAL
class AA(A):
def __init__(self):
super(AA, self).__init__()
class BB(A):
def __init__(self):
super(BB, self).__init__()
class Child(AA, BB):
def __init__(self):
super(Child, self).__init__()
c = Child()
print Child.__mro__
>>>(__main__.Child, __main__.AA, __main__.BB, __main__.A, object)
print c.var
>>>1

不仅避免了祖类A的多次初始化,又保持与方法的查找都是按照 Child.__mro__进行的。

新类的问题


问题一

super函数不能兼容旧类。因此当继承旧类时不能使用super初始化。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
class A:
def __init__(self):
self.a = 'A'
def method(self):
print 'A'
class Child(A):
def __init__(self):
super(Child, self).__init__()
c = Child()
>>>TypeError: must be type, not classobj

有两个办法解决这个问题。
办法一: 利用多重继承object。

1
2
3
4
5
6
7
class Child(A, object):
def __init__(self):
super(Child, self).__init__()
c = Child()
print c.method()
>>> A

办法二: 将is-a修改为has-a,并且利用__getattr__动态获取属性和方法。将继承修改为拥有关系。

1
2
3
4
5
6
7
8
9
class Child(object):
def __init__(self):
self._base = A()
def __getattr__(self, attr):
return getattr(self._base, attr)
c = Child()
print c.method()
>>> A

问题二

当继承多个拥有初始化函数__init__时,无法同时初始化。(强烈不建议这种情况下的多重继承)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
class A(object):
def __init__(self, arg1):
self.arg1 = arg1
class B(object):
def __init__(self, arg2, arg3):
self.arg2 = arg2
self.arg3 = arg3
class Child(A, B):
def __init__(self):
super(Child, self).__init__('arg1')
c = Child()
print c.arg1
>>> arg1
print c.arg2
>>> AttributeError: 'Child' object has no attribute 'arg2'
print c.arg3
>>> AttributeError: 'Child' object has no attribute 'arg3'
print Child.__mro__
>>> (__main__.Child, __main__.A, __main__.B, object)

按照Child.__mro,直接调用 A.__init__初始化函数,而不会调用B.__init__初始化函数。

改变继承顺序Child(A, B)->Child(B, A)观察其变化。

1
2
3
4
5
6
7
8
9
10
class Child(B, A):
def __init__(self):
super(Child, self).__init__()
c = Child()
>>>TypeError: __init__() takes exactly 3
>uments (1 given)
print Child.__mro_
>>>(__main__.Child, __main__.B, __main__.A, object)

此时初始化需要3个参数,可见调用的是 B.__init__。

有一个办法解决这个问题。
像旧类那样,显式的分别初始化父类

1
2
3
4
5
6
7
8
class Child(A, B):
def __init__(self):
A.__init__(self, 'arg1')
B.__init__(self, 'arg2', 'arg3')
c = Child()
print c.arg1, c.arg2, c.arg3
>>>'arg1', 'arg2', 'arg3'

python多重继承的一般性原则


1. 永远使用新类, 继承object; 不继承旧类。
2. 多重继承只在父类中只有一个有初始化函数的情况下。

简单来说,就是像java那样,只采用一个类为基类,其他为接口的情况下

Python Decorator 装饰器

发表于 2014-07-17   |   分类于 python   |  

何为装饰器?


装饰器(decorator)是一种语法糖,需要编程语言支持闭包和first-class函数闭包(提供局部变量支持,first-class函数允许将函数作为参数和任意地方创建函数)。装饰器简单来说就是装饰一个函数(对象),在调用之前或者之后附加点操作逻辑,类似下面的逻辑。

1
2
3
4
5
6
7
8
9
def do_something():
#do_something
==>
def do_something():
pre_do_something #装饰器附加操作
do_something
post_do_somthding #装饰器附件操作

如果没有装饰器语法如何实现相同功能?


如果没有装饰器语法的支持,我们可以用现有的语法实现。例如:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
def decorator(func):
def wrapper(*args, **kwargs):
return func(*args, **kwargs)
return wrapper
def wrapped(arg):
print arg
wrapped = decorator(wrapped)
wrapped('hello,world')
>>> hello,world
wrapped.__name__
>>>'wrapper'

上面的逻辑比较简单。以wrapped为参数调用decorator,返回一个wrapper函数,再赋值给wrapped名字。表面看wrapped没有改变,其实际内容已经被替换成wrapper这个闭包。有了装饰器语法糖的支持,我们可以简写成:

1
2
3
@decorator
def wrapped(arg):
print arg

python解释器负责调用decorator(wrapped)再赋值给wrapped名字的过程。当然上面的例子比较简单,并没有做一些额外的工作,可以稍微修改一下。

1
2
3
4
5
6
7
8
def decorator(func)
def wrapper(*args, **kwarg):
args = tuple(s.title() for s in args)
return func(*args, **kwargs)
return wrapper
wrapped('hello,world')
>>>'Hello,world'

函数如何使用装饰器?


上面的例子已经给出如何使用装饰器的方式,即在被装饰函数的前面加上@decorator。

1
2
3
4
5
6
7
8
9
10
11
def decorator(func):
def wrapper(name):
return func(' '.join(name, 'are a dog'))
return wrapper
@decorator
def echo_name(name):
print name
echo_name('you')
>> 'you are a dog'

如何使用多装饰器?


python解释器负责从底向上调用装饰器赋值,因此可以写为

1
2
3
4
5
@decorator_2
@decorator_1
@decorator_0
def func():
pass

即按照装饰的顺序由下向上调用。类似func = decorator_2(decorator_1(decorator_0(func)))

如何给装饰器转入参数


从前面看,装饰器要求最后能够返回一个函数。这个函数接受唯一的参数func即可。因此实现装饰器传入参数有几种办法。第一种,多层嵌套,装饰器本身接受参数后,再返回一个装饰器即可。当然,为了利用转入装饰器的参数,需要把参数再继续传递下去。

1
2
3
4
5
6
7
def decorator_outer(arg):
def decorator_inner(func, arg=arg):
def inner(*args, **kwargs):
args = args + (arg,)
return func(*args, **kwargs)
return inner
return decorator_inner

第二种,利用类实现参数。

1
2
3
4
5
6
7
8
9
10
class decorator(object):
def __init__(self, arg):
self.arg = arg
def inner(self, *args, **kwargs):
return self.func(*args, **kwarg)
def __call__(self, func):
self.func = func
return self.inner

利用类实现实际上是手工编写了一个闭包,再利用__call__魔术方法将类当做函数运行。

第三种,利用functools.partial 将嵌套扁平化。

1
2
3
4
5
6
7
import functools
def decorator(d_arg)
def inner(func, *args, **kwargs):
args = args + kwargs.get('d_arg', ())
return func(*args, **kwargs)
return functools.partial(inner, d_arg=d_arg)

partial叫做函数的柯西化,即将多参数分阶段传入,等待传入最后一个参数才运行的技术。

1
2
3
4
5
6
7
8
def func(a, b, c):
print a,b,c
func = partial(func, a=1)
func = partial(func, b=2)
func = partial(func, c=3)
func()
>>>1, 2, 3

装饰器的优点?


可以不改变函数名称和参数的情况下实现额外的功能。因为可以在不改变原有逻辑和代码的情况下附加功能,可以实现相同前置/后置逻辑的抽象。

python本身利用装饰器语法实现了很多功能。例如: @classmethod 实现类方法,@staticmethod实现类的静态方法。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
class Klass(object):
@classmethod
def get(cls):
print 'classmethod'
@staticmethod:
def echo():
print 'staticmethod'
@property
def value(self):
if self._value < 1:
return 0
else:
return self._value

还有例如用 @property 装饰器将类方法转成类属性(如上所示)等。编程中可以将所有函数均要做的逻辑抽象为一个装饰器,以此避免代码的重复。例如django中的@login_required函数用来在调用view之前先验证当前请求的用户是否登录。

装饰器的缺点?


装饰器也有缺点,例如上例中的wrapped.__name__返回的并不是wrapped这个名字而是wrapper。这是因为wrapped本身被替换成了一个wrapper闭包,所以返回的是这个闭包的各种属性,可以通过wrapped.func_closure等func_xx函数查看。这叫做装饰器的副作用(side affect)。因此如果原来调用wrapped的逻辑,需要用到这些属性(包括__doc__,__name__,__module__等),那么这些逻辑就会出错。

如何解决装饰器的副作用?

明白了装饰器的副作用的原因,我们就可以手动解决一下。将闭包的属性赋值为被装饰的函数的属性即可。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
def decorator(func):
def wrapper(*args, **kwargs):
return func(*args, **kwargs)
wrapper.__name__ = func.__name__
wrapper.__doc__ = func.__doc__
wrapper.__module__ = func.__module__
return _wraps
@decorator
def wrapped(var):
print var
wrapped.__name__
wrapped.__doc__
wrapped.__module__
>>>'wrapped'
>>>''
>>>'__main__'

在装饰器中直接将装饰器的属性改为被装饰的对象的属性即可。

仔细看可以见到这种技巧的本质,是在装饰器中的wrapper函数之后干了点事情,当然可以写一个通用的装饰wrapper的装饰器来代替手工赋值。最后实现的类似这样:

1
2
3
4
5
def decorator(func):
@wraps(func)
def wrapper(*args, **kwargs):
return func(*args, **kwargs)
return wrapper

利用一个装饰器来完成wrapper.__name__ = func.__name__的工作。因为需要func的属性值,因此这个装饰器需要传入func参数。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
def wraps(wrapped):
def inner(func, wrapped=wrapped):
def _inner(*args, **kwargs):
return func(*args, **kwargs)
_inner.__name__ == wrapped.__name__
return inner
def decorator(func):
@wraps(func) #包装wrapper替换其属性
def wrapper(*args, **kwargs):
args = (s.title() for s in args)
return func(*args, **kwargs)
return wrapper
@decorator
def wrapped(var):
print var
wrapped.__name__
>>>'wrapped'

可以看见最终wraps函数实现了functools.wraps的功能。

当然wraps有两点不够好:第一点是两层的_inner和inner看起来比较难于理解;第二点,如果被装饰对象是一个类,那么类的属性将丢失。对于第一点,通过利用functools.partial将多层嵌套扁平化。对于第二点比较好解决,将类的__dict__属性同样赋值给闭包即可,因此增加updated参数实现要将哪些属性更新。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
WRAPPER_ASSIGNMENT = ('__module', '__name__', '__doc__')
WRAPPER_UPDATES = ('__dict__')
def update_wrapper(wrapper, wrapped,
assigned=WRAPPER_ASSIGNMENT,
updated=WRAPPER_UPDATES):
for attr in assigned:
setattr(wrapper, getattr(wrapped, attr))
for attr in updated:
getattr(wrapper, attr).update(getattr(wrapped, attr, {}))
return wrapper
def wraps(wrapped,
assigned=WRAPPER_ASSIGNMENT,
updated=WRAPPER_UPDATES):
return functools.partial(update_wrapper,
wrapped=wrapped,
assigned=assigned,
updated=updated)

这个wraps就是函数functools.wraps的实现,与传统的写法的不同点是将内部的update_wrapper写在了外面。wraps函数返回了一个这样的函数update_wrapper,这个函数的一些参数都已经给定,只剩下wrapper等待传入。

另外需要说明的是,partial函数是由C语言低层实现,不会修改update_wrapper的各种属性。

python中的多态

发表于 2014-07-16   |   分类于 python   |  

何为多态?


按照松本行弘的说法,多态就是把不同种类的东西当做相同的东西来处理。多态(面向对象)的本质是消息传递。由编译器向实例传递不同的消息来使之进行不同的操作。消息体为传递的方法名、参数个数以及类型等。在没有多态的支持下编程类似这样:

1
2
3
4
5
6
7
def method():
if arg is type of TypeA:
...
elif arg is type of TypeB:
...
else:
...

如果存在多态,编程类似这样

1
2
3
4
5
6
7
8
def method(TypeA arg):
...
def method(TypeB arg):
...
def method(TypeA arga, TypeB argb):
...

由编译器来自动将参数arg分派给正确的方法进行调用。

python中的多态?


从上面的例子可以看到,要想支持多态需要python解释器能够区分不同类型的参数(参数的个数比较好容易区别)。
而动态语言要想做到这一点比较困难(如果做到这一点就不是动态语言了)。所以,python是不支持完整的多态的(可以利用语法部分实现多态的功能)。

如何实现类似多态的支持?


python支持duck typing和多参数,所以部分补偿了不支持多态的问题(按照python老爹的说法,多态不是程序员真正需要的)。当然,多态是语法糖果,即使没有多态也能完成程序。不过在这里只讨论实现类似多态的办法,即方法名相同,不同类型和参数个数的调用。

  • 方法之一,使用if...elif...else 和 *args
1
2
3
4
5
6
7
8
9
10
class Poly(object):
def method(self, *args): #def method(self, arga=None, argb=None)
arga = args.get('arga', None)
argb = args.get('argb', None)
if arga and argb:
...
elif arga:
...
elif argb:
...

即利用 *args 不定参数来实现 或者

1
2
3
4
5
6
class Poly(object):
def method(self, arg):
if type(arg) == TypeA:
....
elif type(arg) == TypeB:
....

显式进行类型判断再分派(不是好的办法)。

  • 方法二, 使用设计模式。将对不同对象(类型)的操作隐藏在操作对象中。
1
2
3
4
5
6
7
8
9
10
11
class Context(object):
def method(self, obj):
obj.op(self)
class ArgA(object):
def op(self, context):
....
class ArgB(object):
def op(self, context):
...

将操作隐含在对象本身中。 因为类似策略模式所以使用了Context来表明类名称。但是,这种方法不支持不定参数个数的多态。

总结


在python中使用多态不是一种好的编程方法。

Python-Threading-Module

发表于 2014-05-15   |  

threading.Lock()


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
lock.acquire([wait]) 非可重入锁。
获得锁返回 True;反之 False
a)wait不设置,或者设置为 True,线程锁处在堵塞模式。
b)wait不设置,或者设置为 True,acquire一个处在unlocked状态的线程锁,将立即返回True:
lock = threading.Lock()
print lock.acquire()
>>>True
acquire一个处在 locked状态的线程锁,堵塞等待锁被释放。如果锁已经被调用线程获取,则导致死锁:
lock = threading.Lock()
print lock.acquire()
>>>True
lock.acquire() #deadlock
c)wait设置为 False,将处在非堵塞模式。
d)wait设置为 False, acquire一个处在unlocked状态的线程锁,将立即返回 True:
lock = threading.lock()
print lock.acquire(False)
>>>True
print lock.acquire(False)
>>>False #不能获取锁,返回False(即使由已经持有锁的线程调用)
acquire一个处在locked状态的线程锁,将返回False。不会导致死锁。
( lock.acquire(False), 类似linux pthread库中的 trylock()函数。获取锁立即返回True,不能获取锁立即返回False )

总结

<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>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
lock.release()
返回None; 或者raise thread.error
a) 可以被任意线程调用,即一个线程锁可以被持有改锁的线程释放,或者其他不持有该锁的线程释放
import threading
import time
def unlock(lock):
time.sleep(3)
lock.release()
print 'unlock in another thread'
lock = threading.Lock()
lock.acquire() #持有锁
release_in_another_thread = threading.Thread(target=unlock, args=[lock,]) #在其他线程调用release
release_in_another_thread.start()
print 'acquire the lock again: ', lock.acquire() #堵塞直到锁被释放,然后再次持有
#2个线程同时print,顺序是任意的。
>>>acquire the lock again: True
>>>unlock in another thread
b) release一个处在unlocked状态的线程锁,会引起 thread.error异常。
lock = threading.Lock()
lock.release()
>>>thread.error: release unlocked lock

threading.RLock(verbose=None) #可重入锁(reentrant lock),


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
a)verbose=None,不打印调试信息
b)acquire和release必须调用次数相同,release不能由不持有锁的线程调用(RuntimeError)
rlock.acquire([wait])
立即返回,获得锁返回True;反之返回False;非第一次获得锁返回 1。
a) 第一次获取锁返回True; 非第一次获取锁返回 1; 不能获取锁返回 False。
b) 持有锁的线程重复调用acquire,wait默认、设置为True、或者设置为False 均立即返回。
c) 不持有锁的线程调用acquire,wait默认或者设置为True则堵塞;wait设置为False则立即返回 False。
d) RLock可以反复获取多次,目前(2.7.5)没有上限(至少1024,估计受限于内存大小)。
rlock.release()
返回None;release非持有的锁raise RuntimeError;
a) 只能由持有锁的线程调用,否则raise RuntimeError。
b)释放一个unlocked的锁,raise RuntimeError 与Lock的情况不一致。

Lock和RLock的区别

  1. Lock能够由非持有锁的线程release,但是RLock不可以。
  2. Lock不能够重入(wait=True则死锁,wait=False则返回False), 但是RLock可以重入多次。
  3. Lock的acquire只返回 True 或者 False; Rlock重复获取锁时返回 1。
  4. 对一个unlocked状态的lock调用 release 引起thread.error异常; RLock 则引起RuntimeError异常。

threading.Condition([lock])


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
创建成功返回None; 失败返回AttributeEror(传入了错误的lock参数)
cv.acquire() --> lock.acquire()
直接调用低层的lock.acquire函数。因此其表现与具体的lock的类型相同。
a) 默认无参数调用,创建RLock
b) 传入threading.Lock(), 类型的lock。
cv.release() --> lock.release()
直接调用低层的lock.release()函数。因此表现与具体的lock的类型相同。
a)默认参数调用,创建RLock, 如果release一个unlocked状态的锁,返回RuntimeError
b)传入threading.Lock, 如果release一个unlocked状态的锁,返回 error: release unlocked lock
c)由于RLock和Lock处理release的情况不同,因此返回也不同,具有不一致性。
cv.wait([timeout]) --> lock.wait()
直接调用低层的lock.wait函数。因此表现与具体的lock的类型相同
cv.notify_all == cv.notifyAll
后者在python3.0中被取消。(所有camelName在py3.0中都被转化为camel_name形式)

令人感兴趣的是,由于Lock与RLock的不同,那么传入一个Lock与使用一个RLock作为Condition的锁时, 其表现究竟如何。

threading.Semaphore(value=1)


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
value默认值为1,即二元锁
threading.acquire(blocking=True)
获取信号量,并将信号值减少1
threading.release()
释放信号量,并将信号值加1
release函数会将信号值增加1。 Semaphore类不会记住信号值的初值(value),如果调用release的次数多于acquire,那么会导致信号值变大。
import threading
sem = threading.Semaphore(1)
sem.acquire(False)
>>>True
sem.acquire(False)
>>>False
sem.relase()
sem.release() #多调用的release导致下面能acquire两次。
sem.acquire(False)
>>>True
sem.acquire(False)
>>>True

threading.Event


1
2
3
4
5
6
>Event本质上就是一个二元信号量
* e.is_set() 检查事件是否发生
* e.set() 设置事件发生
* e.clear() 清除事件
* e.wait([timeout]) 等待事件发生(e.set被调用)或者超时

Python惯用法和技巧等

发表于 2014-01-01   |   分类于 python   |  

字符串格式化

format格式化

支持位置和字典参数格式化。(不支持同时位置和字典参数形式)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
'{0} {0}'.format(1)
>>> 1 1
'{0} {0}'.format("hello")
>>>'hello hello'
'{0} {1}'.format('world') #不能有多余的大括号
>>>IndexError: tuple index out of range
'{var}'.format(var=1) >>>1
'{var}'.format(var="hello")
>>>'hello'
'{ var }'.format(var = 1)
>>> KeyError: ' var '
'{}{world}'.format('hello')
>>>KeyError: 'world' #不能格式化目的字符串有大括号的字符串

%百分号格式化

支持类型、位置和字典参数格式化。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
'%s" % "this is c type'
>>>'this is c type'
'%(var)s, %(num)d' % {'var':'string', 'num':101}#注意这个可以是字符串
>>>'string, 101'
'%r %r' % ('string', 101)
>>>"'string' 101' #%r打印出原始对象,字符会带分号“”; 适合用在MySQL中作为value, 以及不知道对象的类型
'%0.2f' % 1.1111 #c类型的格式化
>>>'1.11'
'%-02d' % 1
>>>'1 '
'%02d' % 1
>>>'01'
'%02d' % 101
>>>'101'

()括号的作用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
t = (1, 2) #tuple
>>>(1, 2)
2 * (1 + 3) #优先级
>>> 8
(1) #作用域, 所以单元素的tuple定义需要尾部的逗号,例如 (1,)
>>>1
type(('a')) #('a') -> 'a', type('a') -> str
>>>str
if (1 + 1):print 'ok' #if(1 + 1), 括号相当于作用域
>>>ok
('not'
'a tuple, but str') #连接符
>>>'not a tuple, but str'

iter函数

  • iter(collections) -> iterator
  • iter(callable, stop) -> iterator
    返回一个迭代器,反复调用callabe函数直到其返回stop
1
2
3
4
5
6
from functools import partial
with open('inputfile', 'rb') as infile:
read64 = partial(infile.read, 64)
blocks = list(iter(read64, '')) # 以64byte为1个block,存入blocks
#lines = list(iter(file.readline, '')) # file.readlines()

类的私有方法

__method以双下划线开头的方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
class TestPrivateMethod(object):
def __private(self):
print 'private'
def public(self):
print 'call in public:', self.__private() #在实例内可以调用
@classmethod
def class(cls):
print 'call in class:', cls.__private() #在类内可以调用
p = TestPrivateMethod()
p.public()
>>>'call in public: private'
TestPrivateMethod.class()
>>>'call in class: private'
p.__private()
>>>AttributeError: 'TestPrivateMethod' class has not attribute '__private'
TestPrivateMethod.__private()
>>>AttributeError: 'TestPrivateMethod' class has not attribute '__private'
p._TestPrivateMethod__private()
>>>'private' #python只是将__开头的私有方法改名,从而达到隐藏的目的

类的私有属性,不能通过实例修改。

类似全局变量和私有变量的关系

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
class Test(object):
attr_in_class = 1
def get(self):
print self.attr_in_class
def set(self):
self.attr_in_class = 'set'
def assign(self):
self.attr_in_class += 1
def set_in_class(self):
self.__class__.attr_in_class = 'class'
@classmethod
def change_in_class(cls):
cls.attr_in_class = 'cls'
Test.attr_in_class
>>>1
ins = Test()
ins.attr_in_class
>>>1

修改类的类变量有两种方式

第一种方式通过类直接修改

1
2
3
4
5
6
7
8
9
Test.change_in_class() #类方法实际是修改类(type的实例)的属性
Test.attr_in_class
>>>'cls'
ins.attr_in_class
>>>'cls'
Test().attr_in_class
>>>'cls' #类的共享变量被修改

第二种方式通过实例的class 修改。

1
2
3
4
5
ins.set_in_class()
>>>'class'
Test.attr_in_class
>>>'class'

当通过实例为 类属性 赋新值时,实际上是创建了一个新的实例属性,不会修改类属性。类似在函数内直接为全局变量赋值,不能修改全局变量,而是创建同名的局部变量

1
2
3
4
5
6
7
8
9
ins.attr_in_class = 'instance'
Test.attr_in_class
>>>'class'
Test().attr_in_class
>>>'class'
in.attr_in_class
>>>'instance' #attr_in_class通过赋值,变为ins的实例变

当然,如果类属性是一个可变变量,例如 [], {} 可以调用对应的方法修改内部元素,而不会创建实例变量

1
2
3
4
5
6
7
8
9
10
11
12
13
class TestMutable(object):
mut = []
t = TestMutable()
t.mut.append(1)
t.mut
>>>[1,]
TestMutable.mut
>>>[1,]
TestMutable().mut
>>>[1,]

类属性的最佳使用方式是,创建所有实例共享的变量,并且不会通过实例修改;或者通过 isntance.__class.var的方式显式的进行修改

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
class SumInstanceNum(object):
cnt = 0
def __init__(self):
self.__class__.cnt += 1
#do other construct thing
@classmethod
def get_instance_num(cls):
return cls.cnt
def __del__(self):
self.__class__.cnt -= 1
```
## 浅copy 和深copy
* 浅复制(shallow copy):只复制最外层对象,不复制内部的子对象(只复制引用)。属于浅复制的有,赋值=、裂片操作[:]、函数copy.copy等。
* 深复制(deep copy): 复制所有,包括对象(类,实例等)的内部对象。属于深复制的是copy.deepcopy
特殊方法 `__copy__` 定义浅复制的操作。
```python
# 浅复制
top = [1, 2, ['a', 'b']]
sec = top[:]
print top.append(3)
>>>[1, 2, ['a', 'b'], 3];
print sec
>>>[1, 2, ['a', 'b']];
top[2].append('c')
print top
>>>[1, 2, ['a', 'b', 'c'], 3];
print sec
>>>[1, 2, ['a', 'b', 'c']]; #同样增加元素 'c'
top[2] is sec[2]
>>>True
# 深复制
deep = [1, 2, ['a', 'b']]
deep2 = copy.deepcopy(deep)
deep[2] is deep2[2]
>>>False

函数参数在定义时实例化,可变参数被共享

1
2
3
4
5
6
7
8
def foo(numbers=[]):
numbers.append(9)
print numbers
foo()
>>>[9]
foo()
>>>[9, 9]
  • 参数numbers在定义时候实例化,并且默认绑定,等于每次传入相同的实例化对象。
  • 更好的实践方式,定义默认的不可变参数,然后处理
1
2
3
4
def foo(numbers=None):
numbers = [] if numbers is None else numbers
numbers.append(9)
print numbers

list列表

list 支持+;不支持-操作符

1
2
3
4
5
6
7
8
la = [1, 2,]
lb = [3, 4,]
la + lb
>>>[1, 2, 3, 4]
lc = [1,]
la - lc
>>>TypeError: unsupported operands types(s) for '-': 'list' and 'list'

输出格式化的文本的第x行的第y列

类似 grep ‘cpu0’ input_file |cut -d” “ -f2 命令, 选取cpu0行的第2个

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
import re
def get_field(data, field, linestart="", sep=" "):
'''
Parse data from string
@param data: Data to parse
example:
data:
cpu 1 2 3 4
cpu0 5 6 7 8
cpu1 9 10 11 12
linestart filed 0 1 2 3
@param field: Position of data after linestart
@param linestart: String to which start line
@param sp: separator between parameters regular expression
'''
search = re.compile(r'(<=^%s)\s*(.*)' % linestart, re.MULTILINE)
find = search.search(data)
if find is not None:
return re.split("%s" % sep, find.group(1))[field]
else:
return None

__slots__减少实例内存使用,

__slots__ 会阻止class建立 __dict__ 属性,即阻止实例动态的添加属性,从而减少创建 __dict__ 的内存消耗。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
#save bits to create __dict__
class Slot(object):
__slots__ = []
s = Slot()
try:
s.var = 1
except AttributeError:
print "can not create attribute, __slot__ stops it"
try:
s.__dict__
except AttributeError:
print "no __dict__ attribute, __slot__ stops it"

一颗树结构

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
import json
from collections import defaultdict
def tree(): return defaultdict(tree)
t = tree()
t['a']['b']['c'] = 1
t['a']['c'] = 2
print t['a']['b']['c']
>>>1
print t['a']['c']
>>>2
print json.dumps(t)
>>> {'a':{'b':{'c':1}, 'c':2}}
def dicts(t):
return {k: dicts(t[k]) for k in t}

context manager上下文管理器

with与as关键词是自动上下文管理的语法支持。

1
2
with open('./temp', 'w') as outfile:
outfile.write('this is a test for context manager')

file 类文件对象均支持 Context Manager。一个类实现 Context Manager的方法是利用 __enter__ 和 __exit__ 特殊方法。实例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
class CM(object):
def __enter__(self):
print 'in enter'
return 'the object to be managered` #返回的值即为被管理的对象,通过`as`赋值给其他名字。
def __exit__(self, exc_type, exc_value, exc_tb):
print 'exit'
return True #返回True,则在`with`程序块中出现的异常不会向外层raise,即异常被忽略(非常不建议这样做)
with CM() as ret:
print 'in block'
print ret
>>>in enter #在程序__enter__运行
>>>in block #在block中运行
>>>the object to be managered #__enter__返回,as赋值给ret
>>>exit #在__exit__运行。

需要注意的点:

  • a)如果在 __enter__内发生异常,则程序不进入context manager机制,直接抛出异常。
1
2
3
4
5
6
7
8
9
10
class EnterException(object):
def __enter__(self):
raise IOError('test') #用IOError模拟异常
def __exit__(self, *args):
print 'exit'
return True
with EnterException():
print 'in block'
>>>IOError: 'test' #程序直接抛出异常,
  • b)如果在block内发生异常, __exit__将捕获异常。如果不返回True,则异常向上层传递。如果返回True,则异常被忽略。
1
2
3
4
5
6
7
8
9
10
11
12
class IngoreException(object):
def __enter__(self):
print 'enter'
def __exit__(self, *args):
print args
return True
with IngoreException():
raise IOError('will be slienced')
>>>enter
>>>[IOError, IOError('will be slienced'), traceback]
>>> #不会有异常被抛出。

如何对函数进行 context manager

利用 contexlib 模块, try...finally...和 yield语法。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
import contexlib
@contexlib.contexmanager
def go():
print 'before try'
try: #__enter__
print 'in enter'
yield 'ret value' # return
except: # __exit__
print 'in exit'
with go() as ret:
print 'in block'
print ret
>>>before try
>>>in enter
>>>in block
>>>ret value
>>>in exit

同时有多个对象需要context manager

例如复制一个文件内容到另一个文件。

1
2
with open('infile', 'r') as infile, open('outfile', 'w') as outfile:
outfile.write(infile.read())

descriptor描述符

描述符是将一个方法转为属性的便捷方式。descriptor类必须包含至少一个 __get__, __set__, __delete__ 特殊方法。 __get__描述一个属性取值时的表现; __set__描述一个属性赋值时的表现, __delete__描述一个属性被删除时的表现。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
class Descriptor(object):
def __init__(self, v):
self.value = v
def __get__(self, instance, owner):
print 'instance:', instance
print 'owner:', owner
return self.value
def __del__(self, instance, owner):
del self.value
def __set__(self, instance, value):
self.value = value
class User(object):
value = Descriptor(0.0)
print User.value #类调用value,instance为None,owner为 User类, value所在的类。
#value的属性被改变,是一个描述符,而不是类的属性。
#(或者说,这个类属性的取值、赋值等行为被改写)
>>>instance None
>>>owner <class '__main__.User'>
>>>0.0
u = User()
print u.value #实例调用value, instance为User的实例, owner为 User类。
>>>instance <__main__.User object at 0x00000000046C7748>
>>>owner <class '__main__.User'>
>>>0.0

更常见的情况下,是将两个相互联系的属性通过descriptor连接起来。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
class Meter(object):
'''Descriptor for a meter.'''
def __init__(self, value=0.0):
self.value = float(value)
def __get__(self, instance, owner):
return self.value
def __set__(self, instance, value):
self.value = float(value)
class Foot(object):
'''Descriptor for a foot.'''
def __get__(self, instance, owner):
return instance.meter * 3.2808
def __set__(self, instance, value):
instance.meter = float(value) / 3.2808
class Distance(object):
'''Class to represent distance holding two descriptors for feet and meters.'''
meter = Meter()
foot = Foot()
d = Distance()
d.meter #d.meter --> Distance.__getattribute__('meter') --> Meter.__get__(meter, d, Distance) --> meter.value --> 0.0
>>>0.0
d.foot
>>>0.0
d.meter = 100
print d.meter, d.foot
>>>(100, 3048.0370641306997)
d.foot = 100
print d.meter, d.foot
>>>(30.480370641306997, 100)

或者作为装饰器修饰类的一个方法,改变其默认行为。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
def memeto(obj, deep=False):
"""memeto is a decrotor to store the attributes"""
"""False == 0; True == 1"""
state = (copy.copy, copy.deepcopy)[deep](obj.__dict__)
def restore():
obj.__dict__.clear()
obj.__dict__.update(state)
return restore
class Transactional(object):
def __init__(self, method):
self.method = method
def __get__(self, instance, owner):
def transaction(*args, **kwargs):
self.memeto = memeto(instance)
try:
self.method(*args, **kwargs)
except:
self.memeto()
return transaction
class NumObj(object):
def __init__(self, v):
self.value = v
def increase(self):
self.value += 1
@Transactional
def DoStuff(self):
self.value = 's'
self.value += 1 #will fail
print 'error'
n = NumObj(0)
n.DoStuff()
print n.value #value is restored by Transactional
>>>0

利用__getattr__和setattr 缓存

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
class Cache(object):
def __getattr__(self, real_name):
print 'through getattr to get attribute %r' % real_name
value = str(real_name)
setattr(self, real_name, value)
return value
cache = Cache()
cache.__dict__
>>>{}
cache.a
>>>through getattr to get attribute 'a'
>>>a
cache.__dict__
>>>{'a':'a'}
cache.a
>>>a

第二次获取属性的时候没有通过 __getattr__ 方法获取,直接获取的属性。

iter迭代器的妙用

iter迭代器中的每个元素只会被迭代一次,可以通过in、next消耗。

1
2
3
4
5
6
7
8
9
10
11
12
# 输出字符串中特殊字符的下一个字符
def print_next_char(s, ch):
s_iter = iter(s)
for c in s_iter:
if c == ch:
try:
print next(s_iter)
except StopIteration:
pass
print_next_char('abadeac', 'a') # b d c

通过slice函数对迭代器进行组合

1
2
3
4
5
i = iter([1, 2, 3, 4, 5])
zip(i, i) # [(1, 2), (3, 4)]
i = iter([1, 2, 3, 4, 5])
zip(i, i, i) # [(1, 2, 3,)]

如果使用map函数,可以产生所有组合,不足的使用None补充

1
2
i = iter([1, 2, 3, 4, 5])
map(None, i, i) # [(1, 2), (3, 4), (5, None)]

再结合s[start:] = s[start:None]的特性就可以输出给定序列的片段。

1
2
3
4
5
6
7
def print_slice(s, iss):
iter_is = iter(iss)
for start, end in map(None, iter_is, iter_is):
print s[start:end],
print_slice('abcde', [0, 1, 2, 7]) # a cde

flock超时函数(stackoverflow)

利用linux中IO堵塞模型中遇到信号返回EINTR,利用Python IO模型中不成功返回IOError

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
import signal, errno
from contextlib import contextmanager
import fcntl
@contextmanager
def timeout(seconds):
def timeout_handler(signum, frame):
pass
original_handler = signal.signal(signal.SIGALRM, timeout_handler)
try:
signal.alarm(seconds)
yield
# 清理部分,获取或者超时都恢复信号
finally:
signal.alarm(0)
signal.signal(signal.SIGALRM, original_handler)
with timeout(1):
f = open("test.lck", "w")
try:
# 如果不能获取就堵塞
fcntl.flock(f.fileno(), fcntl.LOCK_EX)
except IOError, e:
# SIGALRM到达errno.EINTR产生, 其他报错
if e.errno != errno.EINTR:
raise e
print "Lock timed out"

这段代码逻辑清楚,而且对获取锁、超时、其他异常的错误处理都很完美,并且体现了对linux和python都有很深的掌握

你好,世界

发表于 2013-12-30   |  

Helo,World:)

This is the first blog in github pages

1
2
def hello():
print('hello,github')
123
FanChao

FanChao

Enjoin it!

40 日志
19 分类
1 标签
RSS
© 2018 FanChao
由 Hexo 强力驱动
主题 - NexT.Mist