Django信号

信号实现了一个复杂系统中子系统之间的解耦,一个子系统的状态发生改变时,通过信号同步(或通知)其他依赖于该系统的系统更新状态,实现了状态的一致性。

不同于flask,直接使用blinker,django内部实现了信号处理机制。

实现原理

定义信号

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
def __init__(self, providing_args=None, use_caching=False):
"""
Create a new signal.

providing_args
A list of the arguments this signal can pass along in a send() call.
"""
self.receivers = []
if providing_args is None:
providing_args = []
self.providing_args = set(providing_args)
self.lock = threading.Lock()
self.use_caching = use_caching
# For convenience we create empty caches even if they are not used.
# A note about caching: if use_caching is defined, then for each
# distinct sender we cache the receivers that sender has in
# 'sender_receivers_cache'. The cache is cleaned when .connect() or
# .disconnect() is called and populated on send().
self.sender_receivers_cache = weakref.WeakKeyDictionary() if use_caching else {}
self._dead_receivers = False

连接信号

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
def connect(self, receiver, sender=None, weak=True, dispatch_uid=None):
from django.conf import settings

# If DEBUG is on, check that we got a good receiver
if settings.configured and settings.DEBUG:
assert callable(receiver), "Signal receivers must be callable."

# Check for **kwargs
if not func_accepts_kwargs(receiver):
raise ValueError("Signal receivers must accept keyword arguments (**kwargs).")

if dispatch_uid:
lookup_key = (dispatch_uid, _make_id(sender))
else:
lookup_key = (_make_id(receiver), _make_id(sender))

if weak:
ref = weakref.ref
receiver_object = receiver
# Check for bound methods
if hasattr(receiver, '__self__') and hasattr(receiver, '__func__'):
ref = WeakMethod
receiver_object = receiver.__self__
if sys.version_info >= (3, 4):
receiver = ref(receiver)
weakref.finalize(receiver_object, self._remove_receiver)
else:
receiver = ref(receiver, self._remove_receiver)

with self.lock:
self._clear_dead_receivers()
for r_key, _ in self.receivers:
if r_key == lookup_key:
break
else:
self.receivers.append((lookup_key, receiver))
self.sender_receivers_cache.clear()

发送信号

1
2
3
4
5
6
7
8
9
def send(self, sender, **named):
responses = []
if not self.receivers or self.sender_receivers_cache.get(sender) is NO_RECEIVERS:
return responses

for receiver in self._live_receivers(sender):
response = receiver(signal=self, sender=sender, **named)
responses.append((receiver, response))
return responses

断开连接

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
def disconnect(self, receiver=None, sender=None, weak=True, dispatch_uid=None):
if dispatch_uid:
lookup_key = (dispatch_uid, _make_id(sender))
else:
lookup_key = (_make_id(receiver), _make_id(sender))

disconnected = False
with self.lock:
self._clear_dead_receivers()
for index in range(len(self.receivers)):
(r_key, _) = self.receivers[index]
if r_key == lookup_key:
disconnected = True
del self.receivers[index]
break
self.sender_receivers_cache.clear()
return disconnected

信号发生时序图

TODO

weakref

为了防止调用被释放了对象,信号内部保持对receiver调用对象的弱引用,每次发送信号之前,检查该对象是否存在,如果不存在,则标记_dead_receivers为True,等待清楚。

thread lock

使用线程锁保证线程安全

观察者设计模式

定义对象间一对多的依赖关系,当一个对象的状体发生改变时,所有依赖于它的对象都得到通知并被自动更新。

将一系统分割成一系列相互协作的类有一个副作用:需要维护对象间的一致性。我们不希望为了维持一致性而使各类聚合,这样就降低了可重用性。

observer模式描述了如何建立这种关系。这一模式的关键对象是目标(subject)和观察者(observer).一个目标可以有任意数量的观察者。一旦目标的状态发生改变,所有的观察者都得到通知。作为对这个通知的响应,每个观察者都将查询目标以使其状态与目标的状态同步。

这种交互也成为发布-订阅(publish-subscribe).目标是通知的发布者,可以有任意数目的观察者订阅并接收通知。

/django/dispatch/dispatcher.py

References

Git

Git is a free and open source distribute version control system designed to handle evertything from small to very large project wit speed and efficiency.

custom domain redirect

添加DNS解析记录:

记录类型 主机记录 记录值
CNAME blog hotbaby.github.io

修改GitHub pages CNAME记录:

创建CNAME文件,并写入域名。 比如echo 'blog.mengyangyang.org' >> CNAME

hexo 更新覆盖CNAME:

source目录下创建CNAME文件,并写入域名

submodule

以引用PCI项目为例,介绍如何添加子模块

1
2
3
4
$ git clone git@github.com:hotbaby/PCI.git
$ git submodule add https://github.com/uolter/PCI.git code/origin
$ git commit -m 'Add PCI submodule.'
$ git push

tag

添加tag

1
2
3
$ git tag tag_name
$ git push origin tag_name
$ git push origin --tags

某次提交打tag

1
2
3
$ git tag -a tag_name commit_id
$ git push origin tag_name
$ git push origin --tags # 全部tags

查看tag

git tag --list

切换到tag

git checkout tag_name

删除tag

1
2
$ git tag -d tag_name #删除本地tag
$ git push origin --delete tag <tag_name> #删除服务器tag

config

编辑配置文件:

vim ~/.gitconfig

配置编辑器:

git config --global core.editor vim

reference

Django缓存

每次用户请求一个页面,Web服务器都要进行很多计算,查询数据库,合成模板,处理业务逻辑等,再将页面返回给用户.后续的相同资源的请求,服务器都需要重复这些计算.

django提供了缓存机制,每次将资源的响应的副本存储指定的位置,下次用户再发起相同的请求时,服务器不再需要进行类似的计算,直接将上次响应的副本返回给用户.这样既减少服务器的负载,又降低用户的请求时延,提高了用户体验.

HTTP缓存介绍

TODO

缓存框架

缓存是django的一个核心组件,提供缓存服务。

缓存实现

img

config cache

1
2
3
4
5
6
settings.CACHES = {
'default': {
'BACKEND':'django.core.cache.backends.memcached.MemcachedCache',
'LOCATION': '127.0.0.1:11211',
},
}

以memcached为例介绍缓存的配置,BACKEND是缓存实例实现类,LOCATION是缓存实例的位置.

CacheHandler

CacheHandler管理缓存实例的访问。

实现原理: 根据settings.CACHES中配置(运行时)创建缓存实例,重载__getitem__特殊方法管理缓存实例的访问,通过线程局部变量thread.local()保证对于settings.CACHES中每个缓存在每个线程中只有一个实例.

django.core.cache.cachesCacheHandler的一个实例,django.core.cache.cachecaches['default']的代理.

缓存backends

采用模板方法的设计模式,通过抽象基类BaseCache声明了一套缓存操作接口,而将接口实现延迟到具体的子类中。如图所示:
img

BaseCache 是一个抽象类,定义了缓存通用的操作接口和参数默认值.

其中重要的参数:

  • key 生成机制
  • timeout 超时时间
  • max_entries 最大条目数
  • cull_frequency 更新频率

MemcachedCache实现抽象类中声明的接口,通过_cache实现数据的设置和获取操作.

1
2
3
4
5
6
7
8
9
@property
def _cache(self):
"""
Implements transparent thread-safe access to a memcached client.
"""
if getattr(self, '_client', None) is None:
self._client = self._lib.Client(self._servers)

return self._client

LocMemCache实现了线程安全本地内存缓存.

实现原理:_cache一个字典对象用于缓存内存对象,_expire_info一个字典对象用于缓存内存对象的过期时间,_lock是django内部实现一个读写锁保证线程安全.在存储和获取内存对象时,通过pickle进行对象的序列化和反序列化.

FileBasedCache实现了基于文件的缓存机制.

实现原理: 缓存对象,将过期时间和内存对象通过pickle序列化后写入本地文件系统.获取对象,检查是否需要删除就的对象,反序列化,检查是否过期,返回对象.

缓存中间件

如果使能缓存中间件,每个django的页面都会被缓存.

缓存中间件的工作原理(参考实现代码):

  • 只有状态码为200的,方法为HEAD,GET请求的响应被缓存
  • 检测缓存中是否已缓存该请求的响应对象
  • 如果命中,返回原始响应对象的一个浅拷贝(shallow copy)
  • 如果未命中,继续处理view函数
  • 根据请求的header决定是否需要缓存
  • 设置响应的ETag, Last-Modified, Expires, Cache-Control HTTP header.

配置缓存中间件

1
2
3
4
5
settings.MIDDLEWARE = [
'django.middleware.cache.UpdateCacheMiddleware',
...
'django.middleware.cache.FetchFromCacheMiddleware',
]

注意:

在response阶段,中间件的处理顺序是bottom-top, UPdateCacheMiddleware必须最后被执行,因此放在靠前的位置.在request阶段,中间件的处理顺序是top-bottom, FetchFromCacheMiddleware必须最后被执行,因此放在靠后的位置.
img

FetchFromCacheMiddleware

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
  def process_request(self, request):
"""
Checks whether the page is already cached and returns the cached
version if available.
"""
# 只缓存HEAD, GET的请求
if request.method not in ('GET', 'HEAD'):
request._cache_update_cache = False
return None # Don't bother checking the cache.

# 获取GET方法的cache_key,如果不存在,则设置_cache_update_cache标志位为True,需要更新缓存
# try and get the cached GET response
cache_key = get_cache_key(request, self.key_prefix, 'GET', cache=self.cache)
if cache_key is None:
request._cache_update_cache = True
return None # No cache information available, need to rebuild.
response = self.cache.get(cache_key)
# 如果cache为命中,而且请求的方法为HEAD,则获取请求方法为HEAD的cache_key
# if it wasn't found and we are looking for a HEAD, try looking just for that
if response is None and request.method == 'HEAD':
cache_key = get_cache_key(request, self.key_prefix, 'HEAD', cache=self.cache)
response = self.cache.get(cache_key)

# 缓存都未命中,设置_cache_update_cache标志位为True,调用view函数,并更新缓存
if response is None:
request._cache_update_cache = True
return None # No cache information available, need to rebuild.

# 缓存命中,设置_cache_update_cahe标志为False, 不调用view函数,不更新缓存
# hit, return cached response
request._cache_update_cache = False
return response

UpdateCacheMiddleware

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
  def _should_update_cache(self, request, response):
return hasattr(request, '_cache_update_cache') and request._cache_update_cache

def process_response(self, request, response):
"""Sets the cache, if needed."""
# 不缓存,直接返回Response
if not self._should_update_cache(request, response):
# We don't need to update the cache, just return.
return response

# 如果是流数据或状态码不为200, 不缓存
if response.streaming or response.status_code != 200:
return response

# 如果是私有数据,不缓存
# Don't cache responses that set a user-specific (and maybe security
# sensitive) cookie in response to a cookie-less request.
if not request.COOKIES and response.cookies and has_vary_header(response, 'Cookie'):
return response

# Try to get the timeout from the "max-age" section of the "Cache-
# Control" header before reverting to using the default cache_timeout
# length.
timeout = get_max_age(response)
if timeout is None:
timeout = self.cache_timeout
elif timeout == 0:
# max-age was set to 0, don't bother caching.
return response

# 设置缓存的HTTP头部信息
patch_response_headers(response, timeout)
# 缓存响应
if timeout:
cache_key = learn_cache_key(request, response, timeout, self.key_prefix, cache=self.cache)
if hasattr(response, 'render') and callable(response.render):
response.add_post_render_callback(
lambda r: self.cache.set(cache_key, r, timeout)
)
else:
self.cache.set(cache_key, response, timeout)
return response

缓存装饰器

缓存装饰器用于控制view是否缓存.

nerver_cache

1
2
3
4
5
6
7
def never_cache(view_func):
@wraps(view_func, assigned=available_attrs(view_func))
def _wrapped_view_func(request, *args, **kwargs):
response = view_func(request, *args, **kwargs)
add_never_cache_headers(response)
return response
return _wrapped_view_func

view装饰器,在view函数处理完成后,patch缓存相关的HTTP头,控制该相应不被缓存。

相关的HTTP headers:

  • Last-Modifed: current_datetime
  • Expires: current_datetime
  • Cache-Control: max-age=0

cache_control

view装饰器,在view函数处理完成后,patchCache-ControlHTTP头。

cache_page

view装饰器,用于缓存页面。该装饰器是对cache中间件CacheMiddleware的封装。

参考

腾讯传

吴晓波以平铺直叙的方式描述腾讯的发展史,从OICQ到QQ、QQ秀、QQ空间、QQ秀、QQ浏览器、QQ支付、腾讯网、腾讯文学、腾讯影业再到大微信生态。在互联网大发展的过程中,腾讯没有错过任何一场互联网盛宴, 除了搜索、电商领域没有什么大的作为,其他的领域都是攻城略地,鲜有敌手。腾讯只用了18年就做到了市值亚洲第一(总市值19826.40亿)。

读者(也就是我)并不打算描述(也表述不好)腾讯战略思维(马化腾说:腾讯只做两件事,连接和内容,就这么简单),只是从一个程序狗的角度思考一下腾讯的产品思维。

在读《腾讯传》之前,想的都是如何从技术上(准确的说是理论上)进行突破,占领技术制高点,高举高打。比如百度拥有搜索引擎的核心技术和专利,开拓了国内80%以上的搜索份额,实现了自己的价值。读《腾讯传》之后,才幡然醒悟,现在互联网已经由“技术驱动”向“应用驱动”再向“服务和用户驱动”的目标转变

“每个功能不一定要用得多才是好,而是用了的人都觉得好才是真正的好。”就像一位朋友曾经说的那样,腾讯的产品每个功能都不是最完美,但整体用起来都那么很舒服。

“小步快跑,快速迭代”,也许每一次产品的更新都是不完美的,但是如果坚持每天发现、修正一两个小问题,不到一年基本就把作品打磨出来,自己也就有产品的感觉了。

作为一名程序狗,追求技术是我们的天性,但不要把“这个没有技术含量,那个没有技术含量”挂在嘴边,要从用户的角度去考虑如何做一个好的产品,而不是堆砌一大堆高大上的技术,最后用户不买单。要从“技术思维”向“技术+产品思维”转变。

附:《腾讯传》标注

Django调试

django默认情况会将所有的错误信息以HTML形式返回给前端,这样导致在运行nose的单元测试用例出现错误时,无法看到详细的错误栈帧信息,给程序的debug带来一定的困扰。

如何debug django异常栈帧?

方法一:

配置settings.py,将django的所有的debug信息,错误栈帧信息输出到终端

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
LOGGING = {
'version': 1,
'disable_existing_loggers': False,
'handlers': {
'console': {
'class': 'logging.StreamHandler',
},
},
'loggers': {
'django': {
'handlers': ['console'],
'level': os.getenv('DJANGO_LOG_LEVEL', 'INFO'),
},
},
}

连接信号got_request_exception

1
2
3
4
5
6
7
from django.core.signals import got_request_exception

def exc_cb(sender, **kwargs):
import traceback
traceback.print_exc()

got_request_exception.connect(exc_cb)

方法二: 通过middleware,在process_exception中增加exception的调试信息,并将此middleware增加到settings.pyMIDDLEWARE_CLASSES中。

1
2
3
4
5
import traceback

class LogMiddleware(object):
def process_exception(self, request, exception):
traceback.print_exc()

注: 在中间件process_exception方法中打印异常栈帧,只能debug view函数的异常。 如果异常发生在中间件中,无法打印异常信息。

参考

Django中间件

中间件是django处理request/response钩子的框架。它是一个用来修改输入、输出的轻量级的插件系统。 从另外角度上讲,中间件也是一种特殊的“装饰器”,装饰所有的视图函数。

版本说明

django中间件新版本与旧版本不兼容, 本文档是基于django 1.10编写。

新版本中间件

1
2
3
4
5
6
7
class NewMiddlewareCls(object):
def __init__(self, get_response):
self.get_response = get_response

def __call__(self, request):
response = self.get_response(request)
return response

旧版本中间件

1
2
class OldMiddlewarCls(object):
def __init__(self): pass

框架

中间件示例

1
2
3
4
5
6
7
8
9
10
11
from django.utils.depreaction import MiddlewareMixin

class ExampleMiddleware(MiddlewareMixin):
def process_request(self, request):
return None

def process_exceptions(self, request, exception):
return None # or return HttpResponse()

def process_response(self, request, response):
return response

加载中间件

WSGIHandler在初始化时加载中配置文件中的中间件

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
class BaseHandler(object):
...
def load_middleware(self):
if settings.MIDDLEWARE is None:
...
else:
handler = convert_exception_to_response(self._get_response)
for middleware_path in reversed(settings.MIDDLEWARE):
middleware = import_string(middleware_path)
try:
mw_instance = middleware(handler)
except MiddlewareNotUsed as exc:
if settings.DEBUG:
if six.text_type(exc):
logger.debug('MiddlewareNotUsed(%r): %s', middleware_path, exc)
else:
logger.debug('MiddlewareNotUsed: %r', middleware_path)
continue

if mw_instance is None:
raise ImproperlyConfigured(
'Middleware factory %s returned None.' % middleware_path
)

if hasattr(mw_instance, 'process_view'):
self._view_middleware.insert(0, mw_instance.process_view)
if hasattr(mw_instance, 'process_template_response'):
self._template_response_middleware.append(mw_instance.process_template_response)
if hasattr(mw_instance, 'process_exception'):
self._exception_middleware.append(mw_instance.process_exception)

handler = convert_exception_to_response(mw_instance)
class WSGIHandler(base.BaseHandler):
request_class = WSGIRequest

def __init__(self, *args, **kwargs):
super(WSGIHandler, self).__init__(*args, **kwargs)
self.load_middleware()

运行中间件

收到客户端发起的一个请求,调用所有注册的中间件。

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 WSGIHandler(base.BaseHandler):
...
def __call__(self, environ, start_response):
...
response = self.get_response(request)
...
...


class BaseHandler(object):
...
def get_response(self, request):
"""Return an HttpResponse object for the given HttpRequest."""
# Setup default url resolver for this thread
set_urlconf(settings.ROOT_URLCONF)

response = self._middleware_chain(request)

# This block is only needed for legacy MIDDLEWARE_CLASSES; if
# MIDDLEWARE is used, self._response_middleware will be empty.
try:
# Apply response middleware, regardless of the response
for middleware_method in self._response_middleware:
response = middleware_method(request, response)
# Complain if the response middleware returned None (a common error).
if response is None:
raise ValueError(
"%s.process_response didn't return an "
"HttpResponse object. It returned None instead."
% (middleware_method.__self__.__class__.__name__))
except Exception: # Any exception should be gathered and handled
signals.got_request_exception.send(sender=self.__class__, request=request)
response = self.handle_uncaught_exception(request, get_resolver(get_urlconf()), sys.exc_info())

response._closable_objects.append(request)

# If the exception handler returns a TemplateResponse that has not
# been rendered, force it to be rendered.
if not getattr(response, 'is_rendered', True) and callable(getattr(response, 'render', None)):
response = response.render()

if response.status_code == 404:
logger.warning(
'Not Found: %s', request.path,
extra={'status_code': 404, 'request': request},
)

return response

中间件chain

所有的中间件通过MiddlewareMixin链接起来,形成middleware_chain,参考设计模式 chain of responsibility职责链

1
2
3
4
5
6
7
8
9
10
11
12
13
14
class MiddlewareMixin(object):
def __init__(self, get_response=None):
self.get_response = get_response
super(MiddlewareMixin, self).__init__()

def __call__(self, request):
response = None
if hasattr(self, 'process_request'):
response = self.process_request(request)
if not response:
response = self.get_response(request)
if hasattr(self, 'process_response'):
response = self.process_response(request, response)
return response

新版本middleware处理时序:

img

旧版版本middleware处理时序:
img

其他

如果process_request返回结果为不None, 则不再迭代调用下一个层中间件(或视图函数)。

中间件处理分为几个阶段:

  • process_request
  • process_view
  • process_exception
  • process_template_response
  • process_exception
  • process_response

参考

Python Functools

functools.wraps()

wraps(wrapped[,assigned][,updated])
常用装饰器函数中返回的wrapper()函数,解决了被装饰函数namedoc等签名丢失问题。

1
2
3
4
5
6
>>> import functools
>>> def decorator(f):
@functools.wrap(f)
def wrapper(*args, **kwargs):
return f(*args, **kwargs)
return wrapper

functools.partial()

partial(func[,*args][,**kwargs])
是一个装饰器函数,也是一个闭包,返回一个可调用对象,freeze一些参数。

实现原理

1
2
3
4
5
6
7
8
9
def partial(func, *args, **keywords):
def newfunc(*fargs, **fkeywords):
newkeywords = keywords.copy()
newkeywords.update(fkeywords)
return func(*(args + fargs), **newkeywords)
newfunc.func = func
newfunc.args = args
newfunc.keywords = keywords
return newfunc

References

Python装饰器

装饰器是一种设计模型(结构型模式),可以动态给一个对象添加一些额外的职责,而不用修改该对象的任何code。

比如,我们要给一个API增加权限认证,可以通过auth_decorator()装饰这个API,而不必修改每个API的代码;debug一个函数的耗时,可以实现一个time_decorator()装饰要这些函数,而不用修改这些函数的内部实现;给个TextView()增加滚动条的装饰器ScrollDecorator();Flask使用route()装饰器进行路由的注册等等。

装饰器优点:

  • 比静态继承更灵活。与静态继承相比,装饰器可以灵活向对象添加额外的责任
  • 避免在层次结构高层的类有特多的特征。 可以定义一个简单的类,通过装饰器给他逐渐添加功能。

基本装饰器

Python从语法本身就支持装饰器,Python装饰器是一个可调用对象(比如,函数、类),接受一个函数对象作为输入,返回另外一个函数对象。

最简单的Python函数装饰器:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
>>> def decorator(f):
def wrapper(*args, **kwargs):
print('before call %s' % f.__name__)
result = f(*args, **kwargs)
print('after call %s' % f.__name__)
return result
return wrapper

>>> @decorator
def func(*args, **kwargs):
print('call func')


>>> func()
before call func
call func
after call func

函数装饰器

函数装饰器,接受一个函数f作为输入,返回另外一个函数对象。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
'''
function decorator
'''
def time_cost_decorator(f):
def wrapper(*args, **kwargs):
start_time = time.time()
rv = f(*args, **kwargs)
end_time = time.time()
delta = end_time - start_time
print('time cost: %ds' % delta)
return rv
return wrapper

@time_cost_decorator
def sleep_10s_func(*args, **kwargs):
time.sleep(10)

>>> sleep_10s_func()
time cost: 10s

使用类实现函数装饰器,在对象初始化时接受一个函数f作为输入,在模拟调用__call__特殊方法中调用f函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
# class decorator
class TimeCostDecorator(object):
def __init__(self, f):
self._f = f

def __call__(self, *args, **kwargs):
start_time = time.time()
ret = self._f(*args, **kwargs)
end_time = time.time()
delta = end_time - start_time
print('time cost %ds' % delta)
return ret


@TimeCostDecorator
def sleep_5s_func():
time.sleep(5)

类装饰器

类装饰器的与函数装饰器语法非常类似。类装饰器接受cls作为输入,返回另外一个cls。

类装饰器可以用来管理管理类,类实例的创建。

函数实现类装饰器

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
>>> def class_decorator(cls):
class ClassWrapper(object):
def __init__(self, *args, **kwargs):
self._ins = cls(*args, **kwargs)
def __getattr__(self, name):
print('call ClassWapper.__getattr__ func')
return getattr(self._ins, name)
return ClassWrapper

>>> @class_decorator
class Foo(object):
def func(self):
print('call Foo.func')


>>> Foo().func()
call ClassWapper.__getattr__ func
call Foo.func

类实现类装饰器

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
class ClassDecorator(object):
def __init__(self, cls):
self._cls = cls
def __call__(self, *args, **kwargs):
self._ins = self._cls(*args, **kwargs)
return self
def __getattr__(self, name):
print('call ClassDecorator.__getattr__')
return getattr(self._ins, name)


>>> @ClassDecorator
class Foo(object):
def func(self):
print('call Foo.func func')


>>> Foo().func()
call ClassDecorator.__getattr__
call Foo.func func
>>>

装饰器进阶

带参数装饰器

不仅被装饰的函数可以携带参数,装饰器函数也可以携带参数。

Decorator params imply three levels of callables: a callable to accept decorator arguments, which return a callable to serve a callable to serve as decorator, which return a callbale to handle calls to the origin function or class. Each of the three levels may be a function or class and may retain state in the form of scopes or class attributes.

函数实现的带参数的函数装饰器:

以最近写的权限验证为例,描述带参数的装饰器实现

1
2
3
4
5
6
>>> def perm_required(perm, **options):
def decorator(f):
def wrapper(*args, **kwargs):
return f(*args, **kwargs)
return wrapper
return decoraor

类实现的带参数的装饰器:

TODO

函数实现的带参数类装饰器:

TODO

类实现的带参数类装饰器:

TODO

函数签名

functools.wraps() 保留被封装函数的签名,如__module__, __name__, __doc__

以下函数装饰器,被装饰的函数的签名被覆盖

1
2
3
4
5
6
7
8
9
10
11
>>> def decorator(f):
def wrapper(*args, **kwargs):
return f(*args, **kwargs)
return wrapper

>>> @decorator
def func(): pass

>>> func.__name__
'wrapper'
>>>

调用functools.wraps()之后

1
2
3
4
5
6
7
8
9
10
11
12
>>> def decorator(f):
@functools.wraps(f)
def wrapper(*args, **kwargs):
return f(*args, **kwargs)
return wrapper

>>>
>>> @decorator
def func(): pass

>>> func.__name__
'func'

wraps()函数的定义

1
2
3
WRAPPER_ASSIGNMENTS = ('__module__', '__name__', '__doc__')
WRAPPER_UPDATES = ('__dict__')
def wraps(wrapped, assign=WRAPPER_ASSIGNMENTS, updated=WRAPPER_UPDATES):pass

嵌套装饰器

有时一个装饰器不能满足需求,这时,我们可以添加多个装饰器(decorator nesting)。

以下两个装饰器函数分别实现如下功能,scroll_decorator()添加滚动条,border_decorator()添加边框

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
>>> def border_decorator(f):
def wrapper(*args, **kwargs):
print('border wrapper')
return f(*args, **kwargs)
return wrapper

>>> def scroll_decorator(f):
def wrapper(*args, **kwargs):
print('scroll decorator')
return f(*args, **kwargs)
return wrapper

>>>
>>> @border_decorator
@scroll_decorator
def decorated_func(*args, **kwargs):
print('call decorated func')


>>>
>>> decorated_func()
border wrapper
scroll decorator
call decorated func
>>>

以上装饰器从语法与以下等价

1
2
3
4
5
6
7
8
>>> def decorated_func(*args, **kwargs):
print('decorated func')

>>> f = border_decorator(scroll_decorator(decorated_func))
>>> f()
border wrapper
scroll decorator
decorated func

装饰器的其他特性

装饰器与闭包

TODO

装饰器与描述符

TODO

装饰器使用场景

统计API的调用时间

TODO

单例设计模式

TODO

跟踪方法调用

TODO

其他用例

  • Flask 路由管理
  • Django model 事务管理
  • Tornado 异步框架

参考

读书与不读书的区别

没有养成读书习惯的人,以时间和空间而言,是受他眼前的世界所禁锢的。

他的生活是机械化的,刻板的。他只是跟几个朋友和相识者接触谈话,他只看到周遭发生的事情。他在这个监狱里是逃不出去的。

可是当他拿起一本书的时候,他立刻走进一个不同的世界。如果是一本好书,他便立刻接触到世界上一个最健谈的人。

这个谈话者引导他前进,带他到一个不同的国度或不同的时代,或者对他发泄一些私人的悔恨,或者跟他讨论一些他从来不知道的学问或生活问题。

摘自:《林语堂: 读书人和不读书的人,最大的区别是什么》