【语法糖】python函数

函数

为什么要写函数?为了省内存空间,同时使得主函数逻辑清楚,便于读者阅读(不用拘泥于如何实现,只需知道这个函数的功能)

定义函数

def greet_user():
    """显示简单的问候语"""
    print("Hello")
    
greet_user()

输出效果为:

Hello

关键字def表明要定义一个函数

首行 def 函数名():

跟在def greet_user():后的所有缩进行构成了函数体

第二行的文本被称为文档字符串的注释,描述了函数是干嘛的。文档字符串用三个双引号括起来,实质上是用来生成有关程序中函数的文档

调用函数

只需写函数名+()即可

给函数起别名

函数名其实就是指向一个函数对象的引用,所以完全可以把函数名赋给另外一个变量a,就可以通过a来调用,相当于给函数起了别名,当函数名比较长而又经常调用时,给函数起简洁的别名可以简化一定的书写过程和阅读过程。
一个样例:

>>>a = max
>>>a(3,2,-1,5)
5

向函数传参

def greet_user(username):
    """显示简单的问候语"""
    print("Hello, "+username.title()+"!")
    
greet_user('Horace')

输出效果为:Hello, Horace!

定义时向()填入参数即可,同时调用时也要传与定义的形参列表相符的参数个数

函数定义中的username称为形式参数,调用函数时的’Horace’称为实参


实参的分类

位置实参

调用函数中的每个实参都关联到函数定义中的一个形参,最简单的关联方式是基于实参的顺序。这种关联方式叫做位置实参

def describe_pet(animal_type,pet_name):
    print("\nI have a "+animal_type+".")
    print("My "+animal_type+"'s name is "+pet_name.title()+".")
    
describe_pet('hamster','harry')

输出效果为:

I have a hamster.
My hamster's name is Harry.

一一对应,学过C的都知道,不展开讲了


关键字实参

关键字实参是传递给函数的名称-值对。直接在实参中将名称和值关联起来,因此向函数传递参数不会混淆

def describe_pet(animal_type,pet_name):
    print("\nI have a "+animal_type+".")
    print("My "+animal_type+"'s name is "+pet_name.title()+".")
    
describe_pet(pet_name='harry',animal_type='hamster')

输出效果为:

I have a hamster.
My hamster's name is Harry.

函数参数默认值

只需在函数定义处给形参赋初始值

def describe_pet(animal_type = 'dog',pet_name):

如果调用函数时没有给齐参数则会使用函数的默认值,给齐则会优先使用实参
但要注意,函数定义默认参数时,这个默认参数必须指向不变的对象。讲完返回值后再讲为什么


返回值

可返回一个简单值

语法上用return

def get_formatted_name(first_name,last_name):
    """返回整洁的姓名"""
    full_name = first_name +" "+ last_name
    return full_name.title()

musician = get_formatted_name('jimi','hendrix')
print(musician)

输出效果为:

Jimi Hendrix

甚至可以返回多个值

def example(x,y):
    return x+1,y+1
j,k = example(3,5)
# 将example返回的两个值对应赋给j,k
print(j)
print(k)

输出为:

4
6

这跟c语言里函数只能返回一个值显然不同,python那么特殊吗?
但实际上它仍然是返回一个值。为什么这么所=说呢?
如果你试着打印example(3,5)的结果,将会看到输出结果为(3,5)。这个函数返回了一个元组,只不过包含了两个值。所以函数本质上仍是返回一个值

也可返回一个字典

def build_person(first_name,last_name):
    """返回一个字典,其中包含一个人的信息"""
    person = {'first':first_name,'last':last_name}
    return person
musician = build_person('jimi','hendrix')
print(musician)

输出效果为:

{'first': 'jimi', 'last': 'hendrix'}

回顾前面的问题,为什么要强调函数定义默认实参时,默认参数需指向不变的对象呢?
我们来看看一个例子:

def add_end(L=[]):
    L.append('END')
    return L

这个函数在没有传入参数时会加个END,以标识此次没有参数传入,正常运行是没有问题的。

>>> add_end([1, 2, 3])
[1, 2, 3, 'END']
>>> add_end(['x', 'y', 'z'])
['x', 'y', 'z', 'END']

然而当多次不提供参数时…

>>> add_end()
['END']

第一次结果仍是对的,然而第二次调用时

>>> add_end()
['END', 'END']

我们显然不想看到两个END。为什么出现了两个呢?
是因为第一次调用时创建了L这个变量,它指向列表list,而列表是可变的,之后append(‘END’)产生两个不是很自然的事吗?
所以要指向一个不变的对象,如元组


小技巧:让实参变成可选的

通过给字符串空的初始值,再配合if else即可实现,请看示例代码

def get_formatted_name(first_name,last_name,middle_name=''):
    """返回整洁的姓名"""
    if middle_name:
        full_name = first_name +" "+middle_name+" "+ last_name
    else:
        full_name = first_name +" "+ last_name
    print(full_name.title())

musician = get_formatted_name('jimi','hendrix')

这样传参的时候填不填middle_name都没什么所谓,但要对应好位置。

def build_person(first_name,last_name,age=''):
    """返回一个字典,其中包含一个人的信息"""
    person = {'first':first_name,'last':last_name}
    if age:
        person['age']=age
    return person
musician = build_person('jimi','hendrix',str(28))
print(musician)

输出效果为:

{'first': 'jimi', 'last': 'hendrix', 'age': '28'}

如果传参中有年龄(非空字符串),则将age-28的键值对加入字典中**(妙蛙种子)**


传递列表字典等数据结构

def greet(users):
    """问候列表中的每个用户"""
    for user in users:
        print("Hello, "+user.title()+".")
        
user_names = ['Horace','Licko','Jiang']
greet(user_names)

输出效果为:

Hello, Horace.
Hello, Licko.
Hello, Jiang.

字典大同小异,就不在此展开了,接下来均以列表这种数据结构为例讲解

接下来场景是一个3D打印的场景,编写了两个函数,一个用于移动元素,一个用于展示

 def print_models(unprinted_designs,completed_models):
    """
    模拟打印每个设计,直到没有未打印的设计为止
    打印每个设计后,将其移动到列表completed_models中
    """
    while unprinted_designs:
        # 当这个列表非空
        current_design = unprinted_designs.pop()
        print("Printing model: "+current_design)
        completed_models.append(current_design)
        
def show(completed_models):
    """显示所有打印好的模型"""
    for completed_model in completed_models:
        print(completed_model)
        
unprinted_designs = ['iphone','xiaomi','huawei']
completed_models = []

print(unprinted_designs,completed_models)
show(completed_models)

效果为:

Printing model: huawei
Printing model: xiaomi
Printing mode1: iphone
huawei
xiaomi
iphone

那么此时的unprinted列表是什么情况?我们多加一个print(unprinted_designs)看一看

结果发现是个空列表

函数内对列表的操作是可以保留的(跟C不同)

那么如果我不想改变列表呢?该怎么做?

答案很简单:传参时传列表的副本

通式:function(list_name[:])

但缺点是花时间花内存来创建副本,除非有那方面的需求,否则不建议这样做


传任意数量的参数

def make_pizza(*toppings):
    """打印顾客点的所有配料"""
    print(toppings)
    
make_pizza('a')
make_pizza('a','b','c','d','e')

输出效果:

('a',)
('a', 'b', 'c', 'd', 'e')

哟吼!好家伙,不管提供多少个参数,他都能接收,输出还是带圆括号()的,好神奇~

此处形参* toppings中的星号*让python创建一个名为topping的空元组,并将所有收到的值传入空元组中

('a',)
('a', 'b', 'c', 'd', 'e')

可结合使用位置实参和任意数量实参

def make_pizza(size,*toppings):
    """打印顾客点的所有配料"""
    print("Make a inch "+str(size)+" pizza with following toppings:")
    for topping in toppings:
        print("-"+topping)
    
make_pizza(18,'a')
make_pizza(20,'a','b','c','d','e')

输出效果为:

Make a 18 inch pizza with following toppings:
-a
Make a 20 inch pizza with following toppings:
-a
-b
-c
-d
-e

关键字实参+任意数量实参

 def build_profile(first,last,**user_info):
        profile = {}
        # 创建一个空字典
        profile['first_name']=first
        profile['last_name']=last
        
        for key,value in user_info.items():
            profile[key] = value
        
        return profile

user_file = build_profile('Albert','Einstein',
location = 'princeton',field = 'phycis')
print(user_file)

输出效果为:

{'first_name': 'Albert', 'last_name': 'Einstein',
 'location': 'princeton', 'field': 'phycis'}

程序解析:

build_profile函数形参中**让python建立一个user_info的空字典,并将所有的键值对存入该字典中

items()函数大家应该还没忘吧?返回一个字典的键和值

最后函数返回一个字典

调用build_profile函数得到一个字典赋值给user_file,打印。

ps:profile[key] = value的key不要加单引号’'噢


将函数存储在模块之中

函数的优点是将代码与主程序分离。

我们可将函数存储在称为模块的独立文件中,再将模块导入到文件中(用import)

import语句允许在当前运行的程序文件中使用模块中的代码

通过将函数存储在独立的文件中,可隐藏程序代码的细节,将重点放在程序的高层逻辑上,还能让我这个函数被多个程序调用。

学C的知道头文件,模块跟头文件其实差不多dddd


导入整个模块

要让函数是可导入的,得先创建模块。模块是扩展名为.py的文件(ipynb可不可以呢?存疑,等我试试)

以下是vs code的运行环境

创建一个包含函数make_pizza的模块

def make_pizza(size,*toppings):
    """打印顾客点的所有配料"""
    print("Make a "+str(size)+" pizza with following toppings:")
    for topping in toppings:
        print("-"+topping)	

该代码存入pizza.py

在这里插入图片描述

然后我们在pizza.py所在的目录中创建另一个making_pizzas.py的文件

在making_pizzas.py文件中导入pizza模块,调用函数两次
在这里插入图片描述

运行成功。

程序是怎么运行的呢?代码行import pizza让python打开pizza.py,并将其中的所有函数都复制到这个程序中。你是看不到复制的代码的,因为这个程序运行时,Python在幕后复制。

要调用被导入模块中的函数,导入模块名称.函数名(),跟C++调用类里函数是一样的。

总的来说,模块名module_name.py,函数记为function,语法:(以英文字面意思理解)

导入一个模块 import module_name.py

调用模块中的函数 module_name.function_name()


导入特定的函数

语法:from module_name import function_name

from pizza import make_pizza

make_pizza(16,'pepperoni')
make_pizza(12,'mushroom','green peppers','extra cheese')

运行结果如下(在终端界面),成功运行
在这里插入图片描述

可以用逗号分隔函数名,从而导入多个的函数

如果使用这种语法,调用函数时不需要使用 .

这是因为我们在import语句中显式导入了函数,所以调用时只需指定其名称


使用as给函数指定别名

如果要导入的函数的名称可能与程序中现有的名称冲突,或者函数的名称太长,可指定简短而独一无二的别名。如何做呢?

导入时做。请看下面示例

from pizza import make_pizza as mp

mp(16,'pepperoni')
mp(12,'mushroom','green peppers','extra cheese')

运行结果如下,成功运行在这里插入图片描述

因为这里直接导入了特定的函数,所以直接用函数名可调用。然后因为as给make_pizza起了mp的别名(有C函数指针那味了),所以可以通过mp来调用

**语法:from module_name import function_name as

既然可以给函数指定别名,那么模块能有别名吗?能!


使用as给模块指定别名

如果模块名很长就可以简便工作

语法:import module_name as mn

import pizza as p
p.make_pizza(16,'pepperoni')
p.make_pizza(12,'mushroom','green peppers','extra cheese')

运行结果如下,成功运行
在这里插入图片描述


导入模块中的所有函数

from pizza import *
make_pizza(16,'pepperoni')
make_pizza(12,'mushroom','green peppers','extra cheese')

运行结果如下,成功运行
在这里插入图片描述

import语句中的*让python将模块pizza中的每个函数都复制到这个程序文件中

由于导入了每个函数,所以可以通过名称来调用函数

但书上警告说:使用并非自己编写的大型模块时,最好不要采用这种导入方法,如果导入函数名称中与你的项目中使用的名称相同,可能会覆盖掉函数。最好的做法是:只导入你需要使用的函数,要么导入整个模块并使用句点.表示法


函数编写指南

(这些都是约定俗成的东西,大家都遵守这样的规范,代码就方便阅读)

  • 编写函数时应给函数指定描述性名称,且只在其中使用小写字母和下划线。(因为后面类要以大写字母开头以示区分)

  • 每个函数都应包含简要阐述其功能的注释,采用文档字符串格式(如果不熟悉,建议回到前面复习一下)

  • 给形参指定默认值时,等号两边不要有空格,如def function(p0,p1=''),调用时的关键字形参也是如此

  • 如果程序或模块包含多个函数,可使用两个空行将相邻的函数隔开,更容易知道前一个函数的末尾和后一个函数的开头

  • 所有的import语句都应该放在文件开头(除非你在开头写注释hhh)

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 4
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值