查看原文
其他

Python教学 | Python函数的定义与调用【附本文代码和数据】

快点关注→ 数据Seminar 2023-03-20

目录

一、引言

二、函数的定义与调用

    1. 定义

    2. 调用

三、函数的参数类型与返回值

    1. 形参和实参

    2. 函数的返回值

四、四种常用的参数传递方式

    1. 位置参数

    2. 关键字参数

    3. 默认值参数

    4. 不定长参数

五、函数调用实例

六、结束语

七、其他内容

    1. Python教学

    2. 文本识别类

    3. 数据可视化

本文共12618个字,阅读大约需要32分钟,欢迎指正!


💡 公众号对话框内发送关键词“20230224”,即可获取文章中用到的样例数据和代码。

Part1引言

在 Python 教学的前几期文章中,我们向大家介绍了 Python 中的分支结构循环结构,熟练掌握这些技巧,可以让我们在数据处理工作中轻松应对大多数问题,使我们具备批量处理数据的能力。不过仅使用分支和循环处理问题也有一定不足,比如我们用一套标准处理大量数据时,使用循环无可厚非,但N套不同的标准都需要在数据中应用时,再使用循环就只能将类似的代码删删改改再重复 N 遍,这样做显然是令人无法接受的。而在Python中,自定义函数(正文中简称为“函数”)可以很好地解决上述问题。

往期文章传送门:

什么是自定义函数?顾名思义,自定义函数就是用户根据具体的需求而定义的具有特定功能的函数。当完成特定任务的代码块中有变量需要频繁变更取值时,我们可以将其设置为函数的参数。我们通过使用自定义函数名方便地调用它;通过控制参数的取值灵活地改变函数的具体功能

💡举个例子:

现在我们有一份来国家知识产权局企业专利申请&授权信息的微观数据,总数据量四千四百多万,我们需要筛选出专利标题(字段名为PATNAME中含有特定词语(如煤炭、矿物水)的专利数量(下图所示样例数据)

通常的筛选思路是“通过循环遍历标题列,结合判断语句,如果标题中含有指定的词语(“煤炭”、“矿物水”),这条数据就是我们需要的,否则便不是。然而当存在大量特定词语需要筛选时,通过一遍遍复写代码并且修改判断条件来实现目的是不可取的。这时我们就可以定义一个函数来找到专利标题中含有特定词语的部分数据,我们将“特定词语”设置为函数的参数值。那么后续每次调用函数时,只需要使用函数名调用函数并传入需要的参数,就可以实现一行代码完成上述需求。

以上是一个简单的函数应用场景。那么在 Python 中我们应该如何定义与调用函数呢?下面我们一探究竟。

Part2函数的定义与调用

1定义

自定义函数可以通俗的理解为带名字的代码块,用于实现特定功能。Python 中使用def关键字来定义函数,具体的语法格式如下。

def 函数名(形式参数1, 形式参数2……):
    <实现具体功能的代码块>
    return 返回值

其中参数的定义如下所示:

  • 函数名:一个符合Python语法的标识符,最好可以体现该函数的功能。
  • 形式参数:设置该函数可以接受的参数(零到多个),多个参数之间使用逗号分隔。
  • 代码块:实现具体功能的代码,如果想定义没有任何功能的函数,可以使用pass语句作为占位符。
  • return 返回值:结束句,选择性地返回一个值给调用方。函数可以有return语句,也可以没有,是否需要根据具体情况决定。



注意:在定义函数时,即便函数不需要参数,也必须保留函数名后面的小括号(),否则 Python 将提示语法错误。


针对引言中例子——找到专利标题中含有特定词语(‘煤炭’或者'矿物水')的专利数量,我们使用函数定义此功能,定义函数的代码如下。

# 读取随文赠送的样例数据
data = pd.read_csv('./绿色低碳专利(样例数据).csv')

# 定义函数,函数名为MATCH_DATA,形式参数为KeysWords_List
def MATCH_DATA(KeysWords_List:list):          ## 标明形式参数KeysWords_List的数据类型为list
    # 以下是 <实现具体功能的代码块>
    ## 将形式参数中的元素通过'|'连接,作为正则条件
    KeysWords_List = '|'.join(KeysWords_List) 
    PAT = re.compile(KeysWords_List)
    ## 初始值为0,用于记录符合条件的数据量
    count = 0
    ## 遍历所有数据行(通过索引循环)
    for i in range(data.shape[0]):
        ## 取出一行数据的专利标题
        title = data.loc[i, 'PATNAME']
        ## 判断条件:如果在所有条件中找到,则这条专利是我们需要的,计数+1
        if bool(PAT.search(title)):
            count += 1
    # 输出结果
    print(count)

至此,我们完成了这个函数的定义,那么如何使用函数呢?定义好的函数不能直接得到结果,需要经过“调用”才可以。

2调用

函数调用的方法有多种,通常我们使用函数名(实际参数)的方式,如果函数中有return语句,则使用变量接收返回值,即变量名 = 函数名(实际参数)。在定义MATCH_DATA函数时,为了可以接收任意数量的指定词语作为条件,我们将形式参数KeysWords_List指定为列表数据类型,下面演示如何调用该函数。

如果我们需要得到专利标题中含有词语‘煤炭’或者'矿物水'的专利数量,此时只需将这两个词以列表的形式传入函数,结果如下。

当我们需要得到专利标题中含有'石油','矿物水','页岩油','油井'四个词语的专利数量,调用函数的结果如下。

当然,这还不是最方便的。我们还可以将任意数量词语的列表作为实参传入函数中,实现函数的调用


💡 在定义上述函数时,我们标明了形参的类型为列表(KeysWords_List:list),这样做对函数本身的功能没有任何影响,但是可以便于函数的使用者查看参数的类型,使得调用函数时传参更加便捷。


Part3函数的参数类型与返回值

1形参和实参

Python 中自定义函数中参数有两种类型,即形式参数(简称“形参”)和实际参数(简称“实参”)。两种参数的定义和作用如下:

  • 形参:形参是定义函数时的占位符,是函数中用于接收输入值的变量。形参的作用是允许函数在不同的调用中接收不同的输入值。可以通过函数的参数列表来定义形参。
  • 实参:实参是调用函数时传递给函数的实际输入值,可以是常量、变量或表达式。实参的作用是为形参提供具体的输入值,使得函数能够处理相应的任务。

为什么函数中会区分实参与形参?仅根据两者定义,可能无法明确这个答案。我们通过更详细的说明来解答:

  • 形参(parameter)位于函数名之后的()中,多个形参之间使用逗号隔开。形参充当函数内部的局部变量,只在函数内部有效。当函数被调用时,需要传递对应的实参,实参的值将被赋给形参,以便在函数内部处理。
  • 实参(argument)是函数调用时传递给函数的值,是用来填充函数定义中的形参的具体值。一般情况下,只有函数之外的全局变量、常量或表达式才能充当实参。例如,之前我们传入的['煤炭',‘矿物水’],属于常量作为实参。

2函数的返回值

我们在函数的定义中提到过return返回值,但函数的返回值可有可无,需要根据具体情况来定。在当前的例子中,我们已经明确想要的结果是符合要求的专利数统计量,那就需要将其返回给调用方,所以此时在定义函数时必须要使用return语句,这部分内容需要着重讲解。


💡 所有的函数都有返回值,如果没有使用 return 语句,系统会在函数的最后隐式地调用 return None 语句,即返回空值 None;而且函数一旦执行到 return 语句,就会立刻返回结果,并结束本次调用,return 语句之后的其它语句都不会被执行。

函数定义一节中MATCH_DATA函数的最后一行,我们使用print语句将统计结果输出,但是这种方式无法将输出结果赋值给变量。如果想要保存结果,需使用 return 语句将结果返回,此时调用方要提供变量名来接收返回的值,存在多个返回值时,可以使用一个变量(元组数据类型)或者多个变量来接收。

通常情况下,函数中都会使用return语句将值返回给调用方,但是print(*)语句在函数中并非毫无用处,当我们需要对函数功能进行调试时,可以利用该语句输出局部变量,帮助我们定位函数中的bug

我们修改MATCH_DATA函数,改用 return 语句将统计结果返回,代码如下。

# 读取随文赠送的样例数据
data = pd.read_csv('./绿色低碳专利(样例数据).csv')

def MATCH_DATA2(KeysWords_List:list):
    KeysWords_List = '|'.join(KeysWords_List)
    PAT = re.compile(KeysWords_List)
    count = 0
    for i in range(data.shape[0]):
        title = data.loc[i, 'PATNAME']
        if bool(PAT.search(title)):
            count += 1
    # 将统计结果返回,而不是输出
    return count

另外注意一点,return 语句可以出现在函数中的任意位置,但是当函数执行return语句,就会立刻结束本次调用,所以通常情况下,我们将return语句放在达到特定任务的代码块后面。

此时调用函数,通过变量NUM接收返回值,结果如下。

Part4四种常用的参数传递方式

在函数中传递参数的方式有很多种,下面一一向大家介绍。

1位置参数

位置参数是函数中最常用的一种传参方式。它指的是必须按照正确的顺序将实际参数传到函数中,换言之,当我们只使用位置参数,调用函数时传入实参的位置必须与函数定义中参数的位置一一对应,否则Python解释器会抛出TypeError异常。

为了便于讲解,我们将上文定义的函数形参的类型从list更改为str,然后定义函数有两个形参condition_one(形参1)和condition_two(形参2),现在将'煤炭'和'矿物水'作为实参分别传入形参1和形参2,当形参2不在标题且形参1在标题中时,数据量加一,也就是需要计算专利标题中含有'煤炭'但是不含有'矿物水'的专利数据量。代码如下:

# 标题中含有一个指定词语的专利数量,分支结构中的条件不同
def Delivery_Mode(condition_one:str, condition_two:str):
    count = 0
    # 遍历每一行数据
    for i in range(data.shape[0]):
        # 取出该行数据的标题
        title = data.loc[i, 'PATNAME']
        # 如果条件二不在标题中,继续执行
        if condition_two not in title: 
            # 并且条件一在标题中,符合条件,计数+1
            if condition_one in title:
                count += 1
    return count

当我们想要函数满足其他特定的功能时,比如专利标题中含有多个指定词语中的至少一个,可使用函数的返回值一节中MATCH_DATA2函数。如果想要计算专利标题中同时含有多个词语的专利数据量,可以修改函数中的判断条件,代码如下。

# 标题中同时含有传入的所有关键词
def Contain_All(Key_Words: list):
    # 不符合条件的数据量
    Not_Count = 0
    for i in range(data.shape[0]):
        title = data.loc[i, 'PATNAME']
        for Word in Key_Words:
            # 只要有任意一个词语不在标题专利中,直接跳过这条数据
            if Word not in title:
                Not_Count += 1
                break
    # 符合条件的数据量 = 数据总量 - 不符合的数据量
    count = data.shape[0]-Not_Count
    # 将所得结果返回
    return count

现在,我们来看一下位置参数的传递需要注意的地方。

第一点,实参与形参的个数必须一致。我们定义函数Delivery_Mode,有两个形式参数,如果我们传入的实际参数为一个或者三个,结果如下。

 

第二点,需要保证实参与形参的类型一致。因为当实参类型与形参类型不一致,并且函数中这两种类型之间不能正常转换,会提示错误。

此外,如果传入的实参位置与形参不一致,但是两者的数据类型相同,这时程序不会提示异常,但是可能会导致结果出错,比如标题中含有指定词语'煤炭'的专利数量,也就是说,这个词语应该作为实参传递给形参condition_one,如果我们不小心将两个位置参数搞反,将别的词传递给 condition_one,那么所得结果大概率是错误的。如下图所示。

再次强调,当我们仅使用位置参数作为参数的传递方式时,需要避免以上三种情况。

2关键字参数

目前为止,我们调用函数时所使用的都是位置参数,也就是需要确保传入的实参与形参的数量与位置一 一对应。但是当函数的参数比较多时,牢记其位置会比较麻烦,容易出错。为了使函数的调用和传参更加灵活,我们介绍另一种传递参数的方式——关键字参数。其通过形式参数的名字来确定输入的参数值,这种方式传递实参时,不需要与形参位置完全对应。我们通过指定接收传入实参对应的形参来调用函数,结果如下。

3默认值参数

我们在前文中提到,如果在调用函数时传入实参与形参的个数不匹配,Python解释器会抛出异常。但是在实际调用函数时,并不是每一次都需要指定所有的参数,为了解决这个问题,Python允许为参数设置默认值,即在定义函数时,直接为形参指定一个默认值,这样如果在调用函数时没有给指定默认值的形参传递实参,该参数会直接使用默认值作为实参。

Python定义带有默认值参数的函数并不难,其语法格式如下。

def 函数名(形参1,形参2,...,形参名=<默认值>):
    <实现具体功能的代码块>
    return 返回值

💡 需要强调一点,如果在定义函数时使用了默认值参数,那么所有没有默认值的参数必须在有默认值参数的前面,否则会产生语法错误。

假设在前文场景中,我们指定形参2的默认值为'煤炭',形参3的默认值为'石油',为了便于查看结果,使用print语句输出,代码如下。

def DEFAULT(condition_two, condition_one='煤炭', condition_three='石油'):
    print(condition_two)
    print(condition_one)
    print(condition_three)

首先,我们只传入第一个非默认参数,程序正常运行,并且形参2和形参3都使用了默认值,结果如下。

并且在调用函数时,也可以给所有的参数传值,即便形参2和形参3有默认值,也会优先使用主动传入的值,结果如下。

其次,如果我们想要更改非首位默认参数,应该怎么做?这就需要用到上一节的知识点了,结合关键字参数,我们可以通过形参名对默认值参数赋值。此时我们只更改了形参3的参数值,形参2仍然使用默认值,结果如下。

4不定长参数

前几节介绍的参数都是函数中经常使用的,不难发现,它们都有一个特点,那就是函数定义的参数都是单一的。在Python 函数定义中,我们也可以使用可变参数,其主要有两种表现形式:*args**kwargs,前者表示可以接收多个位置参数传递的实参组成一个元组,后者表示可以接收多个关键字参数传递的实参名和值组成一个字典。

下面简单介绍两种形式的用法。

第一种用法的代码如下

def change_args(*args):
   # 输出参数值
   print(args)
   # 查看参数类型
   print(type(args))

从下图可以看到,*args不定长参数可传入也可不传入,并且长度没有限制不同类型的参数可以同时传入,接收传入的实参会转化为元组的形式



💡 注意:在Python 3.0 以后,*args参数的后面只能跟关键词参数,放在后面是为了防止按位置传入参数出现不必要的错误。


第二种用法的代码如下

def change_kargs(**kargs):
   # 输出参数值
   print(kargs)
   # 查看参数类型
   print(type(kargs))

从下图可以看到,**kargs不定长参数,可传入也可不传入,并且长度没有限制,传入的实参是键值对,换句话说,就是批量以关键字参数的方式传参,接收传入的实参会转化为字典的形式

不过,这种参数传递方式在实际应用中并不常见,在后文实例中,将不再介绍这种传参方式。

Part5函数调用实例

本期文章的最后,我们通过一个综合实例来演示函数及其相关知识点的用处与用法。

【问题提出】我们使用引言中提到的,来自国家知识产权局的专利信息数据,样例数据有四个字段PATNAME、PATTYPE、CLASSNO、SUMMARY,分别为专利标题、专利类型、国家专利分类号(简称"IPC")和专利摘要,样例数据如下图所示。

我们需要根据绿色低碳技术专利分类体系中的参考检索式(由国家知识产权局提供),匹配样例数据中满足条件的绿色低碳专利,为其打上相应的技术分支名称作为标签

技术分支编号及名称(示例)如下:

参考检索式(示例)如下:

为了便于理解,先举一个例子,比如某条专利数据满足绿色低碳技术分支(上图第一列)3.5.1的参考检索式,则为其分别打上技术分支编号为3,技术分支编号为3.5,技术分支编号为3.5.1的一级、二级、三级技术分支名称作为标签;同理,如果满足绿色低碳技术分支1.1.1.1的参考检索式,则分别为其打上一级、二级、三级和四级标签。(技术分支编号及名称的示例部分,随文附赠)。

此外,参考检索式的匹配部分,我们使用正则表达式(re模块)来完成,实例中的五个参考检索式,直接写为正则表达式放在代码里。


💡 为了方便举例,我们简化打标签的过程,仅为其打上匹配到的技术编号对应技术分支名称作为标签,不考虑其上级分支标签。同时,只选择5个参考检索式作为条件(仅考虑国家专利分类IPC的检索式,不考虑标题和摘要的条件以及IPC的层级展开)。

【答】

# 导入re库,正则匹配需要
import re

# 读取样例数据
data = pd.read_csv('./绿色低碳专利(样例数据2).csv', dtype=str)
# 添加Label列,用于存放标签
data.insert(0, 'LABEL''')

# 读取绿色低碳技术专利分类体系(示例部分)
PATENT_SYSTEM = pd.read_excel('./绿色低碳技术专利分类体系(示例部分).xlsx')

# 将参考检索是的条件写为正则表达式
# 参考检索式
BRANCH1112 = '1.1.1.2'
PATTERN1112 = 'E21C41/18|E21C41/28|E21C47/02'

BRANCH1111 = '1.1.1.1'
PATTERN1111 = 'E02D17/20|E02D19/06'

BRANCH1113 = '1.1.1.3'
PATTERN1113 = 'C02F103/10|E21B21/01|E21F5/00|E21F7/00'

BRANCH1114 = '1.1.1.4'
PATTERN1114 = 'E21B43/295'

BRANCH1115 = '1.1.1.5'
PATTERN1115 = 'E21B43/24|E21F15/00'
# 将五个技术分支编号及其相应的IPC检索式,存于列表中,用于循环遍历。
BRANCH_ALL = [BRANCH1112, BRANCH1111, BRANCH1113, BRANCH1114, BRANCH1115]
PATTERN_ALL = [PATTERN1112, PATTERN1111, PATTERN1113, PATTERN1114, PATTERN1115]


# 将标签分支转换成字典
PATENT_DICT = {}       ## 存放技术分支编号及对应名称的空字典
NUM = list(PATENT_SYSTEM['技术分支编号'])
NAME = list(PATENT_SYSTEM['技术分支名称'])
for k,v in zip(NUM, NAME):
    PATENT_DICT[k] = v
PATENT_DICT


# 定义打标签的函数
def MARK(Row, BRANCH, PATTERN):
    IPC = Row['CLASSNO']
    # 由于每一个专利数据不一定只打上一个标签,所以需要使用列表存放标签
    ALL_Labels = []
    # 检索专利分类号 & TIABC
    PAT = re.compile(PATTERN)
    # 如果IPC符合要求,打标签;如果没有,则返回
    if bool(PAT.search(str(IPC))):
        # 如果IPC满足条件,为其打上相应标签
        ALL_Labels.append(PATENT_DICT[BRANCH])
    # 如果没有打上标签,即返回的结果为空,保证其仍然是列表格式
    return [ALL_Labels]


# 通过循环遍历,每一次处理一行数据
for i, row in data.iterrows():
    # 外层循环得到的数据行,遍历所有的参考检索式
    ## 如果其中有满足的技术分支,retrun语句会返回标签列表;如果没有,return语句返回空列表
    for BRANCH_ONE,PATTERN_ONE in zip(BRANCH_ALL,PATTERN_ALL):
        # 取出第i个数据行的'LABEl'值,用于接收MARK函数调用的结果
        ## 每一次循环都通过位置参数传递方式,将不同的技术分支编号和IPC条件传给函数的形参,实现调用
        data.loc[i,'LABEL'] = MARK(row, BRANCH_ONE, PATTERN_ONE)

以上代码能够对所有样例数据中符合五条参考检索式的专利分别打上标签。

其中,PATENT_DICT字典是以键值对的形式保存技术分支编号与其对应名称,当数据满足IPC条件时,通过字典名[键]的形式将技术分支名称添加到标签列表中,使用return语句返回该值。变量PATENT_DICT(部分)输出如下。

我们定义的MARK函数,其功能是为数据打上标签,第一个参数Row为一行数据,BRANCHPATTERN为一个技术分支的编号(例如上图中的"1.1.1.3")和其所对应的检索IPC的正则表达式,由于一条专利数据可能会满足多个技术分支的条件,也就是存在多个标签,所以使用列表来存放标签。可以注意到,在最后调用函数时,我们每一次传入的参数都是不同的,这里使用的是位置参数,外层for循环用于遍历每一行数据,内层for循环用于遍历五个检索式,通过data.loc[i,'LABEL']接收函数返回的标签列表

以上就是本期文章介绍 Python 函数的定义与调用的全部内容。如果大家想动手实操,我们免费为大家提供了本期使用到的数据(样例)。

Part6结束语

本期文章我们为大家详细介绍了Python中函数的定义与调用,其中重点介绍了函数的参数,这几种参数几乎可以覆盖函数的日常使用场景。文章最后,我们通过一个实例演示了函数的用法,充分利用了函数强大的可复用性,同时也可以看到,函数通常会搭配循环结构与分支结构,这也是我们前文提到的。想要更好地实现函数价值,Python中的数据类型以及常用的结构都是必需的知识,希望大家认真学习。我们下期再见!


💡 公众号对话框内发送关键词“20230224”,即可获取文章中用到的样例数据和代码。

Part7其他内容

1Python教学

2文本识别类

3数据可视化





星标⭐我们不迷路!想要文章及时到,文末“在看”少不了!

点击搜索你感兴趣的内容吧

往期推荐


基本无害 |《大侦探经济学》要出台湾繁体字版了!

数据可视化 | Python绘制多维柱状图:一图展示西部各省人口变迁【附本文数据和代码】

Python教学 | Python 中的循环结构(下)【附本文代码和数据】

Python教学 | Python 中的循环结构(上)【附本文代码和数据】

Python教学 | Python 中的分支结构(判断语句)【附本文代码和数据】





数据Seminar




这里是大数据、分析技术与学术研究的三叉路口


文 | 《社科领域大数据治理实务手册》


    欢迎扫描👇二维码添加关注    

点击下方“阅读全文”了解更多

您可能也对以下帖子感兴趣

文章有问题?点此查看未经处理的缓存