python量化交易入门教程_Python期货量化交易基础教程(9)

9、模块、包和文件:

复杂的程序设计,不可能把所有的代码都写在一个文件里,也不可能把所有文件都放在同一个文件夹里。

9.1、模块 :

模块就是以“.py”为扩展名的文件,一个文件中可以定义变量、函数、类,因此模块就是Python对象的集合。

想要使用某个模块中的代码,就需要在程序中导入这个模块,例如,我们想要使用datetime这个模块里的类datetime,用其获取当前的日期时间,如下:

>>> import datetime

>>> datetime.datetime.now()

datetime.datetime(2021, 1, 15, 14, 49, 52, 954014)

import语句用来导入模块,import datetime表示导入整个模块,在使用模块中的类或方法时,用“.”操作符调用,因此,本例按“模块名.类名.方法名”的形式执行datetime.datetime.now(),输出结果为datetime.datetime(2021,1,15,14,49,52,954014),即输出的日期时间是datetime模块中的datetime类型。

import导入模块时会执行模块的全部代码,比如定义各种类和函数,定义好了才能被后续调用。

我们也可以用 from 模块名 import 类名/方法名语句导入指定的类或方法,例如:

>>> from datetime import datetime

>>> datetime.now()

datetime.datetime(2021, 1, 15, 15, 0, 48, 327500)

>>>

我们从模块datetime中导入了类datetime,则在调用类中方法时用datetime.now()即可,前面不用再加上模块名了。

如果要导入模块中的多个类,可以用逗号隔开,例如:

>>> from datetime import datetime, time

>>> datetime.now()

datetime.datetime(2021, 1, 15, 15, 9, 23, 987994)

>>> time(14,12,32)

datetime.time(14, 12, 32)

用from 模块名 import *可导入模块中的全部代码,但一般不这样用,按需要导入就行了,而且导入全部代码时,其中的类和方法可能有与程序中的类或方法同名的,会造成覆盖定义。

用import 模块名 as 新名 可以为名字较长的模块重新命名一个短的名字,例如:

>>> import datetime as d

>>> d.datetime.now()

datetime.datetime(2021, 1, 15, 15, 19, 16, 100861)

>>>

Python在导入模块的时候是按照系统环境中的路径搜索的,如果模块不在搜索的路径中,则会报模块无法搜索的错误ModuleNotFoundError。

用sys模块中的path属性可查看Python的搜索路径,如下:

>>> import sys

>>> sys.path

['', 'E:\\ProgramData\\python37.zip', 'E:\\ProgramData\\DLLs', 'E:\\ProgramData\\lib',

'E:\\ProgramData', 'C:\\Users\\Administrator\\AppData\\Roaming\\Python\\Python37\\site-packages',

'E:\\ProgramData\\lib\\site-packages', 'E:\\ProgramData\\lib\\site-packages\\win32',

'E:\\ProgramData\\lib\\site-packages\\win32\\lib', 'E:\\ProgramData\\lib\\site-packages\\Pythonwin']

>>>

如果模块不在上述路径里,也可以把模块所在的路径添加到搜索路径中,例如:

sys.path.append("E:\\hedger")

每个Python模块都有__name__属性,如果模块被导入到其他程序中,其__name__属性被设置为模块的名称,如果模块作为独立程序执行,其__name__属性被设置为__main__,因此我们可以通过判断__name__的值判断模块的运行状态,我们可以根据__name__值有选择的让模块执行某些代码。

例如:

from datetime import time

day_start = time(8, 45)

if __name__ == "__main__":

day_start = time(15, 15)

print(day_start)

以上代码保存成文件,例如文件名为test.py,双击运行就会执行打印print(day_start),因为独立运行时__name__ 的值为"__main__",若test.py作为模块导入到其他模块中,if语句不被执行,因为__name__ 的值为"test"。

有了模块概念,我们可以再扩展介绍下Python创建数据时的处理方式。

赋值、浅拷贝和深拷贝:

赋值:

即用等号“=”赋值,表示把对象引用和对象绑定,可简单的理解为把对象赋值给变量。多个变量连接赋值,将会绑定到同一个对象上,而不是创建新的对象。

判断两个变量是否指向同一个对象,可以用函数id()查看它们的内存地址,内存地址一样,指向的就是同一个对象,例如:

>>> a=[1,2,3,4,5]

>>> b=a

>>> id(a)

4809352

>>> id(b)

4809352

>>>

把列表赋值给变量a,a再赋值给变量b,查看a和b的地址都是4809352,说明a和b指向的是同一个值。

除了用id()查看内存地址,还可以用is运算符判断,当a和b的内存一样时,b is a返回True,例如:

>>> b is a

True

>>>

列表是可变对象,当用b或a对列表修改时,会直接修改内存中列表的值,a和b又指向的是这同一个地址,因此a或b的值会跟着变化,例如:

>>> a[0]=6

>>> b

[6, 2, 3, 4, 5]

>>> b[-1]=6

>>> a

[6, 2, 3, 4, 6]

>>> b is a

True

>>>

用a[0]=6修改第一个元素,用b[-1]=6修改最后一个元素,a、b的值同时变,因为a和b是对同一个地址的值修改。

如果给b赋新值,a、b就不再是指向同一个地址了,例如:

>>> a=[1,2,3,4,5]

>>> b=a

>>> b is a

True

>>> b=[1,2,3,4,5]

>>> b==a

True

>>> b is a

False

>>>

给b赋新值后,a和b的值相等,但不再是指向同一个地址了。

浅拷贝:

浅拷贝指的是用函数copy()拷贝父对象以创建新对象,但不会拷贝父对象内部的子对象,新对象内部指向与父对象相同的子对象,如果子对象又是一种数据集合,即数据嵌套,比如列表,则子对象会类似于变量那样作为标签,引用更深层次的数据集合。

所以,在编程语言中,把实现上面那种拷贝的方式称之为“浅拷贝”。顾名思义,没有解决深层次问题,例如:

>>> d={'x':1,'y':2,'z':[1,2,3,4,5]}

>>> f=d.copy()

>>> f==d

True

>>> f is d

False

>>> f['x'] is d['x']

True

>>> f['z'] is d['z']

True

>>>

d.copy()拷贝一份d的值赋值给f,f和d的值相等,地址不同,但f和d的键映射的值的地址却相同,说明字典内的子对象(值)没有被拷贝成一份新值,还是原来的值。

我们再用“=”连接赋值看看:

>>> g=d

>>> g is d

True

>>> g['z'] is d['z']

True

>>>

“=”赋值确实又让两个变量指向了同一个地址。

其实,字典的键类似于对象引用,即变量,它指向了值所在的地址,浅拷贝虽然创建了一个新的字典对象,但新字典和原字典却又指向了同样的键,而键又指向了同样的值,如果值是可变数据集合,则值也类似于变量,引用更深层次的数据集,如果数据集是可变数据,通过值修改数据集会同时改变新字典和原字典,而如果为键重新赋值,就相当于为变量重新赋值,效果则会不一样,例如:

>>> d={'x':1,'y':2,'z':[1,2,3,4,5]}

>>> f=d.copy()

>>> d['x']=2

>>> f['y']=3

>>> d;f

{'x': 2, 'y': 2, 'z': [1, 2, 3, 4, 5]}

{'x': 1, 'y': 3, 'z': [1, 2, 3, 4, 5]}

>>> d['z'][0]=2

>>> f['z'][4]=6

>>> d;f

{'x': 2, 'y': 2, 'z': [2, 2, 3, 4, 6]}

{'x': 1, 'y': 3, 'z': [2, 2, 3, 4, 6]}

>>> d['z']=8

>>> d;f

{'x': 2, 'y': 2, 'z': 8}

{'x': 1, 'y': 3, 'z': [2, 2, 3, 4, 6]}

>>>

由输出可知,对字典d和f的键用“=”赋新值,则改变各自自身,但若直接对地址中的值修改,则会同时影响两个字典。

深拷贝:

深拷贝需要用copy模块中的deepcopy函数,深拷贝拷贝了父对象,内部的子对象是基础数据时不会拷贝,但如果子对象引用了更深层次的数据,则也会拷贝更深层次的数据,例如:

>>> import copy

>>> d={'x':1,'y':2,'z':[1,2,3,4,5]}

>>> f=copy.deepcopy(d)

>>> f is d

False

>>> f['x'] is d['x']

True

>>> f['z'] is d['z']

False

>>> d['z'][-1]=6

>>> d;f

{'x': 1, 'y': 2, 'z': [1, 2, 3, 4, 6]}

{'x': 1, 'y': 2, 'z': [1, 2, 3, 4, 5]}

>>>

从输出可知,用copy.deepcopy(d)创建了一个新字典并赋值给f,当字典中的值是基础数据时,f和d的值是同一个地址,当值是嵌套数据时,则值只作为对象引用,引用的是更深层的数据,更深层的数据被拷贝了,因此其地址不再相同,d修改其深层数据时,不会影响f的值。

9.2、包 :

模块是一个Python文件,而包是一个文件夹,但这个文件夹里需要有一个名为__init__.py的文件,Python会把含有文件__init__.py的文件夹作为包,__init__.py可以是没有任何代码的空文件,当导入包或该包中的模块时,执行__init__.py。

类似从模块中导入类或方法,使用import语句导入包中的模块时,需要指定对应的包名,基本形式为:

import 包名1.包名2.….模块名

此方法导入包中模块后,使用全限定名称访问包中模块定义的成员,例如:

包名1.包名2.….模块.函数名

格式比较长,所以通常采用from…import语句,例如:

from 包名1.[包名2.….模块名 import 类名/方法名,或者,from 包名1.[包名2.…. import 模块名。

包中的模块如果要导入同一个包里的另一个模块或子包中的模块,可以直接用import 模块名导入。如果要导入同级的其他包里的模块,就必须从父包开始导入。

如果导入父级更上层的包,可能无法搜索成功,这跟Python当前的搜索路径有关,用sys模块中的path属性可查看Python的搜索路径,如果模块不在搜索路径中,可以按前面的方法添加路径。但更好的方法还是把相关模块都放在搜索路径中。

9.3、安装第三方模块库:

丰富的第三方模块库是Python强大的基础,第三方模块都保存在文件夹site-packages里。

查看安装的第三方模块库用命令:pip list

pip是Python包的管理工具,该工具提供了对Python包的查找、下载、安装、卸载的功能。

安装库:pip install 库名 # 安装最新版本

pip install 库名==2.2.5 # 安装指定版本

升级库:pip install --upgrade 库名

pip install -U 库名

默认通过国际服务器下载,速度比较慢,可使用国内镜像源,国内源的下载速度比国际源快很多,国内源有以下四个较为常用:

例如:pip install pyqt5 -i https://pypi.tuna.tsinghua.edu.cn/simple,这样就会从清华的镜像源去安装pyqt5库。

9.4、文件处理:

open():

Python用内建函数open()打开文件,打开文件不是直接操作了文件里的内容,而是先创建一个文件对象,该文件对象是一个迭代器。用open()函数创建文件对象后,就可以调用文件处理方法了,对文件内容的处理都暂时保存在缓存中。open()函数主要参数含义为:file:要打开的文件,一般是文件的绝对路径,例如,E:\ProgramData\rb2101-1m.csv

mode:文件打开模式,指定要对文件做什么操作。

buffering:打开文件的缓存区大小,可以默认。

encoding:编码类型,一般都用'utf-8',可支持中文。

errors:报错级别。

newline:控制换行符模式,操作系统不同文件内容的换行符可能不一样。

主要用到的参数是file、mode、encoding、newline,其他的可以默认。

mode的主要值及含义:

mode的值有r、w、a、b、x、t、+,可以相互结合。

r:只读模式,不可写入,默认。例如:

f = open(r'E:\ProgramData\test.txt',mode='r',encoding='utf-8')

data = f.read()

print(data)

f.close()

创建文件对象f,此时操作标记在文件开头,表示从开头开始读,然后调用read()函数读取文件全部内容并赋值给data,读取的内容默认会保存成字符串。可以用print(data)输出读取到的内容,文件使用完要调用close()函数关闭。

read()函数可传入整数参数,例如read(n)表示读取n字节。

给文件对象赋值,除了用f =open()这种形式外,还可以在with语句中用open() as f,用with语句省却了f.close()。

r+:读写模式,先读后重写,文件需已存在。应当先读后写,因为操作标记默认在文件开头,当读完了以后操作标记移到了文件末尾再进行写入,如先从头写入,新内容会覆盖掉头部的内容,操作标记移到写到的字符位置,再读取时会从该位置向后读,例如:

f = open(r'E:\ProgramData\test.txt',mode='r+',encoding='utf-8')

data = f.read()

f.write('jkl')

f.flush()

print(data)

f.close()

r+模式下,如果先读取了内容,不论读取内容多少操作标记移到了哪里,再写入的时候都是在结尾写入,类似于追加模式。

w:写入模式,如果文件已存在清除内容重写,如果文件不存在创建新文件写入,操作标记移到文件开头表示从头开始写,例如:

f = open(r''E:\ProgramData\test.txt',mode='w',encoding='utf-8')

f.write('abcd')

f.flush()

f.close()

调用write()函数把字符串'abcd'写入文件,写入后是先保存在缓存里的,调用close()关闭文件对象时才会写入本地,但也可以调用flush()函数立即把缓存写入本地。

w模式如果尝试用read()函数读取会报错:io.UnsupportedOperation: not readable。

w+:读写模式,如果文件已存在清除内容重写,如果文件不存在创建新文件。例如:

f = open(r'E:\ProgramData\test.txt',mode='w+',encoding='utf-8')

data = f.read()

f.write('jkl')

f.flush()

print(data)

f.close()

应先读取文件原来的内容,再清空后写入新内容,如果先清空写入新内容,操作标记移到了文件末尾,再读取读的是空内容,这种用法可能没有意义。

a:追加模式,文件已存在,新内容会被写入到已有内容之后,文件不存在创建新文件写入。操作标记在文件末尾,所以写入时从末尾追加。例如:

f = open(r'E:\ProgramData\test.txt',mode='a',encoding='utf-8')

f.write('jkl')

f.flush()

f.close()

如果把操作标记移到了其他位置,写入时操作标记也会自动移到末尾从末尾追加写入。

a+:文件已存在是追加模式,文件不存在创建新文件用于读写,因此操作标记总是在末尾,直接读时无法读取到内容。例如:

f = open(r'E:\ProgramData\test.txt',mode='a+',encoding='utf-8')

data = f.read()

print(data)

f.write('jkl\n')

data = f.read()

print(data)

f.flush()

f.close()

b:二进制模式,即读取成字节bytes类型。读取非文本文件的时候,比如音频视频等信息的时候就需要用到b模式。例如:f = open(r'E:\ProgramData\test.txt',mode='rb+')

操作标记:

'utf-8'编码下,一个中文(含繁体)占三个字节,一个中文标点占三个字节,一个英文字母占一个字节,一个英文标点占一个字节。

操作标记用来表示当前文件内容被执行到哪个字节,类似于我们打字时的光标,光标在哪里,打字时就会从哪里开始从左往右打,文件开头字节(索引)位置为0,即操作标记位置在开头为0。

tell()函数可以获取当前操作标记在什么位置,例如:f.tell()

seek(offset: int,whence:int=...):seek()函数用来移动操作标记,参数offset是偏移量,即需要移动的字节数;参数whence可选,默认值为 0,表示要从哪个位置开始移动,0代表从文件开头,1代表从当前位置,2代表从文件末尾。例如:seek(0,0)或seek(0)表示移动到开头,seek(0,1)表示移动到当前位置,seek(0,2)表示移动到末尾。

没有使用b模式选项打开的文件,只允许从文件开头移动偏移量,例如f.seek(4,0)或f.seek(4)表示从开头向后移动4个字节,但f.seek(4,1)会报错can't do nonzero cur-relative seeks

其他常用的文件方法:

readline(n):读取一行,n为一次读n个字节,调用一次操作标记移动到了读取的位置,再调用会接着读,直到一行读完,n若超出了行的长度则只读到行尾。当行非常长的时候设置n分批读取可减轻缓存压力。

f = open(r'E:\ProgramData\test.txt',mode='r+')

line=f.readline()

print(line)

f.close()

readlines(n):读取全部行,并把行保存为列表,为减轻压力可设置一次读n个字节,行若不长会读取包含在n字节内的所有行。例如:

f = open(r'E:\ProgramData\test.txt',mode='r+')

line=f.readlines(3)

print(line)

f.close()

__next__():文件对象是一个迭代器,因此可用__next__()读取下一行。例如:f.__next__()

可用for循环遍历文件的行,例如:

f = open(r'E:\ProgramData\test.txt',mode='r+')

for line in f:

print(line)

f.close()

writelines(lines:Iterable[AnyStr]):把一个可迭代对象的元素按行写入文件,元素需是字符串类型,字符串最后要带换行符。例如:f.writelines(['a\n','b\n','c\n'])

创建文件:

我们直接给出创建文件的方法。

os模块的getcwd()函数用来获取程序的路径,pathlib模块的Path函数用来创建路径对象,例如让程序在其路径下自动创建日志文件夹,可如下写:

import os

from pathlib import Path

p=Path(os.getcwd()+r'\logs')

p.mkdir(exist_ok=True)

p.mkdir(exist_ok=True)用来把路径对象p创建为路径,exist_ok=True可使路径已存在时不会报错。创建好路径就可用open(str(p)+r'\logs\test.txt','r+')的形式创建文件了。当然也可以创建指定路径,用指定路径替换程序路径就行了。

更多文件的方法可查阅Python相关资料。

9.5、json文件:

用json模块可以把Python数据和json文件相互转化,对于轻量级的数据转存非常方便,比如记录一些成交数据。json模块主要用到四个方法:dumps、dump、loads、load。

dumps()把Python数据序列化为json字符串,再用write()函数把字符串写入文件,例如:

import json

f = open(r'E:\ProgramData\test.json',mode='r+')

data1 = [ { 'a' : 1, 'b' : 2, 'c' : 3, 'd' : 4, 'e' : 5 } ,

{ 'a' : 1, 'b' : 2, 'c' : 3, 'd' : 4, 'e' : 5 },

{ 'a' : 1, 'b' : 2, 'c' : 3, 'd' : 4, 'e' : 5 }]

data2 = json.dumps(data1,indent=2)

f.write(data2)

f.close()

dump()可以直接把Python数据序列化进文件对象里,例如:

import json

f = open(r'E:\ProgramData\test.json',mode='r+')

data1 = [ { 'a' : 1, 'b' : 2, 'c' : 3, 'd' : 4, 'e' : 5 } ,

{ 'a' : 1, 'b' : 2, 'c' : 3, 'd' : 4, 'e' : 5 },

{ 'a' : 1, 'b' : 2, 'c' : 3, 'd' : 4, 'e' : 5 }]

data2 = json.dump(data1,f,indent=2)

f.close()

dump比dumps多了个文件对象参数,省了写入文件那一步,其余的参数都一样,参数indent为格式化操作,默认值为None,表示以紧凑型把Python数据序列化,如果Python数据比较长,序列化的结果就是一行很长的json字符串,可能不方便观察,indent为整数表示以缩进整数个空格对Python数据换行处理,缩进2个空格是比较方便的,例如上例的转换结果为:

[

{

"a": 1,

"b": 2,

"c": 3,

"d": 4,

"e": 5

},

{

"a": 1,

"b": 2,

"c": 3,

"d": 4,

"e": 5

},

{

"a": 1,

"b": 2,

"c": 3,

"d": 4,

"e": 5

}

]

loads()把文件中的内容反序列化为Python数据,需要先读取到文件内容,再反序列化为Python数据,例如:

import json

f = open(r'E:\ProgramData\test.json',mode='r+')

data1 = f.read()

data2 = json.loads(data1)

print(data2)

print(data1)

f.close()

load()可以直接把文件中的内容反序列化为Python数据,不需要先读取文件内容,例如:

import json

f = open(r'E:\ProgramData\test.json',mode='r+')

data2 = json.load(f)

print(data2)

f.close()

从对比可以知道,dumps()和loads()主要用于对Python数据和json字符串转换,dump()和load()主要用于对Python数据和文件对象的直接存取。

你可能感兴趣的:(python量化交易入门教程)