零基础搭建量化投资系统:以Python为工具
上QQ阅读APP看本书,新人免费读10天
设备和账号都新为新人

第4章 自定义函数、类和作用域

4.1 Python的自定义函数

前面我们学习了Python系统中的函数,这节主要介绍用户自定义函数的使用。

1.函数的定义

函数是组织好的,可重复使用的,用来实现相对单一功能或相关联功能的代码段。

在Python 3中,无论是命名函数,还是匿名函数,都是语句和表达式的集合。使用函数的过程称为调用函数。

以下是定义一个函数的简单规则:

(1)函数代码块以“def”关键词开头,后接函数标识符名称和圆括号“()”。

(2)任何传入参数和自变量都必须放在圆括号内,圆括号之间也可以用于定义参数,称为形式参数。

(3)函数内容以冒号“:”开始,并且以空格缩进。

(4)函数的第一行语句可以选择性地使用注释字符串文档,这个文档用于存放函数说明。

(5)使用“return [表达式]”结束函数,选择性地返回一个值给调用方。不带表达式的“return”相当于返回None值。

下面是一个定义函数“def”语法。

        def function_name([参数表]):
          ''’函数介绍文档。'''
          函数命令块
          return [表达式参数表]

函数调用是指在程序中执行函数。在函数调用中输入的参数称为实参,如range(12)中的12。

看示例4-1中的stockname(n)函数定义。

        #文件名:示例4-1.py
        #数字股票代码转换字符串股票代码
        def stockname(n):
            '''
            函数说明
            数字股票代码转换字符串股票代码
            stockname(n)
            参数: n整型
            返回:字符串
            '''
            s=str(n)                        #把数字转为字符串
            s=s.strip()                     #删除字符串前后空格
            if(len(s)<6 and len(s)>0):  #如果字符串长度为1-5的数,前面就用0补够6位长度
              s=s.zfill(6)+'.SZ'             #深圳股后缀加.SZ
            if len(s)==6:                    #上海股票一般是100000以上的数字
              if s[0:1]=='0':                  #第一位为0,是深圳股票代码
                  s=s+'.SZ'                    #深圳股后缀加.SZ
              else:                            #否则是上海股票代码
                  s=s+'.SH'                    #上海股后缀加.SH
            return s
        print(stockname.__doc__)
        print(stockname(776))
        print(stockname(600030))

在Python中有一个奇妙的特性,即文档字符串“DocStrings”,用它可以为模块、类、函数等添加说明性的文字,使程序更易读易懂,更重要的是可以通过Python自带的标准方法将这些描述性文字进行信息输出。

上面提到的自带的标准方法就是__doc__,需要注意,前后各有两个连在一起的下画线。

命令输出结果:

            函数说明
            数字股票代码转换字符串股票代码
            stockname(n)
            参数: n整型
            返回:字符串
        000776.SZ
        600030.SH

2.函数命名规则

函数命名的规则同变量命名的规则,区分大小写,不能使用Python关键字作为函数名。

函数的命名一定要考虑其所完成的功能和语境,不要认为这是在浪费时间。当然,如果程序很短,又仅供自己使用,就无需考虑这些了。如果项目较大,而且文件较多,则最好花费些时间在函数命名上。这样一方面易于他人查看代码,另一方面便于自己维护。

为了更好地阅读程序,一般对函数命名有如下要求。

(1)简单明了,即根据上下文给动词和介词加上名词,如使用playMusic(file)而不是play(file)。不要为了过度的简洁而影响函数名称的清晰和准确性,也可以使用下画线连接词组的方式,如set_volume(volume),这样更方便通过函数名理解函数的功能。

(2)避免歧义,即要考虑函数的命名是否存在多种解释,如在displayName中display是名词还是动词,不够清晰。如果命名不清晰,则需要重新命名消除歧义。再如,add就可以使用append这类词语替换。

(3)保持一致性,即在应用和库中使用相同的术语来描述同一个概念。避免在一个函数中使用screen(),却在另外一个函数中使用display()。

(4)使用容易读懂的名称。最好是使用一段英文,或者英文短语,有时也可以使用拼音,最好不要中文和英文混合,这样读起来很费劲。

(5)在必要时加前缀。前缀可以代表变量作用域的范围,也可以代表返回值的类型或分类。

例如:

        def  strGetUserId():

其中,前缀“str”表示返回值为字符串类型,不然别人会误以为返回整型数据。

3.函数形式参数表

Python3的函数定义非常简单,但灵活度却非常大。除了正常定义的必选参数外,还可以使用默认参数、可变参数和关键字参数,这就使得函数定义的接口不但可以处理复杂的参数,还可以简化调用者的代码。

函数参数的数据类型分为不可变数据和可变数据,其中不可变数据包括Number(数字)、String(字符串)、Tuple(元组),可变数据包括List(列表)、Dictionary(字典)、Set(集合)、对象数据。

不可变数据类型参数传递的只是值,没有影响传递参数变量的本身。

可变数据类型参数传递的是对象指针,如果修改了变量参数数据,传递参数的变量数据就会修改。因此,我们要对引入的数据进行复制备份并对数据副本进行处理,这样结果才是返回处理后的数据副本指针。

见示例4-2。

        #函数演示
        i=20
        f=19.86
        dic = {i:2*i for i in range(10)}
        l=[i for i in range(10)]
        print('dic= ', dic)
        print('l= ', l)

       def fa(x, y):
            x=x*y
            y=x
            return y
        def fb(x):
            x.clear()
            return x
        def fc(x):
            y=x.copy() #使用copy()函数不会破坏原始数据
            y.clear()
            return y
        a=fa(i, f)
        print('a= ', a)
        print('i= ', i)
        print('f= ', f)
        print(’使用copy()函数,不会破坏原始数据。')
        b=fc(dic)
        print('b= ', b)
        print('dic= ', dic)
        c=fc(l)
        print('c= ', c)
        print('l= ', l)
        print(’不使用copy()函数,会破坏原始数据。')
        d=fb(dic)
        print('d= ', d)
        print('dic= ', dic)
        e=fb(l)
        print('e= ', e)
        print('l= ', l)

程序运行结果:

        dic=  {0: 0, 1: 2, 2: 4, 3: 6, 4: 8, 5: 10, 6: 12, 7: 14, 8: 16, 9: 18}
        l=  [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
        a=  397.2
        i=  20
        f=  19.86
        使用copy()函数,不会破坏原始数据。
        b=  {}
        dic=  {0: 0, 1: 2, 2: 4, 3: 6, 4: 8, 5: 10, 6: 12, 7: 14, 8: 16, 9: 18}
        c=  []
        l=  [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
        不使用copy()函数,会破坏原始数据。
        d=  {}
        dic=  {}
        e=  []
        l=  []

1)默认参数

默认参数是预先赋值,有初值的参数。默认参数可以简化函数的调用。在设置默认参数时,必选参数在前,默认参数在后,否则Python 3的解释器就会报错。

当函数有多个参数时,把变化大的参数放前面,变化小的参数放后面作为默认参数。

见示例4-3。

        #函数默认参数演示
        def mypow(x, y=2):
            return x**y
        a=mypow(4,3)
        b=mypow(4)
        print('a=mypow(4,3)= ', a)
        print('b=mypow(4)= ', b)

程序运行结果:

        a=mypow(4,3)=  64
        b=mypow(4)=  16

2)可变参数

可变参数就是在参数前面加了一个“*”号。在调用该函数时,就可以传入0个或任意个参数,这些可变参数会自动组装为一个元组(tuple)。

*args是可变参数,args接收的是一个元组。

元组无法直接修改,我们可以将它转换为一个列表,再来处理。请看一个利用可变参数来装配排序后列表的代码,见示例4-4。

        #函数*kargs参数演示
        def test(*k):
            return k

       def shortall(*kargs):
            '''
            多参数字符串排序
            shortall(*kargs)
            返回字符串列表
            '''
            #过滤非字符串
            l=list(kargs).copy()
            l.sort()
            return l
        s=test('ABC', '222', '1111', 'xixi', 'hahahah')
        print('s= ', s)
        print('type(s)= ', type(s))
        #1.无参数
        s1=shortall()
        print('s1=', s1)
        #2.字符串参数
        s2=shortall('ABC', '222', '1111', 'xixi', 'hahahah')
        print('s2=', s2)
        #3.数值参数
        s3=shortall(88,9,1,5,32423,999)
        print('s3=', s3)

程序运行结果:

        s= ('ABC', '222', '1111', 'xixi', 'hahahah')
        type(s)=  <class 'tuple'>
        s1= []
        s2= ['1111', '222', 'ABC', 'hahahah', 'xixi']
        s3= [1, 5, 9, 88, 999, 32423]

3)关键字参数

关键字参数允许传入0个或任意多个含参数名的参数,这些关键字参数在函数内部会自动组装为一个字典(dict)。

**kw是关键字参数,kw接收的是一个字典。

见示例4-5。

        #函数**kargs参数演示
        def  myset(**kw):
            '''
            **kw是关键字参数
            mayset(**kw)
            返回参数内容
            '''
            d=kw
            return d
        def detail(name=None, **kargs):
            '''
            detail(name=None, **kargs)-> str
            name is a str.return a str like'name, key1:value1, key2:value2'
                这个函数特定的功能
            '''
            data = []
            for x, y in kargs.items():
                data.extend([', ', str(x), ':', str(y)])
                info = ''.join(data)
            return '%s%s'%(name, info)
        a=myset(name='hhh', age=19, a=98.5, b=97, c=98.8)
        print('a= ', a)
        b=detail(name='hhh', age=19, a=98.5, b=97, c=98.8)
        print('b= ', b)

程序运行结果:

        a=  {'name': 'hhh', 'age': 19, 'a': 98.5, 'b': 97, 'c': 98.8}
        b=  hhh, age:19, a:98.5, b:97, c:98.8

4)参数组合

在Python中,定义函数可以用必选参数、默认参数、可变参数和关键字参数,这4种参数还可以一起使用,或者只用其中几种。但是需要注意,参数定义的顺序必须是必选参数、默认参数、可变参数和关键字参数。

使用函数参数的方式:

(1)按位置匹配,如func(name)。

(2)按关键字匹配,如func(key=value)。

(3)按输入参数匹配,如

元组收集:func(name, arg1, arg2)。

字典收集:func(name, key1=value1, key1=value2)。

(4)按参数定义顺序。

4.函数返回值

(1)return语句的作用是结束函数调用并返回值。

return语句可以不带参数也可以带多个参数。

不带参数就是结束函数运行,返回一个None作为返回值,类型是NoneType。其与return, return None等效,都是返回None。

带参数可以返回调用函数的语句,并且给变量表赋值。

一个函数中可以有任意多个return语句,其中任何一个return语句执行,该函数运行结束。

(2)隐含返回值,指在整个函数体内没有return语句时,那么在函数运行结束时就会隐含返回一个None值作为返回值,类型是NoneType。其与return, return None等效,都是返回None。

请看示例4-6。

        #函数返回参数演示
        #1.无return函数
        def func1(x):
            x=x+1
        a=func1(10)
        print('a= ', a)
        print('type(a)= ', type(a))
        #2.有return函数,无返回参数
        def func2(x):
            x=x+1
            return
        #3.有return函数,返回参数None
        def func3(x):
            x=x+1
            return None
        a=func1(10)
        print('a= ', a)
        print('type(a)= ', type(a))
        b=func2(20)
        print('b= ', b)
        print('type(b)= ', type(b))
        c=func3(40)
        print('c= ', c)
        print('type(c)= ', type(c))
        #4.多个返回参数
        def func4(x):
            return x, x*2, x**2
        d, e, f=func4(5)
        print('d= ', d)
        print('e= ', e)
        print('f= ', f)

程序运行结果:

        a=  None
        type(a)=  <class 'NoneType'>
        a=  None
        type(a)=  <class 'NoneType'>
        b=  None
        type(b)=  <class 'NoneType'>
        c=  None
        type(c)=  <class 'NoneType'>
        d=  5
        e=  10
        f=  25

5.匿名函数

匿名函数表达式是基于数学中的λ演算而得名的,直接对应于其中的匿名函数表达式抽象,是一个匿名函数,即没有函数名的函数。

Python也支持匿名函数,这些函数可以使用λ(λ是希腊字母中的第十一个字母,英文为lambda)关键字创建。

当我们在传入函数时,有些时候不需要显式地定义函数,直接传入匿名函数更方便。匿名函数只能有一个表达式,不用写return语句,返回值就是该表达式的结果。由于匿名函数没有名字,所以不必担心函数名冲突。此外,匿名函数也是一个函数对象,可以被赋值给一个变量,再利用变量来调用该函数。

见示例4-7。

        #1.lambda匿名函数
        f = lambda a, b, c:a+b+c
        print(f(1,2,3))
        #2.匿名函数及输入参数(lambda a, b=2:a*b)(i, j)
        i=3
        j=5
        l=[x for x in range(1,(lambda a, b=2:a*b)(i, j))]
        print(l)
        #3.a = lambda *z:z      #*z返回的是一个元组
        f2 = lambda *z:z        #*z返回的是一个元组
        b=f2(1,3,6,9)
        print('b= ', b)
        #4.lambda **Arg: Arg   #arg返回的是一个字典
        f3=lambda **Arg: Arg   #arg返回的是一个字典
        c=f3(name='hhh', age=19, a=98.5, b=97, c=98.8)
        print('c= ', c)
        #5.lambda嵌套到普通函数中,lambda函数本身作为return的值
        def addx(n):
            n+=n
            return(lambda x: n+n)(n)
        d=addx(3)
        print('d= ', d)

程序运行结果:

        6
        [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14]
        b= (1, 3, 6, 9)
        c=  {'name': 'hhh', 'age': 19, 'a': 98.5, 'b': 97, 'c': 98.8}
        d=  12

6.__main__关键字

Python 3用“if __name__ == '__main__':”语句说明程序的起始入口。

通过此语句可以判断:当前运行的模块是否是直接被Python解释器调用执行的,因为只有被Python解释器直接调用执行的模块文件才能获取程序参数。

getopt是一个包装方法,用以读取main函数后面的参数。

getopt.getopt(args, options[, long_options])有如下三个参数:

args就是Python命令行“python demo_main.py -n小白 -w大家好!”后面的参数“demo_main.py -n小白 -w大家好!”,通常是sys.argv数组。不过我们一般会去除第一个元素,因为sys.argv的第一个元素就是文件名本身“demo_main.py”,所以一般的写法是sys.argv[1:]。

options是一个字符串,描述了需要解析哪些参数。如果一个参数不需要参数值,就只写参数名,如h;如果一个参数需要传入参数值,就在后面加冒号“:”,如n:。所以本案例中hn:w:的意思是有三个参数,分别是-h, -n和-w,其中-h无需传入参数值,而-n和-w需要传入参数值。

long_options是一个字符串数组,也表示需要解析哪些参数,它是相对options而言的。在linux中,我们经常会看到一个命令参数有多种写法,最常见的就是帮助参数。帮助参数有-h和--help两种写法,前一种是options,后一种就是long_options。

假如我们有一个--help,那么在long_options中就是['help']。如果一个参数需要传入参数,如--name 'Good’在long_options中就是['name=']。是的,就是多了一个等于号“=”。

getopt.getopt返回一个元组(opts, args),其中opts就是我们解析出来的参数,而args则是剩余没有解析的参数。opts是元组数组,每个元组就相当于一对键值“key-value”,其中key就是我们的参数名(键),而value就是参数值。

见示例4-8。

        #main函数示例
        import getopt
        import sys
        def say(s1, s2):
            print(’你好,我叫’, s1, ', ', s2)
        if __name__ == '__main__':
            opts, args = getopt.getopt(sys.argv[1:], 'hn:w:', ['name=', 'word=',
              'help'])
            name = 'No Name'
            word = 'Hello'
            for key, value in opts:
              if key in ['-h', '--help']:
                    print(’一个向人打招呼的程序’)
                    print(’参数:')
                    print('-h\t显示帮助’)
                    print('-n\t你的姓名’)
                    print('-w\t想要说的话’)
                sys.exit(0)
                if key in ['-n', '--name']:
                    name = value
                if key in ['-w', '--word']:
                    word = value
              say(name, word)

在Spyder中运行的结果:

        你好,我叫No Name , Hello

因为程序中的变量name和word使用的是程序默认初值,所以这个程序需要在Windows系统的cmd窗口来运行,输入下面的命令。

        Python demo_main.py -n小白 -w大家好!

运行结果如图4-1。

图4-1 cmd窗口的运行结果