普通视图

发现新文章,点击刷新页面。
昨天以前阿掖山:一个博客

.pdf | 芥川赏作品·杨逸《時が滲む朝》

2024年6月4日 08:00

早听说有在日本的华人作家以六四事件为题材写了一篇小说,获得了日本文学界的一个很有分量的奖项。

通过推特上的只言片语,得知谈论的作品应该是作家杨逸的小说《時が滲む朝》。标题直译为“浸透了时光的早晨”。

这本书没有用中文出版, 这本书有一本台湾出版的中译本《时光浸染》,但是我没有 买到。花了大约一周的时间,边读日语本边翻译了一下。我不懂日语,所谓翻译,就是:

  • 把几个自然段复制粘贴到翻译软件中,然后把结果复制粘贴回来;
  • 把两种语言并列排版;
  • 根据汉语语感,把明显不通顺的句子改写了一下;
  • 把本不需要翻译的汉语诗句和人名换回原文。

时间仓促,可能有些人名仍未纠正,比如“梁浩远”就经常被误译为“浩源”“弘人”,“大雄”误译为“黛玉”,“民生”误译为“民夫”,“英露”误译为英国和俄国……


我不懂日语,用翻译软件一段一段地读,原文看起来经常有省略主语的语法现象,所以不好判断原文究竟是用第一人称还是第三人称来写作。但无论哪种情况,文章很明显是梁浩远一人的视角,其心友谢志强占据了不少篇幅,但并非双线叙事。

故事从1988年主人公高考开始,两人从黄土高原的农村,考入了西部某省会的大学,学习文学专业。两人认识且爱上了同一个女同学。

在大学,受到老师甘先生的教育,和中国传统的“天下兴亡,匹夫有责”精神的感召,在 89 年学潮中先是参与了在省会的集会活动,后坐火车前往天安门,返校后一段时间才发生中国人民解放军的清场。

主角一行人因为六四遭受的迫害并非直接来自官方对运动的清算,而是在校外饭馆喝酒时,因为与一般市民对运动的理解不同,产生争吵,进而恶化成斗殴,因此被公安机关拘留,被学校开除。

主角前往海外的原因和六四无关,近乎天降巧合:主角的邻居中,有抗战胜利后遗留在中国的日侨,改革开放后返回日本。女儿回日本之后不能适应当地的文化,转学回来寄宿在主角的妹妹处,与主人公产生感情,两人结婚后一并赴日,过程一笔带过。

在海外的时间所占的篇幅不大,情节也不多。略写了海外反共组织之松散,对当地社会几乎没有声量,成员参加活动的目的主要是申请政治庇护签证,组织者有的爱财逐利,有的偏执专断。

故事到2001年北京申奥成功那年结束。妻子不再支持主人公在妻弟的餐厅里张贴反共海报,主人公得知了心友、老师、暗恋的女同学的下落,并且还和他们在日本见了面,回忆了往事,送别他们离开,接受了现实。


作者的写作相当克制。

几乎没有使用倒叙插叙等时间剪辑手段,中间的情节按照时间顺序平铺直叙,每一部分所占篇幅都很平均。而且全文基本只作记叙和描写,不见抒情和议论。

虽无抒情,但是通过对让人产生情感的情节进行记叙,画面进行描写,使读者与主人公共情。通过消除作者在作品中的存在,尽力体现历史现实和个人命运本身的力量。

只采用一个人的视角,而且是一个平凡人物的视角,在当代文学天然有胜过英雄视角的道德高度。

而且也从侧面表现了这场运动空间范围上的广泛,不只限于北京等大城市;社会阶层参与的广泛,普通市民给学生送饭送大衣,并非只是少数精英大学生的表演,所谓境外敌对势力的煽动更非主要因素。


但是,如果说作品有什么问题,那也源自于作者选择的视角。

以一名平凡人物为中心,就没办法介绍六四事件的起因,以“反贪污,反腐败”的简单口号动员起来的政治运动,为什么能够得到全国人民的响应,从书中得不到线索,来的莫名其妙,甚至连“反官倒”的口号在文中都没有出现。

主角团的退学并不明显来自于官方的报复,虽然退学的原因表现了学生运动与人民群众的脱节,但是这种情节安排缺乏对中共秋后算账的描写。

主角去国靠的是跨国婚姻,在文学上的巧合性强而典型性弱,而原文对此的伏笔与描写太少,以至于显得突兀。

这些情节设定本不应该成为批评的对象,因为在一个重大历史事件里,个例的巧合和不全面,无法反映历史的全貌,不仅是正常的,甚至可以说是客观描写下的唯一合理结果。当社会可以对这一事件普遍公开讨论之后,众多不全面的个例统计在一起,自然能让后人近似得到全面的认识。

但是现实是,时至 35 年后的今日,当局对此事依然如临大敌,在其执政范围内,对这一话题的讨论不普遍,不开放。身为在海外的华人,面对文学小说这样一个允许艺术创作的体裁,在有大量一手史料的环境里,稍微多花一点精力就可以在文学真实和情节全面之间取得平衡的情况下,选择对一个不完美素材作此白描,略显可惜。


作者将故事结局定格在了 2001 年,也就是北京申奥成功的那年,而作品写于 2008 年,也就是北京奥运当年,两者都是中华人民共和国政府声望的里程碑。无怪乎文章最后悲情无奈地与现实“和解”。

真的能和解吗?

尽管运动的参与者不可能每人都熟知政治哲学理论,但民众的不满其来有自……[待续]

.doc | 说出她名字:Mahsa Amini

2022年10月6日 08:00
> A nation only deserves as much as its people have courage for. > 物理系每周三有一个例行的讲座,讲座之前会找一些学生和演讲者一起吃午饭,公款报销。本周轮到我们的研究方向,于是我们几个报名吃饭。中午隔壁组的同学来和我们碰头,学姐摘下耳机来,说抱歉自己走神了没听见敲门,在自己国家正在革命关头,实在是很难静下心来。 几天前,一位伊朗的库尔德族年轻女子 Mahsa Amini 走在街头,因为头巾佩戴“不规范”,被道德警察带走,随后死于警察局。以此为导火索,抗争运动骤起。相关消息雪片般传到 Twitter,当时和她同行的两位女士用身体堵在将 Animi 带走的警车前阻止警车离开,被警车推行数米仍不放弃;有位男士帮其他示威者挡子弹,衬衫脱下,背上一片霰弹枪打出的血红的浅浅弹坑;有一名警察独自持械在街上巡逻,被周围涌上来的路人围攻;夜里,地上一垛篝火,用女士点燃的头巾组成,一名年轻女孩解下头巾手握住一端,头巾在转圈的舞步中飘扬在空中,落入火中溅起一片掌声;成群的示威者攀爬一座建筑,撕下了挂在建筑外墙上的政治人物画像…… 周四,我在办公室摸鱼刷推特,学姐进来看到了我的屏幕,让我赶紧往上翻,对对,就是这个,快转发,谢谢了。于是我就转发了,但那条推特是 BBC 中文网对这件事的一个追踪报道,配的文案是“伊朗总统同意调查阿米尼之死”,转发了之后显得我很像是“你看青天大老爷这不是开恩显灵了嘛”的小粉红,不过解释起来实在是过于复杂,于是就没说啥。 问学姐伊朗国内的情况,她说之前已经有过多次抗争了,希望这次不一样。现在伊朗国内已经断网,很多消息传不出来,听说政府已经开始使用致命武器,至少有几十个人牺牲。其实三十多年前,学姐的母亲还年轻的时候,就因为类似的原因被道德警察拘捕,受了鞭刑,和她一起被捕的人里面甚至还有孕妇;她的一个叔叔因为曾经在君主制时期伊朗的军队中服役,被新政府处决,前两年伊斯兰共和国政府甚至捣毁了被处决者墓园的墓碑,就为了阻止人们纪念他们……海外的伊朗人决定在周末集会示威,希望我有时间的话也能参加,还短信发给了我活动的海报。 我给海报配文“学姐的社会实践活动”,发到微信朋友圈,没有被微信删掉。今年新到美国的一个学弟看到了,来问我情况,还问我能不能来我办公室问学姐一些问题。学弟的朋友圈里经常转一些妇女劳工等等弱势群体权益方面的公众号文章,偶尔原创一些痛斥新自由主义的全球化架空了工会剥夺了工人机会的进步主义小作文。虽然我不是左派,但是当下的中国,能站在 political apathy 的对立面,至少能算是个短途的同路人吧。 周五中午物理系自助烧烤的时候,我们年级好多人都收到并且接受了集会的邀请。学姐还说找了当地的 FOX 记者报道此事,于是大家一起开共和党及其中下层选民的玩笑,像什么“我看了一眼 Tucker Carlson 的节目,就一眼哈,说的也不是完全没道理嘛”、“我现在不那么怀疑地平说了,你看我们脚底下这块地不就挺平的嘛”……学弟也来吃烧烤了,本来想把他拉过来直接介绍给学姐,但毕竟我很社恐,看他在和自己的同级同学社交,一直没有机会。 下午学弟发微信来,我去他办公室把他领过来,介绍给学姐,学姐很高兴有更多人关注这件事。学弟的问题主要是这个活动有什么女性主义的组织在策划,有什么女性主义的诉求。学姐说没有,大家主要是靠想法而不是靠组织在一起的,都觉得这个政权该完蛋了。学姐问他中国的情况,学弟说他只是反对清零政策。学弟问我能不能让他搭车,我说我也不打算开车,毕竟虽然是和平示威,但仍有出人意料发展的可能性。然后学弟离开我们办公室又折回来问,现场会不会有伊朗政府的特工。学姐说每个人的想法都不一样,可能也有支持伊朗政府的伊朗人,但他们应该不会来这种活动。 集会在市区一个公园的东南角,是围绕公园四条路中最繁华的路口。因为之前提到的顾虑,我把 UBER 终点定到了公园中心,结果到了发现公园比想象中大很多,走到集会地还要接近 20 分钟。下车的地方正在赶集,具象化的“人类的悲欢并不相通”。走着走者隐约听到汽车的鸣笛声和人声,才确定自己没走错方向。 ![]({{ site.baseurl }}/assets/photos/2022-10-06-say-her-name-wide.jpg) 不断说着“借过”在人群中穿行,找到了学姐,身边是好几位同级的同学。安鲁和女朋友也在,手里举着一个大标语板,背后写的是“拜登,别为了核协议出卖伊朗的百姓”。 迟到有一个好处,就是人群已经成型,你需要做的就是融入人群,滴水入海,心理上的门槛和负担要小很多。饶是如此,刚开始的时候也不敢大声喊口号,之前从学姐那里拿的海报也不好意思举高。后来发现正常说话的声音会完全淹没在人群中,再发现全力喊的声音依然会淹没在人群中,于是渐渐放松下来。 ![]({{ site.baseurl }}/assets/photos/2022-10-06-say-her-name-narrow.jpg) “Women, life, freedom!” “Say her name! Mahsa Amini!” “Your silence, their violence!” “Down with the dictators!” “No to Islamic Republic!” “Iranians, make your choice! USA, be their voice!” 人群里有几个喇叭和音箱,有人领头,其他人附和。短的口号领头者念一遍大家重复一遍,长的口号领头人说上句大家说下句。喊过几句英语口号,就用波斯语重复一遍。我一边跟着喊口号,一边替最后一句话捏一把汗,复习在中国,社会活动者需要付出怎样的努力与境外势力切割,即便如此仍不能幸免于诉诸动机谬误。 人群原本占据公园一角的部分草坪和人行道,后来几个活跃分子提了一个音箱到右转和直行车道之间的安全岛上,面对大家带头喊口号。经过的汽车很多鸣笛响应,打开车窗对我们竖大拇指或者比 V 字。活动的最高潮,有几位伊朗女孩在安全岛上,用剪刀剪掉了自己的长发。街对面商铺的玻璃墙上,人群的映像蔚然。 午后,活动在大家齐唱一首伊朗歌曲后解散。临走之时,一对老夫妇看到我胸前捧着的黑白宣传画,很喜欢,于是送给了他们。学姐和她丈夫开车来,把我送了回去,我们在我住处附近的奶茶店喝了奶茶。 在游行现场我没有看到学弟,但他晚上在朋友圈发了三个单词,是波斯语中 women, life, freedom 的拉丁字母转写。

.py | 一个 PyTorch 机器学习项目长什么样

2022年8月17日 08:00
自学,或者说一切学习和教学,本质就是在已经掌握的知识和未知的目标知识之间修路。路有两种修法,一是理论或者说是第一性原理路线,从不证自明的公理或者已经掌握的知识出发,通过逻辑推理一步步得到新的知识;另一种是实践或者说工程师路线,拿到一个已经可以工作的产品,划分成各个子系统,通过输入的改变来观察输出的不同,直到子系统简化到自己可以理解的地步,不再是黑箱,借此了解整个系统的功能。 但是当学习的对象复杂到一定程度之后,凭借一个人的自学能力,只用其中一种方法往往难以钻透。又或者两种方法学到的路线并非同一条路。对于机器学习,理论路线就是“让输入数据通过一个带有超多参数的函数,根据函数返回值和输出数据之间的差别修正参数,直到函数能够近似输入数据和输出数据之间的关系”;实践中代码往往会使用很多库作者封装好的函数,只读源码往往一头雾水。 所以,看到 PyTorch 官网的这篇教程 **WHAT IS TORCH.NN *REALLY*?:** [https://pytorch.org/tutorials/beginner/nn_tutorial.html](https://pytorch.org/tutorials/beginner/nn_tutorial.html) 可以说是喜出望外,把两种路线写出的代码都给了出来,对于自学者来说,就像罗塞塔石碑一样可以互相对照。这里我把 CNN 相关的部分抽掉了,毕竟 CNN 只是深度学习的一个子集,深度学习只是机器学习的一个子集,和这篇文章的主题关系不大。 原文先按照第一性原理,尽量用原生 python 写了一遍,然后一步一步重构成接近生产环境的代码。这里我把顺序反过来,先放出重构之后的最终结果: ```python from pathlib import Path import requests import pickle import gzip import numpy as np import torch import torch.nn.functional as F from torch import nn from torch import optim from torch.utils.data import TensorDataset,DataLoader # Using GPU print(torch.cuda.is_available()) dev = torch.device("cuda") if torch.cuda.is_available() else torch.device("cpu") # Wrapping DataLoader # https://pytorch.org/tutorials/beginner/basics/data_tutorial.html?highlight=dataloader # https://pytorch.org/tutorials/beginner/data_loading_tutorial.html?highlight=dataloader def preprocess(x, y): return x.view(-1, 1, 28, 28).to(dev), y.to(dev) def get_data(train_ds, valid_ds, bs): return ( DataLoader(train_ds, batch_size=bs, shuffle=True), DataLoader(valid_ds, batch_size=bs * 2), ) class WrappedDataLoader: def __init__(self, dl, func): self.dl = dl self.func = func def __len__(self): return len(self.dl) def __iter__(self): batches = iter(self.dl) for b in batches: yield (self.func(*b)) # Define the neural network model to be trained # # If the model is simple: # model = nn.Sequential(nn.Linear(784, 10)) # generally the model is a class that inherites nn.Module and implements forward() class Mnist_Logistic(nn.Module): def __init__(self): super().__init__() # self.weights = nn.Parameter(torch.randn(784, 10) / math.sqrt(784)) # self.bias = nn.Parameter(torch.zeros(10)) self.lin = nn.Linear(784, 10) def forward(self, xb): # return xb @ self.weights + self.bias return self.lin(xb) # Define the training pipeline in fit() def loss_batch(model, loss_func, xb, yb, opt=None): loss = loss_func(model(xb), yb) if opt is not None: loss.backward() opt.step() opt.zero_grad() return loss.item(), len(xb) def fit(epochs, model, loss_func, opt, train_dl, valid_dl): for epoch in range(epochs): model.train() for xb, yb in train_dl: loss_batch(model, loss_func, xb, yb, opt) model.eval() with torch.no_grad(): losses, nums = zip( *[loss_batch(model, loss_func, xb, yb) for xb, yb in valid_dl] ) val_loss = np.sum(np.multiply(losses, nums)) / np.sum(nums) print(epoch, val_loss) return None # __main()__: # data DATA_PATH = Path("data") PATH = DATA_PATH / "mnist" PATH.mkdir(parents=True, exist_ok=True) URL = "https://github.com/pytorch/tutorials/raw/master/_static/" FILENAME = "mnist.pkl.gz" if not (PATH / FILENAME).exists(): content = requests.get(URL + FILENAME).content (PATH / FILENAME).open("wb").write(content) with gzip.open((PATH / FILENAME).as_posix(), "rb") as f: ((x_train, y_train), (x_valid, y_valid), _) = pickle.load(f, encoding="latin-1") x_train, y_train, x_valid, y_valid = map( torch.tensor, (x_train, y_train, x_valid, y_valid) ) train_dataset = TensorDataset(x_train, y_train) valid_dataset = TensorDataset(x_valid, y_valid) train_dataloader, valid_dataloader = get_data(train_ds, valid_ds, bs) train_dataloader = WrappedDataLoader(train_dataloader, preprocess) valid_dataloader = WrappedDataLoader(valid_dataloader, preprocess) # hyperparameters/model learning_rate = 0.1 epochs = 2 loss_function = F.cross_entropy # loss function model = Mnist_CNN() model.to(dev) optimizer = optim.SGD(model.parameters(), lr=learning_rate , momentum=0.9) # training fit(epochs, model, loss_function, optimizer, train_dataloader, valid_dataloader) ``` 可以看到,一个项目主干可以分成4部分: 1. 准备数据 2. 定义模型 3. 描述流程 4. 实际运行 下面把各部分拆分开来,把两种思路的代码进行对比。 ## 1. 准备数据 ### 重构之前 ```python DATA_PATH = Path("data") PATH = DATA_PATH / "mnist" PATH.mkdir(parents=True, exist_ok=True) URL = "https://github.com/pytorch/tutorials/raw/master/_static/" FILENAME = "mnist.pkl.gz" if not (PATH / FILENAME).exists(): content = requests.get(URL + FILENAME).content (PATH / FILENAME).open("wb").write(content) with gzip.open((PATH / FILENAME).as_posix(), "rb") as f: ((x_train, y_train), (x_valid, y_valid), _) = pickle.load(f, encoding="latin-1") x_train, y_train, x_valid, y_valid = map( torch.tensor, (x_train, y_train, x_valid, y_valid) ) n, c = x_train.shape ``` ### 重构以后: ```python # Wrapping DataLoader # https://pytorch.org/tutorials/beginner/basics/data_tutorial.html?highlight=dataloader # https://pytorch.org/tutorials/beginner/data_loading_tutorial.html?highlight=dataloader def preprocess(x, y): return x.view(-1, 1, 28, 28).to(dev), y.to(dev) def get_data(train_ds, valid_ds, bs): return ( DataLoader(train_ds, batch_size=bs, shuffle=True), DataLoader(valid_ds, batch_size=bs * 2), ) class WrappedDataLoader: def __init__(self, dl, func): self.dl = dl self.func = func def __len__(self): return len(self.dl) def __iter__(self): batches = iter(self.dl) for b in batches: yield (self.func(*b)) ``` ## 2. 定义模型 ### 重构之前 ```python weights = torch.randn(784, 10) / math.sqrt(784) weights.requires_grad_() bias = torch.zeros(10, requires_grad=True) def log_softmax(x): return x - x.exp().sum(-1).log().unsqueeze(-1) def model(xb): return log_softmax(xb @ weights + bias) def nll(input, target): return -input[range(target.shape[0]), target].mean() loss_func = nll def accuracy(out, yb): preds = torch.argmax(out, dim=1) return (preds == yb).float().mean() ``` ### 重构以后 ```python # If the model is simple: model = nn.Sequential(nn.Linear(784, 10)) # generally the model is a class that inherites nn.Module and implements forward() class Mnist_Logistic(nn.Module): def __init__(self): super().__init__() # self.weights = nn.Parameter(torch.randn(784, 10) / math.sqrt(784)) # self.bias = nn.Parameter(torch.zeros(10)) self.lin = nn.Linear(784, 10) def forward(self, xb): # return xb @ self.weights + self.bias return self.lin(xb) ``` ## 3. 描述流程 ### 重构之前 ```python lr = 0.5 # learning rate epochs = 2 # how many epochs to train for for epoch in range(epochs): for i in range((n - 1) // bs + 1): # set_trace() start_i = i * bs end_i = start_i + bs xb = x_train[start_i:end_i] yb = y_train[start_i:end_i] pred = model(xb) loss = loss_func(pred, yb) loss.backward() with torch.no_grad(): weights -= weights.grad * lr bias -= bias.grad * lr weights.grad.zero_() bias.grad.zero_() ``` ### 重构以后 ```python def loss_batch(model, loss_func, xb, yb, opt=None): loss = loss_func(model(xb), yb) if opt is not None: loss.backward() opt.step() opt.zero_grad() return loss.item(), len(xb) def fit(epochs, model, loss_func, opt, train_dl, valid_dl): for epoch in range(epochs): model.train() for xb, yb in train_dl: loss_batch(model, loss_func, xb, yb, opt) model.eval() with torch.no_grad(): losses, nums = zip( *[loss_batch(model, loss_func, xb, yb) for xb, yb in valid_dl] ) val_loss = np.sum(np.multiply(losses, nums)) / np.sum(nums) print(epoch, val_loss) return None ``` ## 4. 实际运行 ### 重构之前 ```python # __main()__: print(loss_func(model(xb), yb), accuracy(model(xb), yb)) ``` ### 重构以后 ```python # __main()__: # data DATA_PATH = Path("data") PATH = DATA_PATH / "mnist" PATH.mkdir(parents=True, exist_ok=True) URL = "https://github.com/pytorch/tutorials/raw/master/_static/" FILENAME = "mnist.pkl.gz" if not (PATH / FILENAME).exists(): content = requests.get(URL + FILENAME).content (PATH / FILENAME).open("wb").write(content) with gzip.open((PATH / FILENAME).as_posix(), "rb") as f: ((x_train, y_train), (x_valid, y_valid), _) = pickle.load(f, encoding="latin-1") x_train, y_train, x_valid, y_valid = map( torch.tensor, (x_train, y_train, x_valid, y_valid) ) train_dataset = TensorDataset(x_train, y_train) valid_dataset = TensorDataset(x_valid, y_valid) train_dataloader, valid_dataloader = get_data(train_ds, valid_ds, bs) train_dataloader = WrappedDataLoader(train_dataloader, preprocess) valid_dataloader = WrappedDataLoader(valid_dataloader, preprocess) # hyperparameters/model learning_rate = 0.1 epochs = 2 loss_function = F.cross_entropy # loss function model = Mnist_CNN() model.to(dev) optimizer = optim.SGD(model.parameters(), lr=learning_rate , momentum=0.9) # training fit(epochs, model, loss_function, optimizer, train_dataloader, valid_dataloader) ```

.py | 转载:为什么俺推荐 Python[1]·作为脚本语言的 Python

2019年2月7日 08:00
### 文章目录 * [脚本语言好在哪?](#pros) * [脚本语言有啥缺点?](#cons) * [Python 和其它脚本语言的比较](#compare) * [总结](#summary) 俺窃以为,Python 的所有特征中,作为脚本语言(“脚本语言”的定义参见“[这里](https://en.wikipedia.org/wiki/Scripting_language)”)是它的首要特征。因此,在本系列帖子中,俺首先来忽悠一下 Python 作为脚本语言,有些啥好处? # 脚本语言好在哪? 要聊 Python 作为脚本语言的好处,首先得说说脚本语言自身有哪些优点。一般来说,当我们提及“脚本语言”,都是强调其解释执行的特性(虽然有些脚本语言也支持编译)。所以,后面陈述的这些优点,大都是拿编译型语言来进行对比。 ## 更高层次的抽象和封装 大部分脚本语言都提供了(内置了)比较高层次的抽象和封装。像很多脚本语言都内置了字符串处理能力以及正则表达式(典型代表就是 Perl)。还有很多脚本语言都内置了高级的数据结构。比如 Python 在语言层面支持了链表(Python 的术语叫 List)、映射(Python 的术语叫 Dict)、元组(Python 的术语叫 Tuple)。 有了这些高层次的封装,你写起代码来,就特别滴简单、特别滴爽。比如,在 Python 中要把一个 List 翻倍,只需这么写: ```python [1,2,3] * 2 ``` 就得到 ```python [1,2,3,1,2,3] ``` ## 更少的代码量 得益于高层次的封装,在完成相同功能的前提下,脚本语言的代码量会比编译型语言少很多。 比如说,要打印出某个文本文件的内容,如果用 Java 实现,正常的写法大概要七、八行代码(把所有代码硬挤到一行的,不予讨论);用 Python 也就三、五行。 再比如说,抓取某个网址对应的 web 网页,用 Python 自带的标准库实现,大概3到5行代码;但如果用 C++ 实现,代码量会增加许多(具体要写多少代码,取决于你用了哪个 http 的库)。 代码量少了之后,至少你看代码的时候(无论是看自个儿滴还是看别人滴),能少敲很多次翻页键,大大延长了键盘的寿命,顺便降低了手指头的劳损。 ## 更好的可读性 当然啦,延长键盘寿命还不是关键,关键在于——代码量少了之后,(通常情况下)会有助于提高可读性。而可读性恰恰是 Python 的强项之一。 比方说:Python 在【语法层面】强制约定了作用域缩进(这是俺很喜欢 Python 的地方之一)。如此一来,即便是新手程序猿写出的 Python 代码,缩进风格也很统一。反观 C/C++/Java 的新手,写出的代码就没有这么整齐了。 ## 更平缓的学习曲线 通常,脚本语言的语法都比较简单、傻瓜化。因此,入门也就容易很多。稍微有一些编程基础的人,就能够在短时间内上手。 比如俺手下的 C++、Java 程序员,以及某些测试人员,都可以在一周内(程序员用不着一周,一般就1、2天)掌握 Python 的语法并用来写一些辅助的小工具。大大节约了俺培训的口水。 ## 支持“交互式” 很多脚本语言的 IDE 支持【交互式写代码】。也就是说,你每写完一行代码,解释器就执行一把。这样能很快发现输入错误,而且还可以立即看到执行结果。 # 脚本语言有啥缺点? 前面说了那么多优点,那脚本语言有啥缺点捏? 主要的缺点就是【性能差】。这是他们为上述优点所付出的沉重代价。所幸当今的计算机硬件突飞猛进,性能差的缺点已经越来越不明显了。 # Python 和其它脚本语言的比较 有同学可能要问了,脚本语言有很多,为啥俺独独青睐 Python 捏? 为了回答这个问题,下面俺拿 Python 和一些常见的脚本语言作一些【肤浅的】比较。 鉴于后面的内容极易引发语言的口水战,俺特此声明:虽然接下来会提及某些语言相对于 python 的缺点,但俺绝无贬低这些语言的企图,也无意证明 python 比这些语言优秀!俺只是陈述一下:当年是如何在几种脚本语言中进行取舍的? 除了 Python 之外,常见的脚本语言还有很多,比如:PHP、JavaScript(以下简称 JS)、Perl、VBScript(以下简称 VBS)、Ruby、Bash、Lua、Tcl(可别误以为是某家电厂商)......Python 是如何从这些脚本语言中脱颖而出的捏?俺挑选的时候,主要考虑了如下几点: ## 通用性(跨领域) 因为俺懒得学太多编程语言。所以,俺希望熟悉一门脚本语言之后,能够尽量多帮俺搞定不同领域的事儿。从这点来看,俺就不会选择 PHP(太偏重于 Web 服务端)、JS(太偏重于 Web 客户端)、诸如 Bash 之类的 Shell 脚本(太偏重于系统管理)。 而 Python 则属于通用的脚本语言,覆盖范围很广。比如 Web 开发、桌面 GUI 应用、系统管理、网络应用、科学计算等许多领域,Python 都可以轻易搞定。 ## 人气够旺 关于“人气”的重要性,俺在《如何选择开源项目》一文中,有介绍过。人气越高、越流行,就意味着更多的资源(包括文档、相关软件),当你碰到问题需要解决,也有更多的人可以咨询。 关于编程语言的流行程度,可以大致参考 TIOBE 的排名(虽然 TIOBE 不能全面反映流行程度,但至少可作为某种参考)。 像 Tcl、PowerShell、Groovy、JavaFX 等都排在30名之外(截至写本文的2009年8月),感觉用的人太少了,俺暂时不予考虑。而 Python 最近几年的排名则一路上升(请看“这里”),截止到2009年8月,已经高踞排行榜第6位。Perl 虽然也身居高位,但是最近几年的排名一路下滑(请看“[这里](http://www.tiobe.com/content/paperinfo/tpci/Perl.html)”)。俺个人认为,其人气不容乐观。 ## 功能够强、库够丰富 另一个俺很看重的地方是功能是否够强大。在这点上,Python 和 Perl 都算是比较强悍的。关于 Python 如何强悍,俺会在本系列的第5篇帖子《作为瑞士军刀的 Python》中加以介绍。 反观 JS、Ruby、Tcl 等语言,则稍显不足(当然,也有可能俺孤陋寡闻)。 ## 跨平台 由于俺平时会使用不同的操作系统,再加上俺负责的产品也是跨平台的。所以,俺对脚本语言有跨平台的要求。说到跨平台,诸如VBS、Bash之流就不予考虑了。 其实,很多脚本语言都支持跨平台。而 Python 在这方面,更为出众。不光支持主流的操作系统,还支持一些冷门的(比如古老的 DOS),还支持手持设备(比如智能手机和平板)。 ## 和其它语言的整合、交互 最后这一点,估计大多数同学不会太关心。俺因为要在公司的产品中引入脚本技术,所以俺还得考虑该脚本语言和其它语言的整合能力。整合能力强的脚本语言,可以作为复杂系统中的胶水,用来把不同模块粘合在一起(关于 Glue Language,可以参见“[这里](https://en.wikipedia.org/wiki/Glue_language)”)。 在这方面,Python 和 Ruby 的表现都不错: 它们和 Java 的整合有Jython、JRuby; 和 dotNet 平台的整合有IronPython、IronRuby。 至于俺常用的 C++,Python 整合得比 Ruby 好。比如 C++ 社区大名鼎鼎的 Boost 库里面,就内置了一个 Boost.Python 的子库(参见“这里”)。 关于 Python 如何用作胶水,俺会在后续的帖子“作为胶合语言的 Python”中会详细介绍。 # 总结 基于上述几个方面的考虑,俺最终选择了 Python 作为日常使用的脚本工具,并把它引入到公司的产品中,作为模块之间的胶合剂。 啰嗦完Python作为脚本语言的方方面面,下一个帖子,咱来聊一下它作为动态语言的那些事儿。 [回到本系列的目录](RP-why-choose-python-0-overview) > *版权声明*
> 本博客(编程随想的博客)所有的原创文章,作者皆保留版权。转载必须包含本声明,保持本文完整,并以超链接形式注明作者编程随想和本文原始地址:
> https://program-think.blogspot.com/2009/08/why-choose-python-1-script.html
❌
❌