西红柿鸡蛋面加狮子头

欣 下午12:56
我 下午12:58
看起来真好吃
欣 下午1:00
多少钱
我 下午1:02
15
欣 下午1:02
7
我 下午1:03
哇塞
欣 下午1:03
番茄鸡蛋面5
欣 下午1:03
狮子头面6.5
欣 下午1:03
另加番茄鸡蛋2
欣 下午1:04
我点了番茄鸡蛋面5
欣 下午1:04
问加狮子头多少钱
我 下午1:04
这不是13.5?
欣 下午1:04
答2
欣 下午1:04
于是我加了狮子头
欣 下午1:04
花了5+2
我 下午1:04
哦我明白了
我 下午1:04
真厉害
欣 下午1:04
我觉得师傅是个傻子
我 下午1:04
研究生就是厉害
我 下午1:05
狮子头3.5
欣 下午1:05
同样的我说狮子头面加番茄鸡蛋就是8.5
我 下午1:05
哈哈哈
 

调教Mac外接显示器(开启HiDPI)

我的Mac外接的Dell U2515H显示器,总觉得很模糊,用的是2048*1152分辨率。觉得Mac只对外接显示器开开了这么几个分辨率很奇怪。

后来发现,原来按住option键点击“缩放”,可以开启隐藏的分辨率。

于是选择了1080P用了一段时间,还挺满意的。直到……某天看到了同事的屏幕,明明是和我一样的显示器,怎么会好这么多!

原来,还有个叫做 HiDPI 的东西。

简单来说,开启 HiDPI 和不开启的情况下,外接显示器截图如下(效果可以代表像素点数)。

不开启 HiDPI 效果
开启 HiDPI 效果

简直是两个世界!

Mac HiDPI 开启方法

1. 关闭系统保护

具体操作是,重启电脑并按住Command+R,进入恢复模式,选择系统语言(仅仅会影响此次保护模式会话),然后选择工具(在最上面的bar)>终端。输入csrutil disable; reboot。更多参考这里

2. 覆盖显示器配置文件

发现一个很有用的项目,点击这里。可以生成显示器 HiDPI 的设置文件。如果你和我一样用的显示器型号是DELL U2515H,可以直接下载DisplayProductID-*.plist文件然后执行sudo cp ~/Downloads/DisplayProductID-d06e.plist /System/Library/Displays/Contents/Resources/Overrides/DisplayVendorID-10ac/DisplayProductID-d06e(注意,如果你没有按照第一步关闭系统保护,那么即使加了sudo也会得到Operation Permitted),重启搞定。如果不是,就要参考这个页面自己config一下显示器的配置文件。

OS X Catalina 用户需要看下文末的文件系统读写权限问题。

3. 设置显示器的分辨率

推荐使用RDM项目管理显示器的分辨率,可以从这里下载编译好的安装包。(2023年更新:macOS 13.4.1 以及以上,不需要 RDM 软件了,见文末)

RDM界面

推荐的分辨率是1920*1080.

关于第二步和第三步,不想这么麻烦可以直接使用SwitchResX这个软件,但是第一步是必须有的。虽然这个软件是收费的,但是听说可以无限试用。

另外,不知道是不是我的错觉,觉得开启之后显示器变得卡了一些。不过显示效果提升了一个档次。

可能遇到的问题

被其他配置文件覆盖。

升级了macOS High Sierra 10.13系统之后,HiDPI 效果没有了。重复操作一次之后也无效。于是怀疑我自定义的显示配置文件被覆盖了。删除其他配置文件,只保留一个之后,成功找回原来的分辨率 1920*1080(HiDPI)。

2019年11月06日更新:OS X Catalina 权限问题

在上面 “2. 覆盖显示器配置文件” 中,如果你的系统是 Catalina ,那么你执行 csrutil disable 也无法用 sudo 覆盖文件。会提示:Read-only file system。

原因如下:

解决方法是:执行 sudo mount -uw / 再执行上述的 cp 就可以了。解决方法来源。

2023年11月11日更新:macOS 13.4.1 以及以上,不需要 RDM 软件了

系统已经支持直接设置了。

第一步,在设置里面打开 Show resolutions as list:Settings > Display > Advanced > Show resolutions as list > Done. 如下图。

show resources as list

然后在设置里面就可以看到所有的分辨率设置选项了。跟 RDM 软件看到的一致。

更多的分辨率选择。

方法来源。

 

奇葩网站吐槽第二弹

很久之前写过一篇《吐槽一些神奇的政府网站》,但是工作中(目前的工作是爬虫)碰到的奇葩网站远远不止这些,后来在那篇文章陆续更新了一些。但是考虑到修改文章不会发送rss feed,而且文章也被更新的越来越长,所以这里拆出来第二弹再发一次吧。而且我相信之后我会碰到更新这种奇葩网站的……估计不知道要写到第几弹。以下按照更新顺序贴出来,文章就不需要承上启下的润色了:


20170616更新:日了狗了,今天又碰到一个神奇的网站,比如某个详情页面如下:

本来是没有什么奇怪的,但是我把url拼起来之后一直是404,然后发现,如果把中文的部分encode两次,就和目标url一样了……日……


20170619更新:日了狗,今天写一个xpath怎么写都不对,看了看发出去的请求,好嘛,都到站外去了。找了半天,终于找到了罪魁祸首。我使用一个标签的id定位的,结果发现,相同id的html标签在这个页面下竟然有五个(没想到吧……像我这么屌的还有四个……)


170627更新:笑死我了,这个网站每次请求都会从相应收到一段cookies,然后就加到请求上去。不是修改,而是一直加一直加,访问几个页面之后,页面就会显示400Bad Request,cookies too big,哈哈哈哈。之前也碰到一个,是post请求不断增加字段,重复的字段越来越多,请求越来越大的…… 这,这应该叫做“饼干泄露”(内存泄露)吧,哈哈


170717更新:在post请求中传SQL语句的……


170802更新:今天要抓一个发布开庭公告的网站,打开一看,很整齐!很规则!很开心有没有!

一看源代码,人都傻了……


170802更新:日了够了,PM给我一个url,结果我发现已经打不开了,但是从主页是可以点进去的。研究了一下,发现这个url里面包含一个session id……

http://www.gzthfy.gov.cn/pa2/wel_3g.seam;jsessionid=04FF3DF586DE3D26555A210796A93B30.nod3?ggbh=91fa6f637dd947819df4c4b45878b514&cid=134752


170802更新:此站通过发送一个AJAX获取下一页的数据,我使用Python模拟发出一样的AJAX,企图在url或者post请求中找到与页码相关的信息改一下。发现此站翻任何一页发送的请求都是一样的!

一毛一样!Form一样、url一样,甚至连他喵的cookie都是一样!

观察一番,发现页码信息是记录在session里面的!也就是说,form里面的乱七八糟的数据(并不知道有什么用)只表示两种信息:往上翻,往下翻。当前你的位置,记录在服务器上(如此反人类,请问管理员你怎么把第二页的url发给你的上司呢?)

还有这种操作。

 

理解Python的UnboundLocalError(Python的作用域)

今天写代码碰到一个百思不得解为什么会出错的代码,简化如下:

意图很明显,首先我定义了一个全局的x,在函数中,如果有特殊需要,就重新重新赋值一下x,否则就使用全局的x。

可以这段代码在运行的时候抛出这个Error:

UnboundLocalError: local variable ‘a’ referenced before assignment

研究了一番,觉得挺有意思的。而且这是一个比较常见的问题,在Stack Overflow的Python tag下面基本上是个周经问题。


出现赋值就是局部变量!

基本的原理很简单,在Python FAQ中提到了:

在Python中,如果变量仅仅是被引用而没有被赋值过,那么默认被视作全局变量。如果一个变量在函数中被赋值过,那么就被视作局部变量。

Effective Python也提到过:

Python是这样处理赋值操作的:如果变量在当前的作用域已经定义过,那么就会变成赋值操作。否则的话会在当前的作用域定义一个新的变量。(Assigning a value to a variable works differently. If the variable is already defined in the current scope, then it will just take on the new value. If the variable doesn’t exist in the current scope, then Python treats the assignment as a variable definition. The scope of the newly defined variable is the function that contains the assignment.)

重点强调一下,这里的被赋值过,指的是在函数体内任何地方被赋值过。无论是否会被执行到(比如在if语句中),甚至是变量引用之后再赋值(参考下面的代码),都被作为“被赋值过”,都变成了局部变量。

其实到这里这个问题的答案已经出来了,只要是在函数体内被赋值过,那么变量就是local的,任何赋值之前的操作都会出现一个RuntimeError。下面会深入解释一下。

赋值操作的编译过程(原理)

Python文档中有关赋值语句提到:

Assignment of an object to a single target is recursively defined as follows. If the target is an identifier (name):

  • If the name does not occur in a global statement in the current code block: the name is bound to the object in the current local namespace.
  • Otherwise: the name is bound to the object in the current global namespace.

就是说,如果赋值操作的变量没有用global声明,那么就将这个name绑定到局部名字空间,否则就绑定到全局名字空间。

我们可以使用symtable这个lib验证一下:

可以看到,x变量确实被绑定到了局部。使用dis库可以看到编译的代码:

其中,LOAD_FAST是从local的stack中读取变量名(LOAD_FAST对之后字节码的优化很重要)。由此可以看到,的确是在局部变量找x没有找到(前面并没有STORE_FAST操作),引发了UnboundLocalError。

所以我的理解是:Python编译建立抽象语法树的时候,根据语法书建立符号表,从语法书的函数体内决定符号是local的还是global(是否出现assignment语句),然后在编译其他语句生成字节码。

那么既然这样,为什么要等到运行的时候才报错,而不是编译的时候就报错呢?

参考下面的代码:

如果something_true(),x的赋值就会执行,那么代码不会抛异常。但是编译器并不会知道这个赋值语句会不会执行。换句话说,函数体内出现了赋值语句,但是Python编译过程无法得知赋值语句会不会执行到的。所以只要出现了赋值语句,就将变量视为局部。至于会不会出现未赋值就使用(UnboundLocalError),就运行看看了。

 Python为什么要这样处理?

这并不是缺陷,而是一个设计选择。

Python不要求声明变量,但是假设函数体内定义的变量都是局部变量。这比JavaScript好多了,JavaScript也不要求声明变量,但是如果不是var声明的变量,可能在不知情的情况下修改了全局变量。《Fluent Python》7.4

(PS:ES6的 let 也有了类似的机制,叫做“temporal dead zone”,参考

这应该很好理解,试想一下,如果在函数中引用了一个函数内不存在的变量,后面又进行了赋值。而Python将这个变量当做全局变量,那么可能隐式地给你覆盖了全局变量。这如果是debug起来肯定是个噩梦。

这种设计选择正是提现了Python的设计哲学:“Explicit is better than implicit.”

解决方法

前面已经提到了,显示地指定使用global就可以,这样即使出现赋值,也不会产生作为local的变量,而是去改变global的变量。

但是依然存在一个问题:

external的x既不是local,也不是global。这种情况应该使用Python3的nonlocal。这样Python不会在当前的作用域找x,会去上一层找。

可惜Python2不支持nonlocal,但是我们可以使用“闭包”来解决。其实思想就是,如果我们无法改变不可变的对象,就将这个对象变成可以改变的对象。

如上代码,x不是一个不可改变的int,而是一个可变的list对象。这样x[0] += 1就会变成一个赋值操作,而不会申请新的变量。

 

2018年8月30日更新:最近读《代码之髓》这本书,对 Python 的作用域以及它的行为有了新的理解。Python 是静态作用域的,而且变量无须声明,赋值即声明。像 Perl,JavaScript 这样的需要是需要声明的,比如带上 var 就是局部变量,否则就是全局变量。Python 这种赋值即声明的方式,好处就是我们在写的时候很爽,一般都符合我们的直觉。缺点就是在嵌套函数内部如果想要赋值,那么依据“赋值即声明”,我们就会创建新的变量,而不会去修改外部函数的变量。

与之类似的语言是 Ruby,在 Ruby 中同样“赋值即声明”,不过行为却与 Python 恰恰相反。

在 Ruby 中,如果嵌套方法,外部方法的变量在内部方法中依然视作外部方法的变量;如果在内部方法创建变量,那么只会存在于内部方法中,不会影响外部方法。通俗一点,如果内部方法对一个变量 a 赋值的话,如果外部方法有 a ,那么外部方法的 a 的值会被修改;否则,会在内部方法创建一个 a,内部方法结束之后,a 就不存在了。一下代码为例:

 

参考资料

  1. Understanding UnboundLocalError in Python
  2. Effective Python:Item 15: Know How Closures Interact with Variable Scope
 

使命召唤全系列游戏汇总

整理了一下使命召唤所有的游戏,可以这么理解,使命召唤火起来的时候基本是从4(也就是现代战争开始),此后Activision(美国动视)每年发布一款游戏,手下有三个小组负责了现代战争、二战、黑色行动三个不同的支线,每年发布一款。所以基本上是间隔发布。这是理解整个使命召唤游戏剧情的基本,不然三条线穿插在一起都乱掉了。

作品序号
年份和开发小组
现代战争
二战
黑色行动 独立
1
2003年10月29日
使命召唤
2
2005年10月25日
使命召唤2
3
2006年11日7日
使命召唤3
4
2007年11月7日
Infinity Ward
5
2008年11月11日
Treyarch
6
2009年11月10日
Infinity Ward
使命召唤:现代战争2
7
2010年11月19日
Treyarch
8
2011年11月8日
Sledgehammer Games、Infinity Ward、Raven Software
9
2012年11月13日
Treyarch
10(登录ps4)
2013年11月5日
Infinity Ward、Raven Software、Neversoft
11
2014年11月4日
Sledgehammer Games
12
2015年11月6日
Treyarch
13
2016年11月4日
Infinity Ward
14
2017年11月3日
来源:维基百科