前言
python相对于其它语言来说入门简单。入门后无论是web开发,还是爬虫都可以应付得了。大部分的工作都是在现有的框架下,继承某类,按照要求重写某些方法,然后就可以完成工作了。python进阶的那些知识,各种数据类型的合理使用,闭包,各种各样的协议,抽象基类,协程,属性描述符,元编程等等,平时写代码很少很少很少很少用到。作为面试官之一,面试了应该有20来个人了吧,就连装饰器(闭包的应用方向之一)也只有少数几个用过,甚至有的工作了3,4年的都基本没有自己写过装饰器。不会用到的原因很简单,这些进阶知识,一般是在写框架,写库等等才会用到,而这样的机会是很少的。这样来说,貌似没有一个很好的理由去学习那些进阶的python知识。虽说很少有机会去写,但是会有很多机会去看。各种框架代码,库基本上都会用到那些进阶的知识,不懂基本上看别人的代码就会出现,每个字母我都认识,但我就是不知道他是用来干啥的,为什么要这样用。总得来说就是,学进阶的python知识是为了看懂别人的代码,在这个基础上学习大牛们的优秀的用法,识别各种乱七八糟的用法。
识别各种乱七八糟的用法
下面那段代码是前人写的,作用是给每个线程都生成一个对象,然后在线程中共享这个对象。localmap这字典生命周期是和进程一样,每个线程都调用,随着线程不断的开关,localmap越来越大,导致了内存泄露。于是,有了版本2。
from threading import local
def threadlocal(localmap={}):
thread_name = threading.currentThread().getName()
if thread_name not in localmap:
# 线程共享的变量,其它线程无法访问
localmap[thread_name] = local()
return localmap[thread_name]
这里是版本2。之前内存泄露是因为线程的生命结束后,没有将localmap中对应的变量删除,导致的。既然如此,就想办法删掉那些不需要的项就好了。问题虽然是解决了,但是糟糕的解决办法。这段代码犯了两个严重的错误。1. 对于全局收集类的,不应该干扰收集对象的生命周期,这里可以用弱引用的容器,引用计数没有增加,不会影响对象的生命周期。2. threading库中的local用错了,它本身就实现了线程共享,线程间互不干涉,只需定义一个全局local对象,在线程中使用就好了。如果不懂得弱引用容器的使用场景,是识别不出这乱七八糟的用法的。当然,对于问题2,这个是对local这个库的误解,一知半解的就用了,闹出的笑话。
from threading import local
def threadlocal(localmap={}):
thread_name = threading.currentThread().getName()
if thread_name not in localmap:
# 线程共享的变量,其它线程无法访问
localmap[thread_name] = local()
if not order_process():
# 获取当前进程的线程
current_threads = [obj.getName() for obj in threading.enumerate()]
for key in localmap.keys():
if key not in current_threads:
localmap.pop(key)
return localmap[thread_name]
看懂代码
继续上面的例子。知道local的用法后,就有点好奇,它是怎么实现的。去看实现的代码如下。这段代码涉及到的知识有,我就不解释那些东西该怎么用了,有兴趣的去翻
流畅的python
这本进阶书。
- 实例创建与销毁:__new__, __init__, __del__
- 属性管理:__getattribute__, __setattr__,__delattr__
- 杂:__slots__
- 锁:RLock
- 垃圾回收机制
缺乏上面的知识是看懂local的实现是怎样的,只会一脸懵逼。local这个类实现的大体思路是,在创建local对象时,同时在线程对象的属性字典__dict__里面保存了local对象的属性字典,然后每次访问加锁,将线程对象保存的local对象的__dict__属性,载入local中。这里的操作很值得玩味,也很好的解决了上面的错误示范中的内存泄露的问题。想要看懂的话,去了解一下垃圾回收机制,然后再慢慢把味一下。这里的神操作,我觉得还是用弱引用来弄比较好,之所以不用,可能是这个库写的时候,还没弱引用那个库或者是说弱引用性能相对来说没这神操作的性能好等等吧。话说,用来弱引用可以不用加锁,并发性能应该会更高。
class _localbase(object):
__slots__ = '_local__key', '_local__args', '_local__lock'
def __new__(cls, *args, **kw):
self = object.__new__(cls)
key = '_local__key', 'thread.local.' + str(id(self))
object.__setattr__(self, '_local__key', key)
object.__setattr__(self, '_local__args', (args, kw))
object.__setattr__(self, '_local__lock', RLock())
if (args or kw) and (cls.__init__ is object.__init__):
raise TypeError("Initialization arguments are not supported")
# We need to create the thread dict in anticipation of
# __init__ being called, to make sure we don't call it
# again ourselves.
dict = object.__getattribute__(self, '__dict__')
current_thread().__dict__[key] = dict
return self
def _patch(self):
key = object.__getattribute__(self, '_local__key')
d = current_thread().__dict__.get(key)
if d is None:
d = {}
current_thread().__dict__[key] = d
object.__setattr__(self, '__dict__', d)
# we have a new instance dict, so call out __init__ if we have
# one
cls = type(self)
if cls.__init__ is not object.__init__:
args, kw = object.__getattribute__(self, '_local__args')
cls.__init__(self, *args, **kw)
else:
object.__setattr__(self, '__dict__', d)
class local(_localbase):
def __getattribute__(self, name):
lock = object.__getattribute__(self, '_local__lock')
lock.acquire()
try:
_patch(self)
return object.__getattribute__(self, name)
finally:
lock.release()
def __setattr__(self, name, value):
if name == '__dict__':
raise AttributeError(
"%r object attribute '__dict__' is read-only"
% self.__class__.__name__)
lock = object.__getattribute__(self, '_local__lock')
lock.acquire()
try:
_patch(self)
return object.__setattr__(self, name, value)
finally:
lock.release()
def __delattr__(self, name):
if name == '__dict__':
raise AttributeError(
"%r object attribute '__dict__' is read-only"
% self.__class__.__name__)
lock = object.__getattribute__(self, '_local__lock')
lock.acquire()
try:
_patch(self)
return object.__delattr__(self, name)
finally:
lock.release()
def __del__(self):
import threading
key = object.__getattribute__(self, '_local__key')
try:
# We use the non-locking API since we might already hold the lock
# (__del__ can be called at any point by the cyclic GC).
threads = threading._enumerate()
except:
# If enumerating the current threads fails, as it seems to do
# during shutdown, we'll skip cleanup under the assumption
# that there is nothing to clean up.
return
for thread in threads:
try:
__dict__ = thread.__dict__
except AttributeError:
# Thread is dying, rest in peace.
continue
if key in __dict__:
try:
del __dict__[key]
except KeyError:
pass # didn't have anything in this thread
结束语
在写代码的过程中,确实是很少用到python的进阶知识,但是看代码的过程中是会经常用到的。如果想要知其然,知其所以然还是要学习python的进阶知识的。最后,虽然超级大牛说只有1%的python程序员会用到python那些高深的知识,但梦想还是要有的,万一有机会成为那1%,但没有知识储备,机会就溜掉了。
网友评论