SQLAlchemy session 使用问题

在更改 SQLAlchemy Session 从每次请求都创建到共享同一个 Session 之后遇到了如下问题:

StatementError: (sqlalchemy.exc.InvalidRequestError) Can’t reconnect until invalid transaction is rolled back [SQL: ]

或者是

raised unexpected: OperationalError(“(_mysql_exceptions.OperationalError) (2006, ‘MySQL server has gone away’)”,)

错误是 SQLAlchemy 抛出。原因是你从 pool 拿的 connection 没有以 session.commit 或 session.rollback 或者 session.close 放回 pool 里。这时 connection 的 transaction 没有完结(rollback or commit)。 而不知什么原因(recyle 了,timeout 了)你的 connection 又死掉了,你的 sqlalchemy 尝试重新连接。由于 transaction 还没完结,无法重连。

正确用法确保 session 在使用完成后用 session.close, session.commit 或者 session.rollback 把连接还回 pool

Session 是一个和数据库交互的会话。在 SQLAlchemy 中使用 Session 来创建和管理数据库连接的会话。

SQLAlchemy 数据库连接池使用

sessions 和 connections 不是相同的东西, session 使用连接来操作数据库,一旦任务完成 session 会将数据库 connection 交还给 pool。

在使用 create_engine 创建引擎时,如果默认不指定连接池设置的话,一般情况下,SQLAlchemy 会使用一个 QueuePool 绑定在新创建的引擎上。并附上合适的连接池参数。

在以默认的方法 create_engine 时(如下),就会创建一个带连接池的引擎。

engine = create_engine('mysql+mysqldb://root:password@127.0.0.1:3306/dbname')

在这种情况下,当你使用了 session 后就算显式地调用 session.close(),也不能把连接关闭。连接会由 QueuePool 连接池进行管理并复用。

这种特性在一般情况下并不会有问题,不过当数据库服务器因为一些原因进行了重启的话。最初保持的数据库连接就失效了。随后进行的 session.query() 等方法就会抛出异常导致程序出错。

如果想禁用 SQLAlchemy 提供的数据库连接池,只需要在调用 create_engine 是指定连接池为 NullPool,SQLAlchemy 就会在执行 session.close() 后立刻断开数据库连接。当然,如果 session 对象被析构但是没有被调用 session.close(),则数据库连接不会被断开,直到程序终止。

下面的代码就可以避免 SQLAlchemy 使用连接池:

#!/usr/bin/env python
#-*- coding: utf-8 -*-

from sqlalchemy import create_engine
from sqlalchemy.orm import sessionmaker
from sqlalchemy.pool import NullPool

engine = create_engine('mysql+mysqldb://root:password@127.0.0.1:3306/dbname', poolclass=NullPool)
Session = sessionmaker(bind=engine)
session = Session()
usr_obj_list = session.query(UsrObj).all()
print usr_obj_list[0].id
session.close()

create_engine() 函数和连接池相关的参数有:

  • -pool_recycle, 默认为 -1, 推荐设置为 7200, 即如果 connection 空闲了 7200 秒,自动重新获取,以防止 connection 被 db server 关闭。
  • -pool_size=5, 连接数大小,默认为 5,正式环境该数值太小,需根据实际情况调大
  • -max_overflow=10, 超出 pool_size 后可允许的最大连接数,默认为 10, 这 10 个连接在使用过后,不放在 pool 中,而是被真正关闭的。
  • -pool_timeout=30, 获取连接的超时阈值,默认为 30 秒

直接只用 create_engine 时,就会创建一个带连接池的引擎

engine = create_engine('postgresql://postgres@127.0.0.1/dbname')

当使用 session 后就显示地调用 session.close(),也不能把连接关闭,连接由 QueuePool 连接池管理并复用。

引发问题

当数据库重启,最初保持的连接就会失败,随后进行 session.query() 就会失败抛出异常 mysql 数据 ,interactive_timeout 等参数处理连接的空闲时间超过(配置时间),断开

何时定义 session,何时提交,何时关闭

基本

  • 通常来说,将 session 的生命周期和访问操作数据库的方法对象隔离和独立
  • 确保 transaction 有非常清晰的开始和结束,保持 transaction 简短,也就意味着让 transaction 能在一系列操作之后终止,而不是一直开放着。

    from contextlib import contextmanager

    @contextmanager def session_scope(): “"”Provide a transactional scope around a series of operations.””” session = Session() try: yield session session.commit() except: session.rollback() raise finally: session.close()

是否线程安全

Session 不是为了线程安全而设计的,因此确保只在同一个线程中使用。

如果实际上有多个线程参与同一任务,那么您考虑在这些线程之间共享 Session 及其对象;但是在这种极不寻常的情况下,应用程序需要确保实现正确的 locking scheme,以便不会同时访问 Session 或其状态。处理这种情况的一种更常见的方法是为每个并发线程维护一个 Session,而是将对象从一个 Session 复制到另一个 Session,通常使用 Session.merge() 方法将对象的状态复制到本地的新对象中。

scoped session

想要线程安全时使用 scoped_session() ,文档解释

the scoped_session() function is provided which produces a thread-managed registry of Session objects. It is commonly used in web applications so that a single global variable can be used to safely represent transactional sessions with sets of objects, localized to a single thread.

using transactional=False is one solution, but a better one is to simply rollback(), commit(), or close() the Session when operations are complete - transactional mode (which is called “autocommit=False” in 0.5) has the advantage that a series of select operations will all share the same isolated transactional context..this can be more or less important depending on the isolation mode in effect and the kind of application.

DBAPI has no implicit “autocommit” mode so there is always a transaction implicitly in progress when queries are made.

This would be a fairly late answer. This is what happens: While using the session, a sqlalchemy Error is raised (anything which would also throw an error when be used as pure SQL: syntax errors, unique constraints, key collisions etc.).

You would have to find this error, wrap it into a try/except-block and perform a session.rollback().

After this you can reinstate your session.

flush 和 commit 区别

  • flush 预提交,等于提交到数据库内存,还未写入数据库文件;
  • commit 就是把内存里面的东西直接写入,可以提供查询了;

Session 的生命周期

  • Session 被创建,没有和 model 绑定,无状态
  • Session 接受查询语句,执行结果,关联对象到 Session
  • Session 管理对象
  • 一旦 Session 管理的对象有变化,commit 或者 rollback

reference


2017-05-12 python , sqlalchemy , mysql , orm , sql , session , flask

保持 SSH 连接

记录一些 SSH 相关的内容,经常使用。

SSH 是 Secure Shell 的缩写,是一个应用层的加密网络协议,它不只可以用于远程登录,远程命令执行,还可用于数据传输。 当然它由 ssh Client 和 ssh Server 端组成,有很多实现,Ubuntu 上就默认安装的 OpenSSH, Client 端叫做 ssh, Server 端叫做 sshd. OpenSSH 只用来做远程登录和命令执行。

免密登录

查看本地 ~/.ssh/ 目录是否有 id_rsa.pub,如果没有,在本地创建公钥

ssh-keygen -t rsa

一路回车到底

然后把本地公钥复制到远程机器的 ~/.ssh/ 目录下,并命名为 authorized_keys

scp ~/.ssh/id_rsa.pub username@hostname:~/.ssh/authorized_keys
# or
ssh-copy-id -i ~/.ssh/id_rsa.pub username@hostname

如果远程主机配置了多台机器免密登录,最好将 id_ras.pub 追加而不是覆盖到 authorized_keys

cat id_rsa.pub >> .ssh/authorized_keys

保持连接

配置服务端

SSH 总是被强行中断,导致效率低下,可以在服务端配置,让 server 每隔 30 秒向 client 发送一个 keep-alive 包来保持连接:

vim /etc/ssh/sshd_config

添加

ClientAliveInterval 30
ClientAliveCountMax 60

第二行配置表示如果发送 keep-alive 包数量达到 60 次,客户端依然没有反应,则服务端 sshd 断开连接。如果什么都不操作,该配置可以让连接保持 30s*60 , 30 min

重启本地 ssh

sudo service ssh restart

如果找不到 ssh,”Failed to restart ssh.service: Unit ssh.service not found.” ,需要安装

sudo apt-get install openssh-server

配置客户端

如果服务端没有权限配置,或者无法配置,可以配置客户端 ssh,使客户端发起的所有会话都保持连接:

vim /etc/ssh/ssh_config

添加

ServerAliveInterval 30
ServerAliveCountMax 60

本地 ssh 每隔 30s 向 server 端 sshd 发送 keep-alive 包,如果发送 60 次,server 无回应断开连接。

下面是 man ssh_config 的内容

ServerAliveCountMax Sets the number of server alive messages (see below) which may be sent without ssh(1) receiving any messages back from the server. If this threshold is reached while server alive messages are being sent, ssh will disconnect from the server, terminating the session. It is important to note that the use of server alive messages is very different from TCPKeepAlive (below). The server alive messages are sent through the encrypted channel and therefore will not be spoofable. The TCP keepalive option enabled by TCPKeepAlive is spoofable. The server alive mechanism is valuable when the client or server depend on knowing when a connection has become inactive.

The default value is 3. If, for example, ServerAliveInterval (see below) is set to 15 and ServerAliveCountMax is left at the default, if the server becomes unresponsive, ssh will disconnect after approximately 45 seconds. This option applies to protocol version 2 only; in protocol version 1 there is no mechanism to request a response from the server to the server alive messages, so disconnection is the responsibility of the TCP stack.

ServerAliveInterval Sets a timeout interval in seconds after which if no data has been received from the server, ssh(1) will send a message through the encrypted channel to request a response from the server. The default is 0, indicating that these messages will not be sent to the server, or 300 if the BatchMode option is set. This option applies to protocol version 2 only. ProtocolKeepAlives and SetupTimeOut are Debian-specific compatibility aliases for this option.

共享 SSH 连接

如果需要在多个窗口中打开同一个服务器连接,可以尝试添加 ~/.ssh/config,添加两行

ControlMaster auto
ControlPath ~/.ssh/connection-%r@%h:%p

配置之后,第二条连接共享第一次建立的连接,加快速度。

添加长连接配置

ControlPersist 4h

每次 SSH 连接建立之后,此条连接会被保持 4 小时,退出服务器之后依然可以重用。

配置连接中转

ForwardAgent yes

当需要从一台服务器连接另外一个服务器,而在两台服务器中传输数据时,可以不用通过本地电脑中转,直接配置以上 ForwardAgent 即可。

最终, ~/.ssh/config 下的配置:

Host *
	ForwardAgent yes
	ServerAliveInterval 3
	ServerAliveCountMax 20
	TCPKeepAlive no
	ControlMaster auto
	ControlPath ~/.ssh/connection-%r@%h:%p
	ControlPersist 4h
	Compression yes

同一台机器配置多个 key

之前写过一篇文章总结,在同一台机器上同时使用 GitHub,和 GitLab 的 key,可以具体参考这里


2017-05-07 ssh , linux , git , github , gitlab

MySQL 命令记录

MySQL 命令行操作相关内容,防止遗忘。MySQL 常用命令记录,总结。

安装

Under Ubuntu

sudo apt update
sudo apt-get install mysql-server
sudo mysql_secure_installation

如果安装过程中没有弹出设置密码的对话,那么可以在安装完成后执行:

sudo mysql_secure_installation

来设置密码,及一些安全的设置。之后就可以用

sudo mysql -u root -p

来登录。

启动停止 MySQL 服务

可以使用如下命令启动,停止,重启 MySQL 服务

sudo /etc/init.d/mysql {start | stop | status | restart}

sudo service mysql {start | stop | status | restart}

Windows 下可以使用 net 命令

net start mysql

同理,启动其他比如微软自己的 SQL Server 可以使用

net start mssqlserver
# 或者重启 tomcat
net start tomcat6

mysql_commands

Access mysql shell

终端下输入

mysql -u [root] -p

之后输入 root 的密码

需要注意:

  • 所有的 mysql 命令以分号结束,如果没有分号结束,命令不会被执行
  • 不是必须的,但是通常 MySQL 命令大写,数据库,表,用户名或者其他 text 小写。 MySQL 命令并不区分大小写。

常用命令

常用命令中也大致可以分成几类,一类是通用命令,包括查看 MySQL 数据库,及查看基本表结构的。还有就是创建修改表结构,最后最常用的就是增删改查数据的命令。

通用命令

查看数据库

SHOW DATABASES;

输出

mysql> show DATABASES;
+--------------------+
| Database           |
+--------------------+
| information_schema |
| mysql              |
| parker             |
| performance_schema |
| sys                |
| test               |
| youku              |
+--------------------+
7 rows in set (0.00 sec)

创建数据库

CREATE DATABASE dbname;

删除数据库

DROP DATABASE dbname;

使用数据库

USE dbname;

显示数据库中表

SHOW tables;

定义修改表结构

创建表

CREATE TABLE table_name (id INT NOT NULL PRIMARY KEY AUTO_INCREMENT, name VARCHAR(20) , signup_date DATE);

查看表结构

DESCRIBE table_name;

增加新列

ALTER TABLE table_name **ADD COLUMN** [column] VARCHAR(40);

比如新增一列自增 id

ALTER TABLE [table] ADD COLUMN [column] int NOT NULL AUTO_INCREMENT PRIMARY KEY;

如果想要自定义新列的位置,可以使用 AFTER

ALTER TABLE table_name **ADD** email VARCHAR(40) AFTER name;

删除列

ALTER TABLE table_name DROP column

修改列,或者修改列类型

ALTER TABLE tablename **MODIFY COLUMN** column_name VARCHAR(20);
ALTER TABLE tablename **ALTER COLUMN** column_name VARCHAR(20);

修改表结构,添加组合 Primary Key,将两列数据作为 PK

如果 PRIMARY KEY 不存在

ALTER TABLE [table]  ADD primary key(column1, column2);

如果 PRIMARY KEY 存在

ALTER TABLE [table]  DROP PRIMARY KEY, ADD primary key(column1, column2);

修改表名

ALTER TABLE origin_table_name RENAME TO new_table_name

修改自增ID:

ALTER TABLE table_name AUTO_INCREMENT = 1000;

增删改查

Like 通配符

  • % 表示任意数量的未知字符串
  • _ 一个未知字符串

插入记录

INSERT INTO `table_name` (`name`, `signup_date`) VALUES ("Verne", "2017-05-01");

更新记录

UPDATE [table] SET [column] = 'Y' WHERE `potluck`.`name` ='Sandy';

删除一行记录

DELETE from table_name where column_name = "value";

获取记录条数

SELECT COUNT([column]) FROM [table];

模糊查询

SELECT * FROM [table] WHERE [column] LIKE '%value%';

排序

SELECT * FROM [table] WHERE [column] ORDER BY [column] ASC LIMIT [value];

Order 可以使用 DESC, ASC

删除表中所有记录

TRUNCATE table [table]

其他命令

查看创建表 DDL

show create table [table_name];

查看表的索引

SHOW INDEX FROM [table];

导出数据

mysqldump -u [username] -p [database] > db_backup.sql
mysqldump -u [username] -p [database] [table_name] > db_backup.sql

导入还原数据

mysql -u [username] -p [database] < db_backup.sql
mysql -u [username] -p -h localhost [database] < db_backup.sql

查看数据库中所有用户

SELECT User,Host FROM mysql.user;

创建新用户:

CREATE USER 'username'@'localhost' IDENTIFIED BY 'password';

记住这里的 localhost,可以替换成客户端的 IP,或者任何要需要授权访问的 IP 地址。

创建新用户时,如果密码选择太简单可能会导致密码安全检查无法过去,这时可以设置 MySQL 的 validate_password_policy 来使用简单密码:

mysql> set global validate_password_policy=0;

在 0 或者 LOW 下,密码只验证长度。在 1 或者 MEDIUM 下,密码会验证长度,必须包含数字,大小写,特殊字符。在 2 或者 STRONG 下,还会验证是否在字典中。

其中

mysql> select @@validate_password_length;

指定了使用密码的长度,默认为 8 位。

授予用户某个数据库全部权限

GRANT ALL ON [database].* TO 'user'@'localhost';

授予某个用户全部的权限:

GRANT ALL PRIVILEGES ON *.* TO 'user'@'localhost' WITH GRANT OPTION;

修改密码

mysqladmin -u root -p old_password password new_password

删除用户

DROP USER 'user1'@'localhost';

如果在创建新用户时提醒密码 weak,则可以使用如下命令来禁用密码校验

uninstall plugin validate_password;
// MySQL 8.0.4 以上
UNINSTALL COMPONENT 'file://component_validate_password';

将表从一个 schema 中移动到另外的 schema 中

alter table old_db.table_name rename new_db.table_name

mysql in Batch Mode

mysql -h host -u user -p < batch-file

远程连接

如果想要远程通过 root 连接 MySQL,先查看一下 MySQL 配置 /etc/mysql.my.cnf,需要注释其中

#bind-address = 127.0.0.1

默认 3306 端口只允许本地访问,然后重启 /etc/init.d/mysql restart

修改 MySQL 数据库中的 user 表,使得 root 能够远程登录

mysql -u root –p
mysql>use mysql;
mysql>update user set host = '%' where user = 'root';
mysql>select host, user from user;

ERROR 1819 HY000 Your password does not satisfy the current policy requirements

可以通过如下语句查看 MySQL 密码规则:

SHOW VARIABLES LIKE 'validate_password%';

mysql> SHOW VARIABLES LIKE 'validate_password%';
+--------------------------------------+--------+
| Variable_name                        | Value  |
+--------------------------------------+--------+
| validate_password_check_user_name    | OFF    |
| validate_password_dictionary_file    |        |
| validate_password_length             | 8      |
| validate_password_mixed_case_count   | 1      |
| validate_password_number_count       | 1      |
| validate_password_policy             | MEDIUM |
| validate_password_special_char_count | 1      |
+--------------------------------------+--------+
7 rows in set (0.01 sec)

改变密码策略:

SET GLOBAL validate_password_policy=MEDIUM;        // LOW  MEDIUM HIGH
SET GLOBAL validate_password_policy=1;        // Low=0 Medium=1 High=2

Host ‘xxx.xx.xxx.xxx’ is not allowed to connect to this MySQL server

通过如下方式创建用户并赋予权限。

mysql> CREATE USER 'einverne'@'localhost' IDENTIFIED BY 'some_pass';
mysql> GRANT ALL PRIVILEGES ON *.* TO 'einverne'@'localhost'
	->     WITH GRANT OPTION;
mysql> CREATE USER 'einverne'@'%' IDENTIFIED BY 'some_pass';
mysql> GRANT ALL PRIVILEGES ON *.* TO 'einverne'@'%'
	->     WITH GRANT OPTION;

FLUSH PRIVILEGES;

ERROR 2003 (HY000): Can’t connect to MySQL server on ‘some host’ (113)

解决问题的思路,看看防火墙是不是开着,检查 MySQL 用户的权限设置是否正确。

ERROR 1698 (28000): Access denied for user ‘root’@’localhost’

一般在新安装之后首次登录的时候会出现这个错误。 在 Ubuntu 下,MySQL 服务器默认使用了 UNIX auth socket plugin,这个时候必须要使用 sudo mysql -u root -p

通过如下 SQL 可以看到 root 使用了 unix_socket 插件

ERROR 1698 (28000): Access denied for user 'root'@'localhost'
MariaDB [(none)]> SELECT User, Host, plugin FROM mysql.user;
+------+-----------+-------------+
| User | Host      | plugin      |
+------+-----------+-------------+
| root | localhost | unix_socket |
+------+-----------+-------------+
1 row in set (0.00 sec)

有两种方式可以解决这个问题:

  • 使用 mysql_native_password 插件来设置 root 用户
  • 创建新的 db_user 用户

Option 1

sudo mysql -u root
mysql> USE mysql;
mysql> UPDATE user set plugin='mysql_native_password' WHERE user='root';
mysql>FLUSH PRIVILEGES;
mysql>exit

然后重启服务:

sudo service mysql restart

Option 2

$ sudo mysql -u root # I had to use "sudo" since is new installation

mysql> USE mysql;
mysql> CREATE USER 'YOUR_SYSTEM_USER'@'localhost' IDENTIFIED BY 'YOUR_PASSWD';
mysql> GRANT ALL PRIVILEGES ON *.* TO 'YOUR_SYSTEM_USER'@'localhost';
mysql> UPDATE user SET plugin='auth_socket' WHERE User='YOUR_SYSTEM_USER';
mysql> FLUSH PRIVILEGES;
mysql> exit;

重启服务:

$ sudo service mysql restart

sql 命令中的 \G

在使用 mysql 命令行的时候如果查询结果比较大的时候,可以在语句后面加上 \G; 来将结果垂直方式展现,可以更好的查看结果。那么 \G 到底是什么意思呢。

我们都知道 sql 语句需要使用分号 ; 来结束,事实上分号是 \g 的速记。go 命令在 sql 的历史中曾经存在,现在有些批量语句也可以通过 go 命令来提交给 server 执行。\G 命令似乎继承了 \g 命令的字母,大写来表示另外一种行为,查看 help 能看到

mysql> help
  ...
  \g  go  Send command to mysql server.
  \G  ego Send command to mysql server, display result vertically.
  ...

在上述的 help 中也会发现 mysql 有一个 ego 命令,字母 e 表示的是垂直模式,然而在从 mysql 的选项来看 mysql --vertical 或者 mysql -E 可以开启使用垂直方式显示结果的行为

man mysql...
  ...
  --vertical, -E
  Print query output rows vertically (one line per column value).
  Without this option, you can specify vertical output for individual
  statements by terminating them with \G.
  ...

你为什么使用 -E 来作为垂直模式呢,因为 -V, -v-e 都已经被占用有别的行为了。

Python 连接操作 MySQL

Python 2.x 中使用 MySQLdb 来连接 MySQL 数据库。在 Python 3.x 中使用 P 有 MySQL,使用方式 import pymysql,而其他操作几乎一致。

#!/usr/bin/env python3
# -*- coding:utf-8 -*-

import MySQLdb

"""
pip install MySQL-python
MySQLdb 是用于 Python 链接 Mysql 数据库的接口,它实现了 Python 数据库 API 规范 V2.0,基于 MySQL C API 上建立的。

在使用 Python 连接之前确保已经有数据表建立

> mysql -u root -p
Enter password:
Welcome to the MySQL monitor.  Commands end with ; or \g.
Your MySQL connection id is 25
Server version: 5.7.18-0ubuntu0.16.04.1 (Ubuntu)

Copyright (c) 2000, 2017, Oracle and/or its affiliates. All rights reserved.

Oracle is a registered trademark of Oracle Corporation and/or its
affiliates. Other names may be trademarks of their respective
owners.

Type 'help;' or '\h' for help. Type '\c' to clear the current input statement.

mysql> SHOW DATABASES;
+--------------------+
| Database           |
+--------------------+
| information_schema |
| mysql              |
| performance_schema |
| spy                |
| sys                |
+--------------------+
5 rows in set (0.00 sec)

mysql> CREATE DATABASE testdb;
Query OK, 1 row affected (0.00 sec)

mysql> CREATE USER 'testuser'@'localhost' IDENTIFIED BY '12345678';
Query OK, 0 rows affected (0.01 sec)

mysql> USE testdb;
Database changed
mysql> GRANT ALL ON testdb.* TO 'testuser'@'localhost';
Query OK, 0 rows affected (0.00 sec)

"""

con = MySQLdb.connect('localhost', 'testuser', '12345678', 'testdb', charset='utf8')


def create_table():
    with con:
        cur = con.cursor()
        cur.execute("DROP TABLE IF EXISTS Users")
        cur.execute("CREATE TABLE Users(id INT PRIMARY KEY AUTO_INCREMENT, name VARCHAR(50))")


def insert(name):
    with con:
        cur = con.cursor()
        cur.execute("INSERT INTO Users(name) VALUES('" + name + "')")


def get():
    with con:
        cur = con.cursor()
        cur.execute("SELECT * FROM Users")

        rows = cur.fetchall()
        for row in rows:
            print row


def update(old_name, new_name):
    with con:
        cur = con.cursor()
        cur.execute("UPDATE Users SET name = %s WHERE name = %s", (new_name, old_name))


def transaction():
    try:
        con.cursor()
        insert("CC")
        insert("David")
        insert("Einstein")
        con.commit()
    except MySQLdb.Error, e:
        if con:
            con.rollback()
        print "Error %d: %s" % (e.args[0],e.args[1])


if __name__ == '__main__':

    try:

        cur = con.cursor()
        sql = "SELECT VERSION()"
        cur.execute(sql)

        version = cur.fetchone()
        print "MySQL version : %s " % version
    except MySQLdb.Error, e:
        print "Error %d: %s" % (e.args[0], e.args[1])
        if con:
            con.close()

    create_table()
    insert("Alex")
    insert("Verne")
    get()
    update("Alex", "Bob")
    get()
    transaction()
    get()
    if con:
        con.close()

reference

一个更加详细的 Cheatsheet

https://gist.github.com/einverne/0c256fe6351a89c7815b75f0d9964bfe

推荐命令行

mysql 自带的命令行工具不会自动补全,这里推荐 mycli 可以实现 MySQL 命令行的自动补全和语法高亮。

安装

使用 pip 安装

pip install mycli

使用

其基本使用和 mysql 命令行基本一致

mycli [OPTIONS] [DATABASE]

Options:
  -h, --host TEXT               Host address of the database.
  -P, --port INTEGER            Port number to use for connection. Honors
                                $MYSQL_TCP_PORT
  -u, --user TEXT               User name to connect to the database.
  -S, --socket TEXT             The socket file to use for connection.
  -p, --password TEXT           Password to connect to the database
  --pass TEXT                   Password to connect to the database
  --ssl-ca PATH                 CA file in PEM format
  --ssl-capath TEXT             CA directory
  --ssl-cert PATH               X509 cert in PEM format
  --ssl-key PATH                X509 key in PEM format
  --ssl-cipher TEXT             SSL cipher to use
  --ssl-verify-server-cert      Verify server's "Common Name" in its cert
                                against hostname used when connecting. This
                                option is disabled by default
  -v, --version                 Version of mycli.
  -D, --database TEXT           Database to use.
  -R, --prompt TEXT             Prompt format (Default: "\t \u@\h:\d> ")
  -l, --logfile FILENAME        Log every query and its results to a file.
  --defaults-group-suffix TEXT  Read config group with the specified suffix.
  --defaults-file PATH          Only read default options from the given file
  --myclirc PATH                Location of myclirc file.
  --auto-vertical-output        Automatically switch to vertical output mode
                                if the result is wider than the terminal
                                width.
  -t, --table                   Display batch output in table format.
  --csv                         Display batch output in CSV format.
  --warn / --no-warn            Warn before running a destructive query.
  --local-infile BOOLEAN        Enable/disable LOAD DATA LOCAL INFILE.
  --login-path TEXT             Read this path from the login file.
  -e, --execute TEXT            Execute query to the database.
  --help                        Show this message and exit.

reference


2017-05-04 mysql , database , linux

Dockerfile 基础镜像

首先随便看看 Python 的镜像 就能看到非常多的版本,这些版本怎么选择呢。

首先解释几个常见的名词,其实都是 Linux 的发行版本

  • Stretch Debian 9
  • Jessie , 指的是 Debian 8
  • Wheezy Debian 7
  • Alpine 轻型 Linux 发行版

其次基于不同版本系统制作的镜像自身大小都有区别,比较精简的 python:2.7-slim 可能也要比 python:2.7-alpine 来的大。


2017-05-02 linux , docker , dockerfile , docker-image

借助 scrapy-redis 实现 scrapy 分布式爬虫

最原始的 Scrapy 项目是只能将爬虫部署到单机上,如果要实现分布式爬虫就需要手动去维护一个待抓取的列表,那么 scrapy-redis 项目就是这样一个存在。

特性:

  • 分布式抓取,可以部署多个 spider 实例,共享同一个 redis 队列
  • 分布式后处理,抓取的内容会放到一个队列中,这样就意味可以开启足够多的实例来处理结果
  • 提供了即插即用的组件,包括定时,去重,等等

Scrapy 的局限

Scrapy 已经能够满足大部分爬虫的需要,但是有一些场景 Scrapy 并不适用。

  • 对于页面数量比较少的站点,并不需要 Scrapy,通过 Requests 就能够满足需要
  • 需要增量爬取数据时,Scrapy 并不能实现。

安装

pip install scrapy-redis

使用

先要在 settings 中配置,具体参考官方文档,代码集成如下:

from scrapy_redis.spiders import RedisSpider

class MySpider(RedisSpider):
    name = 'myspider'

    def parse(self, response):
        # do stuff
        pass

reference


2017-04-30 scrapy , scrapy-redis , redis , spider , distributed , python

Linux 安装 VMware workstation 12

VMware Workstation 12 虚拟机,适用于 RHEL/CentOS 7, Fedora 20-24, Debian 7-9, Ubuntu 16.04-14.14 and Linux Mint 17-18.

Prerequisites

  • 确保系统 64 位
  • VMware 12 不支持 32 位 CPU
  • 确保有 root 权限

安装

更新

apt-get update && apt-get upgrade	# On Debian Systems

下载

wget 'http://www.vmware.com/go/tryworkstation-linux-64'

执行权限

chmod +x VMware-Workstation-Full-12.5.5-5234757.x86_64.bundle

执行安装

./VMware-Workstation-Full-12.5.5-5234757.x86_64.bundle

启动之后,如果没有自动找到 gcc ,需要手动指定 gcc 版本, gcc-4.8 版本,在 /usr/bin/ 目录下。

安装系统

这一步只要有系统镜像,一步步安装是很快的。省略。

宿主共享文件

在 VM 菜单下, Setting 中 Option 可以添加宿主机的共享文件夹。

reference


2017-04-29 linux , linux-mint , vmware , virtual-machine

在 Python 中使用 redis 作为任务队列 Python RQ 使用

在学习 Flask 时接触 flask-rq2,然后知道了 python-rq 因为之前使用过 Celery ,所以对 python-rq 倒也没有多大的困惑。因为 python-rq 只依赖于 redis,相较于 Celery 轻量很多,如果在 Flask 中不需要非常繁重的后台任务队列(比如只有发送注册邮件任务)可以考虑使用 python-rq,毕竟有 flask-rq2 为 Flask 度身定做,结合 Flask 要比 Celery 容易很多。

RQ (Redis Queue) is a simple Python library for queueing jobs and processing them in the background with workers. It is backed by Redis and it is designed to have a low barrier to entry. It can be integrated in your web stack easily.

官方的介绍也非常清晰,RQ 是一个用于后台任务的简单的 Python library。他使用 Redis 作为后台,他的设计目标就是降低入门门槛。他可以被轻松的整合到 Web 应用中。在项目的历史中,也不避讳的直言该项目受到 Celery,Resque 和 Flask snippet 中设计精美的部分的启发,然后作为一个轻量的任务队列框架而设计。

python-rq 的内容就不多介绍,感兴趣可以去官网 直接了解,非常简单。不过有几点需要注意。作为一个任务队列基本都要完成几个步骤

  • 定义耗时任务 def count_words_at_url(url): pass
  • 启动任务 worker rq worker
  • 提交任务到队列中 q = Queue(connection=redis_conn); q.enqueue(count_words_at_url, 'http://nvie.com')

但其实本文想要介绍的是 Flask-RQ2

Flask RQ2

安装

pip install Flask-RQ2

快速整合 Flask 参考 doc

第一步定义任务

from flask_rq2 import RQ

rq = RQ()

@rq.job
def add(x, y):
    return x + y

第二步启动 worker

flask rq worker

第三步提交到任务队列

job = add.queue(1, 2)
job2 = add.queue(3, 4, queue='high', timeout=60 * 2)

定时任务相关

@rq.job
def add(x, y):
    return x + y

# queue job in 60 seconds
add.schedule(timedelta(seconds=60), 1, 2)

# queue job at a certain datetime (UTC!)
add.schedule(datetime(2016, 12, 31, 23, 59, 59), 1, 2)

# queue job in 14 days and then repeat once 14 days later
add.schedule(timedelta(days=14), 1, 2, repeat=1)

# queue job in 12 hours with a different queue
add.schedule(timedelta(hours=12), 1, 2, queue='high', timeout=60 * 2)

# queue job every day at noon (UTC!)
add.cron('0 0 12 * * ?', 'add-one-two', 1, 2)

# queue job every minute with a different queue
add.cron('* * * * *', 'add-one-two', 1, 2, queue='high', timeout=55)

Bug

不过在使用的过程中启动 flask rq worker 时遇到一个 bug

redis.exceptions.ResponseError: Command # 7 (EXPIRE rq:worker:ev.22880 None) of pipeline caused error: value is not an integer or out of range

类似这样的 bug 似乎是因为新版本原因造成的,我降级为 rq==0.10.0 就好了。

python-rq 和 Celery 比较

一句话总结,RQ 的设计目标是简洁,而 Celery 是更加健壮完善。

区别 RQ Celery 总结
文档 RQ doc 非常简洁易懂 Celery 文档虽然复杂,但是也很清晰,Celery 有非常多的选项 都很好
监控 RQ Dashboard Celery Flower 两者都非常容易部署,能满足 90 % 的需求 很好
Broker support RQ only supports Redis Celery 能够支持很多 Redis,RabbitMQ 等等,如果要确保任务执行,Celery 可能是更好的选择 Celery 优势
优先队列 RQ 的设计简洁高效,worker 可以从不同的队列中 消费 Celery 需要不同的 worker 从不同的队列中消费 都有方法轻松实现
系统支持 RQ 仅仅支持有 fork 操作的系统(Unix) Celery 不限 Celery 获胜
语言支持 RQ 仅仅支持 Python Celery 允许从一个语言发送任务到另外的语言 Celery 更胜一筹
API RQ API simple Celery API 非常灵活 multiple result backends, nice config format, workflow canvas support,支持很多特性,同样带来的是配置的复杂 Celery 更胜一筹
子任务支持 RQ 不支持 支持从任务中创建新任务 Celery 赞
Community and Stability 活跃 活跃 两个项目都是活跃开发状态

reference


2017-04-25 python , redis , queue , python-rq , flask-rq2

每天学习一个命令:tcpdump 命令行下抓包

tcpdump 是一个运行在命令行下的抓包工具,如果想知道网络中有什么在流通,那么 tcpdump 就是一个很好的工具。它允许用户拦截和显示发送或收到过程中网络连接到该计算机的 TCP/IP 和其他数据包。tcpdump 适用于大多数的类 Unix 系统操作系统(如 Linux,BSD 等)。类 Unix 系统的 tcpdump 需要使用 libpcap 这个捕捉数据的库就像 Windows 下的 WinPcap。

tcpdump 可以识别的协议有:

  • ARP
  • RARP
  • ICMP
  • TCP
  • UDP
  • IP
  • IPv6
  • AppleTalk
  • IPX

常见用法

不带任何参数运行:

tcpdump

过滤端口

tcpdump -i eth1 port 25

网络过滤

tcpdump -i eth1 net 192.168

使用 -vv 打印更多的信息。

tcpdump -vv -i eth0

过滤 TCP 数据包

tcpdump 可以识别很多数据包,可以使用 tcpdump 的过滤来过滤,比如只输出 TCP 数据包:

tcpdump tcp

再比如想要过滤 udp 或者 80 端口的 TCP 包:

tcpdump udp or port 80

过滤主机

tcpdump -i eth1 host 192.168.1.1

表达元

已经有上面这些例子,就能看到 tcp, udp 这些都是过滤的表达式。tcpdump 有一些比较重要的过滤表达式:

  • tcp
  • udp
  • port some.port
  • host some.host
  • net some.network

运算符

上面的例子中有一个 or,表示或,tcpdump 支持很多运算符

  • or
  • and
  • not

更多的运算符可以参考手册。

同类工具

Tshark 是 wireshark 的命令行版本,类似于 tcpdump,可以用于网络抓包,封包解析等。

抓取指定设备的网络包

tshark -i eth0

抓取目的端口 80 的包

tshark tcp dst port 80

2017-04-23 tcpdump , linux , network

Scrapy 学习笔记及简单使用

Scrapy 是纯 Python 实现的爬虫框架(scraping and crawling framework),可以非常轻松地提取网页结构信息。最初设计时 Scrapy 仅仅作为网页抓取工具,但因其功能强大,配置简单,逐渐的被扩大使用范围,也经常被用于以下方面:

  • 数据挖掘 Data Mining
  • 信息处理 information processing
  • 历史信息存储 historical archival
  • 检测及自动化测试 monitoring and automated testing

因为网上的教程已经非常详细了,这里就重点记录解决的几个问题。

  • Scrapy 的官网地址:http://scrapy.org
  • Scrapy 在 Github 上的项目地址:https://github.com/scrapy/scrapy.git
  • Scrapy 的官方文档地址:http://doc.scrapy.org/

搭建环境

安装 python 2.7

一般 Ubuntu/Linux Mint 都会预装,查看一下即可

python -V
Python 2.7.12

如果没有安装 Python,可以使用之前推荐的 pyenv 来安装。下面的步骤也同样可以放到 pyenv 中执行。

安装 virtualenv

在开发目录中虚拟化 python 环境,避免和系统依赖冲突

sudo pip install virtualenv
source ./bin/active # 开启
# 此后再使用 pip install 时会安装在独立的目录下

具体用法可参考官网

安装依赖

sudo apt-get install libxml2-dev libxslt1-dev python-dev
pip install scrapy

项目结构

安装完成之后使用如下命令生成初始项目

scrapy startproject demo

初始目录结构如下:

$ tree demo
demo
├── demo
│   ├── __init__.py
│   ├── items.py
│   ├── middlewares.py
│   ├── pipelines.py
│   ├── settings.py
│   └── spiders
│       └── __init__.py
└── scrapy.cfg

2 directories, 7 files

文件说明:

  • scrapy.cfg 项目的配置信息,主要为 Scrapy 命令行工具提供一个基础的配置信息。(爬虫相关的配置信息在 settings.py 文件中)
  • items.py 设置数据存储模板,用于结构化数据
  • middlewares 中间件,全局处理请求
  • pipelines 数据处理行为,如:一般结构化的数据持久化,存储数据库等操作
  • settings.py 爬虫的配置文件,如:递归的层数、并发数,延迟下载等
  • spiders 爬虫目录,如:创建文件,编写爬虫规则

进入目录

cd demo
scrapy genspider example example.com   # 使用该命令安装模板生成 Spider

更详细的入门见官网:https://doc.scrapy.org/en/latest/intro/tutorial.html

架构

Scrapy 使用了 Twisted 异步网络库来处理网络,可以对网站页面进行大量非阻塞的异步请求,能够对目标网站按照网站结构的层级次序逐级向下采集,并可以在已采集到的页面中提取其他符合要求的目标网页地址资源,从而实现从单个或多个入口进入,对目标网站进行全面扫描并获取所需的数据。结构如下:

Scrapy 的核心组件:

  • Scrapy Engine
  • Scheduler
  • Downloader
  • Spider
  • Pipeline
  • Item
  • Middlewares
    • Downloader Middlewares
    • Spider Middlewares
    • Scheduler Middlewares

引擎(Scrapy Engine)

用来处理整个系统的数据流,触发事务(框架核心),负责控制和调度各个组件

调度器(Scheduler)

接受 Engine 发过来的请求,压入队列中,并在引擎再次请求的时候返回,如:要抓取的链接(URL)的优先队列,由它来决定下一个要抓取的 URL 是什么,并进行去重。

下载器(Downloader)

下载器负责对目标页面发出请求并获取页面反馈的数据,之后传递给 Scrapy 引擎,最终传递给爬虫进行数据提取。

爬虫(Spider)

爬虫是 Scrapy 的用户自行编写的一段数据提取程序,针对下载器返回的数据结构进行分析(一般为 HTML),并提取出其中的结构化数据,并可以指定其他需要跟进的 URL 和处理方法。每个爬虫负责处理一个或多个特定的网站。

项目管道(Pipline)

负责处理爬虫从网页中抽取的实体,主要的功能是持久化实体(Item)、验证实体的有效性、清除垃圾信息。当页面被爬虫解析后,解析后内容将会发送到项目管理通道,经过几个特定的次序处理。

数据 (Item)

Item 是爬虫针对网页数据做解析后返回的数据,需要在使用之前预先定义好 Item 的数据结构,爬虫的解析程序负责将提取到的数据填充到 Item 中,并将 Item 返回,传递给数据管道进行后续处理。

下载器中间件(Downloader Middlewares)

位于 Scrapy 引擎和下载器之间的框架,主要是处理 Scrapy 引擎和下载器之间的请求与响应。

爬虫中间件(Spider Middlewares)

介于 Scrapy 引擎和 Spider 之间的框架,处理爬虫的响应输入和请求输出。

调度中间件(Scheduler Middlewares)

介于 Scrapy 引擎和调度之间的中间件,从 Scrapy 引擎发送到调度的请求和响应。

图解见官网:https://doc.scrapy.org/en/latest/topics/architecture.html

使用 ImagesPipeline 下载图片

在 scrapy 中有实现的 ImagesPipeline , 默认即可下载大量的图片,如果想要实现自己的下载图片 Pipeline,并且自定义输出图片的文件的名字,可以重写 file_path() 方法。

import scrapy
from scrapy.pipelines.images import ImagesPipeline

class ImagePipeline(ImagesPipeline):
    default_headers = {
        'user-agent': 'Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/56.0.2924.87 Safari/537.36'
    }

    # 对各个图片 URL 返回一个 Request
    def get_media_requests(self, item, info):
        try:
            for image_url in item['image_urls']:
                f = image_url.split('.')[-1]
                yield scrapy.Request(image_url, meta={'image_name': item['image_name'], 'format': f}, headers=self.default_headers)
        except Exception as error:
            print error

    # 当一个单独项目中的所有图片请求完成时 (success, image_info_or_failure)
    def item_completed(self, results, item, info):
        image_paths = [x['path'] for ok, x in results if ok]
        if not image_paths:
            # raise DropItem("Item contains no images")
            print "Image path no exist"
        return item


    # Override the convert_image method to disable image conversion

    # scrapy convert image to jpg 重写此方法,可以下载自定的图片格式,不过可能需要特殊处理格式
    # def convert_image(self, image, size=None):
    #     buf = StringIO()
    #     try:
    #         image.save(buf, image.format)
    #     except Exception, ex:
    #         raise ImageException("Cannot process image. Error: %s" % ex)
    #
    #     return image, buf

    # 默认情况下,使用 ImagePipeline 组件下载图片的时候,图片名称是以图片 URL 的 SHA1 值进行保存的。
    # scrapy 0.12 可以覆盖 image_key 方法,在此后版本中 使用 file_path 来自定义下载图片名称
    # def image_key(self, url):
    #     image_guid = hashlib.sha1(url).hexdigest()
    #     return 'full/%s.jpg' % (image_guid)

    # http://stackoverflow.com/questions/6194041/scrapy-image-download-how-to-use-custom-filename/22263951#22263951
    def file_path(self, request, response=None, info=None):
        name = request.meta['image_name']
        f = request.meta['format']
        return 'full/%s.jpg' % name

定义 middlewares

middlewares 是 Scrapy 在请求时中间必须经过的步骤,在 settings 中有设置 DOWNLOADER_MIDDLEWARES

import random

from scrapy.downloadermiddlewares.useragent import UserAgentMiddleware

from scrapy.conf import settings


class RandomUserAgentMiddleware(UserAgentMiddleware):

    def __init__(self, user_agent=''):
        self.user_agent = user_agent

    # 每一请求都会走这个函数,在这里随机挑选 UA
    def process_request(self, request, spider):
        ua = random.choice(settings.get('USER_AGENT_LIST'))
        if ua:
            print "******Current UserAgent: %s **************" % ua

            request.headers.setdefault("User-Agent", ua)


class ProxyMiddleware(object):
    def process_request(self, request, spider):
        request.meta['proxy'] = random.choice(settings.get('HTTP_PROXY_LIST'))

多 pipeline 协同处理

Item 在 Spider 中构造之后会被传送到 Pipeline 中,按照一定的顺序执行。一般情况下 pipeline 会做一些数据处理或存储的事情,一般写数据库操作都放到 Pipeline 中。

当一个 Item 要被多个 pipeline 处理时,需要定义:

ITEM_PIPELINES = {
    'imdb.pipelines.MoviePipeline': 300,
    'imdb.image_pipeline.ImagePipeline': 300
}

此时,Item 就会被两个 pipeline 处理,如果某个 pipeline 处理某一类事件,比如上述例子中, MoviePipeline 处理数据的存储,而 ImagePipeline 处理图片的下载。


2017-04-23 scrapy , python , crawler , spider , 学习笔记

使用 pyenv 管理 Python 版本

记录一下使用过程,留备以后使用。

pyenv 是 Python 版本管理工具。 pyenv 可以改变全局的 Python 版本,在系统中安装多个版本的 Python, 设置目录级别的 Python 版本,还能创建和管理 virtual python environments 。所有的设置都是用户级别的操作,不需要 sudo 命令。

pyenv 的一个典型使用场景就是,比如一个老项目需要使用 Python 2.x ,而另一个新项目需要 Python 3.x 。而 virtualenv 主要是用来管理相同版本 Python 不同项目的包的依赖不同的问题,就无法解决这个问题,这个时候就需要 pyenv。

pyenv 通过修改系统环境变量来实现不同 Python 版本的切换。而 virtualenv 通过将 Python 包安装到一个目录来作为 Python 包虚拟环境,通过切换目录来实现不同包环境间的切换。

pyenv 实现的精髓之处在于,它并没有使用将不同的 $PATH 植入不同的 shell 这种高耦合的工作方式,而是简单地在 $PATH 的最前面插入了一个垫片路径(shims):~/.pyenv/shims:/usr/local/bin:/usr/bin:/bin。所有对 Python 可执行文件的查找都会首先被这个 shims 路径截获,从而使后方的系统路径失效。

安装之前

不同系统请参考 Common build problems,安装必须的工具。

sudo apt-get update; sudo apt-get install make build-essential libssl-dev zlib1g-dev \
libbz2-dev libreadline-dev libsqlite3-dev wget curl llvm \
libncursesw5-dev xz-utils tk-dev libxml2-dev libxmlsec1-dev libffi-dev liblzma-dev libedit-dev

pyenv 安装

根据官网的 安装说明 或者 自动安装 。如果使用 Mac 直接使用 Homebrew。安装成功后记得在 .bashrc 或者 .bash_profile 中添加三行来开启自动补全。

export PATH="$HOME/.pyenv/bin:$PATH"
eval "$(pyenv init -)"
eval "$(pyenv virtualenv-init -)"

根据自己的环境配置。

自动安装

保证系统有 git ,否则需要新安装 git。

pyenv 提供了自动安装的工具,执行命令安装即可:1

或者:

curl -L https://raw.githubusercontent.com/yyuu/pyenv-installer/master/bin/pyenv-installer | bash

这里需要特别提醒一句,不要在任何网站上复制粘贴命令到你的终端,除非你明确的知道你在做什么,如果你要检视这里的脚本可以先将脚本下载到本地,然后检查源码后执行安装,或者你可以使用后文提到的源码安装。

执行安装后提供了如下工具:

  • pyenv: pyenv 工具自身
  • pyenv-virtualenv: pyenv 的插件可以用来管理 vierual environments
  • pyenv-update: 用来更新 pyenv 的插件
  • pyenv-doctor: 验证 pyenv 和依赖是否安装的插件
  • pyenv-which-ext: 用来寻找相同命令的插件

pyenv 会从源代码编译 Python,所以需要安装必须的编译工具,安装 build 工具:

Debian/Ubuntu/Linux Mint:

sudo apt-get install -y make build-essential libssl-dev zlib1g-dev \
libbz2-dev libreadline-dev libsqlite3-dev wget curl llvm libncurses5-dev \
libncursesw5-dev xz-utils tk-dev libffi-dev liblzma-dev python-openssl

Fedora/CentOS/RHEL:

sudo yum install zlib-devel bzip2 bzip2-devel readline-devel sqlite \
sqlite-devel openssl-devel xz xz-devel libffi-devel

MacOS:

brew install openssl readline sqlite3 xz zlib

手动安装

如果想要更加详细的了解安装过程,可以使用手动安装。直接 clone pyenv 项目到 HOME 根目录。2 建议路径为:$HOME/.pyenv

然后将如下内容复制到 bashrc:

export PATH="$HOME/.pyenv/bin:$PATH"
eval "$(pyenv init -)"
eval "$(pyenv virtualenv-init -)"

reload bashrc:

source ~/.bashrc

这里的 shell 配置文件(~/.bash_profile)需要按照不同 Linux 作修改,如果使用 Zsh 则需要相应的配置 ~/.zshrc

在使用 pyenv 之后使用 pip 安装的第三方模块会自动安装到当前使用 python 版本下,不会和系统模块产生冲突。使用 pip 安装模块之后,如果没有生效,记得使用 pyenv rehash 来更新 shims 路径。

pyenv 常用命令

使用 pyenv commands 显示所有可用命令

python 安装与卸载

就如上文所说,pyenv 从源码安装 Python, 每一个安装的版本都会在 pyenv 根目录的 versions 目录下。

pyenv install 2.7.3   # 安装 python

安装后可以检视:

ls ~/.pyenv/versions/

从本机卸载对应版本的 Python 也特别简单:

pyenv uninstall 2.7.3 # 卸载 python

或者直接删除掉 ~/.pyenv/versions/2.7.3 对应的目录也可以。

rm -rf ~/.pyenv/versions/2.7.3

查看本机安装 Python 版本

使用如下命令查看本机安装版本

pyenv versions

星号表示当前正在使用的 Python 版本。使用 python -V 确认版本。

查看可安装 Python 版本

使用如下命令查看可安装版本

pyenv install -l

Python 切换

用这些命令可以切换全局或者项目中的 Python 版本:

pyenv global 2.7.3  # 设置全局的 Python 版本,通过将版本号写入 ~/.pyenv/version 文件的方式。
pyenv local 2.7.3 # 设置 Python 本地版本,通过将版本号写入当前目录下的 .python-version 文件的方式。通过这种方式设置的 Python 版本优先级较 global 高。

需同在寻找 python 的时候优先级

shell > local > global

pyenv 会从当前目录开始向上逐级查找 .python-version 文件,直到根目录为止。若找不到,就用 global 版本。

pyenv shell 2.7.3 # 设置面向 shell 的 Python 版本,通过设置当前 shell 的 PYENV_VERSION 环境变量的方式。这个版本的优先级比 local 和 global 都要高。`--unset` 参数可以用于取消当前 shell 设定的版本。

pyenv shell --unset

pyenv rehash  # 创建垫片路径(为所有已安装的可执行文件创建 shims,如:~/.pyenv/versions/*/bin/*,因此,每当你增删了 Python 版本或带有可执行文件的包(如 pip)以后,都应该执行一次本命令)

pyenv-virtualenv

pyenv 插件:pyenv-virtualenv

使用自动安装 pyenv 后,它会自动安装部分插件,通过 pyenv-virtualenv 插件可以很好的和 virtualenv 结合:

cd ~/.pyenv/plugins
ll
total 24K
drwxr-xr-x 4 einverne einverne 4.0K Apr 22 10:55 pyenv-doctor
drwxr-xr-x 5 einverne einverne 4.0K Apr 22 10:55 pyenv-installer
drwxr-xr-x 4 einverne einverne 4.0K Apr 22 10:55 pyenv-update
drwxr-xr-x 7 einverne einverne 4.0K Apr 22 10:55 pyenv-virtualenv
drwxr-xr-x 4 einverne einverne 4.0K Apr 22 10:55 pyenv-which-ext
drwxr-xr-x 5 einverne einverne 4.0K Apr 22 10:54 python-build

创建虚拟环境

pyenv virtualenv 2.7.15 env-name

若不指定 python 版本,会默认使用当前环境 python 版本。如果指定 Python 版本,则一定要是已经安装过的版本,否则会出错。环境的真实目录位于 ~/.pyenv/versions

列出当前虚拟环境

pyenv virtualenvs
pyenv activate env-name  # 激活虚拟环境
pyenv deactivate #退出虚拟环境,回到系统环境

删除虚拟环境

pyenv uninstall env-name
rm -rf ~/.pyenv/versions/env-name  # 或者删除其真实目录

使用 pyenv 来管理 python,使用 pyenv-virtualenv 插件来管理多版本 python 包。此时,还需注意,当我们将项目运行的 env 环境部署到生产环境时,由于我们的 python 包是依赖 python 的,需要注意生产环境的 python 版本问题。

所有命令

$ pyenv commands
activate
commands
completions
deactivate
doctor
exec
global
help
hooks
init
install
installer
local
offline-installer
prefix
rehash
root
shell
shims
uninstall
update                 # 更新 pyenv 及插件
version
--version
version-file
version-file-read
version-file-write
version-name
version-origin
versions
virtualenv
virtualenv-delete
virtualenv-init
virtualenv-prefix
virtualenvs
whence
which

PyCharm

PyCharm 中可以非常方便的切换 Python 环境非常方便。但是因为个人原因 Java 和 Python 同时用的概率比较高,所以一直使用 Intellij IDEA 安装了 Python 插件。PyCharm 是为 Python 单独开发,所以如果不需要在语言之间切换用 PyCharm 还是比较方便的。

Tips

更换 pip 源

因为国内网络环境,如果在局域网内下载 pip 慢,可以尝试使用 aliyun 提供的镜像,创建 vim ~/.pip/pip.conf ,然后填入:

[global]
index-url = http://mirrors.aliyun.com/pypi/simple/

[install]
trusted-host=mirrors.aliyun.com

延伸

不仅 Python 有 pyenv 可以用来管理 Python 的不同版本,Java,Ruby 也都有相对应的工具。

比如说 Java 的 jenv, Ruby 有 rbenv,它们命令的用法也都和 pyenv 相差不多,基本上用了 pyenv,其他几个命令的使用也都水到渠成了。

参考

  1. https://github.com/pyenv/pyenv-installer

    curl https://pyenv.run bash

  2. https://github.com/pyenv/pyenv#basic-github-checkout

    cd ~ git clone git://github.com/yyuu/pyenv.git .pyenv 


2017-04-22 python , pyenv , 经验总结 , dev

电子书

本站提供服务

最近文章

  • Dinox 又一款 AI 语音实时转录工具 前两天介绍过 [[Voicenotes]],也是一款 AI 转录文字的笔记软件,之前在调查 Voicenotes 的时候就留意到了 Dinox,因为是在小红书留意到的,所以猜测应该是国内的某位独立开发者的作品,整个应用使用起来也比较舒服,但相较于 Voicenotes,Dinox 更偏向于一个手机端的笔记软件,因为他整体的设计中没有将语音作为首选,用户也可以添加文字的笔记,反而在 Voicenotes 中,语音作为了所有笔记的首选,当然 Voicenotes 也可以自己编辑笔记,但是语音是它的核心。
  • 音流:一款支持 Navidrom 兼容 Subsonic 的跨平台音乐播放器 之前一篇文章介绍了Navidrome,搭建了一个自己在线音乐流媒体库,把我本地通过 [[Syncthing]] 同步的 80 G 音乐导入了。自己也尝试了 Navidrome 官网列出的 Subsonic 兼容客户端 [[substreamer]],以及 macOS 上面的 [[Sonixd]],体验都还不错。但是在了解的过程中又发现了一款中文名叫做「音流」(英文 Stream Music)的应用,初步体验了一下感觉还不错,所以分享出来。
  • 泰国 DTV 数字游民签证 泰国一直是 [[Digital Nomad]] 数字游民青睐的选择地,尤其是清迈以其优美的自然环境、低廉的生活成本和友好的社区氛围而闻名。许多数字游民选择在泰国清迈定居,可以在清迈租用廉价的公寓或民宿,享受美食和文化,并与其他数字游民分享经验和资源。
  • VoceChat 一款可以自托管的在线聊天室 VoceChat 是一款使用 Rust(后端),React(前端),Flutter(移动端)开发的,开源,支持独立部署的在线聊天服务。VoceChat 非常轻量,后端服务只有 15MB 的大小,打包的 Docker 镜像文件也只有 61 MB,VoceChat 可部署在任何的服务器上。
  • 结合了 Google 和 AI 的对话搜索引擎:Perplexity AI 在日本,因为 SoftBank 和 Perplexity AI 开展了合作 ,所以最近大量的使用 Perplexity ,这一篇文章就总结一下 Perplexity 的优势和使用技巧。