函数的进阶:从零开始,轻松入门Python函数!

引言

从今天开始,我们将开始写一个新的栏目,主要内容是读书的一些笔记。今天要阅读的是《「Python工匠:案例、技巧与工程实践」》中的 「第七章函数-基础知识 」。有兴趣的同学,一起参与进来吧。

「内容简介」

本书基于广受好评的“Python工匠”系列开源文章。全书从工程实践角度出发,通过剖析核心知识、展示典型案例与总结实用技巧,帮助大家系统进阶Python,写好工程代码,做好实践项目。

本书共计13章,分为五大部分:「变量与基础类型、语法结构、函数与装饰器、面向对象编程、总结与延伸,涵盖Python编程的方方面面。」本书的写作方式别具一格,核心知识点都会通过三大板块来阐述:基础知识、案例故事、编程建议。其中基础知识帮助大家快速回顾Python基础;案例故事由作者经历的编程项目与案例改编而来,兼具实战性与趣味性;编程建议以大家喜闻乐见的条目式知识点呈现,短小精悍,可直接应用于自己的编程实践中。

函数的常用技巧

01.别将可变类型作为参数默认值

这样的函数定义,看起来没有什么问题,但当你多次调用就会发现和你预期的不同了:

def append_value(value,items=[]):

     items.append(value)

     return items

print(append_value("fool"))

print(append_value("cool"))

运行代码发现,第一次调用,值为“['fool']”,第二次调用返回的是:“['fool', 'cool']”。之所以出现这样的问题是因为 「python函数的参数默认值在函数定义的阶段被创建一次,之后不管调用的次数为多少,函数内拿到的默认值都是同一个对象。」

通过__defaults__属性可以直接获取函数的参数默认值

print(append_value.__defaults__[0])   

修改这个参数的默认值,可以直接影响函数的调用结果。

append_value.__defaults__[0].append('baz')

print(append_value('value'))

['fool', 'cool', 'baz', 'value']

为了规避这个问题,使用None来替代可变类型默认值是比较常见的做法:

def append_value(value, items=None):
    if items is None:
        items = []
    items.append(value)
    return item

这样修改后,假如调用方没有提供items参数,函数每次都会构建一个新列表,不会再出现之前的问题。

02.定义特殊对象来区分是否提供了默认参数

当我们为函数参数设置了默认值,不强制要求调用方提供这些参数以后,会引入另一件麻烦事儿:无法严格区分调用方是不是真的提供了这个默认参数。

def dump_value(value,extra=None):
    if extra is None:
        ...
# 两种调用方式
dump_value("one")
dump_value("two",extra=None)

对于dump_value()函数来说,当调用方使用上面两种方式来调用它时,它其实无法分辨。因为在这两种情况下,函数内拿到的extra参数的值都是None.

#定义标记变量
# object 通常不会单独使用,但是拿来做这种标记变量刚刚好
_not_set = object()
def dump_value(value, extra=_not_set):
    if extra is _not_set:
        # 调用方没有传递 extra 参数
        print("w")
    else:
        print("cc")
dump_value("111")
dump_value("111",extra={})
dump_value("111",extra=None)
# w
# cc
# cc

相比None,_not_set是一个独一无二、无法随意获取的标记值.假如函数在执行时判断extra的值等于_not_set,那我们基本可以认定:调用方没有提供extra参数。

03.定义仅限关键字参数

「Python里的函数不光支持通过有序位置参数(positional argument)调用,还能指定参数名,通过关键字参数(keyword argument)的方式调用。」

虽然关键字参数调用模式很有用,但是它并不是强制使用的。那么怎么办呢?我们可以通过在参数列表中插入“*”号,该符号后的所有参数都变成了仅限关键字参数。

#注意参数列表中的* 符号
def query_users(limit, offset, *, min_followers_count, include_profile):
    ...

query_users(10,1,3,100,"hello")
# Traceback (most recent call last):
#   File "/Users/milong/Desktop/codes/python/daydayup/day1104/test_3.py", line 4, in <module>
#     query_users(10,1,3,100,"hello")
# TypeError: query_users() takes 2 positional arguments but 5 were given

# 正确的调用方式
query_users(20, 0, min_followers_count=100, include_profile=True)

「注意:这是一个新的知识点,我们会专门写一篇文章来介绍。」

「当参数较多时,通过这种方式把部分参数变为仅限关键字参数,可以强制调用方提供参数,提升代码可读性。」

函数返回的常见模式

01.尽量只返回一种类型

def get_users(user_id=None):
    if user_id is not None:
        return User.get(user_id)
    else:
        return User.filter(is_active=True)
 
 
# 返回单个用户
user = get_users(user_id=1)
# 返回多个用户
users = get_users()

虽然这样的“多功能函数”看上去很实用,像瑞士军刀一样“多才多艺”,但在现实世界里,这样的函数只会更容易让调用方困惑——“明明get_users()函数名字里写的是users,为什么有时候只返回了单个用户呢?”

像上面的例子,更好的做法是将它拆分为两个独立的函数。

(1) get_user_by_id(user_id):返回单个用户。
(2) get_active_users():返回多个用户列表。

02.谨慎返回None值

操作类函数的默认返回值
def close_ignore_errors(fp):
# 操作类函数,默认返回 None
try:
    fp.close()
except IOError:
    logger.warning('error closing file')
意料之中的缺失值

在标准库中,正则表达式模块re下的re.search()、re.match()函数均属于此类。这两个函数在找到匹配结果时,会返回re.Match对象,否则返回None

执行失败时代表“错误”
def create_user_from_name(username):
    """通过用户名创建一个 User 实例"""
    if validate_username(username):
        return User.from_username(username)
    else:
        return None
 
 
user = create_user_from_name(username)
if user is not None:
    user.do_something()

从上面的分析来看,适合返回None的函数需要满足以下两个特点:

「(1)函数的名称和参数必须表达“结果可能缺失”的意思;」

「(2)如果函数执行无法产生结果,调用方也不关心具体原因。」

对上面的代码进行一个改进,代码如下:

class UnableToCreateUser(Exception):
    """当无法创建用户时抛出"""
 
 
def create_user_from_name(username):
    """通过用户名创建一个 User 实例
    :raises: 当无法创建用户时抛出 UnableToCreateUser
    """
    if validate_username(username):
        return User.from_username(username)
    else:
        raise UnableToCreateUser(f'unable to create user from {username}')
 
 
try:
    user = create_user_from_name(username)
except UnableToCreateUser:
    # 此处编写异常处理逻辑
else:
    user.do_something()

03.早返回,多返回

「对于读代码的人来说,return是一种有效的思维减负工具。当我们自上而下阅读代码时,假如遇到了return,就会清楚知道:“这条执行路线已经结束了”。」这部分逻辑在大脑里占用的空间会立刻得到释放,让我们可以专注于下一段逻辑。因此,「在编写函数时,请不要纠结函数是不是应该只有一个return,只要尽早返回结果可以提升代码可读性,那就多多返回吧。」

总结

感兴趣的小伙伴,赠送电子书和全套Python学习资料,具体看下方。

一、Python所有方向的学习路线

Python所有方向的技术点做的整理,形成各个领域的知识点汇总,它的用处就在于,你可以按照下面的知识点去找对应的学习资源,保证自己学得较为全面。

img
img

二、Python必备开发工具

工具都帮大家整理好了,安装就可直接上手!img

三、最新Python学习笔记

当我学到一定基础,有自己的理解能力的时候,会去阅读一些前辈整理的书籍或者手写的笔记资料,这些笔记详细记载了他们对一些技术点的理解,这些理解是比较独到,可以学到不一样的思路。

img

四、Python视频合集

观看全面零基础学习视频,看视频学习是最快捷也是最有效果的方式,跟着视频中老师的思路,从基础到深入,还是很容易入门的。

img

五、实战案例

纸上得来终觉浅,要学会跟着视频一起敲,要动手实操,才能将自己的所学运用到实际当中去,这时候可以搞点实战案例来学习。

img

六、面试宝典

在这里插入图片描述

在这里插入图片描述

简历模板在这里插入图片描述
若有侵权,请联系删除
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值