《Python核心技术与实战》学习笔记

动机

最近在极客时间上买了一个课程名字叫《Python核心技术与实战》。链接戳这里:https://time.geekbang.org/column/intro/176

我挺喜欢这种课程形式:一个业内比较资深的老师整理内容,然后找一个专业的声优再朗读一遍;文章中还有一些代码。我比较习惯上下班的时候听,这种碎片时间巩固下编程语言还是可以的。

不过后面感觉这个课程讲的不错,有些内容值得复习和深度阅读,因此开这篇笔记。

基础篇

01 | 如何逐步突破,成为Python高手?

02 | Jupyter Notebook为什么是现代Python的必学技术?

Jupyter Notebook的优点:

  • 整合所有的资源
  • 交互性编程体验
  • 零成本重现结果

03 | 列表和元组,到底用哪一个?

基础区别:

  • 列表是动态的,长度大小不固定,可以随意地增加、删减或者改变元素(mutable)。
  • 而元组是静态的,长度大小固定,无法增加删减或者改变(immutable).

04 | 字典、集合,你真的了解吗?

集合并不支持索引操作,因为集合本质上是一个哈希表,和列表不一样。

新版哈希表结构:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
Indices
----------------------------------------------------
None | index | None | None | index | None | index ...
----------------------------------------------------

Entries
--------------------
hash0 key0 value0
---------------------
hash1 key1 value1
---------------------
hash2 key2 value2
---------------------
...
---------------------

进阶篇

15 | Python对象的比较、拷贝

== VS is

  • ==判断两个对象的值是否相等,执行a == b相当于是去执行a.__eq__(b)
  • is比较两个对象的身份标识符是否相等。执行a is b等价于id(a) == id(b)

17 | 强大的装饰器

函数装饰器

1
2
3
4
5
6
7
8
9
10
11
12
def my_decorator(func):
@functools.wrap
def wrapper(*args, **kwargs):
print('wrapper of decorator')
func(*args, **kwargs)
return wrapper

@my_decorator
def greet():
print('hello world')

greet()
  • @是语法糖@my_decorator就相当于前面的greet=my_decorator(greet)
  • 一般使用装饰器后,原函数的元信息会丢失。使用@functools.wrap帮助保留原函数的元信息。

类装饰器

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28

class Count:
def __init__(self, func):
self.func = func
self.num_calls = 0

def __call__(self, *args, **kwargs):
self.num_calls += 1
print('num of calls is: {}'.format(self.num_calls))
return self.func(*args, **kwargs)

@Count
def example():
print("hello world")

example()

# 输出
num of calls is: 1
hello world

example()

# 输出
num of calls is: 2
hello world

...
  • @Count等价于example=Count(example)
  • 后面继续调用example()等于调用Count__call__

18 | metaclass,是潘多拉魔盒还是阿拉丁神灯?

Meta的两种意思:

  • “Beyond”,例如技术词汇 metadata,意思是描述数据的超越数据;
  • “Change”,例如技术词汇 metamorphosis,意思是改变的形态。

即,metaclass具有超越变形特性。

YAMLObject源代码:

1
2
3
4
5
6
7
8
9
class YAMLObjectMetaclass(type):
def __init__(cls, name, bases, kwds):
super(YAMLObjectMetaclass, cls).__init__(name, bases, kwds)
if 'yaml_tag' in kwds and kwds['yaml_tag'] is not None:
cls.yaml_loader.add_constructor(cls.yaml_tag, cls.from_yaml)

class YAMLObject(metaclass=YAMLObjectMetaclass):
yaml_loader = Loader
# 省略其余定义

在你定义任何YAMLObject子类时,Python会强行插入运行下面这段代码:

1
cls.yaml_loader.add_constructor(cls.yaml_tag, cls.from_yaml)

原理

  • 所有Python的用户定义类都type这个类的实例。
    • class MyClass: ... 等价于MyClass = type(classname, superclasses, attributedict)
    • type__call__又会进一步调用:type.__new__(typeclass, classname, superclasses, attributedict)type.__init__(class, classname, superclasses, attributedict)
  • metaclasstype子类,通过重载type__call__运算符,“超越变形”正常的类。

建议:metaclass用于框架层开发,不用在应用层开发。

19 | 深入理解迭代器和生成器

对象的抽象就是类,而对象的集合就是容器。所有的容器都是可迭代的(iterable)。

  • 可迭代对象(Iterable):实现了__iter__,通过iter()函数可以返回一个迭代器。
  • 迭代器(Iterator):实现了__next__,通过next()方法,要么获取容器的下一个对象,要么得到一个StopIteration错误。
  • 生成器(Generator):生成器是懒人版的迭代器。外部程序在调用next(gen)时,进入生成器,直到yield处返回,生成器暂停;下次再调用next(gen)时,回到生成器继续执行直到下一处yield

21 | Python并发编程之Futures

并发 vs 并行

  • 并发(Concurrency):多个任务同时进行,但在具体的同一时刻只有一个任务在进行。应用于
    I/O 操作频繁的场景,比如你要从网站上下载多个文件,I/O 操作的时间可能会比 CPU
    运行处理的时间长得多。python中一般用threadingasyncio
  • 并行(Parallelism):多个任务是同一时刻、同时发生。更多应用于 CPU heavy
    的场景,比如 MapReduce
    中的并行计算,为了加快运行速度,一般会用多台机器、多个处理器来完成。python中一般用multi-processing。

Futures

Python 中的 Futures 模块,位于 concurrent.futuresasyncio
中,它们都表示带有延迟的操作

future = xxx.submit(func):future一般通过某个类的submit方法生成。

future的重要方法:

  • done():表示相对应的操作是否完成,会立即返回结果。
  • add_done_callback(fn):表示future完成后,回调函数fn会被执行。
  • result():表示当future完成后,返回其对应的结果或异常。
  • as_completed(fs):给定的future迭代器fs,在其完成后,返回完成后的迭代器。

22 | 并发编程之Asyncio

多线程的缺点

  • 多线程运行过程容易被打断,出现资源竞争的现象;
  • 线程切换本身存在一定的损耗,线程数不能无限增加。

sync vs. async

  • 同步是指操作一个接一个执行(按顺序,等待上一个操作完成)。
  • 异步是指不同的操作间可以互相交替执行。(交替,上一个操作如果被block,可以不等待,先执行)

asyncio工作原理

  • 单线程,即主线程。
  • 可以进行任务(对应特殊的future对象)切换。
  • 不同的任务,被一个event loop对象控制,控制逻辑:
    • 维护两个任务状态列表:1.预备状态任务列表,该列表的任务目前随时准备开始运行;2.等待状态任务列表,该列表的任务已经运行但正在等待外部操作完成(比如IO)。
    • 控制权:
      • event loop选择task开始运行。
      • task运行直到结束或者等待。
      • event loop根据task是结束还是等待,更新维护的两个状态列表,结束则放入预备状态列表,等待则放入等待状态列表。然后再次选择下一个要运行的task。

ayncio用法

asyncawait关键字是asyncio最新写法,表示这个语句 / 函数是 non-block 的。async关键字定义一个协程(函数或者对象),await关键字用于挂起阻塞的异步调用接口。如果一个对象可以在await语句中使用,那么它就是awaitables对象,有三种主要类型: coroutines , TaskFuture.

asyncio.run()是python3.7才引入的,老版本等价代码:

1
2
3
4
5
loop = asyncio.get_event_loop()
try:
loop.run_until_complete(coro)
finally:
loop.close()

24 | 带你解析 Python 垃圾回收机制

sys.getrefcount()函数可以查看一个对象的引用次数,该函数本身也会引入一次计数。

可以显式调用gc.collect(),来启动垃圾回收。Python
使用标记清除(mark-sweep)算法和分代收集(generational),来启用针对循环引用的自动垃圾回收。

规范篇

29 | 巧用上下文管理器和With语句精简代码

基于类的上下文管理器

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28

class FileManager:
def __init__(self, name, mode):
print('calling __init__ method')
self.name = name
self.mode = mode
self.file = None

def __enter__(self):
print('calling __enter__ method')
self.file = open(self.name, self.mode)
return self.file


def __exit__(self, exc_type, exc_val, exc_tb):
print('calling __exit__ method')
if self.file:
self.file.close()

with FileManager('test.txt', 'w') as f:
print('ready to write to file')
f.write('hello world')

## 输出
# calling __init__ method
# calling __enter__ method
# ready to write to file
# calling __exit__ method

基于生成器的上下文管理器:

1
2
3
4
5
6
7
8
9
10
11
12
13

from contextlib import contextmanager

@contextmanager
def file_manager(name, mode):
try:
f = open(name, mode)
yield f
finally:
f.close()

with file_manager('test.txt', 'w') as f:
f.write('hello world')
  • 基于类的上下文管理器更加 flexible,适用于大型的系统开发;
  • 而基于生成器的上下文管理器更加方便、简洁,适用于中小型程序。

量化交易实战篇

33 | 带你初探量化世界

  • 程序化交易:它通常用计算机程序代替交易员,来具体执行金融产品的买卖。比如,一个基金经理需要卖出大量股票。如果直接挂一个大的卖单,可能会影响市场,那就用计算机程序拆分成小单慢慢执行。量化交易的下层通常是程序交易。

  • 算法交易:通常用于高频交易中。它指的是,通过算法快速判定买卖的时间点,快速买卖多个产品。

  • 量化交易:通常是指使用数学、统计甚至机器学习的方法,去找寻合适的买卖时机。所以,在这个维度的定义之下,算法交易、高频交易还有统计套利都可以算作量化交易。

  • 盈透证券:提供支持股票、期权的行情数据获取和交易的接口。

  • Gemini、OKCoin(交易所):提供进行数字货币行情获取和交易的接口。

一个基本的交易系统包括:

  • 行情模块:获取市场的行情数据,负责获取交易账户的状态。
  • 策略模块:订阅市场的数据,根据设定的算法发出买、卖指令给执行模块。
  • 执行模块:接受并把策略模块发过来的买、卖指令封装并转发到交易所;同时,监督并确保策略买卖的完整执行。
  • 回测系统:模拟检测当前的策略。

34 | RESTful & Socket: 搭建交易执行层核心

REST 的全称是表征层状态转移(REpresentational State Transfer)。实质可以理解为:通过 URL 定位资源,用 GET、POST、PUT、DELETE 等动词来描述操作。每个 REST 请求都是独立的,不需要服务器在会话(Session)中缓存中间状态来完成这个请求,即无状态。

  • maker:挂单者。
  • taker:吃单者。
  • 买(buy):用美元买入比特币的行为。
  • 卖(sell):用比特币换取美元的行为。
  • 市价单(market order):给交易所一个方向(买或者卖)和一个数量,交易所把给定数量的美元(或者比特币)换成比特币(或者美元)的单子。
  • 限价单(limit order):给交易所一个价格、一个方向(买或者卖)和一个数量,交易所在价格达到给定价格的时候,把给定数量的美元(或者比特币)换成比特币(或者美元)的单子。

35 | RESTful & Socket: 行情数据对接和抓取

交易所主要有两种行情数据:委托账本(Order Book)和活动行情(Tick data)。

WebSocket 是一种在单个 TCP/TSL 连接上,进行全双工、双向通信的协议。WebSocket 可以让客户端与服务器之间的数据交换变得更加简单高效,服务端也可以主动向客户端推送数据。在 WebSocket API 中,浏览器和服务器只需要完成一次握手,两者之间就可以直接创建持久性的连接,并进行双向数据传输。

行情模块可以使用Gemini提供的两种Websocket接口:

  • public:公开orderbook,每个人都能看到的当前挂单价和深度。
  • private:个人订单被执行等变动的通知。

36 | Pandas & Numpy: 策略与回测系统

OHLCV 数据:开盘价(Open)、最高价(High)、最低价(Low)和收盘价(Close),还有一个成交量(Volume)。

常见的两类回测框架:

  • 向量化回测框架:通常基于 Pandas+Numpy 来自己搭建计算核心;后端则是用 MySQL 或者 MongoDB 作为源。这种框架通过 Pandas+Numpy 对 OHLC 数组进行向量运算,可以在较长的历史数据上进行回测。不过,因为这类框架一般只用 OHLC,所以模拟会比较粗糙。
  • 事件驱动型回测框架:本质上是针对每一个 tick 的变动或者 orderbook 的变动生成事件;然后,再把一个个事件交给策略进行执行。因此,虽然它的拓展性很强,可以允许更加灵活的策略,但回测速度是很慢的。

成熟的回测框架:

  • Zipline,就是一个热门的事件驱动型回测框架,背后有大型社区和文档的支持。
  • PyAlgoTrade 也是事件驱动的回测框架,文档相对完整,整合了知名的技术分析(Techique Analysis)库 TA-Lib。在速度和灵活方面,它比 Zipline 强。不过,它的一大硬伤是不支持 Pandas 的模块和对象。

37 | Kafka & ZMQ:自动化交易流水线

中间件,是将技术底层工具和应用层进行连接的组件。

简而言之,消息队列就是一个临时存放消息的容器,有人向消息队列中推送消息;有人则监听消息队列,发现新消息就会取走。根据我们刚刚对中间件的解释,清晰可见,消息队列也是一种中间件。

市面上使用较多的消息队列有 RabbitMQ、Kafka、RocketMQ、ZMQ 等。