python 期货交易_Python期货量化交易基础教程(3)
3、函数式编程:我们在用Python程序处理实际问题时,有些代码可能需要重复使用,如果每次使用都要编写一遍代码会耗费不少工作量,我们可以把这部分代码编写成函数,每次调用函数就能完成工作,不用再重复编写代码了,函数使编程效率大大提高,也使程序代码更为简洁。我们在前文已经介绍过两个Python内置函数:input()和print()。3.1、函数的声明和调用:声明和定义的含义虽有区别,但本教程不做特别
3、函数式编程:
我们在用Python程序处理实际问题时,有些代码可能需要重复使用,如果每次使用都要编写一遍代码会耗费不少工作量,我们可以把这部分代码编写成函数,每次调用函数就能完成工作,不用再重复编写代码了,函数使编程效率大大提高,也使程序代码更为简洁。
我们在前文已经介绍过两个Python内置函数:input()和print()。
3.1、函数的声明和调用:
声明和定义的含义虽有区别,但本教程不做特别区分,后续内容会混合使用声明或定义,按同样的含义理解不影响量化学习。
函数声明时不会执行,只是告诉Python声明了一个函数,函数被调用时才会执行函数体的语句。(函数是可调用对象,在被调用时才会执行)
因为Python代码的执行顺序是从上到下,存在先后顺序,所以在Python中必须先声明函数然后再调用函数,否则在调用函数时会提示找不到函数。调用函数时,只要按照函数声明的形式传递参数,就可以使用函数完成相应的功能,并可以获取函数执行完后的返回值。
声明函数的关键字是def,在函数中以缩进表示各语句属于函数体。声明函数的形式如下:
def 函数名(参数):
语句块
return 返回值
参数是函数需要处理的数据,可以有多个,也可以没有,返回值是当函数执行完后抛出的值,返回值以关键字return引导,return后也可以没有返回值,return语句也可以没有,当未指定返回值时,函数默认返回None值。若函数中有多个return语句,当一个return语句被执行后,其后的语句将不再执行,函数抛出返回值并结束。
前文介绍了数据对象、表达式和流程控制语句,函数可看做是数据对象、表达式和流程控制语句的结合。
示例:
>>> def func(a,b,c):
... print(a,b,c)
... return '执行完成'
...
>>> x=func(1,2,3)
1 2 3
>>> print(x)
执行完成
>>>
示例声明了一个名称为func的函数,有三个参数a,b,c,函数语句块是调用输出函数print()打印a,b,c,函数执行完后抛出返回值'执行完成',返回值是一个字符串。调用函数时传入了三个实参1,2,3,并把函数返回值赋值给了x,所以打印x的值便输出字符串'执行完成'。
声明函数时定义的参数称为形参,在调用函数时具体传给函数的参数称为实参。
函数若需要抛出多个返回值,多个返回值可用逗号“,”隔开,多个返回值会以元组类型抛出,例如:
>>> def func(a,b,c):
... print(a,b,c)
... return (a,b,c)
...
>>> x=func(1,2,3)
1 2 3
>>> print(x)
(1, 2, 3)
>>>
多个返回值可以不用小括号括起来,在前文介绍元组时可知,Python会把用逗号“,”隔开的多个对象创建为元组,因此小括号可以省略,当返回值数量非常多的情况下用小括号会使语句结构更为清晰。
Python3允许定义函数时给参数和返回值增加注释,以便调用者知道应该传给函数什么类型的参数及返回值类型。参数的注释以:value的形式放在参数名后“=”前,返回值以-> value的形式放在小括号后冒号前,例如:
def func(a:str,b:list,c:int=8) ->tuple:
print(a,b,c)
return (a,b,c)
注释会被收集在函数的__annotations__属性中,例如:
>>> func.__annotations__
{'a': , 'b': , 'c': , 'return': }
>>>
有了注释,调用者在调用func时会知道应该给a传入字符串,给b传入列表,给c传入整数,并且函数的返回值是元组。
3.2、函数的参数传递:
3.2.1、无默认值参数:
声明函数时,诸如def func(a,b,c):,参数a,b,c的值未知,此类参数称为位置参数,当调用函数时,可以按位置传递实参,例如,func(1,2,3),实参按位置顺序1传给a、2传给b、3传给c。
也可以按关键字(参数名)传递,例如,func(a=1,b=2,c=3),此时直接给a、b、c赋值,清晰明了的知道了a,b,c的值,不会出现传参错误,传参的顺序便无所谓了,func(a=1,c=3,b=2)也是正确的。
如果传递实参时既有位置参数也有关键字参数,Python传递参数的规则是先传位置参数,后传关键字参数,因此,func(1,b=2,c=3)正确,func(a=1,2,c=3)错误,所以在传递实参时,要先按位置顺序传递参数,再传递关键字参数,顺序不能颠倒。
3.2.2、默认值参数:
在声明函数时,也可以给参数赋值默认值,在调用函数时如果不给有默认值的形参传递实参,函数体就会以形参的默认值执行。在声明函数时,默认值参数也要放在无默认值参数后面。
例如:
>>> def func(a,b,c=6):
... print(a,b,c)
... return a,b,c
...
>>> x=func(1,b=2)
1 2 6
>>> print(x)
(1, 2, 6)
>>>
函数func有默认值c=6,在调用函数时,实参1按位置传给了a,b=2按关键字传递,参数c没有传递实参,取得默认值6。
3.2.3、可变参数:
在有些情形下,我们需要传给函数的参数数量不是固定的,可以按需传递。
可变无默认值参数
声明函数时,在参数前加一个星号“*”,该参数便是可变数量无默认值参数,传给该参数的实参会被收集到一个元组里。例如:
>>> def func(*a):
... print(a)
...
>>> func(1,2,3,4,5,6)
(1, 2, 3, 4, 5, 6)
参数*a是不定量参数,调用函数时,传入的参数1,2,3,4,5,6以元组类型传递(赋值)给a,即a=(1,2,3,4,5,6)。
若函数中既有不定量无默认值参数又有定量无默认值参数,则不定量参数应放在定量参数之后。例如:
>>> def func(b,c,*a):
... print(b,c,a)
...
>>> func(7,8,1,2,3,4,5,6)
7 8 (1, 2, 3, 4, 5, 6)
>>>
调用函数时,实参7传给参数b,实参8传给参数c,剩下的1,2,3,4,5,6以元组类型传给a。
实际上,b、c、*a都可以看做无默认值的位置参数,Python在传递实参时按位置依次给b、c传参,b、c传参完,剩下的再都传给位置*a。
调用函数时,也可以按关键字传递参数,传递参数时仍然遵循位置参数在前关键字参数在后,例如:
>>> def func(*a,b,c):
... print(b,c,a)
...
>>> func(1,2,3,4,5,6,b=7,c=8)
7 8 (1, 2, 3, 4, 5, 6)
>>>
调用函数时,实参1,2,3,4,5,6先按位置传给*a,接着遇到关键字传参,则按关键字传递:b=7,c=8。若b、c有一个没有按关键字传参会报错,例如,func(1,2,3,4,5,6,7,c=8)错误,因为b没有被传参。为避免按位置传参出错,声明函数时不定量参数最好放在定量参数之后。
既然可变无默认值参数传参时会被收集到元组里,可不可以直接把元组传递给可变参数呢?当然可以的,不仅是元组,列表、字符串都可以传递给可变参数,例如:
>>> def func(*a):
... print(a)
...
>>> b=(1,2,3,4);c=[5,6,7,8];d='abcde'
>>> func(*b);func(*c);func(*d)
(1, 2, 3, 4)
(5, 6, 7, 8)
('a', 'b', 'c', 'd', 'e')
>>>
调用函数时分别传入了元组、列表和字符串,其中的元素被作为可变参数又以元组类型收集到a中,传参的时候b、c、d前都要有一个星号“*”,星号“*”在此处是“拆解”的意思,表示将元组、列表和字符串的元素拆解出来。
可变有默认值参数
除了无默认值参数可变,有默认值参数也可变,可变有默认值参数也称为可变关键字参数。
声明函数时,在参数前加两个星号“**”,该参数便是可变数量有默认值参数,传给该参数的关键字实参会按“'参数名':参数值”收集到一个字典里。例如:
>>> def func(**a):
... print(a)
...
>>> func(a=1,b=2,c=3,d=4)
{'a': 1, 'b': 2, 'c': 3, 'd': 4}
>>>
除了以关键字向可变关键字参数传参,还可以把字典传给可变关键字参数,传参时在字典前加两个星号“**”,例如:
>>> def func(**a):
... print(a)
...
>>> b={'a': 1, 'b': 2, 'c': 3, 'd': 4}
>>> func(**b)
{'a': 1, 'b': 2, 'c': 3, 'd': 4}
>>>
同样,两个星号“**”在这里是“拆解”的意思,表示把字典的“键:值”对拆解成关键字参数。
因为Python先按位置再按关键字传参,所以,当函数中定量无默认值参数、可变无默认值参数、定量关键字参数及可变关键字参数同时存在时,排列顺序应为:定量无默认值参数,可变无默认值参数,定量关键字参数,可变关键字参数。例如:
>>> def func(a,*b,c=7,**d):
... print(a,b,c,d)
...
>>> func(1,2,3,4,c=5,e=6,f=7,g=8)
1 (2, 3, 4) 5 {'e': 6, 'f': 7, 'g': 8}
>>>
调用函数时,先按位置依次传参,例如1传参给位置a,定量无默认值参数传参完后,剩下的位置参数2,3,4以元组传给*b,接着按关键字一一对应传参,例如c=5,关键字对应传参完后,剩下的关键字参数e=6,f=7,g=8没有了对应关系,便以字典传给**d。
在函数式编程中,有时参数的名称有着特定含义,给参数一一对应赋值可使编程逻辑更为清晰,此时即便在声明函数时没有给参数赋默认值,但参数仍具有关键字参数的意义,也可以把这些参数放在可变无默认值参数之后,只要在调用函数时以关键字形式传参即可,整体规则还是位置参数在前,关键字参数在后,例如:
>>> def func(*b,a,c=7,**d):
... print(a,b,c,d)
...
>>> func(2,3,4,a=1,c=5,e=6,f=7,g=8)
1 (2, 3, 4) 5 {'e': 6, 'f': 7, 'g': 8}
>>>
3.2.4、以函数作为参数:
Python中函数也是对象,因此也可以将函数作为实参传递给另一个函数的形参,传参时只需要传入实参函数的名称,如果实参函数也有参数可能需要再传入其参数,在调用方函数的语句块中调用实参函数。例如:
>>> def func1(c,d):
... print(c+d)
... return '函数f执行完成'
...
>>> def func2(f,a,b):
... x=f(c=a,d=b)
... print(x)
...
>>> func2(f=func1,a=2,b=3)
5
函数f执行完成
>>>
上例声明了两个函数,func1和func2,func1有参数c,d,函数语句块是打印参数之和c+d并抛出返回值'函数f执行完成';func2有参数f,a,b,函数语句块是调用函数f(c=a,d=b),并把返回值赋值给变量x,(f的参数名和函数func1的参数名要保持一致),最后打印x的值。
声明了函数func1和func2之后,调用函数func2(f=func1,a=2,b=3),函数名func1传递给f,2传递给a,3传递给b,由于func1也有参数,从func2的语句块可知func1会使用a和b的值。执行时func2便调用func1打印5,最后再打印x的值'函数f执行完成'。
函数也可以调用其本身,函数调用自身常用在递归问题中,例如求解斐波那契数列:
>>> def fib(n):
... if n <= 1:
... return n
... return fib(n-1) + fib(n-2)
...
>>> for n in range(11):
... print(fib(n),end=',')
...
0,1,1,2,3,5,8,13,21,34,55,
函数fib有两个返回值,当n <=1返回n,当n>1时返回fib(n-1)+ fib(n-2),return语句中调用了函数fib自身。通过for循环求解fib(0)···fib(10)并将结果输出。range( )是一个生成器,每次循环生成一个整数,range( )本身是一个类,且经常被用到。
3.3、变量的作用域:
Python的作用域可分为:内置作用域:Python预先定义的
全局作用域:所编写的整个程序
局部作用域:某个函数的内部范围
声明函数时,Python会检查函数是否存在语法错误,创建函数的名称,但在调用函数时Python才会执行函数体,为函数对象创建一个命名空间,该命名空间就是局部作用域。
不同函数的作用域是相互独立的,同一函数在不同时间调用,其作用域也是相互独立的。作用域相互独立,在函数内声明的名字相同的变量便也是相互独立的,各函数对其内部变量处理时互不影响。例如:
def func1():
c=345
print(c)
def func2():
c=567
print(c)
函数func1和func2有同名变量c,但在调用时互不影响。
Python会默认函数内部声明的变量为局部变量,但若函数内没有声明变量,Python便会从全局作用域中(向前)查找同名变量。例如:
>>> c=456
>>> def func1():
... c=345
... print(c)
...
>>> def func2():
... print(c)
...
>>> def func3():
... print(c)
... c=567
...
>>> func1()
345
>>> func2()
456
>>> func3()
Traceback (most recent call last):
File "", line 1, in
File "", line 2, in func3
UnboundLocalError: local variable 'c' referenced before assignment
>>>
上面例子声明了全局变量c=456和函数func1、func2、func3,函数func1内部声明了变量c=345,调用func1便输出345,函数func2内部没有声明变量,调用func2便(向上)查找同名全局变量c输出456,函数func3内部声明了变量c=567,变量c便是局部变量,但因print(c)先使用变量c后声明变量c,便提示了错误。
实际上,是函数内部声明变量时覆盖掉了同名的全局变量,并使函数内部声明的变量成为了局部变量。Python会先从函数内部查找声明的变量,若函数内没有声明变量,Python便会从全局作用域中(向前)查找同名变量。
但如果把全局变量作为实参传给函数,函数会先使用全局变量,直到遇到同名变量声明再转换为局部变量,这一点需要注意。例如:
>>> a=3;b=[456]
>>> def func3(a,b):
... print(a,b)
... a=5 #声明
... b.append(789) #修改
... print(a,b)
... b=[3,4,5] #声明
... print(a,b)
... b.pop() #修改
... print(a,b)
...
>>> func3(a=a,b=b)
3 [456]
5 [456, 789]
5 [3, 4, 5]
5 [3, 4]
>>> print(a,b)
3 [456, 789]
>>>
上面例子定义了全局变量a=3;b=[456]和函数func3(a,b),调用func3时把全局变量a、b的值传给func3,所以第一句print(a,b)先使用全局变量值输出了3 [456],接着声明了局部变量a=5,并给b添加一个元素789,第二句print(a,b)输出局部变量a的值5和b的新值[456,789],接着再给b赋值b=[3,4,5],b此时变成了局部变量,第三句print(a,b)输出局部变量a、b的值5 [3,4,5],接着再删除局部变量b最后一个元素,第四句print(a,b)输出5 [3,4]。
函数执行完成之后,再用print(a,b)输出全局变量a、b的值,a的值还是3,但b的值变成了第一次修改的值[456,789],第二次修改的b值是局部变量,没有影响全局变量b。
因此,函数调用全局变量时要注意,是否会与内部声明的变量存在值的覆盖,以及函数是否会修改全局变量。函数内部声明的变量最好不要与全局变量同名,直接调用全局变量时也需要明晰是否有修改全局变量的行为,以免造成难以预料的结果。
如果明确要修改全局变量,最好以关键字global声明,关键字global表示该变量是全局变量,对该变量修改便会直接修改全局变量,以global声明需要修改的全局变量会使代码逻辑更加清晰,便于对复杂的代码维护。例如:
>>> a=3;b=[456]
>>> def func4():
... global a,b,c
... a=567
... b=[3,4,5]
... c=89
... print(a,b)
...
>>> func4()
567 [3, 4, 5]
>>> print(a,b,c)
567 [3, 4, 5] 89
>>>
上例声明了全局变量a=3;b=[456]和函数func4,函数内把a、b声明为全局变量,所以函数内对a、b赋值之后也修改了全局变量a、b的值,global关键字同时也声明了全局变量c,因此c也可以在全局使用了,函数内print(a,b)输出了值567 [3,4,5],函数外print(a,b,c)输出了值567 [3,4,5] 89。
3.4、匿名函数 lambda:
关键字lambda用来创建匿名函数,其语法形式为:
func = lambda 参数:(表达式1,表达式2) #声明匿名函数
func(参数) #调用函数
参数是表达式要处理的数据,多个参数以逗号隔开,表达式的结果是函数的返回值,多个表达式用小括号括起来,会以元组类型返回,匿名函数赋值给变量func,之后像调用其他函数那样调用匿名函数func(参数),例如:
>>> func = lambda x:(x**2,x**3)
>>> print(func(2))
(4, 8)
>>>
匿名函数func有一个参数x,两个表达式分别求x的平方和x的立方,调用func并传入参数2,输出元组(4,8)。
匿名函数的声明只有一行,在有些情形下采用匿名函数会使代码更加简洁。
3.5、Python常用内置函数:dir() :列出对象的相关信息
help() :列出对象的帮助信息
type() :查看对象的数据类型(本身是类)
isinstance(obj, str):判断obj是否是str类型
更多推荐



所有评论(0)