博客迁移记录

之前在《决定转投 DigitalOcean》这篇文章中说了一下决定迁移网站的原因。 趁十一假期终于完成了博客的迁移工作,在此记录。

  1. Dump PostgreSQL data

    pg_dump -U laike9m -f dump.sql database1
    

    生成 425KB 的 dump.sql, 下载到本地,scp 到 DO machine。安装 PostgreSQL:

    sudo apt-get update
    sudo apt-get install postgresql postgresql-contrib
    

    创建用户和数据库,参考 how-to-install-and-use-postgresql-on-ubuntu-14-04。 还要给用户 laike9m 数据库的管理员权限。先切到 postgres 用户,然后参考 error-must-be-owner-of-language-plpgsql 给 laike9m 管理员权限。

  2. Load data

    psql -U laike9m database1 < dump.sql
    
  3. 配置虚拟环境
    安装 virtualenvwrapper,mkvirtualenv, 安装依赖。在安装之前先

    sudo apt-get install libpq-dev
    sudo apt-get install python3-dev
    

    因为要安装 psycopg2。

  4. 下载并修改代码
    在家目录创建 static, media 文件夹作为 STATIC_ROOTMEDIA_ROOT。 clone 代码,clone media 文件夹,按照新路径修改 settings.py, 下载 media 文件。设定密码环境变量(实际上这个用不到了)。
    之前的 Django 版本是 1.5,这次我想趁迁移升级成 1.8。参照官方的教程, 先把 south 从 INSTALLED_APPS 中去掉,然后在 css3two_blog 文件夹中

    mkdir migrations
    cd migrations
    touch __init__.py
    

    最后在根目录执行 python manage.py makemigrationspython manage.py migrate --fake-initial

    (blog)laike9m@laike9m1:~/Envs/blog/My_Blog$ python manage.py makemigrations
    Migrations for 'css3two_blog':
      0001_initial.py:
        - Create model BlogPost
        - Create model BlogPostImage
    (blog)laike9m@laike9m1:~/Envs/blog/My_Blog$ python manage.py migrate --fake-initial
    Operations to perform:
      Synchronize unmigrated apps: staticfiles, admindocs, messages, mytemplatetags, contact_form
      Apply all migrations: css3two_blog, contenttypes, taggit, admin, auth, sites, sessions
    Synchronizing apps without migrations:
      Creating tables...
        Running deferred SQL...
      Installing custom SQL...
    Running migrations:
      Rendering model states... DONE
      Applying contenttypes.0001_initial... FAKED
      Applying auth.0001_initial... FAKED
      Applying admin.0001_initial... FAKED
      Applying contenttypes.0002_remove_content_type_name... OK
      Applying auth.0002_alter_permission_name_max_length... OK
      Applying auth.0003_alter_user_email_max_length... OK
      Applying auth.0004_alter_user_username_opts... OK
      Applying auth.0005_alter_user_last_login_null... OK
      Applying auth.0006_require_contenttypes_0002... OK
      Applying taggit.0001_initial... FAKED
      Applying taggit.0002_auto_20150616_2121... OK
      Applying css3two_blog.0001_initial... FAKED
      Applying sessions.0001_initial... FAKED
      Applying sites.0001_initial... FAKED
    

    最后拷贝静态文件

    python manage.py collectstatic
    

    runserver 启动测试服务器,通过 ip 访问网站并执行各种操作,均没有出现问题。
    这里不得不赞一下 Django,原先以为 1.5 升级 1.8 将遇到各种问题,尤其是跨过了 1.7 这个内置 south 的重大改进版本,没想到居然顺利完成了。

  5. 安装配置 Nginx, uWSGI
    Nginx 我比较熟了,但是每次遇到 uWSGI 都很麻烦,这次也是。花了很长时间去解决 这个问题。后来发现在文档里已经有说明。
    uWSGI 配置文件 uwsgi.ini 如下

    [uwsgi]
    chdir           = /home/laike9m/Envs/blog/My_Blog
    module          = my_blog.wsgi:application
    home            = /home/laike9m/Envs/blog/
    master          = true
    processes       = 3
    socket          = /tmp/uwsgi.sock
    chmod-socket    = 777
    vacuum          = true
    logto           = /home/laike9m/uwsgi.log
    

    Nginx 配置在这里
    原本想直接开个 screen 来运行 uwsgi,后来发现用 upstart 更好。 下面是 uWSGI 的配置文件 /etc/init/uwsgi.conf:

    env DJANGO_DB_PASSWORD=
    env EMAIL_HOST_PASSWORD=
    env LC_ALL=en_US.UTF-8
    env LANG=en_US.UTF-8
    
    start on runlevel [2345]
    stop on runlevel [!2345]
    
    setuid laike9m
    setgid www-data
    
    exec /usr/local/bin/uwsgi --ini /home/laike9m/Envs/blog/My_Blog/uwsgi.ini
    

    这样之后用 sudo service uwsgi start/stop/restart 就能操作 uWSGI 了

  6. 添加 HTTPS
    证书还是用之前的,主要是修改 Nginx 配置。对 80 端口的访问要自动跳转。
    这里只贴关键部分的配置。

    server {
        listen 80;
        server_name  laike9m.com laike9m.com;
        return 301 https://$server_name$request_uri;
    }
    
    server {
        ...
        listen 443 ssl;
        server_name  laike9m.com laike9m.com;
        ssl_certificate /home/laike9m/files/laike9m_com/laike9m_com.crt;
        ssl_certificate_key /home/laike9m/files/laike9m_com/key;
        ...
    }
    


    基本就是这样了。

一点关于 iter 的历史

本文是另两篇文章的预备。想必很多人都知道 iterator 这个概念,也知道 Python 里有一个 __iter__ 方法。 这里想讲一些关于 iterator 的历史。

Iterator 这个概念在很多编程语言里都有,Python 引入 iterator 是在 2.2 版本,也就是 2001 年的时候。 Iterator 是用来干嘛的,维基百科已经写得很清楚:

In computer programming, an iterator is an object that enables a programmer to traverse a container, particularly lists.

那么问题来了,在引入 iterator 之前,Python 是怎么遍历,比如说,list 或者 dict 的呢?

先来看 list。实际上是依赖于 __getitem__ 方法。估计大部分人对这个方法并不熟悉,其实很简单:

>>> [1,2,3].__getitem__(1)
2

给一个 index,返回相应的元素,相当于调用 [1,2,3][1]
于是,只要定义了这一个方法,我们就可以遍历自定义的类了:

class MyClass:
    def __getitem__(self, index):
        if index > 3:
            raise IndexError("that's enough!")

        return index

for name in MyClass():
    print(name)

输出 shell 0 1 2 3 看见 IndexErrorfor in 就知道遍历到头了,所以并不会报错。实际上 2.2 之前的 for in 都是调用 __getitem__

再看 dict。在 2.2 之前,我们并不能 for x in some_dict 因为 dict 并没有定义 __getitem__。 想遍历 dict 的话只能这样: python for key in some_dict.keys(): print(some_dict[key]) 显然这样性能不会好,因为要先生成一个包含所有 key 的 list。

Python2.2 引入了 iterator,相关文档参见 What’s New in Python 2.2 - PEP 234: Iterators。总结起来有以下改进:

  1. for x in C 会默认调用 iter(C),如果 C 没有定义 __iter__ 方法,会使用 __getitem__
  2. dict(以及其它一些内置类型)现在实现了 __iter__ 方法,所以可以 for x in some_dict 了。这样遍历 dict 比原来快很多,因为不需要生成一个 key list;
  3. for in 默认调用 iter() 的好处在于,用户可以对遍历过程应该返回什么,如何返回有更强的控制。另一方面就是拓宽了 for in 的使用范围,因为并不是所有我们希望遍历的 object 都拥有有意义的 index。比如之前 MyClass 的例子,index 就没有含义,所以用 __getitem__ 实现并不合适。


这篇文章主要是讲历史。下一篇文章详细讲 iterator 要怎么用,最后一篇对三种实现遍历的方式进行比较。

参考资料:
https://en.wikipedia.org/wiki/Iterator
http://effbot.org/zone/python-for-statement.htm
https://docs.python.org/3/whatsnew/2.2.html#pep-234-iterators

开始学 Go 了

虽然早就想开始学,然而一直没有时间。正好之后也不面试了,全力准备 team match,正是开始学 Go 的好时机。

目前在跟着无闻的《Go编程基础》学。感觉看视频还是最快的。

学了变量的声明&定义的写法之后,我感觉 Go 搞出三种不同的写法完全就是为了迎合不同语言的开发者。

原来是写 C/C++/Java 的,你还是可以像以前那样明确地声明类型 var a string = "ss",也可以先声明再定义。

原来是写 Js 的,那还犹豫啥,var a = "ss" 走起。

原来是写 Python/Ruby 等动态语言的,就加个冒号吧:a := "ss"

于是大家都觉得,嗯?Go 好像和我之前写的语言挺像的,不错。

不过也许这么设计是有其它原因的吧。

9.16

又多学了一些语法,发现 Go 的设计特别有意思,经常让我想笑(并非贬义)。这些设计我把它总结为:消除最佳实践,消除争辩。什么意思?在对任何一种编程语言的讨论中,程序员往往会为完成同一件事的程序到底怎么写是最好的而争论不休。最著名且吵得最厉害的例子自然要数“大括号到底放在函数定义的同一行还是下一行”。这种争议也不能说完全没有价值,但是很浪费时间。Go 的设计者说,让我们从最开始设计语言的时候就消除这些争议:

  • a++/a-- 只能单独作为一行,再也不怕谭浩强出的那些奇奇怪怪的问题了

  • switch 语句默认不 fallthrough,防止一些自以为很聪明的人到处宣扬自己多么会利用 fallthrough 特性

  • 用首字母大小写决定可见性,避免每个程序员自己发明一套可见性命名规则

  • 大括号规定必须放在同一行。看到这个我真的笑了,那些鼓吹新开一行好的程序员要尴尬死了,不过就让他们继续写 C++/Java 吧

以上几个算是为了消除争议而做的设计。还有为了消除最佳实践的设计:

  • break LABEL/continue LABEL 简直不要太爽
    看看这个问题 How to break out of multiple loops in Python?,最高票答案提出了一种最佳实践:把 nested loop 部分单独写成函数,然后把 returnbreak 用。我觉得这个方法相当丑陋,怎么可能 nested loop 部分的逻辑正好适合抽象成函数?更逗的是,下面有人提到:

    PEP 3136 proposes labeled break/continue. Guido rejected it because "code so complicated to require this feature is very rare". The PEP does mention some workarounds, though (such as the exception technique), while Guido feels refactoring to use return will be simpler in most cases.

    虽然我支持 Guido 强推 Python3,但不得不承认老爹有时候是比较顽固,因为我实在不觉得这是 “very rare” 的情况。

  • 数组作为值而不是引用传递
    把列表传入函数并对其进行修改是常见需求。 如果是 Java/Python 这种默认传引用的语言,显然既可以在原处修改列表,也可以返回新列表。于是有人提出最佳实践,说应该返回新的列表而不是在原地修改。Go 这种新语言居然不是传引用而是像 C 一样默认传值,我认为是刻意设计的(当然也可能是我想多了),即是说,我们不需要讨论最佳实践,你们老实地返回新列表就行了。

9.25

现在又学了 slice, map 和函数。在理解 panicrecover 机制上花了许多时间,找了几个教程包括官方文档都没讲清楚。最后终于找到一个:Understanding Defer, Panic and Recover。现在总算是明白了。里面有一个比喻非常好:调用 panic 就好像推倒一块多米诺骨牌,函数会逐层停止执行,直到 main 函数,然后程序崩溃。而 recover 就好像是把其中一块骨牌抽走,把程序崩溃的过程在 recover 处停止。所以 recover 之后的代码还能够正常执行。

这篇文章里还讲了使用 panic, defer, recover 的一些最佳实践,比如为了让 main 函数可以接收到 panic errordefer 要传 &err,然后在 defer 函数里给 *errrecover 接收到的 Error。看来 Go 还是有最佳实践的,只不过需要应用的东西不同了。


top