默认用boost python包裹的C++对象是不支持pickle的,如果要用pickle.dumps(obj)的话那会提示错误

Pickling of "xxx" instances is not enabled.

这边吐槽一下在最新的代码里,给的reference链接其实还是不可用的。真正正确的是https://www.boost.org/doc/libs/1_74_0/libs/python/doc/html/reference/topics/pickle_support.html。

让你的class支持Pickle协议

若要让你的C++ Class支持Pickle协议,比较“正统”的方法是利用boost提供的boost::python::pickle_suite. 拿代码说话:

struct world_t {
    world_t(const string& country) { ... }
};

struct world_pickle_suite : boost::python::pickle_suite
  {
    static
    boost::python::tuple
    getinitargs(const world_t& w)
    {
      // [可选实现] 返回一个boost::python::tuple元组,其值被用来构造
      // 如果一个类的构造函数**不需要参数**的话,可以不用重载这个方法。
      return boost::python::make_tuple(w.country());
    }

    static
    boost::python::tuple
    getstate(const world_t& w)
    {
      // [可选实现] 如果对象的构造函数并不能完全恢复对象的状态,
      // 那么要用此函数返回其状态值
    }

    static
    void
    setstate(world_t& w, boost::python::tuple state)
    {
      // [可选实现] 如果对象的构造函数并不能完全恢复对象的状态,
      // 那么要用此函数把state里的状态恢复到w
    }
  };

boost::python::class_<world_t>("world", args<const std::string&>())
  // 定义world_t的boost python包装类
  .def_pickle(world_pickle_suite());
  // ...

需要注意的是,如果在suite里定义了getstate/setstate并且这个类的__dict__属性非空,boost是会报错的。一种可能的情况是你包装的类是一个子类,这时候还需要自己实现__getstate_manages_dict__属性。这边不赘述,可参考这里

原理简释

总所周知,Python3里若要在自定义的类里实现可Pickle,至少需要:

  • 它的__dict__属性是可pickle的,或;
  • 调用__getstate__()拿到的返回值是可pickle的,随后会调用__setstate__()方法在unpickle的时候恢复状态. 但在这些函数的背后,再稍微底层一点其实是通过__reduce__()方法实现的(更严格一点,它会先去寻找__reduce_ex__()是否可用)。这个方法简单来说就是返回一个tuple, 这个tuple有从构建对象到恢复状态所需的各种元素.

In fact, these methods are part of the copy protocol which implements the reduce() special method. -- https://docs.python.org/3/library/pickle.html

所以boost::python其实自己实现了一个对象的__reduce__()方法,在src/object/pickle_support.cpp里(源文件在这)。主要做了几件事

  1. 拿到当前对象的__class__属性(也就是class object);
  2. 检查对象是否有__safe_for_unpickling__属性。如果没有的话,就是本文最早提到的报错了;
  3. 检查对象是否有__getinitargs__()方法,若有则取值。没有的话,unpickle的时候按照无参数构造来处理;
  4. 检查对象是否有__getstate__()方法,若有则取值;
  5. 检查对象是否有__dict__属性,若有则会检查是否有__getstate_manages_dict__属性并获取这两个属性的值。
  6. 返回tuple(),内容依次为1-5里拿到的值。

可以看到,这个__reduce__()方法是遵循了Python里的object.__reduce__()协定的。当然了,如果某些使用者觉得继承一个suite类都觉得麻烦或者达不到自己的需求,也可以通过其他手段完成,只要记得自己整一个__reduce__()方法,只需满足Python的协定即可.

class_obj.attr("__reduce__") = boost::python::object(/*YOUR REDUCE FUNCTION*/);

再深一点~

如果你跟我一样无聊,那就来看看cpython自己想要__reduce__()的时候是怎么用到__getstate__()的吧.

cpython/Objects/typeobject.c这边有个函数叫

static PyObject* _common_reduce(PyObject *self, int proto)

,在pickle protocol>2时会调用static PyObject * reduce_newobj(PyObject *obj)方法,这个方法是Python里大多数默认对象的默认reduce逻辑。其中有一句

state = _PyObject_GetState(obj,
                !hasargs && !PyList_Check(obj) && !PyDict_Check(obj));

_PyObject_GetState里大致是从__dict__或者__slots__属性去获取对象的状态。

这边再往上一些还能看到有调用_PyObject_GetNewArguments()的,里面的逻辑就会去拿__getnewargs_ex__或是__getnewargs__属性的值作为__new__一个新对象时候的传入参数。这两个函数是Pickle协议文档里介绍的四个推荐覆盖的“高阶”函数之一。与之对应,Python并不希望一般人去自己写一个__reduce__()出来.

看到这里,再回想一下boost::python,pickle_suite里面的getinitargs()getstate()就分别对应到__getnewargs__()__getstate__()

题外话

迫于妹子生日要到了,今年手头又比较紧,所以打算做个微信公众号的小东西骗骗她😜 目前跑网站的服务器虽然配置不咋地,但是服务器上也就一个typecho的php项目要跑,平时访问量也门可罗雀,所以打算利用原有的服务器搭个反向代理到微信公众号的服务端。

目标

主域名example.com:

  • 80/443端口直接到原有的网站

子域名wechat.example.com

  • 80端口反向代理到本地服务http://localhost:8766/

实作

其实很简单,新建一个VirtualHost即可。 创建一个/etc/apache2/sites-available/wechat.conf内容如下:

<VirtualHost *:80>
        ServerName wechat.example.com

        ServerAdmin webmaster@localhost

        ProxyPass / http://localhost:8766/
        ProxyPassReverse / http://localhost:8766/

        ErrorLog ${APACHE_LOG_DIR}/error.log
        CustomLog ${APACHE_LOG_DIR}/access.log combined
</VirtualHost>

其中的关键是ProxyPass以及ProxyPassReverse.

完成之后,用a2ensite wechat启用这个VirtualHost, 再systemctl reload apache2便万事大吉。

背景

之前发现Jupyter Notebook下面,如果数据占用多的话,开多进程池会特别的慢。一开始以为是Python的锅,但是把multiprocessing.pool改成直接用os.fork()调用以后,问题依旧。照理来说unix下面使用fork开进程,会启用copy-on-write机制,内存增长并不是特别明显,但是实际在htop下面看内存仍然会在fork之后增长,并且和进程数量是线性相关的。

原因

随后想了老半天,想到了可能和页表有关系。查了一下,跑的服务器上huge page确实被禁用了(不知为何...).

fork的机制简单地说,是在创建新进程的时候把老的进程控制块(Process Control Block)里内存页表拷贝给了新的PCB——这边具体内存的信息是不拷贝的。由于当时Notebook跑的数据处理任务,里面已经用了不少内存(100GB+),所以拷贝的时候如果用默认的4KB内存页,将会有100 * 1024 * 1024 / 4 = 104,857,600个页表! 按典型一个页表项(Page Table Entry)大小4Bytes计算,一个进程开出来光页表会耗400MB内存.

ctmacao_prepaid.jpg

主要是看到了“不过期数据包”的噱头,这边顺便就买了一张中国电信(澳门)的漫游流量卡。注意一下这边评测的是预付费,不是上台卡,尽管学生卡近期特别流行,不过一想手头的联通香港卡还在协议期,就不折腾了,万一毁约了不大好。

激活

大陆地区可以激活的,不过当时实验的结果最好在CDMA模式下(关闭4G)或者随便打个电话出去就能收到要你实名激活的消息了。随后就是微信上实名制验证,用身份证即可,验证之后会让选一个流量套餐,这时候选那个单价最便宜的只能买一次的包就行,因为后面再也没机会选到那么便宜的包了~

不过期数据包

最吸引人的地方。具体套餐如下: ctm_prepaid_data_package.png

网速(限速)

是的,我体验到限速了。下面两张图是本地的电信卡和ctm卡的测速比较,使用Galaxy A90 5G手机测速: ctm_speedtest.jpg

chinatelecom_speed.jpg

测速的信号不是特别好,不过依然能看出来区别。这个就跟联通香港的上台卡形成了区别,要知道CuniqHK的上台卡在大陆漫游可是有5G权限的,4G下面也有满速可以跑。 但是这边是预付费卡,不确定上台卡情形如何。

苹果公司的厉害之处,从大学毕业用到现在,感觉有那么两个点

  1. 恰到好处的软硬件结合,一点不多一点不少
  2. 完善的生态环境,这包括软件生态和硬件生态

在我这个产品设计的外行来看,这两个是相辅相成的。第1点是保障产品设计出来既能得到消费者欢迎,又能节约成本。产品线的设计(包括同款产品不同的子品牌以及规格配置)都做过严苛的调查,保证最大程度榨取利润。第2点最大化了用户粘性和“离场”成本,并且能逐渐增加用户在苹果产品上的消费水平——例如越来越不够用的iCloud空间。

而且我越来越觉得设备只是平台,接入各种服务把你绑定得死死的平台。

鄙人目前的苹果设备以及服务:

  • 手机若干: 主力机,车上导航的机器
  • 平板若干:主力平板,给儿子放歌的平板,给老婆看剧的平板
  • 笔记本一台:非主力,因为好奇macOS买的
  • 手表一块:基本日常携带,主要就是看时间然后算“运动”
  • iCloud:基本上我的照片都在上面
  • Apple Music:一般性使用
  • Airpods: 一直在用

那么我的“离场成本”是什么,有什么能够替代呢?

手机:【难度★★★★★】主力机我尝试过换成三星(当时的Note10旗舰)、华为的(当时的P30Pro旗舰)。三星当时的感觉是都中规中矩,拍照其实已经很好了,后来换回苹果的原因是觉得还是有点卡;P30pro当时直接买的顶配(并且老婆大人也有一台),后来替换的原因是觉得屏幕素质太差了。这两台印象中,各用了半年。此外最不能忍的是安卓的通知系统,有人会说你挂个代理然后走FCM推送呀~怎么说呢,还是有一些App会让我很不爽。手机还有一个问题是我的相册,从iCloud导出再导入是一种解法,不过当时耗了我大半天时间先把iCloud照片的原件同步到电脑上然后再传到手机上....

备用机倒是无所谓,三星的A90 5G也被我拿来当导航机子使过,只不过后来买了SE2没地方用,就放车上了...

平板:【难度★★★】平板不是我的生产力工具,所以我只要屏幕好+看剧爽+偶尔能连个远程桌面就行了。这点其实我没什么粘性。为什么目前是苹果产品呢?因为懒得挑其他牌子了。

笔记本:【难度★】这个最好“脱瘾”,因为我的真正生产力是在Windows本上。这个MBA现在是“有空临幸一下”的状态。

手表【难度★★★】:其实三星也有Galaxy Watch可选,而且看同事的表也挺酷炫的,还有些运动狂人直接用专业的运动表——他们看不起AppleWatch的很,哈哈~ 但怎么说呢,苹果的运动三环,这个设计比较容易让人“上瘾”。我之前为了拿到2020年的全民运动日奖章,晚上8点去西湖骑了一趟...要知道自从小屁孩出生,我已经快半年没有骑行了。

iCloud【难度★★★★】:我的备忘录,待办事项,日程,照片全在上面。诚然其他厂牌的预装程序里肯定有“从iPhone”转移一项,但是iCloud龌龊的地方就在于他会使绊子让你不能轻松地完全把数据转过去。比如,照片本地存储自动压缩的功能。

Apple Music【难度★★】:我怀疑Android客户端做那么难用是故意的。另外最近发现Spotify免费又好用,再加上国区的Apple Music有稀奇古怪的缺歌,所以这个粘性不是很大。

Airpods【难度★★★】:用过其他竞品,这又是一个软硬件结合的产物,有点难戒除

目前觉得要换设备,只能先从手机开始“脱瘾”。且看今年苹果的新款是否有亮点把,Cook的模具用了那么多代也要换了?