RWPy4learner 11.3.30 documentation

Version: 11.3.30

函数化

函数的好处

小白学习完文本处理并完成用文本存储联系人(请见第二章第二节ReadFile2Dict.py)后,对学习 Python的表现非常满意。他兴致勃勃地把通讯录给舅舅展示了一番,“舅舅,怎么样?我用 Python 开发的通讯录还不错吧?”。

舅舅看了看小白写的通讯录 (ReadFile2Dict.py),“还不错!进步很快呀!不过,你 Python 的功力才在第一层次。”

“什么?!才第一层次。我最近一致很努力学习 Python 呢。” 小白对舅舅的评价不太满意。他继续追问道,“什么是 Python的第一层次? 怎么才能提高 Python 功力到更高层次呢?”

舅舅耐心地向小白解释道,“你开发的通讯录已经不错了,进步很快。不过,你的代码是从上到下逐行运行的,没有函数和类等功能。这是初学编程的程序员的典型特点。代码从上到下逐行运行符合初级程序员较简单的逻辑思维,这样的代码写着比较容易。但这样的代码重用性很低。比如,你再次要用到读取通讯录文本,或者搜索联系人这些功能时,需要重新写这些代码。这种编程方式使得代码重复度很高,也不利于维护。如果你能够把一些基本功能,比如,读取通讯录文本文件 ,搜索联系人等用函数或者类实现,你就可以重用这些代码。使用函数和类等特性,可以使程序开发效率更高,更容易维护。”

听了舅舅一席话,小白感到胜读十年书。“函数有这么多好处呀!那我尝试把读取通讯录,搜索联系人这些功能‘函数化’吧。”

什么是函数?

“舅舅,什么是函数呀?Python 中的函数和我们初中数学中学的函数有什么不同呢?” 小白向舅舅追问,希望舅舅能够将他引入 “函数化”的大门。 舅舅看小白对新的概念 “函数”有点儿迷惑不解,便举了一个很简单的例子。我们上初中数学时,都学过函数。比如y = x +1,就是一个函数,x是自变量, y是因变量。我们有一个x值,希望做一定计算后得到最终的 y值。有时候,我们也记作y = f(x),表示y是关于x的函数,函数名为f 。数学学得好,编程也会很牛逼的。 程序中的函数和数学中的函数非常相似。我们写程序时,经常会重复很多功能,比如多次搜索联系人信息。函数就是一块完成一定功能的代码。最简单的函数可以只有一个函数名,使用函数名就可以调用函数里的代码块。复杂一点儿的函数有输入变量,也就是我们提供给函数的变量信息,比如 y = f (x),x就是函数的输入变量,f就是函数名。我们在执行函数后,通常还希望得到函数的运行结果,因此,一些函数还有返回值,比如 y 。

Python 提供了很多自带的函数,比如你作文本处理时用到的open, write, close等都是系统的函数。你自己写的函数,就叫自定义函数。

小白听了舅舅讲了这么多,有点儿云里雾里的。“舅舅,行胜于言,你讲这么多,还不如直接给我看一个函数。”

Hello,函数

舅舅想到了所有程序语言的入门程序,”Hello World”系列。“小白,你刚刚学 Python 时,写的Hello World程序在哪里呀?”。 小白翻了翻自己的学习笔记,在第一章节找到了Hello World程序。对于 Python,就一行代码 “print ‘Hello World’”。

舅舅告诉小白,函数不难,给这个helloworld.py程序,加一个函数名就是函数了。舅舅写了个函数示例代码给小白, hellofunction.py 如下:

'''
hellofunction.py
By Jibo He @ueseo.org
hejibo@ueseo.org
Function:
        demo function in python
        based on helloworld.py in chapter 1
'''
# 定义 HelloWorld 函数
def HelloWorld():
        print 'Hello World'

#调用 HelloWorld
HelloWorld()

小白比较了helloworld.py和hellofunction.py,发现舅舅也就增加了两行代码。”def HelloWorld()” 定义了一个叫作 HelloWorld 的函数,HelloWorld()调用这个函数。 其中,”def” 是 Python 的关键词,是英文 “define” (定义)的缩写,用于创建一个函数。HelloWorld是 定义的函数名。括号内可以加入函数的参数。这个简单的 HelloWorld 函数没有参数,因此,括号内是空白的。要调用这个函数时,只需要使用函数名,就可以运行函数内的全部代码块了。

变量作用域

看了代码后,小白初不了解了函数。“函数不就是给要重复使用的代码块取个名字,方便重复使用。这还不简单。”小白决定,把自己写的通讯录 (ReadFile2Dict.py)给函数化。 小白决定仿照helloworld.py函数化的方式。他把ReadFiles2Dict.py重新取名为ReadFile2DictFunctoin-variable.py。然后把从文本文件中读取联系人的代码块缩进,在前面加上” def ReadContact(): “,把这段代码块定义为一个函数,取名为ReadContact。

# -*- coding: cp936 -*-
'''
ReadFile2DictFunctoin-variable.py
By Jibo He @ueseo.org
hejibo@ueseo.org
Function:
        - read data from Contact.txt
        - Convert data into a dict
        - combine features in ReadFileDemo.py and dict.py
        - demo variable scope in a function
Dependencies:
        Contact.txt
'''

def ReadContact():
        _Info = {}

        fh = open('Contact.txt')
        for line in fh.readlines():
                #print line
                # 将每一行分隔开,提取姓名和联系方式
                cell = line.split('\t')
                name = cell[0]
                contact = cell[1]
                #print cell
                _Info[name] = contact

        print _Info

_run = True
while _run:
        _User_input = raw_input("输入联系人名:")

        ReadContact()

        if _Info.has_key(_User_input) == True:
                print _Info[_User_input]
        elif _User_input == "退出":
                _run = False
        else:
                print "无此联系人"

小白本以为函数化会像Hello World一样简单,可是当他运行上面的代码时,却出现了错误。错误如下:

>>>
输入联系人名:张三
{'\xd5\xc5\xc8\xfd': '138-1000-2345\n', '\xc0\xee\xcb\xc4': '138-8888-8888\n', '\xcd\xf5\xb6\xfe\xc2\xe9\xd7\xd3': '138-8888-9999\n'}

Traceback (most recent call last):
  File "D:\Dropbox\python book\ch02\ReadFile2DictFunction-variable.py", line 36, in <module>
        if _Info.has_key(_User_input) == True:
NameError: name '_Info' is not defined

小白还是略微懂英文的。“NameError: name ‘_Info’ is not defined”是说’_Info’没有被定义。这是怎么回事呢?原来定义了的变量’_Info’,在函数化后,就变成了没有定义了。小白在必应中搜索”NameError: name ‘_Info’ is not defined”这个错误信息,以尽量找到更多的信息。在阅读了一些搜索结果后,小白了解到”name XXX is not defined”的错误就是表明某个变量没有被定义。小白接着在必应中搜索“python 函数 变量 定义”,想解决变量在函数化后没有被定义的问题。小白逐渐接触到了局域变量和全局变量。 “哦!原来函数内的变量是局域变量,在函数外是不存在的。” 小白终于找到了问题的症结所在。可是,怎么才能获得函数内的变量值,比如’_Info’ 呢?

小白大致了解问题所在,可是不知道应该用什么关键词进行搜索。实在想不到别的办法了,小白决定向 Python 邮件列表求助。他把错误信息和代码发到了 Python 邮件列表,很快得到了回复。邮件列表里的热心行者们很快做出了回复。 他们说, 函数里的变量通常是局域变量,在函数外是不能访问的。要解决这个问题,可以把函数内的变量通过”global _Info”申明为全局变量,或者通过”return _Info”把变量作为函数返回值。一些行者跟贴说,global 申明全局变量的方法虽然可行,但是不推荐,因为这可能导致变量冲突。

根据行者们的帮助, 小白先试了试在ReadContact函数内加了一行代码 “global _Info”,把_Info申明为全局变量。这果然轻松解决了问题。 小白的代码如下:

# -*- coding: utf-8 -*-
'''
ReadFile2DictFunctionGlobal.py
By Jibo He @ueseo.org
hejibo@ueseo.org
Function:
        - read data from Contact.txt
        - Convert data into a dict
        - combine features in ReadFileDemo.py and dict.py
        - demo function in python
Reference:
        http://www.ibm.com/developerworks/cn/linux/sdk/python/python-5/index.html
Dependencies:
        Contact.txt
'''

def ReadContact():
        #申明全局变量
        global _Info
        _Info = {}

        fh = open('Contact.txt')
        for line in fh.readlines():
                #print line
                # 将每一行分隔开,提取姓名和联系方式
                cell = line.split('\t')
                name = cell[0]
                contact = cell[1]
                #print cell
                _Info[name] = contact

        print _Info

_run = True
while _run:
        _User_input = raw_input("输入联系人名:")

        # 调用读取联系人文本文件函数
        ReadContact()

        if _Info.has_key(_User_input) == True:
                print _Info[_User_input]
        elif _User_input == "退出":
                _run = False
        else:
                print "无此联系人"

小白又尝试了一下函数返回值的解决方法。他在ReadContact的末尾加上了”return _Info” 这行代码。然后再通过”_Info = ReadContact()”把函数运行后的值赋给_Info。这种方法也成功地解决了前面遇到的_Info变量没有被定义的问题。 小白的代码如下:

# -*- coding: utf-8 -*-
'''
ReadFile2DictFunctoin.py
By Jibo He @ueseo.org
hejibo@ueseo.org
Function:
        - read data from Contact.txt
        - Convert data into a dict
        - combine features in ReadFileDemo.py and dict.py
        - demo function in python
Reference:
        http://www.ibm.com/developerworks/cn/linux/sdk/python/python-5/index.html
Dependencies:
        Contact.txt
'''

def ReadContact():
        _Info = {}

        fh = open('Contact.txt')
        for line in fh.readlines():
                #print line
                # 将每一行分隔开,提取姓名和联系方式
                cell = line.split('\t')
                name = cell[0]
                contact = cell[1]
                #print cell
                _Info[name] = contact

        print _Info
        # 返回函数值
        return _Info


_run = True
while _run:
        _User_input = raw_input("输入联系人名:")

        # 调用读取联系人文本文件函数
        _Info = ReadContact()

        if _Info.has_key(_User_input) == True:
                print _Info[_User_input]
        elif _User_input == "退出":
                _run = False
        else:
                print "无此联系人"

带参数的函数,搜索联系人函数

小白通过搜索引擎和行者们的帮助,完成了读取联系人文本文件的函数化。比较了一下原来的代码,和函数化后的代码,他发现,函数化使代码结构更清晰了。”恩!使用函数有挺多优势的。何不把其它功能,比如搜索联系人的代码块也函数化呢?这样我就不用每次查找联系人都重复这一大块代码了。” 于是,小白动手写搜索联系人的函数。他首先把搜索联系人的代码块缩进,使用”def SearchContact():”给这段代码取了一个函数名,叫做SearchContact。与前面两次函数化不同的是,小白希望能够把用户查询的联系人做为自变量输入函数。他隐约记得舅舅说过,函数括号里是放变量名的。于是,他把SearchQuery加到了自定义的函数中,这行代码变为了”def SearchContact(SearchQuery):”。下面是小白完成函数化后的通讯录软件。舅舅所言不虚,函数化后的程序果然结构更清楚易懂了。

# -*- coding: utf-8 -*-
'''
ReadFile2DictFunctoinParameter.py
By Jibo He @ueseo.org
hejibo@ueseo.org
Function:
        - read data from Contact.txt
        - Convert data into a dict
        - combine features in ReadFileDemo.py and dict.py
        - demo function parameter in python
Dependencies:
        Contact.txt
'''

def ReadContact():
        '''读取联系人信息文本文件'''
        _Info = {}

        fh = open('Contact.txt')
        for line in fh.readlines():
                #print line
                # 将每一行分隔开,提取姓名和联系方式
                cell = line.split('\t')
                name = cell[0]
                contact = cell[1]
                #print cell
                _Info[name] = contact

        print _Info
        # 返回函数值
        return _Info

def SearchContact(SearchQuery):
        '''搜索联系人函数'''
        if _Info.has_key(SearchQuery) == True:
                print _Info[SearchQuery]
        elif SearchQuery == "退出":
                _run = False
        else:
                print "无此联系人"


_run = True
while _run:
        _User_input = raw_input("输入联系人名:")

        # 调用读取联系人文本文件函数
        _Info = ReadContact()
        SearchContact(_User_input)

保存联系人的函数

在完成通讯录的函数化后,小白对自己学习 Python 的能力有了更多的自信。他决定进一步开发通讯录的功能。现在的通讯录,只有查询功能。但是他用过的其它通讯录软件都有新建联系人和更新联系人等功能。小白计划先给软件增加一个新建联系人功能。当查不到联系人时,提醒用户提供这个联系人的信息,然后保存到文本文件中。小白将这个新建联系人的函数取名为”CreateNewContact”,定义这个函数的代码为 “def CreateNewContact():” 。当搜索联系人函数找不到联系人时,即”print “无此联系人”“代码之后,调用CreateNewContact函数。

这个新建联系人的功能主要包括两部分。一是提示用户输入,二是将用户输入的联系人信息写入文件文件中。对于提示用户输入,可以使用NewContact = raw_input(“输入新的联系人信息:”)就可以了。 对于将信息写入文本文件,小白在上一节文本处理时已经有不少了解。使用fh = open(‘Contact.txt’, ‘w’)是创建一个新的叫做Contact.txt文件。小白需要的是在已经存在的Contact.txt的末尾增加新的记录。小白回忆了学习文本处理时看到的帖子,更新已经存在的文本,应该使用’a’(append的英文缩写)这个参数,代码为”fh = open(‘Contact.txt’, ‘a’)”。

小白在函数化后的通讯录(见代码ReadFile2DictFunctoinParameter.py)的基础上,加上了这个新建联系人的函数。下面是小白完成的代码(SearchCreateContact.py):

# -*- coding: utf-8 -*-
'''
SearchCreateContact.py
By Jibo He @ueseo.org
hejibo@ueseo.org
Function:
        - search contact
        - create new contact
Dependencies:
        Contact.txt
'''

def ReadContact():
        '''读取联系人信息文本文件'''
        _Info = {}

        fh = open('Contact.txt')
        for line in fh.readlines():
                #print line
                # 将每一行分隔开,提取姓名和联系方式
                cell = line.split('\t')
                name = cell[0]
                contact = cell[1]
                #print cell
                _Info[name] = contact

        print _Info
        # 返回函数值
        return _Info

def SearchContact(SearchQuery):
        '''搜索联系人函数'''
        if _Info.has_key(SearchQuery) == True:
                print _Info[SearchQuery]
        elif SearchQuery == "退出":
                _run = False
        else:
                print "无此联系人"
                CreateNewContact()

def CreateNewContact():
        '''在联系人不存在时,新建联系人信息,并保存到Contact.txt中'''
        NewContact = raw_input("输入新的联系人信息(以制表符Tab 分隔):\n")
        fh = open('Contact.txt', 'a')
        fh.seek(2)
        fh.write(NewContact)
        fh.close()
        print "已经将新的联系人加入数据库。"

_run = True
while _run:
        _User_input = raw_input("输入联系人名:")

        # 调用读取联系人文本文件函数
        _Info = ReadContact()
        SearchContact(_User_input)

小白反复测试了一下自己的通讯录。这个软件已经可以查询联系人和新建联系人了。小白对这个软件的功能非常满意。

小结

小白在舅舅的建议下,将通讯录软件函数化,完成了读取通讯录文本,查询通讯录联系人和新建联系人的功能。函数是完成特定功能的代码块。函数化使得通讯录软件的结构更加清晰了。

关键词

def return global