Dec
25
== 卸载OneDrive ==
C:\windows\syswow64\onedrivesetup.exe /uninstall
下载文件 (已下载 1174 次)
== 卸载 Cortana ==
下载文件 (已下载 1303 次)
== 禁用Win+S ==
下载文件 (已下载 1228 次)
== 从“此电脑”删除垃圾链接(图片、视频等) ==
下载文件 (已下载 1226 次)
== 禁用锁屏界面 ==
组策略-》计算机配置-》管理模版-》控制面板-》个性化-》不显示锁屏
p.s. 周年更新以后这个方法失效了,得这么用:
本地安全策略 -> 软件限制策略 -> 新建路径规则, 路径填写“C:\Windows\SystemApps\Microsoft.LockApp_cw5n1h2txyewy”,安全级别“不允许”
== 禁止自动更新 ==
不让我设置禁止自动重启,那只好禁止自动更新了。
services.msc -> windows update -> 禁止
== windows图片查看器 ==
下载文件 (已下载 1206 次)
== 图片查看器背景色发黄 ==
显示设置->高级显示设置->显示适配器属性->颜色管理
设备:选择显示器,勾上“使用我对此设备的设置”,“添加(A)...",选择 “sRGB IEC61966-2.1”,"设置为默认配置"
== 垃圾APP ==
有些右键就卸载掉了(但实际上文件还在),这样删比较干净点。
打开Powershell执行:
Get-AppxPackage -name Microsoft.ZuneMusic | remove-appxpackage
Get-AppxPackage -name Microsoft.XboxApp | remove-appxpackage
Get-AppxPackage -name Microsoft.WindowsMaps | remove-appxpackage
Get-AppxPackage -name Microsoft.BingWeather | remove-appxpackage
Get-AppxPackage -name Microsoft.ZuneVideo | remove-appxpackage
Get-AppxPackage -name Microsoft.BingSports | remove-appxpackage
Get-AppxPackage -name Microsoft.3DBuilder | remove-appxpackage
p.s. 打包下载
下载文件 (已下载 1148 次)
C:\windows\syswow64\onedrivesetup.exe /uninstall

== 卸载 Cortana ==

== 禁用Win+S ==

== 从“此电脑”删除垃圾链接(图片、视频等) ==

== 禁用锁屏界面 ==
组策略-》计算机配置-》管理模版-》控制面板-》个性化-》不显示锁屏
p.s. 周年更新以后这个方法失效了,得这么用:
本地安全策略 -> 软件限制策略 -> 新建路径规则, 路径填写“C:\Windows\SystemApps\Microsoft.LockApp_cw5n1h2txyewy”,安全级别“不允许”
== 禁止自动更新 ==
不让我设置禁止自动重启,那只好禁止自动更新了。
services.msc -> windows update -> 禁止
== windows图片查看器 ==

== 图片查看器背景色发黄 ==
显示设置->高级显示设置->显示适配器属性->颜色管理
设备:选择显示器,勾上“使用我对此设备的设置”,“添加(A)...",选择 “sRGB IEC61966-2.1”,"设置为默认配置"
== 垃圾APP ==
有些右键就卸载掉了(但实际上文件还在),这样删比较干净点。
打开Powershell执行:
Get-AppxPackage -name Microsoft.ZuneMusic | remove-appxpackage
Get-AppxPackage -name Microsoft.XboxApp | remove-appxpackage
Get-AppxPackage -name Microsoft.WindowsMaps | remove-appxpackage
Get-AppxPackage -name Microsoft.BingWeather | remove-appxpackage
Get-AppxPackage -name Microsoft.ZuneVideo | remove-appxpackage
Get-AppxPackage -name Microsoft.BingSports | remove-appxpackage
Get-AppxPackage -name Microsoft.3DBuilder | remove-appxpackage
p.s. 打包下载

Dec
17
好早以前看到过这个项目,不过当时没有测试成功,昨天晚上看了一下,有点眉目了,先记录一点东西。
1. 项目主页:http://usbip.sourceforge.net/ ,但是这里实际上已经没有再维护了,内核模块已经merge到linux kernel(drivers/usb/usbip)。这里有提供经过ReactOS(这个神奇的项目)签名的windows驱动程序(windows下只有client,没有server),但是不知道四年过去了还能不能用……
2. ubuntu默认安装没有相关模块,需要安装 linux-image-extras ,然后 cd /lib/modules/[KERNEL_VER]/kernel/drivers/usb/usbip && sudo insmod *.ko
3. ubuntu源里的usbip包已经是很老的版本,无法利用新的内核模块,会提示“usbipd requires usbip_common_mod.ko and usbip.ko kernel modules”,实际上userspace tool也已经挪到kernel source里了,位于 linux-source/tools/usb/usbip,编译方法可参考该目录下的README文件,类似于:
但是我是直接下载的kernel source,没有安装在系统目录,所以在编译的时候会提示 libsrc/usbip_common.h 找不到 linux/usbip.h ,解决方法很简单,把那一行改为 #include "usbip.h" ,然后把 linux-source/include/uapi/linux/usbip.h 复制到 libsrc 目录,再编译就行了(对,就是这么粗暴简单)。
4. update@2015-12-25:在linux下测试成功。windows的driver废弃太久,已经不能支持新版代码的协议了。
p.s. 还有一个收费的商业项目 usb-over-ethernet ,有free trial版本,但是没尝试过。
p.s. 2. 还有一个开源的项目SPICE,有一个 usbredir 模块 http://www.spice-space.org/page/UsbRedir ,不过好像还不完善。
1. 项目主页:http://usbip.sourceforge.net/ ,但是这里实际上已经没有再维护了,内核模块已经merge到linux kernel(drivers/usb/usbip)。这里有提供经过ReactOS(这个神奇的项目)签名的windows驱动程序(windows下只有client,没有server),但是不知道四年过去了还能不能用……
2. ubuntu默认安装没有相关模块,需要安装 linux-image-extras ,然后 cd /lib/modules/[KERNEL_VER]/kernel/drivers/usb/usbip && sudo insmod *.ko
3. ubuntu源里的usbip包已经是很老的版本,无法利用新的内核模块,会提示“usbipd requires usbip_common_mod.ko and usbip.ko kernel modules”,实际上userspace tool也已经挪到kernel source里了,位于 linux-source/tools/usb/usbip,编译方法可参考该目录下的README文件,类似于:
引用
./autogen.sh
./configure --with-usbids-dir=/usr/share/misc/
sudo make install
./configure --with-usbids-dir=/usr/share/misc/
sudo make install
但是我是直接下载的kernel source,没有安装在系统目录,所以在编译的时候会提示 libsrc/usbip_common.h 找不到 linux/usbip.h ,解决方法很简单,把那一行改为 #include "usbip.h" ,然后把 linux-source/include/uapi/linux/usbip.h 复制到 libsrc 目录,再编译就行了(对,就是这么粗暴简单)。
4. update@2015-12-25:在linux下测试成功。windows的driver废弃太久,已经不能支持新版代码的协议了。
p.s. 还有一个收费的商业项目 usb-over-ethernet ,有free trial版本,但是没尝试过。
p.s. 2. 还有一个开源的项目SPICE,有一个 usbredir 模块 http://www.spice-space.org/page/UsbRedir ,不过好像还不完善。
Dec
6
友情提示:本文不一定适合阅读,如果执意要读,请备好晕车药。
== 题记 ==
"Metaclasses are deeper magic than 99% of users should ever worry about. If you wonder whether you need them, you don't."
-- Tim Peters
== 起因 ==
这句话听起来就很诱人,曾经试图去理解它,但是因为没有实际的需求,就因为烧脑子而放弃了。不妨摘录一段Python document里关于metaclass的概述,简直就是绕口令:
昨天心血来潮想写一个带class initializer的class,发现绕不过metaclass了,于是又翻出来看。
== 概述 ==
其实是要理解metaclass的本质,无非是要时刻牢记两点:1. Python中一切皆对象; 2. class也是一个对象,它的class就是metaclass。
举例来说:
其中第一个print很好理解:a是一个A的实例,有自己的id(其实就是内存地址)、a的class是A。
第二个print就有点烧脑子了:A是一个class,也有自己的id(因为A也是一个对象,虽然print出来的时候没有明确说),A的class是type。
而第三个就晕乎了:type是一个type,也有自己的id(因为type也是一个对象),type的class是type,也就是它自己。
再回想上面提到的两点:A是一个对象,它的class是metaclass。也就是说 type 是一个metaclass,而A类是type类的一个对象。
唉,本来想好好解释的,没想到还是说成绕口令了。算了,反正我懂了,继续。
== type ==
没有仔细了解type是什么的同学可能会以为type是一个函数:type(X)用于返回X的类对象。
然而并不完全是这样的:在python里,X(args)可能是调用一个函数,也可能是在实例化一个X的对象——而很不幸地,type(X)实际上是介于二者之间的一个调用:虽然type是一个class,但是它的__call__方法是存在的,于是python把它当成一个函数来调用,实际调用到了源码中的type_call;type_call调用了type.__new__试图初始化一个type类的实例,然而type.__new__(位于源码中的type_new函数)发现卧槽居然只有一个参数,于是就返回了这个参数的type(源码是这么写的:"return (PyObject *) Py_TYPE(x);",并没有生成新的对象)。也就是说,本来是个函数调用,里面却是要初始化一个对象,然而最后返回的却不是初始化的对象!尼玛那个特殊情况为毛不放到函数调用里面啊,开发者脑抽了吗!
感到脑抽的同学可以暂时忽略上面那段话,跟本文没太大关系。继续。
实际上type是在builtin模块中定义,指向源码中PyType_Type对象的一个引用:
这个PyType_Type又是个什么鬼?好吧,继续贴源码
注意2点:
0. PyType_Type,也就是python里的type,是在源码中生成的一个对象;这个对象的类型是PyTypeObject,所以它恰好又是一个类,至于你信不信,反正我信了。后面我把它叫做类对象,注意:不是类的对象,而是类本身是一个对象。
1. PyVarObject_HEAD_INIT递归引用了自己(PyType_Type)作为它的type(在源码中,指定某个对象的type为X,就是指定了它在python环境中的class为X),所以前面第三个print中可以看到,type(type) == type(哈哈哈,写绕口令真好玩)
2. 在PyType_Type的定义指定了 tp_init = type_init 和 tp_new = type_new 这两个属性值。这是两个函数,也位于源码中的Object/typeobject.c。
关于第2点,在Python document中关于__new__方法的说明里有详细的介绍,这里简单总结一下:在new一个对象的时候,只会调用这个class的__new__方法,它需要生成一个对象、调用这个对象的__init__方法对它进行初始化,然后返回这个对象。
好吧,我发现不得不把简单总结展开,否则确实说不清楚。
== 实例化 ==
这是一个很有意思的设计:把实例化的流程暴露给码农,意味着码农可以在对象的生成前、生成后返回前两个环节对这个对象进行修改(【甚至】在__new__方法中生成并返回的对象并没有强制要求一定是该class的实例!不过在document里建议,如果要覆盖__new__方法,那么【应当】返回这个class的父类的__new__方法返回的对象)。这里还有一个非常tricky的地方:虽然没有明确指定,但是__new__方法被硬编码为一个staticmethod(有兴趣的话可以去翻type_new函数),它的第一个参数是需要被实例化的class,其余参数则是需要传给__init__的参数。
说起来非常枯燥,还是举一个例子吧,就用document里给出的Singleton:
代码并不复杂,但是可能有点玄乎,需要理解一下那个cls参数,前面说了,它是需要被实例化的class,也就是说,最后一行实际执行的是:
而DbConnection的__new__方法直接继承于Singleton, 所以实际调用的是
主要注意的地方,在上面这段代码的第六行,Singleton是继承于object(这里特指python中的那个object对象),因此调用了object.__new__(DbConnection)来生成一个对象,生成过程位于C源码中的object_new函数(Objects/typeobject.c),它会将新生成对象的type指定为DbConnection,然后直接返回。
Singleton.__new__在拿到了生成的DbConnection实例以后,将它保存在了DbConnection类的__it__属性中,然后对该实例进行初始化,最后返回。
可以看到,任何继承于Singleton类的子类,只要不覆盖其__new__方法,每个类永远只会被实例化一次。
好了,第2点暂告一段落,接下来回归正题,尼玛我都快忘了要讲的是metaclass啊。
== metaclass ==
还记的上面可以暂时忽略的那段话吗?type(X)是试图实例化type对象,但是因为只有一个参数,所以源码中只是返回了X的类。而type的标准初始化参数应当有三个:class_name, bases, attributes。最前面那个"class A(object): pass",python解释器实际的流程是:
1. 解析这段代码,得知它需要创建一个【类对象】,这个类的名字叫做'A', 它的父类列表(用tuple表示)是 (object,),它的属性用一个dict来表示就是 {} 。
2. 查找用于生成这个类的metaclass。(终于讲到重点了有木有!)
查找过程比较蛋疼,位于Python/ceval.c : build_class函数,按顺序优先采用以下几个:
2.1 定义中使用 __metaclass__ 属性指定的(本例:没有)
2.2 如果有父类,使用第一个父类的 __class__ 属性,也就是父类的metaclass(本例:object的class,也就是type)
2.2.1 如果第一个父类没有 __class__ 属性,那就用父类的type(这是针对父类没有父类的情况)
2.3 使用当前Globals()中的 __metaclass__ 指定的(本例:没有,不过2.2里已经找到了)
2.4 使用PyClass_Type
注:2.2.1和2.4中提到了没有父类,或者父类没有父类的情形,这就是python中的old-style class,在python2.2之前所有的对象都是这样的,而2.2之后可以继承于object类,就变成了new-style class。这种设计保持了向后兼容。
3. 使用metaclass来创建这个A类。由于A类的class就是metaclass,所以这个过程其实就是实例化metaclass的过程。本例中找到的metaclass是type,所以最终python执行的相当于这一句:
再回想一下前面提到的实例化过程,实际上这一句分成两步: 1. 调用type.__new__(type, 'A', (object,), {})生成type的一个实例(也就是A类对象);2. 调用type.__init__(A, 'A', (object,), {}) 对A类对象进行初始化。注意:这里调用的是type.__init__,而不是A.__init__:因为A是type的一个实例。
流程终于解释完啦,不过我觉得还是举个栗子会比较好。就用我看到的那个有点二二的栗子吧:定义一个class,把它的所有属性都改成全大写的。我感觉这个栗子唯一的作用就是用来当栗子了。还好还有这个作用,否则连出生的机会都没有。
== 栗子 ==
直接上代码好了:
请不要说“说好的metaclass呢!怎么变成了一个函数!我摔!”,回顾一下最最前面提到的一点:everything is an object in python。upper_meta作为一个函数,它也是一个对象啊。而metaclass也不过就是个对象,并没有本质上的差别——只要它被call的时候能接受name, bases, attrs这三个参数并返回一个类对象就行了。duck-typing的语言用起来就是有这样的一种不可言状的酸爽感。
理解了这一点,这段代码就能理解了,upper_meta返回了一个type类的实例——也就是Foo类,并且可以看到print出来的属性里头只有HELLO而没有hello。
考虑到可能有人不满意,想看使用class来作为metaclass的情形,我就勉为其难换个姿势再举一下这个栗子(真累)。
写的太长了,换了一个短一点的oneliner,但是效果不变(其实我就是想炫一下,不服来咬我呀)。
这段代码虽然形式上跟前面的upper_meta函数不一样,但是本质是一样的:调用了upper_meta('Foo', (object,), {'hello': 'world'}),生成了一个新的名为Foo的类对象。
理论上,故事讲到这里应该结束了,然而我想说,压轴戏还没上呢。
== 压轴戏 ==
我要把这栗子举得更高更远,也更符合实际开发的需求:继承。
这段代码太简单了,但是埋在下面的逻辑却太复杂了。
它的输出并不是{'HI': 'there'}, 而是{'hi': 'there'}。你print Bar.HELLO, Bar.__metaclass__都能得到预期的输出,但是偏偏没有HI,只有hi。
为什么?这真是个烧脑细胞的事情。我已经把所有的逻辑都展现出来了,甚至还做了特别的标记。然而即便如此,想要把这个逻辑理顺,也是一件非常有挑战性的事情,幸好我已经想明白了:苦海无涯,回头是岸。啊呸,应该是——学海无涯苦作舟,不想明白不回头。
我想说“甚至还做了特别标记”这句话的意思是,我还给【甚至】这两个字做了特别标记:在__new__方法中生成并返回的对象并没有强制要求一定是该class的实例!
问题的关键就在这里:前面两个栗子中给出的upper_meta,返回的并不是upper_meta的实例,而是type的实例,而是type的实例,而是type的实例。重说三。
什么意思?再看看代码,最后return的是type(name, bases, attrs),也就是说,Foo类对象并不是upper_meta的实例,而是type的实例(也就是说:虽然指定并被使用的metaclass是upper_meta,但是最终创建出来的Foo类的metaclass是type)。不信你print type(Foo)试试,结果就是type,而不是upper_meta。
为什么这会导致继承于Foo类的Bar类不能由upper_meta来搭建?Bar.__metaclass__不还是upper_meta吗?
这个问题就没有那么困难了,有兴趣的同学可以自己试着分析一下,没兴趣的大概也不会有耐心看到这里吧。
Bar.__metaclass__并不是Bar的原生属性,而是继承于Foo的——所以在print Bar.__dict__的时候看不到__metaclass__。也就是说,在试图创建Bar时,attrs里并没有__metaclass__属性,所以并不会直接采用upper_meta。再回顾一下选择metaclass的顺序就可以发现,实际上在2.2里会选择Foo的metaclass——Foo的metaclass是type,而不是指定的upper_meta。
解决方法很简单:关键就是前面被特别标记了的【应当】返回这个class的父类的__new__方法返回的对象。具体到代码应当是这样:
新增的__init__方法并不是必须的,有兴趣的同学可以跟上面的栗子对比一下,由于前面返回的是type类的实例,调用到的是type.__init__;而这样正确的写法就会调用到upper_meta.__init__。(p.s. super也是烧脑细胞的东西,但用于解决钻石继承的问很有意思,有兴趣的同学可以看看Cooperative methods and "super")
果然很烧脑细胞吧。
关于metaclass的选择,还有另外一个坑:在metaclass 2.3提到了,找不到metaclass的情况下,会使用Globals()中定义的__metaclass__属性指定的元类来创建类,那么为什么下面的代码却没有生效呢?
== class initializer ==
回到我最初的需求:我需要创建带class initializer的类。为什么会有这样的需求?最常见的metaclass的应用场景是对数据库的封装。举例来说,我希望创建一个Table类,所有表都是继承于这个类,同时我还想给每一个表都设置一个缓存dict(使用主键作为key缓存查询结果)。一个很自然的想法是这样的:
可惜这是错的。从输出结果就能看出来,返回的是一个Grade对象,而不是预期的Student对象。原因很简单:子类们并不直接拥有_pk_cache ,它们访问的是Table的_pk_cache ,而该dict只被初始化了一次。
当然,我可以在每一个继承于Table的class里新增一句 _pk_cache = {},但是这样的实现太丑了,而且一不注意就会漏掉导致出错。
所以我需要一个class initializer,在class被创建的时候,给它新增一个_pk_cache 。
在搞清楚了metaclass之后,解决方法特别简单:
完。(终于完结了,我写了一整个下午啊...)
== 题记 ==
"Metaclasses are deeper magic than 99% of users should ever worry about. If you wonder whether you need them, you don't."
-- Tim Peters
== 起因 ==
这句话听起来就很诱人,曾经试图去理解它,但是因为没有实际的需求,就因为烧脑子而放弃了。不妨摘录一段Python document里关于metaclass的概述,简直就是绕口令:
引用
Terminology-wise, a metaclass is simply "the class of a class". Any class whose instances are themselves classes, is a metaclass. When we talk about an instance that's not a class, the instance's metaclass is the class of its class: by definition, x's metaclass is x.__class__.__class__. But when we talk about a class C, we often refer to its metaclass when we mean C.__class__ (not C.__class__.__class__, which would be a meta-metaclass; there's not much use for those although we don't rule them out).
昨天心血来潮想写一个带class initializer的class,发现绕不过metaclass了,于是又翻出来看。
== 概述 ==
其实是要理解metaclass的本质,无非是要时刻牢记两点:1. Python中一切皆对象; 2. class也是一个对象,它的class就是metaclass。
举例来说:
>>> class A(object): pass
...
>>> a = A()
>>> print (a, id(a), type(a))
(<__main__.A object at 0xb183d0>, 11633616, <class '__main__.A'>)
>>> print (A, id(A), type(A))
(<class '__main__.A'>, 11991040, <type 'type'>)
>>> print (type, id(type), type(type))
(<type 'type'>, 1891232, <type 'type'>)
...
>>> a = A()
>>> print (a, id(a), type(a))
(<__main__.A object at 0xb183d0>, 11633616, <class '__main__.A'>)
>>> print (A, id(A), type(A))
(<class '__main__.A'>, 11991040, <type 'type'>)
>>> print (type, id(type), type(type))
(<type 'type'>, 1891232, <type 'type'>)
其中第一个print很好理解:a是一个A的实例,有自己的id(其实就是内存地址)、a的class是A。
第二个print就有点烧脑子了:A是一个class,也有自己的id(因为A也是一个对象,虽然print出来的时候没有明确说),A的class是type。
而第三个就晕乎了:type是一个type,也有自己的id(因为type也是一个对象),type的class是type,也就是它自己。
再回想上面提到的两点:A是一个对象,它的class是metaclass。也就是说 type 是一个metaclass,而A类是type类的一个对象。
唉,本来想好好解释的,没想到还是说成绕口令了。算了,反正我懂了,继续。
== type ==
没有仔细了解type是什么的同学可能会以为type是一个函数:type(X)用于返回X的类对象。
然而并不完全是这样的:在python里,X(args)可能是调用一个函数,也可能是在实例化一个X的对象——而很不幸地,type(X)实际上是介于二者之间的一个调用:虽然type是一个class,但是它的__call__方法是存在的,于是python把它当成一个函数来调用,实际调用到了源码中的type_call;type_call调用了type.__new__试图初始化一个type类的实例,然而type.__new__(位于源码中的type_new函数)发现卧槽居然只有一个参数,于是就返回了这个参数的type(源码是这么写的:"return (PyObject *) Py_TYPE(x);",并没有生成新的对象)。也就是说,本来是个函数调用,里面却是要初始化一个对象,然而最后返回的却不是初始化的对象!尼玛那个特殊情况为毛不放到函数调用里面啊,开发者脑抽了吗!
感到脑抽的同学可以暂时忽略上面那段话,跟本文没太大关系。继续。
实际上type是在builtin模块中定义,指向源码中PyType_Type对象的一个引用:
//位于Python/bltinmodule.c
PyObject * _PyBuiltin_Init(void)
{
...
SETBUILTIN("type", &PyType_Type);
...
}
PyObject * _PyBuiltin_Init(void)
{
...
SETBUILTIN("type", &PyType_Type);
...
}
这个PyType_Type又是个什么鬼?好吧,继续贴源码
//位于Objects/typeobject.c
PyTypeObject PyType_Type = {
PyVarObject_HEAD_INIT(&PyType_Type, 0)
"type", /* tp_name */
...
type_init, /* tp_init */
0, /* tp_alloc */
type_new, /* tp_new */
...
};
PyTypeObject PyType_Type = {
PyVarObject_HEAD_INIT(&PyType_Type, 0)
"type", /* tp_name */
...
type_init, /* tp_init */
0, /* tp_alloc */
type_new, /* tp_new */
...
};
注意2点:
0. PyType_Type,也就是python里的type,是在源码中生成的一个对象;这个对象的类型是PyTypeObject,所以它恰好又是一个类,至于你信不信,反正我信了。后面我把它叫做类对象,注意:不是类的对象,而是类本身是一个对象。
1. PyVarObject_HEAD_INIT递归引用了自己(PyType_Type)作为它的type(在源码中,指定某个对象的type为X,就是指定了它在python环境中的class为X),所以前面第三个print中可以看到,type(type) == type(哈哈哈,写绕口令真好玩)
2. 在PyType_Type的定义指定了 tp_init = type_init 和 tp_new = type_new 这两个属性值。这是两个函数,也位于源码中的Object/typeobject.c。
关于第2点,在Python document中关于__new__方法的说明里有详细的介绍,这里简单总结一下:在new一个对象的时候,只会调用这个class的__new__方法,它需要生成一个对象、调用这个对象的__init__方法对它进行初始化,然后返回这个对象。
好吧,我发现不得不把简单总结展开,否则确实说不清楚。
== 实例化 ==
这是一个很有意思的设计:把实例化的流程暴露给码农,意味着码农可以在对象的生成前、生成后返回前两个环节对这个对象进行修改(【甚至】在__new__方法中生成并返回的对象并没有强制要求一定是该class的实例!不过在document里建议,如果要覆盖__new__方法,那么【应当】返回这个class的父类的__new__方法返回的对象)。这里还有一个非常tricky的地方:虽然没有明确指定,但是__new__方法被硬编码为一个staticmethod(有兴趣的话可以去翻type_new函数),它的第一个参数是需要被实例化的class,其余参数则是需要传给__init__的参数。
说起来非常枯燥,还是举一个例子吧,就用document里给出的Singleton:
class Singleton(object):
def __new__(cls, *args, **kwargs):
it = cls.__dict__.get("__it__")
if it is not None:
return it
cls.__it__ = it = object.__new__(cls) #注意
it.__init__(*args, **kwargs)
return it
def __init__(self, *args, **kwargs):
pass
class DbConnection(Singleton):
def __init__(self, db_config):
self._connection = AnyHowToConnectBy(db_config)
conn = new DbConnection(db_config)
def __new__(cls, *args, **kwargs):
it = cls.__dict__.get("__it__")
if it is not None:
return it
cls.__it__ = it = object.__new__(cls) #注意
it.__init__(*args, **kwargs)
return it
def __init__(self, *args, **kwargs):
pass
class DbConnection(Singleton):
def __init__(self, db_config):
self._connection = AnyHowToConnectBy(db_config)
conn = new DbConnection(db_config)
代码并不复杂,但是可能有点玄乎,需要理解一下那个cls参数,前面说了,它是需要被实例化的class,也就是说,最后一行实际执行的是:
DbConnection.__new__(DbConnection, db_config)
而DbConnection的__new__方法直接继承于Singleton, 所以实际调用的是
Singleton.__new__(DbConnection, db_config)
主要注意的地方,在上面这段代码的第六行,Singleton是继承于object(这里特指python中的那个object对象),因此调用了object.__new__(DbConnection)来生成一个对象,生成过程位于C源码中的object_new函数(Objects/typeobject.c),它会将新生成对象的type指定为DbConnection,然后直接返回。
Singleton.__new__在拿到了生成的DbConnection实例以后,将它保存在了DbConnection类的__it__属性中,然后对该实例进行初始化,最后返回。
可以看到,任何继承于Singleton类的子类,只要不覆盖其__new__方法,每个类永远只会被实例化一次。
好了,第2点暂告一段落,接下来回归正题,尼玛我都快忘了要讲的是metaclass啊。
== metaclass ==
还记的上面可以暂时忽略的那段话吗?type(X)是试图实例化type对象,但是因为只有一个参数,所以源码中只是返回了X的类。而type的标准初始化参数应当有三个:class_name, bases, attributes。最前面那个"class A(object): pass",python解释器实际的流程是:
1. 解析这段代码,得知它需要创建一个【类对象】,这个类的名字叫做'A', 它的父类列表(用tuple表示)是 (object,),它的属性用一个dict来表示就是 {} 。
2. 查找用于生成这个类的metaclass。(终于讲到重点了有木有!)
查找过程比较蛋疼,位于Python/ceval.c : build_class函数,按顺序优先采用以下几个:
2.1 定义中使用 __metaclass__ 属性指定的(本例:没有)
2.2 如果有父类,使用第一个父类的 __class__ 属性,也就是父类的metaclass(本例:object的class,也就是type)
2.2.1 如果第一个父类没有 __class__ 属性,那就用父类的type(这是针对父类没有父类的情况)
2.3 使用当前Globals()中的 __metaclass__ 指定的(本例:没有,不过2.2里已经找到了)
2.4 使用PyClass_Type
注:2.2.1和2.4中提到了没有父类,或者父类没有父类的情形,这就是python中的old-style class,在python2.2之前所有的对象都是这样的,而2.2之后可以继承于object类,就变成了new-style class。这种设计保持了向后兼容。
3. 使用metaclass来创建这个A类。由于A类的class就是metaclass,所以这个过程其实就是实例化metaclass的过程。本例中找到的metaclass是type,所以最终python执行的相当于这一句:
type('A', (object,), {})
再回想一下前面提到的实例化过程,实际上这一句分成两步: 1. 调用type.__new__(type, 'A', (object,), {})生成type的一个实例(也就是A类对象);2. 调用type.__init__(A, 'A', (object,), {}) 对A类对象进行初始化。注意:这里调用的是type.__init__,而不是A.__init__:因为A是type的一个实例。
流程终于解释完啦,不过我觉得还是举个栗子会比较好。就用我看到的那个有点二二的栗子吧:定义一个class,把它的所有属性都改成全大写的。我感觉这个栗子唯一的作用就是用来当栗子了。还好还有这个作用,否则连出生的机会都没有。
== 栗子 ==
直接上代码好了:
def upper_meta(name, bases, attrs):
new_attrs = {}
for name, value in attrs.items():
if not name.startswith('__'):
new_attrs[name.upper()] = value
else:
new_attrs[name] = value
return type(name, bases, new_attrs)
class Foo(object):
__metaclass__ = upper_meta
hello = 'world'
print Foo.__dict__
new_attrs = {}
for name, value in attrs.items():
if not name.startswith('__'):
new_attrs[name.upper()] = value
else:
new_attrs[name] = value
return type(name, bases, new_attrs)
class Foo(object):
__metaclass__ = upper_meta
hello = 'world'
print Foo.__dict__
请不要说“说好的metaclass呢!怎么变成了一个函数!我摔!”,回顾一下最最前面提到的一点:everything is an object in python。upper_meta作为一个函数,它也是一个对象啊。而metaclass也不过就是个对象,并没有本质上的差别——只要它被call的时候能接受name, bases, attrs这三个参数并返回一个类对象就行了。duck-typing的语言用起来就是有这样的一种不可言状的酸爽感。
理解了这一点,这段代码就能理解了,upper_meta返回了一个type类的实例——也就是Foo类,并且可以看到print出来的属性里头只有HELLO而没有hello。
考虑到可能有人不满意,想看使用class来作为metaclass的情形,我就勉为其难换个姿势再举一下这个栗子(真累)。
class upper_meta(type):
def __new__(cls, name, bases, attrs):
attrs = dict([(n if n.startswith('__') else n.upper(), v) for n, v in attrs.items()])
return type(name, bases, attrs)
def __new__(cls, name, bases, attrs):
attrs = dict([(n if n.startswith('__') else n.upper(), v) for n, v in attrs.items()])
return type(name, bases, attrs)
写的太长了,换了一个短一点的oneliner,但是效果不变(其实我就是想炫一下,不服来咬我呀)。
这段代码虽然形式上跟前面的upper_meta函数不一样,但是本质是一样的:调用了upper_meta('Foo', (object,), {'hello': 'world'}),生成了一个新的名为Foo的类对象。
理论上,故事讲到这里应该结束了,然而我想说,压轴戏还没上呢。
== 压轴戏 ==
我要把这栗子举得更高更远,也更符合实际开发的需求:继承。
class Bar(Foo):
hi = 'there'
print Bar.__dict__
hi = 'there'
print Bar.__dict__
这段代码太简单了,但是埋在下面的逻辑却太复杂了。
它的输出并不是{'HI': 'there'}, 而是{'hi': 'there'}。你print Bar.HELLO, Bar.__metaclass__都能得到预期的输出,但是偏偏没有HI,只有hi。
为什么?这真是个烧脑细胞的事情。我已经把所有的逻辑都展现出来了,甚至还做了特别的标记。然而即便如此,想要把这个逻辑理顺,也是一件非常有挑战性的事情,幸好我已经想明白了:苦海无涯,回头是岸。啊呸,应该是——学海无涯苦作舟,不想明白不回头。
我想说“甚至还做了特别标记”这句话的意思是,我还给【甚至】这两个字做了特别标记:在__new__方法中生成并返回的对象并没有强制要求一定是该class的实例!
问题的关键就在这里:前面两个栗子中给出的upper_meta,返回的并不是upper_meta的实例,而是type的实例,而是type的实例,而是type的实例。重说三。
什么意思?再看看代码,最后return的是type(name, bases, attrs),也就是说,Foo类对象并不是upper_meta的实例,而是type的实例(也就是说:虽然指定并被使用的metaclass是upper_meta,但是最终创建出来的Foo类的metaclass是type)。不信你print type(Foo)试试,结果就是type,而不是upper_meta。
为什么这会导致继承于Foo类的Bar类不能由upper_meta来搭建?Bar.__metaclass__不还是upper_meta吗?
这个问题就没有那么困难了,有兴趣的同学可以自己试着分析一下,没兴趣的大概也不会有耐心看到这里吧。
Bar.__metaclass__并不是Bar的原生属性,而是继承于Foo的——所以在print Bar.__dict__的时候看不到__metaclass__。也就是说,在试图创建Bar时,attrs里并没有__metaclass__属性,所以并不会直接采用upper_meta。再回顾一下选择metaclass的顺序就可以发现,实际上在2.2里会选择Foo的metaclass——Foo的metaclass是type,而不是指定的upper_meta。
解决方法很简单:关键就是前面被特别标记了的【应当】返回这个class的父类的__new__方法返回的对象。具体到代码应当是这样:
class upper_meta(type):
def __new__(cls, name, bases, attrs):
attrs = dict([(n if n.startswith('__') else n.upper(), v) for n, v in attrs.items()])
return super(upper_meta, cls).__new__(cls, name, bases, attrs)
def __init__(cls, name, bases, attrs):
print >>sys.stderr, 'in upper_meta.__init__' #FOR TEST ONLY
def __new__(cls, name, bases, attrs):
attrs = dict([(n if n.startswith('__') else n.upper(), v) for n, v in attrs.items()])
return super(upper_meta, cls).__new__(cls, name, bases, attrs)
def __init__(cls, name, bases, attrs):
print >>sys.stderr, 'in upper_meta.__init__' #FOR TEST ONLY
新增的__init__方法并不是必须的,有兴趣的同学可以跟上面的栗子对比一下,由于前面返回的是type类的实例,调用到的是type.__init__;而这样正确的写法就会调用到upper_meta.__init__。(p.s. super也是烧脑细胞的东西,但用于解决钻石继承的问很有意思,有兴趣的同学可以看看Cooperative methods and "super")
果然很烧脑细胞吧。
关于metaclass的选择,还有另外一个坑:在metaclass 2.3提到了,找不到metaclass的情况下,会使用Globals()中定义的__metaclass__属性指定的元类来创建类,那么为什么下面的代码却没有生效呢?
def __metaclass__(name, bases, attrs):
attrs = dict([(n if n.startswith('__') else n.upper(), v) for n, v in attrs.items()])
return type(name, bases, attrs)
class Foo(object):
hello = 'world'
print Foo.__dict__
attrs = dict([(n if n.startswith('__') else n.upper(), v) for n, v in attrs.items()])
return type(name, bases, attrs)
class Foo(object):
hello = 'world'
print Foo.__dict__
== class initializer ==
回到我最初的需求:我需要创建带class initializer的类。为什么会有这样的需求?最常见的metaclass的应用场景是对数据库的封装。举例来说,我希望创建一个Table类,所有表都是继承于这个类,同时我还想给每一个表都设置一个缓存dict(使用主键作为key缓存查询结果)。一个很自然的想法是这样的:
class Table(object):
_pk_cache = {}
@classmethod
def cache(cls, obj):
cls._pk_cache[obj.pkey()] = obj;
@classmethod
def findByPk(cls, pkey):
return cls._pk_cache[pkey]
def __init__(self, pkey, args):
self._pkey = pkey
self._args = args
type(self).cache(self)
def pkey(self):
return self._pkey
def __repr__(self):
return type(self).__name__ + ':' + repr(self._args)
class Student(Table):
pass
class Grade(Table):
pass
s1 = Student(1, 's1')
g1 = Grade(1, 'g1')
print Student.findByPk(1)
_pk_cache = {}
@classmethod
def cache(cls, obj):
cls._pk_cache[obj.pkey()] = obj;
@classmethod
def findByPk(cls, pkey):
return cls._pk_cache[pkey]
def __init__(self, pkey, args):
self._pkey = pkey
self._args = args
type(self).cache(self)
def pkey(self):
return self._pkey
def __repr__(self):
return type(self).__name__ + ':' + repr(self._args)
class Student(Table):
pass
class Grade(Table):
pass
s1 = Student(1, 's1')
g1 = Grade(1, 'g1')
print Student.findByPk(1)
可惜这是错的。从输出结果就能看出来,返回的是一个Grade对象,而不是预期的Student对象。原因很简单:子类们并不直接拥有_pk_cache ,它们访问的是Table的_pk_cache ,而该dict只被初始化了一次。
当然,我可以在每一个继承于Table的class里新增一句 _pk_cache = {},但是这样的实现太丑了,而且一不注意就会漏掉导致出错。
所以我需要一个class initializer,在class被创建的时候,给它新增一个_pk_cache 。
在搞清楚了metaclass之后,解决方法特别简单:
class TableInitializer(type):
def __new__(cls, name, bases, attrs):
attrs['_pk_cache'] = {}
return super(TableInitializer, cls).__new__(cls, name, bases, attrs)
class Table(object):
__metaclass__ = TableInitializer
... #以下不变
def __new__(cls, name, bases, attrs):
attrs['_pk_cache'] = {}
return super(TableInitializer, cls).__new__(cls, name, bases, attrs)
class Table(object):
__metaclass__ = TableInitializer
... #以下不变
完。(终于完结了,我写了一整个下午啊...)
Dec
4
今天要实现一个简单的功能:按需给出长度为n的空格串。
如果是用Python,我就直接用这一句来解决了,既简单又直观:
可惜PHP就是不好使,只好写一个函数。最简单的办法当然是一个循环,但是看起来太蠢了,效率也低(虽然可以用一个数组缓存来解决),看起来也不帅。所以试着写了一个递归的版本(其实也应该要用数组来缓存提高效率,但是以下代码省略了相关逻辑,看起来清晰一点):
看起来酷多了对不对?
不对……因为一调用就递归到死啊魂淡。什么破玩意,连这种代码也不能一次跑对,简直让我对自己的能力产生了怀疑。
好吧,那就调试一下。
printf竟然输出了"3 = 1 + 1",这尼玛我都要开始怀疑人生了,什么鬼!
经过各种折腾,最后不知为什么放弃治疗,用了大概这样的代码(说真的,我在运行之前都觉得这是徒劳):
输出:
卧槽……
卧槽……
卧槽……
重说三。
当时已经接近崩溃了,于是到Google去试图寻找一点安慰,“php int division get float”返回给我的第一条,竟然是一个StackoverFlow的问题:How do I get a float value when dividing two integers? (PHP),提问者表示他用 12 / 13 得到的结果是 1 。SF的大神们纷纷出招,甚至还有人回答:
我看着左边的-1,深藏功与名。
然后继续解决问题。我想知道为什么我这儿两个整数一除就变成了浮点数,然而试了几个不同的环境,都是一样的,看起来倒了这血霉的似乎不是我一个。
查来查去也没查出什么来,于是我鼓起勇气、抛弃羞耻心,找到了一个我觉得我这辈子都不会需要打开的页面:
PHP:算术运算符 - http://php.net/manual/zh/language.operators.arithmetic.php
这个页面标题里的第一句话是“还记得学校里学到的基本数学知识吗?就和它们一样。”。
接下来是一个表格,列出了负号、加、减、乘、除、取余的说明。
表格后面还跟了一句话:
除法运算符总是返回浮点数。
除法运算符总是返回浮点数。
除法运算符总是返回浮点数。
尼玛……这说的学校居然TMD是小学啊……我了个大槽……谁家小学还教 “% 取余” 符号的!!谁家小学教的除法返回浮点数还带余数的!!
然而我还是不太相信,我用了八九年的PHP,我竟然不知道它没有整数除法?
翻,翻源码。
在 php5-src/Zend/zend_vm_def.h 可以看到 除法操作符(ZEND_DIV )的Handler,调用的是 fast_div_function ,这个函数里面有一段被注释掉的老的实现,目前实际调用的是 div_function ,实际上都是一样的,截取后者的一部分:
真的什么都不用说了。
EDIT UPDATE: 那个stackoverflow的问题,其实有人在问题的评论里指出了结果本来就是浮点数,不知道提问者怎么脑抽的。
EDIT UPDATE 2: PHP 7新增了一个 intdiv 函数,还真是非常PHP-Style的函数名和解决方法,看着就令人蛋疼。
如果是用Python,我就直接用这一句来解决了,既简单又直观:
' ' * n
可惜PHP就是不好使,只好写一个函数。最简单的办法当然是一个循环,但是看起来太蠢了,效率也低(虽然可以用一个数组缓存来解决),看起来也不帅。所以试着写了一个递归的版本(其实也应该要用数组来缓存提高效率,但是以下代码省略了相关逻辑,看起来清晰一点):
function spaces($n)
{
if ($n == 1)
return ' ';
return spaces($n / 2) . spaces($n - $n / 2);
}
{
if ($n == 1)
return ' ';
return spaces($n / 2) . spaces($n - $n / 2);
}
看起来酷多了对不对?
不对……因为一调用就递归到死啊魂淡。什么破玩意,连这种代码也不能一次跑对,简直让我对自己的能力产生了怀疑。
好吧,那就调试一下。
function spaces($n)
{
if ($n == 1)
return ' ';
printf("%d = %d + %d", $n, $n / 2, $n - $n / 2);
fgetc(STDIN);
return spaces($n / 2) . spaces($n - $n / 2);
}
{
if ($n == 1)
return ' ';
printf("%d = %d + %d", $n, $n / 2, $n - $n / 2);
fgetc(STDIN);
return spaces($n / 2) . spaces($n - $n / 2);
}
printf竟然输出了"3 = 1 + 1",这尼玛我都要开始怀疑人生了,什么鬼!
经过各种折腾,最后不知为什么放弃治疗,用了大概这样的代码(说真的,我在运行之前都觉得这是徒劳):
function spaces($n)
{
if ($n == 1)
return ' ';
var_dump($n / 2); var_dump($n - $n / 2);
fgetc(STDIN);
return spaces($n / 2) . spaces($n - $n / 2);
}
{
if ($n == 1)
return ' ';
var_dump($n / 2); var_dump($n - $n / 2);
fgetc(STDIN);
return spaces($n / 2) . spaces($n - $n / 2);
}
输出:
引用
float(1.5)
float(1.5)
float(1.5)
卧槽……
卧槽……
卧槽……
重说三。
当时已经接近崩溃了,于是到Google去试图寻找一点安慰,“php int division get float”返回给我的第一条,竟然是一个StackoverFlow的问题:How do I get a float value when dividing two integers? (PHP),提问者表示他用 12 / 13 得到的结果是 1 。SF的大神们纷纷出招,甚至还有人回答:
引用
Just use $value = (float)($x/$y);
我看着左边的-1,深藏功与名。
然后继续解决问题。我想知道为什么我这儿两个整数一除就变成了浮点数,然而试了几个不同的环境,都是一样的,看起来倒了这血霉的似乎不是我一个。
查来查去也没查出什么来,于是我鼓起勇气、抛弃羞耻心,找到了一个我觉得我这辈子都不会需要打开的页面:
PHP:算术运算符 - http://php.net/manual/zh/language.operators.arithmetic.php
这个页面标题里的第一句话是“还记得学校里学到的基本数学知识吗?就和它们一样。”。
接下来是一个表格,列出了负号、加、减、乘、除、取余的说明。
表格后面还跟了一句话:
除法运算符总是返回浮点数。
除法运算符总是返回浮点数。
除法运算符总是返回浮点数。
尼玛……这说的学校居然TMD是小学啊……我了个大槽……谁家小学还教 “% 取余” 符号的!!谁家小学教的除法返回浮点数还带余数的!!
然而我还是不太相信,我用了八九年的PHP,我竟然不知道它没有整数除法?
翻,翻源码。
在 php5-src/Zend/zend_vm_def.h 可以看到 除法操作符(ZEND_DIV )的Handler,调用的是 fast_div_function ,这个函数里面有一段被注释掉的老的实现,目前实际调用的是 div_function ,实际上都是一样的,截取后者的一部分:
ZEND_API int div_function(zval *result, zval *op1, zval *op2 TSRMLS_DC) /* {{{ */
{
zval op1_copy, op2_copy;
int converted = 0;
while (1) {
switch (TYPE_PAIR(Z_TYPE_P(op1), Z_TYPE_P(op2))) {
case TYPE_PAIR(IS_LONG, IS_LONG):
if (Z_LVAL_P(op2) == 0) {
zend_error(E_WARNING, "Division by zero");
ZVAL_BOOL(result, 0);
return FAILURE; /* division by zero */
} else if (Z_LVAL_P(op2) == -1 && Z_LVAL_P(op1) == LONG_MIN) {
/* Prevent overflow error/crash */
ZVAL_DOUBLE(result, (double) LONG_MIN / -1);
return SUCCESS;
}
if (Z_LVAL_P(op1) % Z_LVAL_P(op2) == 0) { /* integer */
ZVAL_LONG(result, Z_LVAL_P(op1) / Z_LVAL_P(op2));
} else {
ZVAL_DOUBLE(result, ((double) Z_LVAL_P(op1)) / Z_LVAL_P(op2));
}
return SUCCESS;
....
{
zval op1_copy, op2_copy;
int converted = 0;
while (1) {
switch (TYPE_PAIR(Z_TYPE_P(op1), Z_TYPE_P(op2))) {
case TYPE_PAIR(IS_LONG, IS_LONG):
if (Z_LVAL_P(op2) == 0) {
zend_error(E_WARNING, "Division by zero");
ZVAL_BOOL(result, 0);
return FAILURE; /* division by zero */
} else if (Z_LVAL_P(op2) == -1 && Z_LVAL_P(op1) == LONG_MIN) {
/* Prevent overflow error/crash */
ZVAL_DOUBLE(result, (double) LONG_MIN / -1);
return SUCCESS;
}
if (Z_LVAL_P(op1) % Z_LVAL_P(op2) == 0) { /* integer */
ZVAL_LONG(result, Z_LVAL_P(op1) / Z_LVAL_P(op2));
} else {
ZVAL_DOUBLE(result, ((double) Z_LVAL_P(op1)) / Z_LVAL_P(op2));
}
return SUCCESS;
....
真的什么都不用说了。
EDIT UPDATE: 那个stackoverflow的问题,其实有人在问题的评论里指出了结果本来就是浮点数,不知道提问者怎么脑抽的。
EDIT UPDATE 2: PHP 7新增了一个 intdiv 函数,还真是非常PHP-Style的函数名和解决方法,看着就令人蛋疼。
Nov
21
手头一个T03和Dell Venue 8 Pro,用的都是Intel BayTrail架构1.33GHz(Turbo 1.8GHz)级别的处理器(Z3735F/Z3740D),日常使用问题不大,但是大型任务比较困难,印象中MS Office比较臃肿沉重,所以给装了看起来好像很轻巧的(而且还是免费的)WPS Office。
但是发现这货这几年变化太大了,集成了所谓的“云服务”,以及各种广告、新闻插件,烦不胜烦,于是把wps的wtoolex目录(内含updatesef, wpsupdate, wpsnotify, desktoptips等无良exe)给删掉换成一个只有System用户有权限的空文件,消停了一段时间。
但是这还没完,今天突然发现桌面上出现了一个“爱淘宝”图标,链到
C:\Users\felix021\AppData\Roaming\software\ntatb\AlimamaAgent.exe
ntatb目录下还有个index.html,里面写着一句
想起早前看到的一句话:“如果商家为你提供免费服务,那你就不是他们的客户,而是他们的产品"。
简而言之就是,你被卖了。
知乎有一个问题讨论得很有意思,推荐阅读:
为什么说「免费的其实是最贵的」? http://www.zhihu.com/question/22084816
但是发现这货这几年变化太大了,集成了所谓的“云服务”,以及各种广告、新闻插件,烦不胜烦,于是把wps的wtoolex目录(内含updatesef, wpsupdate, wpsnotify, desktoptips等无良exe)给删掉换成一个只有System用户有权限的空文件,消停了一段时间。
但是这还没完,今天突然发现桌面上出现了一个“爱淘宝”图标,链到
C:\Users\felix021\AppData\Roaming\software\ntatb\AlimamaAgent.exe
ntatb目录下还有个index.html,里面写着一句
引用
meta name="author" content="kingsoft-WPS"/
想起早前看到的一句话:“如果商家为你提供免费服务,那你就不是他们的客户,而是他们的产品"。
简而言之就是,你被卖了。
知乎有一个问题讨论得很有意思,推荐阅读:
为什么说「免费的其实是最贵的」? http://www.zhihu.com/question/22084816
Oct
29
用框架的cache时发现,command和controller设置的cache互相之间居然是独立的,对于CFileCache,通过grep可以在 app/runtime/cache 下面看到,相同的key在两边set以后,会存在两个不同的文件里面:显然是在hash得到cache key的时候用了一个额外的串。
没办法,只能去翻Yii的源码,在 framework/cache/CCache.php 可以看到get/set方法是调用了 generateUniqueKey 来生成cache key;该方法用到的keyPrefix 是取值于 "Yii::app()->getId()" 。追过去,在 framework/base/CApplication.php 里面可以看到,这个getId()的实现是"crc32($this->getBasePath().$this->name)"。
于是去看了一下,果然 config 下面 main.php 和 console.php 配置的 name 不同,所以框架把它们认为是两个project。改成一样,就解决了。
但是……
还有一个隐藏得更深的坑:
缓存文件(例如runtime/cache/04/51/045113ce863440795dab03b29a4ec384.bin)里的内容是这样的:
也就是说,这个缓存在遇到两个md5相同的key时,无法做出区分!
这个只能看命了。
UPDATE:
缓存冲突问题,虽然不能彻底解决,但是可以通过简单的包装,把原先set的value换成 {'key': ..., 'value': value}, 然后在get的时候检查一下是否匹配,不匹配的话简单丢弃,这样至少不会遇到莫名其妙的BUG。
没办法,只能去翻Yii的源码,在 framework/cache/CCache.php 可以看到get/set方法是调用了 generateUniqueKey 来生成cache key;该方法用到的keyPrefix 是取值于 "Yii::app()->getId()" 。追过去,在 framework/base/CApplication.php 里面可以看到,这个getId()的实现是"crc32($this->getBasePath().$this->name)"。
于是去看了一下,果然 config 下面 main.php 和 console.php 配置的 name 不同,所以框架把它们认为是两个project。改成一样,就解决了。
但是……
还有一个隐藏得更深的坑:
缓存文件(例如runtime/cache/04/51/045113ce863440795dab03b29a4ec384.bin)里的内容是这样的:
引用
a:2:{i:0;s:26:"2015-10-29 18:28:03command";i:1;N;}
也就是说,这个缓存在遇到两个md5相同的key时,无法做出区分!
这个只能看命了。
UPDATE:
缓存冲突问题,虽然不能彻底解决,但是可以通过简单的包装,把原先set的value换成 {'key': ..., 'value': value}, 然后在get的时候检查一下是否匹配,不匹配的话简单丢弃,这样至少不会遇到莫名其妙的BUG。
Oct
29
转置二维数组:
utf-8字符串转为utf-8字符数组:
按显示宽度截取utf-8字符串
让进程在后台运行(detached process),出乎意料地简单
function transpose($array) {
array_unshift($array, null);
return call_user_func_array('array_map', $array);
}
array_unshift($array, null);
return call_user_func_array('array_map', $array);
}
utf-8字符串转为utf-8字符数组:
function utf8_str2arr($str)
{
preg_match_all("/./u", $str, $arr);
return $arr[0];
}
{
preg_match_all("/./u", $str, $arr);
return $arr[0];
}
按显示宽度截取utf-8字符串
function substr_width($str, $start, $width)
{
$arr = utf8_str2arr($str);
$arr_ret = [];
$i = 0;
while ($width > 0 and $i < count($arr))
{
$arr_ret[] = $arr[$start + $i];
if (strlen($arr_ret[$i]) == 1) //ascii,width=1
$width -= 1;
else
$width -= 2;
$i++;
}
if ($width < 0)
array_pop($arr_ret);
return join('', $arr_ret);
}
{
$arr = utf8_str2arr($str);
$arr_ret = [];
$i = 0;
while ($width > 0 and $i < count($arr))
{
$arr_ret[] = $arr[$start + $i];
if (strlen($arr_ret[$i]) == 1) //ascii,width=1
$width -= 1;
else
$width -= 2;
$i++;
}
if ($width < 0)
array_pop($arr_ret);
return join('', $arr_ret);
}
让进程在后台运行(detached process),出乎意料地简单
pclose(popen("nohup $cmd &", 'r'));
Oct
16
在Windows下用惯了Secure CRT的Clone Session功能,切换到mac下面,还真有点怀念。于是搜了一下,发现达到类似的效果倒是也不难。
1. mkdir ~/.ssh/cm_socket
2. 创建 ~/.ssh/config
Host *
ControlMaster auto
ControlPath ~/.ssh/cm_socket/%r@%h:%p
3. 之后只要是相同的 user@host:port 都会共用一个tcp connection,不需要再输入密码了。
p.s. 补充说明一下,通常来说,能登录服务器也就意味着可以建立信任关系,那么clone session的意义就减少了很多,但是仍然有两个优势:
1. 类似B公司的relay机器,必须使用token认证才能登录的,信任关系不能满足需求(Google Authenticator这样的两步认证方案也类似)
2. 对于延迟比较高的机器,直接复用tcp connection,可以减少相当多的时间(tcp三次握手、ssh认证的各种交互等)。
1. mkdir ~/.ssh/cm_socket
2. 创建 ~/.ssh/config
Host *
ControlMaster auto
ControlPath ~/.ssh/cm_socket/%r@%h:%p
3. 之后只要是相同的 user@host:port 都会共用一个tcp connection,不需要再输入密码了。
p.s. 补充说明一下,通常来说,能登录服务器也就意味着可以建立信任关系,那么clone session的意义就减少了很多,但是仍然有两个优势:
1. 类似B公司的relay机器,必须使用token认证才能登录的,信任关系不能满足需求(Google Authenticator这样的两步认证方案也类似)
2. 对于延迟比较高的机器,直接复用tcp connection,可以减少相当多的时间(tcp三次握手、ssh认证的各种交互等)。