Python从入门到进阶15函数的定义和使用

Posted 光仔December

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Python从入门到进阶15函数的定义和使用相关的知识,希望对你有一定的参考价值。

接上篇《14、字典高级应用
上一篇我们学习了有关字典的高级应用操作(字典的增删改查),本篇我们来学习Python中函数的定义和使用,包括函数的参数、返回值、局部变量和全景变量等操作。

一、一个思考

例如这里有一段大东北洗浴中心的门童代码,他见到人就要喊一下欢迎并报人数几位,这里默认上午有4人要来,分别是李大哥、刘大爷和他儿子、马大娘、申少爷和他媳妇,要分别欢迎和报人数,如果直接写代码,是这样的:

print("欢迎李大哥光临白金翰!")
print("男宾1位!")

print("欢迎刘大爷和儿子光临白金翰!")
print("男宾2位!")

print("欢迎马大娘光临白金翰!")
print("女宾1位!")

print("欢迎申少爷和嫂子临白金翰!")
print("宾客2位!")

思考一下,这样的代码有什么问题?主要有以下问题:

1、代码重复度过高,只是不同的输出内容,但是重复写了一样的逻辑;
2、代码无任何可复用性,在另一个程序里想引入门童进行报人,还要复制代码粘贴过去,或者是写个一模一样的代码;
3、代码没有动态适应性,即每一段代码只能适应当前来的客人,无法适应动态信息,例如上午来几个人压根不知道,来了谁再叫谁,这种情况就无法适应,必须先写好静态代码才行,这样太死板。

为了改造上面的代码,我们需要使用函数的概念来解决(下面小节会详细介绍函数是什么、怎么用,这里先通过实例思考一下),代码修改如下:

# _*_ coding : utf-8 _*_
# @Time : 2023-04-09 9:35
# @Author : 光仔December
# @File : 澡堂子函数示例
# @Project : Python基础

#定义一个函数,能够完成澡堂报名报号的工作
def welcome(name,sex,num):
    print(f"欢迎name光临白金翰!")
    print(f"sexnum位!")

while True: # while死循环,可以一直迎接宾客
    a_name = input("请输入来宾姓名: ")
    a_sex = input("请输入来宾性别: ")
    a_num = input("请输入来宾数量: ")
    welcome(a_name,a_sex,a_num) # 直接调用门童方法

我们封装了一个函数,这个函数可以接收主客户姓名的参数、主客户性别,以及当前人员的数量,然后执行报名报号的操作。
效果:

这样封装完毕之后,报名报号的操作代码可以被复用,只要想执行门童的功能,就调用函数即可,不用再写一模一样的代码;然后就是有动态适应性,会根据传来的参数,来执行相应的逻辑,输出参数参与计算的结果。

以上就是函数应用的一个实例,通过这里大家可以看到函数在日常编程过程中的重要性。

二、什么是函数

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

函数能提高应用的模块性,和代码的重复利用率。我们其实已经用过了很多Python提供的内建函数,比如print()。我们自己也可以创建函数,这种函数一般叫做用户自定义函数。

三、定义函数

1、定义规则

怎么来定义一个函数呢?需要遵循一下规则:

● 函数代码块以 def 关键词开头,后接函数标识符名称和圆括号()。
● 在圆括号中间放入参数和自变量。
● 可以在函数的第一行放一个字符串作为函数的说明。
● 函数内容以冒号起始,并且缩进。
● return [表达式] 结束函数,选择性地返回一个值给调用方。不带表达式的return相当于返回 None。

2、语法

def functionname( parameters ): # 定义函数名称和参数
   "函数说明备注"
   function_suite # 函数具体逻辑
   return [expression] # 函数返回值,也可以不写return

以下就定义了一个函数,执行后就会打印狗叫:

def dog_bark():
   "打印狗叫的声音"
   print("汪!汪!")
   return

这里我们将鼠标放在方法名上,可以看到方法的提示信息,提示该函数返回了什么数据,以及展示了我们方法的备注说明。这样也是给其他调用我们方法的人一个说明:

四、调用函数

如何来调用函数呢?
只需要声明这个函数的名称+括号,括号中指定函数包含的参数,如果函数本身没有参数,就只跟一个括号即可。
例如上一节的狗叫函数,就是这样调用的:

#调用狗叫函数
dog_bark()

效果:

汪!汪!

这里定义了一个带参数的函数,可以画正三角形和倒三角形,如果输入1就画正三角形,输入2画倒三角形:

#定义一个画三角形的函数
def draw_a_triangle(type):
    if type==1:
        layer = int(input("开始画正三角,输入行数: "))
        for i in range(layer, 0, -1):
            print(" " * (i - 1), end="")
            print("*" * ((layer - i) * 2 + 1))
    else:
        layer = int(input("开始画倒三角,输入行数: "))
        for i in range(layer, 0, -1):
            print(" " * (layer - i), end="")
            print(("*" * (2 * i - 1)))

#调用该函数
#画正三角
draw_a_triangle(1)
#画倒三角
draw_a_triangle(2)

效果:

这里就是调用的带参数的函数,括号里面传入需要的参数即可。

五、函数的参数

调用函数时可使用的正式参数有以下几种类型:

●必备参数
●关键字参数
●默认参数
●不定长参数

下面我们来逐一讲解。

1、必备参数

必备参数须以正确的顺序传入函数。调用时的数量必须和声明时的一样,否则会出现语法错误,例如下面的函数:

def introduce(name,age,sex):
    "介绍人员函数,传入姓名、龄和性别"
    if age>=18:
        isAdult = "成年"
    else:
        isAdult = "未成年"
    print(f"人员姓名:name,年龄age,性别sex,isAdult")

调用时类型错误:

# 调用人员介绍函数
# 参数类型错误
introduce("张三","x","男")

效果:

调用时参数数量不足:

# 调用人员介绍函数
# 参数数量错误
introduce("张三",23)

效果:

注:可以看到sex是“required positional argument”必备的参数。
这里必须要求我们输入相同数量和相同类型的参数。如:

# 调用人员介绍函数
introduce("张三",23,"男")

效果:

人员姓名:张三,年龄23,性别男,成年

2、关键字参数

关键字参数和函数调用关系紧密,函数调用使用关键字参数来确定传入的参数值。

使用关键字参数允许函数调用时参数的顺序与声明时不一致,因为Python解释器能够用参数名匹配参数值。

这里拿上面的introduce方法举个例子,我们要输入“name,age,sex”三个参数,如果使用关键字参数方法来定义,就不需要在乎顺序,只要参数名称和定义函数的参数名称一样,Python解释器就可以识别,如:

# 调用人员介绍函数
introduce(sex="男",name="张三",age=23)

效果:

人员姓名:张三,年龄23,性别男,成年

3、默认参数

调用函数时,默认参数的值如果没有传入,则被认为是默认值。默认值是在定义函数的时候,写在参数上的,如:

def introduce(name,age,sex="未知"):
    "介绍人员函数,传入姓名、龄和性别"
    if age>=18:
        isAdult = "成年"
    else:
        isAdult = "未成年"
    print(f"人员姓名:name,年龄age,性别sex,isAdult")

#注:这里注意,有默认值的参数一定要放在没有默认值参数的后面,否则会报错。
这里如果没有传name进去,则name就会直接被定义为"某某"。值得注意的是,我们必须要用“关键字参数”的方式调用方法,才能引起默认参数起作用,如果按照直接传参的方法,少传一个参数是会报错的:

# 调用人员介绍函数
introduce(name="张三",sex="男",age=23)
introduce(name="张三",age=23)
#introduce("张三",23) #这样调用会报错

效果:

人员姓名:张三,年龄23,性别男,成年
人员姓名:张三,年龄23,性别未知,成年

4、不定长参数

我们可能需要一个函数能处理比当初声明时更多的参数。这些参数叫做不定长参数,和上述2种参数不同,声明时不会命名。基本语法如下:

def functionname([formal_args,] *var_args_tuple ):
   "函数_文档字符串"
   function_suite
   return [expression]

加了星号(*)的变量名会存放所有未命名的变量参数。实例如下:

# 打印传入的不定长参数
def printinfo(arg1, *vartuple):
    "打印任何传入的参数"
    print("************")
    print("固定参数:",arg1)
    if not vartuple:
        print("没有不固定参数")
    else:
        print("不固定参数:",)
        for var in vartuple:
            print(var)
    return

# 调用printinfo 函数
printinfo(11)
printinfo(34, 35, 87, 99)
printinfo(88, "哈哈哈", "name":"jack","age":23, 99)

效果:

不固定参数可以接收的类型也不是固定的,总体可以理解为是一个列表集合。

六、函数的返回值

return语句[表达式]退出函数,选择性地向调用方返回一个结果或者表达式。不带参数值的return语句返回None。例如一个加法计算器:

# 加法计算器
def sum(arg1, arg2):
    # 返回2个参数的和."
    total = arg1 + arg2
    return total

# 调用sum函数
a = int(input("请输入第一个数字:"))
b = int(input("请输入第二个数字:"))
print(f"a加b等于sum(a, b)")

效果:

请输入第一个数字:1
请输入第二个数字:2
1加2等于3

七、局部变量和全局变量

1、局部变量

什么是局部变量?局部变量,就是在函数内部定义的变量。其作用范围是这个函数内部,即只能在这个函数中使用,在函数的外部是不能使用的。例如:

def test1():
    a = 88
    print(f"----test1----修改前--a=a")
    a = 99
    print(f"----test1----修改后--a=a")

def test2():
    a = 77
    print(f"----test2----a=a")

#调用函数
test1()
test2()

结果:

----test1----修改前--a=88
----test1----修改后--a=99
----test2----a=77

可以看到,修改test1函数中的a,并不会影响到test2中的a,这两个函数中的变量是相互隔离,相互没有影响的。

2、全局变量

什么是全局变量?全局变量是定义在函数外部的变量,这样的变量既能在函数外部正常使用,也可以在任何一个函数内中使用。例如:

# 全局变量,既可以在函数外使用,也可以在函数内使用
b = 1
print(f"函数外打印b:b")

def test3():
    print(f"函数内打印b:b")
#调用函数
test3()

效果:

函数外打印b:1
函数内打印b:1

这里b变量在函数内和函数外都可以使用。有一点要注意,如果函数内有变量的名字,和函数外的一样,此时两个参数相互不冲突,函数内该名字的变量是其私有参数,是一个全新的变量,和外面不一样,而全局变量也不会因为函数内的同名变量修改而改变,二者也是隔离的,例如:

total = 0  # 这是一个全局变量

# 加法函数
def sum(arg1, arg2):
    # 返回2个参数的和."
    total = arg1 + arg2  # total在这里是局部变量.
    print("函数内是局部变量 : ", total)
    return total

# 调用sum函数
sum(10, 20)
print("函数外是全局变量 : ", total)

可以看到,函数内的total变量值发生变化,并不影响函数外的全局变量。但是我们一定要避免函数内的变量名称与全局变量名一样,否则容易发生混淆,搞不清楚

最后提示:在日常编程中,不要滥用全局变量,一般是在满足条件的情况下,使用作用域最小的那个变量范围。

八、参数的传入类型

在传递参数的时候,我们要注意,参数有两种情况,分别是“不可变类型”变量和“可变类型”变量。在python中,strings、tuples和numbers是不可更改的对象,而list,dict等则是可以修改的对象。

1、不可变类型

这里的“不可变类型”类似c++的值传递,如整数、字符串、元组。如fun(a),传递的只是a的值,没有影响a对象本身。比如在fun(a)内部修改a的值,只是修改另一个复制的对象,不会影响a本身,例如:

def ChangeInt( a ):
    a = 10
    
b = 5
ChangeInt(b)
print b # 结果是 5

实例中有int对象5,指向它的变量是b,在传递给ChangeInt函数时,按传值的方式复制了变量b,a和b都指向了同一个Int对象,在a=10时,则新生成一个int值对象10,并让a指向它。

2、可变类型

这里的“可变类型”,即类似c++的引用传递,如列表、字典。如fun(la),则是将la真正的传过去,修改后fun外部的la也会受影响,例如:

def changeme(list):
    "修改传入的列表"
    list.append(40)
    print("函数内取值: ", list)
    return

# 调用changeme函数
mylist = [10, 20, 30]
changeme(mylist)
print("函数外取值: ", mylist)

这里全局变量的mylist和传入函数中末尾添加新内容的mylist对象用的是同一个引用,故输出结果如下:

函数内取值:  [10, 20, 30, 40]
函数外取值:  [10, 20, 30, 40]

至此,函数相关的所有内容讲解完毕,下一篇我们来学习文件相关的操作。

参考:尚硅谷Python爬虫教程小白零基础速通教学视频

转载请注明出处:https://blog.csdn.net/acmman/article/details/130039917

Python爬虫从入门到进阶之xpath的使用

官网地址:https://lxml.de/xpathxslt.html

导入:

 from lxml import etree

lxml.tree 支持 ElementTree 和 Element 上的 find,findall,findtext方法的简单路径语法,作为特定的 lxml 扩展,这些类提供了 xpath()方法,该方法支持完整xpath语法中的表达式,以及定制的扩展函数。

xpath()方法

对于ElementTree,xpath 方法对文档(绝对路径)或者根节点执行全局(相对路径) xpath查询

def demo_1():
    f = StringIO(<foo><bar></bar></foo>)
    tree = etree.parse(f)
    r = tree.xpath(/foo/bar)
    print(len(r))
    print(r[0].tag)

    r_2 = tree.xpath(bar)
    print(r_2[0].tag)

在 Element 上使用 xpath() 时,xpath()表达式根据元素(相对路径)或根树(绝对路径)查询:

def demo_2():
    f = StringIO(<foo><bar></bar></foo>)
    tree = etree.parse(f)

    root = tree.getroot()
    r = root.xpath(bar)
    print(r[0].tag)

    bar = root[0]
    r = bar.xpath(/foo/bar)
    print(r[0].tag)

xpath()方法支持xpath变量:

def demo_3():
    f = StringIO(<foo><bar></bar></foo>)
    tree = etree.parse(f)
    root = tree.getroot()

    expr = "//*[local-name() = $name]"
    print(root.xpath(expr, name=foo)[0].tag)
    print(root.xpath(expr, name="bar")[0].tag)
    print(root.xpath("$text", text="Hello World!"))
    

命名空间和前缀

如果XPath表达式使用名称空间前缀,则必须在前缀映射中定义它们。为此,将一个字典传递给namespace关键字参数,该参数将XPath表达式中使用的名称空间前缀映射到名称空间uri

def demo_4():
    f = StringIO(‘‘‘    <a:foo xmlns:a="http://codespeak.net/ns/test1"
           xmlns:b="http://codespeak.net/ns/test2">
        <b:bar>Text</b:bar>
    </a:foo>
    ‘‘‘)

    doc = etree.parse(f)
    r = doc.xpath(/x:foo/b:bar,
                  namespaces={x: http://codespeak.net/ns/test1,
                              b: http://codespeak.net/ns/test2})
    print(len(r))
    print(r[0].tag)
    print(r[0].text)

在这里选择的前缀并没有连接到XML文档中使用的前缀,文档可以定义任何前缀,包括空前缀,也不会破坏上面的代码

注意 XPath 没有默认的命名空间,因此,XPath 中没有定义空前缀,不能在命名空间前缀映射中使用

XPath返回值

XPath返回值的类型取决于使用的Xpath 表达式:

  (1) True 或者 False

  (2) float

  (3) “智能的”string

    XPath字符串的结果是“智能的”,因为它们提供了一个getparent()方法,该方法知道它们的起源:

      (i)对于属性值,result.getparent()返回携带它们的元素。例如//foo/@attribute,它的父元素是一个foo元素。

      (ii)对于text()函数(如//text()),它返回包含返回的文本或尾部的元素。

    以使用布尔属性is_text、is_tail和is_attribute来区分不同的文本源。

    注意,getparent()不一定总是返回一个元素。例如,XPath函数string()和concat()将构造没有原点的字符串。对于它们,getparent()将不返回任何值。

    有些情况下 smart string 并不受欢迎。例如:它意味着树将字符串保持活动状态,如果字符串值是树中唯一真正需要的东西,那么它可能会对内存产生相当大的影响。对于这些情况,可以使用关键字 smart_strings禁用父关系 

def demo_5():
    root = etree.XML("<root><a>TEXT</a></root>")
    find_text = etree.XPath("//text()")
    text = find_text(root)[0]
    print(text)
    print(text.getparent().text)

    # 禁用父关系
    find_text = etree.XPath("//text()", smart_strings=False)
    text = find_text(root)[0]
    print(text)
    hasattr(text, getparent)   

  (4) list 或者 items

生成XPath表达式

ElementTree对象有一个getpath(element)方法,它返回一个结构的、绝对的XPath表达式来查找该元素:

def demo_6():
    a = etree.Element("a")
    b = etree.SubElement(a, "b")
    c = etree.SubElement(a, "c")
    d1 = etree.SubElement(c, "d")
    d2 = etree.SubElement(c, "d")
    tree = etree.ElementTree(c)
    print(tree.getpath(d2))  # /c/d[2]
    print(tree.xpath(tree.getpath(d2)) == [d2])

XPath类

XPath类将XPath表达式编译为可调用函数

def demo_7():
    root = etree.XML("<root><a><b/></a><b/></root>")
    find = etree.XPath("//b")
    print(find(root)[0].tag)

编译花费的时间和 xpath()方法相同,但是每个类实例化编译一次,这能提高重复计算相同 Xpath 表达式的效率。就像xpath()方法一样,XPpath类支持xpath变量

def demo_8():
    root = etree.XML("<root><a><b/></a><b/></root>")
    count_elements = etree.XPath("count(//*[local-name() = $name])")
    print(count_elements(root, name="a"))
    print(count_elements(root, name="b"))

这支持非常有效地计算XPath表达式的修改版本,因为编译仍然只需要一次。

前缀到命名空间的映射可以作为第二个参数传递:

def demo_9():
    root = etree.XML("<root xmlns=‘NS‘><a><b/></a><b/></root>")
    find = etree.XPath("//n:b", namespaces={n: NS})
    print(find(root)[0].tag)

XPath中的正则表达式

默认情况下,XPath支持EXSLT名称空间中的正则表达式,也可以使用 regexp 关键字禁用它,默认值是 True

def demo_10():
    regexpNS = "http://exslt.org/regular-expressions"
    find = etree.XPath("//*[re:test(., ‘^abc$‘, ‘i‘)]", namespaces = {re: regexpNS})
    root = etree.XML("<root><a>aB</a><b>aBc</b></root>")
    print(find(root)[0].text)

后面还有一些看不下去了,下一篇写下 xpath 的常规用法,点击下载代码

 


以上是关于Python从入门到进阶15函数的定义和使用的主要内容,如果未能解决你的问题,请参考以下文章

Python从入门到进阶2Python环境的安装

Python从入门到进阶5变量的定义及数据类型

Python从入门到进阶5变量的定义及数据类型

30天Python入门到进阶——第7天:函数

C语言学习 从入门到进阶

C语言学习 从入门到进阶