最近几个月虽说没有以前补番那么疯狂了,但看的几部作品都是绝对佳作,记录一下。
首先是神枪少女/Gunslinger Girl,以下简称GSG
神枪少女是日本漫画家相田裕的作品,故事舞台是在现代的意大利都市。政府机关“社会福址公社”表面上是收容重伤残障的少女给予医治和帮助职业训练的福利机构,但实际上是将收容的少女加以义体改造并投入药物加以洗脑制约使其成为对抗恐怖份子的尖兵。每位少女都搭配一位辅佐官,而这组合称为"fratello",是意大利语中“兄妹”之意,每位少女对其辅佐官都有制约造成的混合了爱情及亲情的绝对忠诚。(来自百度百科)
GSG的动画是03年的,和钢炼03一样,在动画化的时候漫画未完结,所以动画原创了一个结局。但是,这个结局又改编得非常好,所以虽然是原创但也成了经典,这点也和钢炼一样。在那个高质量原创作品数不胜数的年代,连漫改都能为原作添彩,也算是顺理成章的事。现在的改编呢?极黑就是典型。所以当下的漫改作品只要监督能把漫画复制一遍观众就该谢天谢地了。好吧我又扯远了,但是GSG是属于过去那个十年的作品,我用一句话评价:GSG是一部现在的日本无法拍出的动画。写到这里才想起来之前在豆瓣发了篇影评,所以直接搬过来了:
本来是抱着看一部爽片的心态来的。枪战、少女、人体改造,怎么想也不会是一部无聊的作品吧.
但是看过几集,发现,它完全不是我想的那样,因为,我根本就没见过类似的动画.
GUNSLINGER GIRL和我看过所有动画最大的区别,就在于它全篇从头到尾都笼罩在一种沉静又略带悲伤的气氛之中。无论是画风、色调还是音乐都如此配合。观看这部动画就是一种纯粹的享受。
动画花了大量笔墨描绘意大利的人文景观,右边那幅图就是罗马著名的西班牙广场。虽然内容来自漫画,但不得不说这种细致的风景描写和整部动画的感觉十分契合。
以前我就说,日本自从所谓动画复兴,其实也就是萌的风潮占据主导地位以来,对人物的刻画就越来越简单了。漫画改编的作品倒还好,问题是原创的和由轻小说改编的动画,人物基本就是纸片,剧情呢?无非也就是那样,卖萌卖肉,废柴男主养后宫。日本人的创作功力没有减退,但是风潮之所以是风潮,就在于它能影响人,既影响观众,也影响创作者。谁不希望自己的作品迎合受众从而大卖呢?这种情况下,要产生神枪少女这样的漫画,难度就大了不少,而要产生这样的动画,已然不可能。2007年之后动画复兴?我从来都不认为。因为过于注重萌属性而导致人物描写的缺失,是当今(大多)动画的致命硬伤。
GUNSINGER GIRL,如同少女的生命那样短暂而绚烂的,是日本动画曾经的年代。
补一下上文提到的西班牙广场的图
GSG是一部泪点很多的作品,因为基本所有人的故事都是悲剧,比如合榭和 崔耶拉/Triela 是我最喜欢的组合,他们死的时候也是最让我伤心的,还有动画中 安杰丽卡/Angelica 死的那段,算是动画的点睛之笔。BTW,克拉耶丝/Claes 是我最喜欢的角色,而她和辅佐官的那段故事(漫画中才有)也看得我想哭。
GSG 动画第二季据说风格大变,我就没有看。也不推荐看。建议直接看完第一季去看漫画就好。
第二部要说的作品是 魔女猎人罗宾/Witch Hunter Robin,以下简称 WHR
WHR 这部动画个人感觉比神枪少女还冷。和 Cowboy 一样超级慢热,毕竟是曾经的动画,前十几集基本都是单元剧。我一直很好奇现在的观众能不能接受单元剧,因为最近的动画除了渡边信一郎在 Space Dandy 里面依然在玩单元剧,根本就见不到。不过这个问题其实本不存在,因为现在哪有钱给你去拍单元剧,还能拍十几集呢。
单元剧基本不会推进剧情,但正因为没有推动剧情的压力,反而能通过每一集看似平常的剧情中的各种细节展现人物性格,这是所有单元剧的共同点,WHR 并不例外。抛开这些不谈,WHR 最后十集左右的剧情,也就是主线剧情,相当特别。这部分剧情不如死亡笔记那样让人停不下来,也不像鲁鲁修让人猜不到剧情,没有石头门燃,没有 BACCANO 酷。但是观看过程中,第一次让我体会到一件事:我,作为一个观众,并没有上帝视角。
这是我之前看过无数动画都没有体会到的感觉。这是一种奇妙的体验,作为观众,我能看到片中所有主要人物的行动,但是,故事中发生了一件事,我却和主角们一样不知道为什么。WHR 给观众看的剧情就像是冰山露出水面的那部分,你知道在水下有巨大的冰块,但是看不见,你唯一能确信的是露出水面的部分是由水下部分撑着的。换句话说,并不是动画神展开,而是它留给观众思考的空间,让我们去发掘动画中的人是如何决策的,他们为什么会这样行动。举个现实世界的例子,一九七二年的中国人听到尼克松访华的消息,无疑感到很突然。但是现在知道,这件事情早就在酝酿,根本原因是共同利益,在无数次秘密交流之后,基辛格又进行了秘密访问,而尼克松访华这件事只不过是之前所有输入的输出结果。
这样描述 WHR 给人的感觉,应该算是很详尽了,但不去看,是无法真正体会的。
其实我一直很喜欢Windows。作为一个OS,Windows可以说是成功得不能再成功了。普遍的观点是,Windows虽然适合使用,但是不适合做开发,所以程序员需要一个 *nix 操作系统的电脑。这么说自然是有道理的,在我看来,Windows不适合做开发,主要因为以下几点:
编码
搞Python开发的同学绝对被 Windows 蛋疼的 GBK 编码折磨过,更不要说 Python2 本身就存在诸多编码的坑,需要大量的经验或者指导才能够顺利跳过去。所以我后来有个强迫症,就是所有代码文件,不管里面有没有中文字符,统统弄成 utf-8,并且还写了个工具来把某个文件夹中所有内容转成utf-8。
CRLF
这一点其实还好,大多时候不会造成什么影响。就算是跨平台工作,问题也不大。顺便说一下我 msysgit 的设置一直是 line ending 强制转成 LF,checkout 的时候不修改。因为 Windows 下 \n 就可以正常完成换行工作。
shell 不够强大
这是致命的弱点。对于正常的程序员来讲,很多工作用命令行完成效率的确更高。好在有个工具能够弥补:GetGnuWin32。说实话,我认为有了 GetGnuWin32 之后,在 Windows 下做开发的障碍就扫清了不少(cmd.exe 太丑陋这点估计是无法改变)。但是还存在一个最大的障碍,就是第4点:
工具链缺乏
之前我没有意识到工具链对平台的影响,认为 Windows 之所以不适合做开发要归结于OS自身的问题。某天我偶然在 Youtube 上看到了 Jessica McKellar 的一个演讲 The Future of Python。她主要是在讲 Python 的话题,但是其中提到了重要的一点,就是 Python 用户在 Windows 上使用 Python 的时间不够多(毕竟大家主要还是在 *nix 上工作)导致 Python 代码的 Windows 版本(不论是 stdlib 还是 third party)都更新缓慢,并且存在大量未发现的 bug。实际上,导致我最终决定要买 Mac 的原因之一就是,之前某项任务需要的几个第三方库无论如何在 Windows 上都装不好。
工具链缺乏的问题,和平台本身的质量其实不应该混为一谈,在我看来,两者对于 Windows 作为开发平台的负面影响各占一半,而如果我们把 GetGnuWin32 以及 msysgit/clink/chocolatey/Vim... 这些东西都装到 Windows 上之后,其实前者的影响就已经很小了。如果要探究 Windows 工具链缺乏的原因,无非也就是两个,首先是历史原因,毕竟 Unix hacker 之所以叫 Unix hacker 是因为他们都在 Unix 下工作,再有就是恶性循环,工具链越缺乏,就越没有人在 Windows 下做开发,在 Windows 下做开发的人越少,工具链就越缺乏。为什么大家觉得在 Windows 上开发 C++ 还是不错的,甚至强于 *nix 平台,就是因为微软花大力气把 Windows C++ 开发的工具链给完成了,铺平了这条道路。
好吧,说了这么多,最后还是要抱怨一句:为什么Evernote不打算出Linux版??。我曾经花了很多时间研究怎么在 Linux 上顺畅使用 Evernote 或者类似产品,虽然取得了一些成效,但是结果仍然不满意(比如Wine下Evernote字体显示极为丑陋还有各种乱码,Wizbook 剪藏功能一坨屎,NixNote 客户端丑得没法看),最后只能勉强靠网页版度日。网页版用来浏览笔记是不错,编辑功能却烂的要命。可以这么说,如果印象笔记有官方Linux版,我绝对不会花钱去买 Mac。
PS: 有见过中学就开始编程且现在仍从事CS相关工作且编程水平一般的人吗?我感觉,从中学开始编程的人如果仍然在编程,不是大牛的可能性几乎是零。这让我感觉到,编程的学习其实是一个逐渐加速的过程(似乎很多人都这么觉得)。有时间看看能不能就这个问题写篇文章。
之前根据Django自带的缓存写了一个简单的缓存系统,针对views而不是页面,同时支持function based和class based views。因为并没有实际投入使用,所以只是个demo(但是测试过)。所有的代码都在一个文件里,参见 djangocachefor_views.py
Django缓存系统的介绍可见官方文档,这里用到的缓存方式是Local-memory caching,也就是说不依赖外部的数据库或者文件,直接把缓存放在内存中。使用方法如下:
在settings.py
中加入设置
CACHES = {
'default': {
'BACKEND': 'django.core.cache.backends.locmem.LocMemCache',
'KEY_PREFIX': 'kratos_server'
}
}
使用utils.py
中定义的函数,需要在外部调用的有3个
generate_key
, get_cachevalue
, set_cachevalue
具体的参数请直接参考代码的注释
使用举例
@api_view(['GET'])
def cost_month_detail(request):
"""
获取某个成本某月的详细采购记录
"""
costs = Cost .objects.all()
item = request.QUERY_PARAMS.get('item', 'undefined')
cost_type = COST_TYPE_KEY_MAP.get(item, COST_TYPE_UNDEFINED)
month = request.QUERY_PARAMS.get('month', None)
key = generate_key('.' , *[item, month])
data = get_cachevalue(key)
if not data:
req_ym_str = month.split('.')
req_ym_int = [int(req_ym_str[0]), int(req_ym_str[1])]
if cost_type and req_ym_int:
costs = costs.filter( cost_type=cost_type,\
purchase_date__year=req_ym_int[0],\
purchase_date__month=req_ym_int[1])
serializer = CostSerializer(costs)
data = serializer.data
set_cachevalue(key, data, [models[item]])
return Response(data)
说明
Django的缓存在手动操作的情况下就是key-value的结构,添加、读取、删除,这几个操作Django已经封装得很好,无非就是 cache.add(key,value)
,cache.get(key)
,cache.delete(key)
。关键之处在于,缓存的key要如何设计。我们当然可以对每一项需要缓存的东西事先定好key,比如有个Model叫做 Car
,那么缓存数据库中全部车辆信息的key就叫做 car_info
。这么做的问题在于,缓存项多了之后,容易搞混,比如我们希望缓存车辆价格信息,是不是又要弄一个 car_price_info
呢。而且在添加/获取缓存项的时候,如果每一个地方都要手动填写key,实在是非常麻烦又易出错。
要避免这种麻烦,只剩下一个选择,那就是自动生成所需要的 key,并且在读取缓存时,程序也能自己知道该用什么key去找缓存。自动生成key的代码,在 generate_key
这个函数里
import inspect
def generate_key(seperator, *args):
"""
:param seperator: key中的分隔符, 比如'-', '.'
:param args: 需要添加到key中的参数
:return: 生成的key
这个函数用来生成缓存的key, 有两种模式
1. class based view
会读取class name和method name
2. function view
会读取function name
最后用传入的seperator把所有东西, 包括args连接起来, 作为key返回
"""
key = []
frame_back = inspect.currentframe().f_back
func_name = frame_back.f_code.co_name
key.append(func_name)
if 'self' in frame_back.f_locals:
class_name = frame_back.f_locals['self'].__class__.__name__
key.append(class_name)
key = seperator.join(key + [str(arg) for arg in args])
return key
这段代码起作用的部分一共就9行,不过我自己都觉得看懂好难啊(:3」∠)。其实现在记下来的一个目的就是怕之后看不懂(雾。不如我们先看看效果吧:
假设有一个 function based view 函数 fbv
:
def fbv(request):
...
key = generate_key('-', 'args1', 'args2')
...
先不考虑上下文,只关注生成key这件事。这里,生成的key是 fbv-args1-args2
再看一个 class based view:
class cbv(View):
def get(self, request):
...
key = generate_key('-', 'args1', 'args2')
...
这里生成的 key 是 cbv-get-args1-args2
现在,generate_key
这个函数的效果就很清楚了,说白了就是把所有对生成一个有意义且唯一的 key 有帮助的信息用用户自定的分隔符连接起来,就得到了这个 key。这些信息包括:
- view 函数名
- class based view 类名(如果有的话)
- 用户自己添加的信息
如果用户懒得添加额外信息,那就直接 generate_key('-')
就行了,这样一来,每一个view函数都能够生成一个key,并且这个key具有唯一性,之后进行 add/get/delete
操作时,直接使用这个key就好,达成了我们希望的自动化。
那么生成key这件事到底是如何做到?我使用了 inspect 这个模块。之前没有用过它,为了这个任务不得已去看了看,结果发现 inspect 真的是非常强大,能完成好多看似不可能的任务。
由于这不是一篇讲 inspect 的文章,所以这块就不详细展开,看官方文档即可。要说的是三条语句所完成的事情:
frame_back = inspect.currentframe().f_back
获取当前堆栈帧的上一个堆栈帧,换句话说实际上就是调用generate_key函数的堆栈帧,也即view函数的堆栈帧
func_name = frame_back.f_code.co_name
从堆栈帧里面读取代码信息,找到这段代码的名字,其实就是获取view函数名
if 'self' in frame_back.f_locals:
从frame_back也就是view函数所在的堆栈帧读取local namespace,看看 self
是否在里面,用来判断这个view function是不是某个类的方法,即判断是否是class based view
class_name = frame_back.f_locals['self'].__class__.__name__
如果是class based view,那我们就通过 self.__class__.__name__
获取类的名字
以上就是缓存系统介绍的第一部分,之后应该还会写一篇,讲一下缓存如何和某个 Model 绑定以及缓存的更新。