Twitter机器人:今年还要工作多少天?

今天突然想,去年Twitter将字数限制放开到了280,那么就可以模仿YearProgress做一个工作日的进度条了。每天用一个字符表示,一年的工作日有250天,可以放得下。于是就写了一个脚本,用cron每天发出一条twitter提醒工作日还剩下多少天。

Github: https://github.com/laixintao/workdays-bot

Twitter: https://twitter.com/workdays_bot

脚本很简单,就是计算一下一年工作日的天数。然后用tweepy调用一个接口就行了。但是脚本中我用了namedtuple list存放所有天数和类型,后来发现不是很好,应该用OrderedDict,会方便很多。但是这个脚本每天只跑一次,效率不是什么问题,就没去优化。

twitter,github,telegram这些公司接口设计的真好啊!要是国内微信淘宝这些公司的接口,半个小时写出来个能work的东西,我觉得是肯定不可能的……(Telegram和微信的比较

还发现了两个好玩的网站:

http://china.workingdays.org 这个网站可以计算每个国家的工作天数。可以发现不看公司政策,每个国家一年的工作时间都是250±2的,惊人的一致…… 还以为基督教国家有复活节和圣诞节会多很多呢……而且神奇的是,这个网站有N个域名……每个国家都是一个域名后缀。如果不好买(比如我猜.cn就不好买)才会用二级域名。

 

谈谈Python for循环的作用域

对于从其他语言转到Python的人来说,下面这段代码显得很诡异:

你期望的是i变量不存在报错,而实际上打印结果是:

这是因为,在Pyhton中,是没有block这个概念的。

Python中的作用域只有四种,即LEGB规则:

L, local – 在lambda函数内或者def函数内部的变量

E, Enclosing-function – 闭包的作用域(了解Python的闭包可以看《闭包初探》)

G,Global – 全局作用域

B, Build-in – 内建作用域

举个例子:

由此看来,for循环的作用域会污染局部作用域,Python2的列表生成式也会有这个副作用,但是已经在Python3中得到了修复。

曾经Python邮件列表中有人想“如果在for-loop中有函数引用变量,就将此变量变成for-loop局部变量”,但是造成这个的问题并不是for循环的问题,而是闭包的迟绑定

 

2017年鉴

瞄了一眼2016年写的总结,发现定的目标果然没有完成,哈哈,当成2018年的目标好了。

年初的时候买了《最后的守护者:食人的大鹫》,上天人文的作品,传说是游戏里面最智能的一条狗,可惜买来之后发现自己并不是很喜欢。纯粹的解谜游戏,没有什么提示,游戏的过程比较无聊,到现在也没有通关。

后来玩了《使命召唤13》,没啥感觉……大约花了8个小时通关了,去线上老是被虐,就封箱了。不顾最近出了《使命召唤二战》,看着挺想玩的。不是很喜欢未来向的游戏,喜欢基于历史的,枪械也比较老的那种风格。等降价入一下《二战》吧。

有一段时间在公司和同事一起玩《BROFORCE》,4人同屏横版闯关游戏,有点像魂斗罗,非常欢乐。后来打到最后的BOSS,打了几次都过不了,渐渐的就不玩了。然后大家基本上不一块玩游戏了。

中间去尝试了一些PS PLUS赠送的游戏,比如《HUE》《刺客信条 黑旗·自由的呐喊》《合金装备V幻痛》,但是都没有深入玩下去。

后来认真通关了《恶名昭彰2 次子》,这个游戏设计的画面很好,游戏性一般,UI也很一般,界面上没有用的画面占了很多地方(菜单)。趁着打折买了心仪已久的《辐射4》,也不错,只不过游戏系统太复杂了,很多操作需要重复做,比如整理道具之类的,不太友好。世界观设定蛮好的,缺点就是太不人性化了。年底会员免费了《拉结特与克拉克》,太喜欢这个游戏了!画面,游戏性,音乐,剧情都可以给满分了!而且非常低龄化,游戏性非常高,可以玩很久!

工作之后玩游戏的时间少了,不过这并不是抱怨,以前在学校很无聊,也不知道做什么,学东西也不知道用在什么地方,学校又不教啥有用的东西,所以很多时间想打游戏。工作之后比较充实,研究一些技术问题、写代码很多时候都感觉比打游戏有意思!今年也没在游戏上花什么钱,倒是Switch出了很多好游戏(年度游戏《塞尔达·荒野之息》),想买,明年看看吧。

7月份延迟1个月顺利拿到了毕业证,在公司入职,一切都很顺利。工作之后真的爽很多啊,比起在学校,做的东西都更有意义了,有了更多的学习目标,学到了更多有有意义的东西,生活也感觉更幸福了。花了很多时间补充Python的知识,用Linux更熟练了。

10月份搬家了,在市区,新家有冰箱和厨房可以用, 感觉生活质量上升了一个档次。之前租的房子打算凑活一下,没想也整整住了一年。做饭这种事情吧,确实费时间,但这就是生活吧,可以学做不一样的东西。

这一年的生活大多数时候还是挺开心的。明年想多花点时间锻炼身体。

很喜欢现在工作的一点是:工作上的事情大家都直来直去,review代码,讨论意见可以就事论事,没有太多顾虑。新方案的提出所有人都可以参与讨论,决定事情比较快,实施也快。现在我们基本上都迁移到Python3了,用的数据库,Elasticsearch基本也都更新很及时,跟着官方的新版本。每天上班心情都很好。

不足的是,有一些项目因为不断添加新的东西,添加的时候一直在复制粘贴,导致线上API那边几百行的函数是很正常的事情。以及前端的代码,同样的逻辑要写在很多地方很多次。现在添加展示一种新的数据类型,经常要grep一下之前的数据是怎么展示的,然后对照这些文件复制粘贴来添加新的。很不好。明年希望能重构一下。首先得熟练用pytest保证不挂掉东西。

去年定的目标,看完《SICP》和《CSAPP》,到年底了,两本书都没有动。今年系统性的认真看完的书很好,学习的东西比较浅,基本就是感兴趣某个方面或者某个问题,去网上翻翻资料,整理一下。豆瓣上“想读”的书还是那么多,今年看的书好像就《永恒的边缘》。

明年少花点时间逛论坛了,纯粹就是浪费时间。很多东西看了还让自己不爽。多花点时间读代码、文档、书,尝试一下流行的技术。多写点博客。

今年7月20日,林肯公园主唱查斯特·贝宁顿在家中自杀身亡。

最后,汇总一下2018年的目标:

  1. 读完《SICP》和《CSAPP》(这好像连续3年被列为目标了)
  2. 在LeetCode做完500道题目
  3. 学习Rust(能直接编译成程序码很酷,打算实现一个HTTPS over DNS)
  4. 买switch

过去四年:

  1. 2013年
  2. 2014年
  3. 2015年
  4. 2016年
 

闭包初探

看过了很多有关闭包的文章,也看了很多书,还是对闭包一知半解。听说闭包是面向对象系统实现的基础,但是一直不明白这其中有什么关系。今天看《Fluent Python》终于有点理解了!

我更新了一下之前的《理解Python的UnboundLocalError》这篇博文,这篇讨论了Python的作用域以及原因。建议读本文之前先阅读一下这篇。

维基百科是这样解释的:

在计算机科学中,闭包(英语:Closure),又称词法闭包(Lexical Closure)或函数闭包(function closures),是引用了自由变量的函数。

这里的“自由变量”是一个技术术语,指的是没有在本地作用域绑定的变量。用通俗的话说,就是一个函数内使用的变量并没有在函数内定义,而是在函数外定义的。这个函数和它之外的这个变量就构成了闭包。“闭包”的概念很难理解,我觉得是因为你乍一听上面这段话,就算明白了什么意思,也想不出有什么用。请看下面这个例子(来自《Flunt Python》):

有一个商品,它的价格每天都会变动。我们想要求出它的平均价格(其实这个例子用Python的生成器更加方便)。

averager函数和series构成了闭包。虽然make_averager函数执行一次之后退出了,但是函数内部的变量series并没有被销毁,多次调用avg发现series确实是之前的状态。

假如说不用闭包这个特性,使用面向对象来实现,代码要写成这样:

从中可以体会到,其实面向对象的中对象的概念,包含函数和属性。而函数里面使用的对象的属性,而不是函数内的局部变量,这就是有闭包的支持。属性和函数形成了闭包,属性就不会被销毁。闭包可以引用函数外的变量,但是这个变量又可以不是全局变量,从而对外隐藏了内部的一个状态,从而即达到了维持一个状态的目的,又做到了封装。我想,这就是为什么“闭包”是面向对象的基础吧。

2018年1月3日更新:我又有了一个想法,上面这个例子是“不用闭包这个特性”,为了体会一下闭包的“封装”功能,假设我们“没有闭包”这个特性,那么第一个例子的代码中,内部函数将不能访问外部函数的局部变量,因为没有了“闭包”这个通道。按照Python寻找变量的规则(大多数编程语言的规则)会到上一层函数->全局变量这个顺序查找。即,我们的代码将写成这样:

因为averager函数内用到了一个内部的状态变量,这个变量要独立于函数调用的生命周期,所以如果没有闭包,我们只要在每次使用averager函数之前创建一个变量series供它使用。这样即破坏了函数封装的作用,又失去了函数黑箱调用的方便性。

有关Python中闭包的实现,因为Python有“一等函数”的特性(函数是对象),“自由变量”保存在函数的__code__.co_freevars中,然后在avg.__closure__中保存了cell对象,对应每一个co_freevarscell对象中的cell_content中保存了真实的值。

Python的这种实现有一个需要特别注意的陷阱。这种绑定闭包的方式是“迟绑定的”,这意味着,闭包绑定的值只有在函数真正运行的时候才去查询。在讨论for循环作用域的时候,Python邮件列表的邮件提到这段代码:

这段代码的执行结果是10个9,而不是0-9.因为只有lambda函数执行的时候才去查询i的值,这个时候i已经变成了9.一个“不太优美”的解决方法是使用默认参数立即绑定 lst.append(lambda i=i: i)。提醒一下,造成这个陷阱的原因不是Python的Lambda函数,lambda并没有什么特殊的,真正的原因是Python闭包的迟绑定实现方式。

很多博客将“闭包”和“匿名函数”混为一谈,通过这篇文章,显然这不是等价的。只不过只有是嵌套在其他函数中的函数才可能需要处理不是全局作用域的外部变量,而这种函数通常是匿名函数罢了。

 

象棋与围棋

《Fluent Python》这本书非常值得读,此书不仅讨论Python,经常结合其他编程语言讨论一些语言设计上的问题。读此书就和一位经验老道的开发者聊天一样,非常尽兴。

今天在书上(16章协程)看到这样一段话,很震撼:

对编程语言来说,关键字的作用是建立控制流程和表达式计算的基本规则。

语言的关键字像是棋盘游戏中的棋子。对国际象棋来说,关键字是王、后、车、马、象兵;对围棋来说,关键字是●。

国际象棋的棋手实现计划时,有六种类型的棋子可用;而围棋的棋手看起来只有一种类型的棋子可用。可是,在围棋的玩法中,相邻的棋子能构成更大更稳定的棋子,形状各异,不受束缚。围棋棋子的某些排列是不可摧毁的。围棋的表现力比国际象棋强。围棋的开局走法有 361 种,大约有 1e+170 个合规的位置;而国际象棋的开局走法有 20 种,有 1e+50 个位置。

如果为国际象棋添加一个新棋子,将带来颠覆性的改变;为编程语言添加一个新的关键字也是如此。因此,语言的设计者谨慎考虑引入新关键字是合理的。

Scheme 继承了 Lisp 的宏,允许任何人创建特殊的形式,为语言添加新的控制结构和计算规则。用户定义的这种标识符叫作“句法关键字”。Scheme R5RS 标准声称,“这门语言没有保留的标识符”(标准的第 45页),但是MIT/GNUScheme这种特殊的实现预定义了 34 个句法关键字,例如 if、lambda 和 definesyntax(用于创建新关键字的关键字)。

Python 像国际象棋,而 Scheme 像围棋。

现在,回到 Python 句法。我觉得 Guido 对关键字的态度过于保守了。关键字的数量应该少,添加新关键字可能会破坏大量代码,但是在循环中使用 else 揭示了一个递归问题:在更适合使用新关键字的地方重用现有的关键字。在 for、while 和 try 的上下文中,应该使用 then 关键字,而不该妄用 else。

在这个问题上,最严重的一点是重用 def。现在,这个关键字用于定义函数、生成器和协程,而这些对象之间的差异很大,不应该使用相同的句法声明。

引入 yield from 句法尤其让人失望。再次声明,我觉得真的应该为 Python 使用者提供新的关键字。更糟的是,这开启了新的趋势:把现有的关键字串起来,创建新的句法,而不添加描述性的合理关键字。恐怕有一天我们要苦苦思索 raise from lambda 是什么意思。

这段文字很有意思,作者抱怨的是,该引入新的关键字的时候却不引入,过于保守。

比如 for-else 语句就很让人困惑—— “for循环跑完了,这段else会不会执行呢?” 我每次看 for-else 代码,都得在心里来回算几遍。因为 else 表示转折,但是这里并没有转折的意思——“如果循环正常结束,就直接执行这段代码。” 我的方法是将这个 else 当成是 “then” 来理解,显然如果改成 “then”,就一点歧义也没有了。“循环结束,then 执行这个”。对于 “try…expect…else”来说,换成 then 更是好理解的多,“try…except…then”。

将人们已经熟悉的内容重复赋予新的意义,甚至组合一些关键字来使用,会更加让人困惑。

不过好在现在Python已经引入了async/await来定义协程(PEP 492),好的多了。

贴一下书中推荐的两个链接,很有意思:

  1. The Value Of Syntax? 这个讨论组叫做lambda-the-ultimate,是编程语言极客的聚集地
  2. What color is your function?