Browsed by
标签:Python

聊聊 Python 中的依赖管理工具

聊聊 Python 中的依赖管理工具

相信初学 Python 的伙伴都碰到过这个麻烦事,想装个环境,在搜索引擎上一搜,蹦出来不少让人迷惑的字眼,比如 pip、virtualenv、virtualenvwrapper、venv、pyenv 等。某篇教程可能用的是这个工具,另一篇教程又可能是那个工具,还有的教程可能几个工具都在用。看了几篇后,整个人都懵了,这些工具各自都是干嘛的啊?就算最后环境弄好了,估计心里还在犯嘀咕,这些有什么区别和联系呢?好了,本篇文章就带你捋捋常见的一些包、环境管理工具,让你对这些工具有个相对清晰的认识。

先说下文章的思路,我会以碰到问题、解决问题的方式,引入几个典型工具的简单使用,从而理解它们的定位,然后根据定位梳理认识下现在比较流行的工具。这样一来,每个工具干什么的,该选用哪个工具,自己心里应该也清楚了。

Pyenv

现在的 Unix/Linux 系统基本上都装有 Python 解释器,不过都是 Python 2 版本的,这个版本已经在今年(2020 年)1 月 1 日宣布停止维护了,所以我们学习的话基本上都会选择 Python 3。这就带来一个问题,Python 3 怎么安装?

阅读全文

Scrapy 源码阅读(二):看源码

Scrapy 源码阅读(二):看源码

(接上文 Scrapy 源码阅读(一):Twisted 基础和 Scrapy 数据流

3 看源码

注意:本节内容基于 Scrapy 1.7

不知道大家有没有这样的体会?平时写业务代码的时候,一般是不会去关注各个模块怎么关联起来的。我要写控制器、写服务层、写模型层等,就按部就班的找到相应目录下的文件开始写了。当碰到问题在网上找不到好的解决方法或者闲时想学习一些设计方法的时候,我们就会去关注某些模块的底层实现以及它们的关系。这里我会用一个类似的方式,先给大家了解下 Scrapy 中的主要几个类的关系,在熟悉了这些类后,再根据需要去关注特定的类之间是如何关联的。

阅读全文

Scrapy 源码阅读(一):Twisted 基础和 Scrapy 数据流

Scrapy 源码阅读(一):Twisted 基础和 Scrapy 数据流

本文记录下自己看 Scrapy 源码的一点经验,没有涉及框架的方方面面,更多的是关注数据的流转以及代码的组织。如果你想深入框架的某个细节,那么这篇文字可以给你一个切入点。

阅读源码常规的步骤

  1. 准备好必须的基础知识
  2. 熟悉框架有哪些功能
  3. 看源码,了解代码是怎么组织的,最后按需关注特定部分的实现细节

我们一步一步来。

1 Twisted

Scrapy 基于 Twisted,所以除了要有一定的 Python 基础外,还要对 Twisted 有一些了解。

你可以将 Twisted 和 Asyncio 类比,它们都是为了支持协程而诞生的,只是前者比后者出现的更早。这 2 个技术实现的核心都是事件循环,当程序执行到某个耗时的 IO 操作时,程序的执行权限会被退回给事件循环,事件循环会检测其它准备就绪的协程,然后将执行权限交给它,当之前的协程 IO 操作完毕后,事件循环会将执行权限转给它,继续后面的操作。这样,就能在单线程内实现并发,作用和多线程类似,只是比多线程更轻量。事件循环在 Asyncio 中被叫做 event_loop,在 Twisted 中叫做 reactor。我们看一些简单的例子

阅读全文

聊聊 Scrapy 分布式的几种方案

聊聊 Scrapy 分布式的几种方案

Scrapy 是一款开源爬虫框架,但是官方对分布式支持并不多,本文聊聊几种简单可行的方案。

需要注意的是,本文的爬虫并非对几个网站的纵向爬取,而是爬取一批不同的地址。如果是其它类型的爬虫,可能不适用,但应该能让你多个思考方向。

网址分批处理

既然是一批不同的地址,那么最直接的方案就是把网址分几个部分,然后起几个进程同时处理就行了。

举个例子,我们要爬取的地址是放到 Mongo 中的,存储格式类似这样

{ "_id" : 1, "url" : "https://www.baidu.com" }
{ "_id" : 2, "url" : "https://www.google.com" }
{ "_id" : 3, "url" : "https://www.csdn.net" }
...

那我们就在编写爬虫的时候,接受 2 个参数(起始的地址主键和结束的主键),就能爬取指定的地址了。之后的工作,便是怎么分配地址了。

阅读全文

聊聊 SQLAlchemy 连接池中的连接失效问题

聊聊 SQLAlchemy 连接池中的连接失效问题

最近项目中事情比较多,也遇到了一些问题,其中有一个是关于连接池的,比较有意思,这里分享下。

一天早上,进入业务系统,点击了一个功能按钮,页面上突然弹出个 MySQL gone away 的错误,我擦,数据库挂了吗,上服务器一看正常的。又点击了一下,又报事务未正常关闭的错误,有点懵。当然,是在测试环境上 :)。仔细想了想,发现是连接池的问题,下面我重现下这个错误并说下自己的一些解决办法。

阅读全文

灵活使用 SQLAlchemy 中的 ORM 查询

灵活使用 SQLAlchemy 中的 ORM 查询

之前做查询一直觉得直接拼 SQL 比较方便,用了 SQLAlchemy 的 ORM 查询之后,发现也还可以,还提高了可读性。

这篇文章主要说说 SQLAlchemy 常用的 ORM 查询方式,偏实践。看了之后,对付开发中的查询需求,我觉得可以满足不少。

为方便说明,假设有如下数据

图书表 books

+----+--------+--------------------------+-------+
| id | cat_id | name                     | price |
+----+--------+--------------------------+-------+
|  1 |      1 | 生死疲劳                 | 40.40 |
|  2 |      1 | 皮囊                     | 31.80 |
|  3 |      2 | 半小时漫画中国史         | 33.60 |
|  4 |      2 | 耶路撒冷三千年           | 55.60 |
|  5 |      2 | 国家宝藏                 | 52.80 |
|  6 |      3 | 时间简史                 | 31.10 |
|  7 |      3 | 宇宙简史                 | 22.10 |
|  8 |      3 | 自然史                   | 26.10 |
|  9 |      3 | 人类简史                 | 40.80 |
| 10 |      3 | 万物简史                 | 33.20 |
+----+--------+--------------------------+-------+

分类表 categories

+----+--------------+
| id | name         |
+----+--------------+
|  1 | 文学         |
|  2 | 人文社科     |
|  3 | 科技         |
+----+--------------+

ORM 对象定义如下


注意:本文 Python 代码在以下环境测试通过

  • Python 3.6.0
  • PyMySQL 0.8.1
  • SQLAlchemy 1.2.8

# coding=utf-8

from sqlalchemy import create_engine
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy import Column, Integer, String, Numeric
from sqlalchemy.orm import sessionmaker

Base = declarative_base()
engine = create_engine('mysql+pymysql://username:password'
                       '@127.0.0.1:3306/db_name?charset=utf8')
Session = sessionmaker(bind=engine)

session = Session()

def to_dict(self):
    return {c.name: getattr(self, c.name, None)
            for c in self.__table__.columns}
Base.to_dict = to_dict

class Book(Base):
    __tablename__ = 'books'

    id = Column(Integer, primary_key=True)
    cat_id = Column(Integer)
    name = Column('name', String(120))
    price = Column('price', Numeric)

class Category(Base):
    __tablename__ = 'categories'

    id = Column(Integer, primary_key=True)
    name = Column('name', String(30))

好了,下面进入正题。

1 根据主键获取记录

当我们获取图书的详情时,很容易用到。

book_id = 1
book = session.query(Book).get(book_id)
print(book and book.to_dict())

直接 get(primary_key) 就得到结果

{'id': 1, 'cat_id': 1, 'name': '生死疲劳',
 'price': Decimal('40.40')}

当然,这样也可以

book_id = 1
book = session.query(Book) \
    .filter(Book.id == book_id) \
    .first()
print(book and book.to_dict())

不过,还是前一种方式简洁一些。

阅读全文

说说我对 WSGI 的理解

说说我对 WSGI 的理解

先说下 WSGI 的表面意思,Web Server Gateway Interface 的缩写,即 Web 服务器网关接口。

之前不知道 WSGI 意思的伙伴,看了上面的解释后,我估计也还是不清楚,所以下面结合实际场景说明,先让大家有个大致的认识。最后我们再自己实现一个,加深对 WSGI 的理解。

我们现在使用 Python 编写 Web 应用,可以用比较流行的 Flask、Django 框架,也可以按自己的想法直接写一个。可选的服务器软件也特别多,比如常见的有 Apache、Nginx、IIS 等,除此外,也有很多小众的软件。但是,现在问题来了,我该怎么部署?在没有 WSGI 规范之前,一个服务器调度 Python 应用是用这种方式,另一款服务器使用的是那种方式,这样的话,编写出来的应用部署时只能选择局限的某个或某些服务器,达不到通用的效果。

注意:下文中的代码基于 Python 3.6 编写。

假如有这么一个服务器

wsgi/server.py

# coding=utf-8

import socket

listener = socket.socket()
listener.setsockopt(socket.SOL_SOCKET,
                    socket.SO_REUSEADDR, 1)
listener.bind(('0.0.0.0', 8080))
listener.listen(1)
print('Serving HTTP on 0.0.0.0 port 8080 ...')

while True:
    client_connection, client_address = \
        listener.accept()
    print(f'Server received connection'
          f' from {client_address}')
    request = client_connection.recv(1024)
    print(f'request we received: {request}')

    response = b"""
HTTP/1.1 200 OK

Hello, World!
"""
    client_connection.sendall(response)
    client_connection.close()

实现比较简单,就是监听 8080 端口,如果有请求在终端进行打印,并返回 Hello, World! 的响应。

终端中启动服务器

➜  wsgi python server.py
Serving HTTP on 0.0.0.0 port 8080 ...

再开一个终端,请求下

➜  ~ curl 127.0.0.1:8080
HTTP/1.1 200 OK

Hello, World!

说明服务器工作正常。

另外有一个 Web 应用

wsgi/app.py

# coding=utf-8

def simple_app():
    return b'Hello, World!\r\n'

现在要部署(也就是让这个整体跑起来),简单粗暴的做法就是在服务器里面直接调用 app 中相应的方法。就像这样

wsgi/server2.py

# coding=utf-8

import socket

listener = socket.socket()
listener.setsockopt(socket.SOL_SOCKET,
                    socket.SO_REUSEADDR, 1)
listener.bind(('0.0.0.0', 8080))
listener.listen(1)
print('Serving HTTP on 0.0.0.0 port 8080 ...')

while True:
    client_connection, client_address = \
        listener.accept()
    print(f'Server received connection'
          f' from {client_address}')
    request = client_connection.recv(1024)
    print(f'request we received: {request}')

    from app import simple_app
    response = 'HTTP/1.1 200 OK\r\n\r\n'
    response = response.encode('utf-8')
    response += simple_app()

    client_connection.sendall(response)
    client_connection.close()

运行脚本

注意:因为使用端口相同的缘故,请先关闭上次的脚本,然后再执行,不然会由于端口冲突而报错。

➜  wsgi python server2.py
Serving HTTP on 0.0.0.0 port 8080 ...

然后请求一下看看效果

➜  ~ curl 127.0.0.1:8080
Hello, World!

嗯,可以了。但是,上面的服务器和应用整体是跑起来了,那么我换一个服务器或者应用呢。由于服务器与应用之间怎么交互完全没有规范,比如服务器应该如何把请求信息传给应用,应用处理完毕后又怎么告诉服务器开始返回响应,如果都是各搞各的,服务器需要定制应用,应用也要定制服务器,这要一个应用能跑起来也太麻烦了点吧。

所以,WSGI 的出现就是为了解决上面的问题,它规定了服务器怎么把请求信息告诉给应用,应用怎么把执行情况回传给服务器,这样的话,服务器与应用都按一个标准办事,只要实现了这个标准,服务器与应用随意搭配就可以,灵活度大大提高。

阅读全文

搞清楚 Python 的迭代器、可迭代对象、生成器

搞清楚 Python 的迭代器、可迭代对象、生成器

很多伙伴对 Python 的迭代器、可迭代对象、生成器这几个概念有点搞不清楚,我来说说我的理解,希望对需要的朋友有所帮助。

1 迭代器协议

迭代器协议是核心,搞懂了这个,上面的几个概念也就很好理解了。

所谓迭代器协议,就是要求一个迭代器必须要实现如下两个方法

iterator.__iter__()
Return the iterator object itself.

iterator.__next__()
Return the next item from the container.

也就是说,一个对象只要支持上面两个方法,就是迭代器。__iter__() 需要返回迭代器本身,而 __next__() 需要返回下一个元素。

2 可迭代对象

知道了迭代器的概念,那可迭代对象又是啥呢?

这个更简单,只要对象实现了 __iter__() 方法,并且返回的是一个迭代器,那么这个对象就是可迭代对象。

比如我们常见的列表就是可迭代对象

>>> l = [1, 3, 5]
>>> iter(l)
<list_iterator object at 0x101a1d9e8>

使用 iter() 会调用对应的 __iter__() 方法,这里返回的是一个列表迭代器,所以说列表就是一个可迭代对象。

阅读全文