保姆级入门系列阿ken教你学 Python ——函数

Posted 请叫我阿ken

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了保姆级入门系列阿ken教你学 Python ——函数相关的知识,希望对你有一定的参考价值。

你好,我是阿ken

我又双叒叕来了!

                                                                                                                                                           

蓝色、灰色为了解内容,其中蓝色多为引用举例。红色为重点记忆。

                                                                                                                                                           

目录

5.1 函数概述

5.2 函数的基础知识

 5.2.1 函数的定义 

5.2.2 函数的调用

5.3 函数的参数传递

5.3.1 参数的位置传递

5.3.2 参数的关键字传递

5.3.3 参数的默认值传递

5.3.4 包裹传递

5.3.5 解包裹传递

5.3.6 混合传递

5.4 函数的返回值

5.5 变量作用域

5.5.1 局部变量

5.5.2 全局变量

5.6 函数的特殊形式

5.6.1 匿名函数

5.6.2 递归函数

5.7 时间处理模块 —— datetime

5.8 代码抽象与模块化设计

5.9 本文总结


快速通道:

【保姆级入门系列】阿ken教你学 Python(四)

🐮阿ken:咳咳从此刻开始,调整心情,开始上课!

5.1 函数概述


🐮阿ken:函数是组织好的、可重复使用的、用来实现单一或相关联功能的代码段,通过函数的名称表示和调用。函数也可以看作是一段有名字的子程序,可以在需要的地方使用函数名调用执行。在学习本文内容之前,其实我们已经接触过一些函数,比如输出信息到命令行窗口的 print() 函数、接收键盘输入信息的 input() 函数等。

函数是一种功能抽象,它可以完成特定的功能,与黑箱模型的原理一样。黑箱模型是指所建立的模型只考虑输入与输出,而与过程机理无关。现实生活中,应用黑箱原理的实物有很多,比如洗衣机,对于使用者来说,大家只需要了解洗衣机的使用方法,将洗衣粉、水放入,最终得到洗干净的衣服,这个过程是完全封闭的。对于函数外界不需要了解其内部的实现原理,只需要了解函数的输入输出方式即可使用,换言之,调用函数时以不同的参数作为输入,执行函数后以函数的返回值作为输出。
函数大体可以划分为两类,一类是系统内置函数,它们由 Python内置函数库提供,例如我们在前面章节中学习的 print()、input()、type()、int() 等函数;另一类是用户根据需求定义的具有特定功能的一段代码。自定义函数像一个具有某种特殊功能的容器 —— 将多条语句组成一个有名称的代码段,以实现具体的功能。

使用函数的好处主要体现在以下几方面:
(1) 将程序分解成更小的块(模块化)。
(2) 降低理解难度,提高程序质量。
(3) 减小程序体积,提高代码可重用性。
(4) 降低了软件开发和维护的成本。

                                                                          
5.2 函数的基础知识                                                         

 

🐮阿ken:函数的使用可以分为函数的定义和函数的用两部分,它只需要定义一次,便可
                  以无限次地被重复使用。


 5.2.1 函数的定义 


Python 使用 def 关键字定义函数,基本语法格式如下: 

def 函数名([参数列表]):
['''文档字符串''']
函数体
[return语句]


上述语法的介绍如下: 
(1) 关键字def:标志着函数的开始。
(2) 函数名:函数的唯一标识,其命:名方式遵循标识符的命名规则。
(3) 参数列表:可以有零个、一个或多个参数,多个参数之间使用逗号分隔。根据参数的有无,函数分为带参函数和无参函数。
(4) 冒号:用于标记函数体的开始。
(5) 文档字符串:用于描述函数的功能,可以省略。
(6) 函数体:函数每次调用时执行的代码,由一行或多行Python语句构成。
(7) return语句:标志着函数的结束,用于将函数中的数据返回给函数调用者。若函数需要返回值,则使用 return语句返回,否则 return语句可以省略,函数在函数体顺序执行完毕后结束。

 

定义函数时,函数参数列表中的参数是形式参数,简称为“形参”,形参用来接收调用该函数时传入函数的参数。注意,形参只会在函数被调用的时候才分配内存空间,一旦调用结束就会即刻释放,因此,形参只在函数内部有效。
定义一个求绝对值的函数,示例如下:

def my_absolute(x):
if x >= 0:
return x
else:
return -x


以上定义的 my absolute() 函数接收参数x,使用 if-else 语句区分 x的正负,若 x为正数,它的绝对值就是它本身,直接返回 x;否则返回它的相反数。

5.2.2 函数的调用

函数定义好之后不会立即执行,直到被程序调用时才会生效。调用函数的方式非常简单,一般形式如下:

函数名(参数列表)


以上形式的参数列表为会被传递给函数的形参、在函数执行过程中会使用的参数,这些参数是实际参数,简称为“实参”。实参可以是常量、变量、表达式、函数等。
调用上述中定义好的 my_absolute()函数,代码如下:

my_absolute(-10.0)

                                                                                                                                       
以上代码中的-10.0是实参,它将被传递给函数定义中的形参x。注意,函数在使用前必须已经被定义,否则解释器会报错。

                                                                                                                                               
程序执行时若遇到函数调用,会经历以下流程:
(1) 程序在函数调用处暂停执行。
(2) 为函数传入实参。
(3) 执行函数体中的语句。
(4) 程序接收函数的返回值(可选)并继续执行。

                                                                                                                                               
定义和调用函数 my_absolute() 的完整代码如下:

def my absolute(x):
if x >= 0:
print(x)
else:
print(-x)
my_absolute(-10.0)
print("---程序结束---")


对以上程序进行分析: Python 解释器读取第 1 ~ 5 行代码时判定此处定义了一个函数,它先将函数名和函数体存储在内存空间中,但不执行;解释器执行第 6行代码,由于此处调了 my_absolute() 函数,程序首先暂停执行,将该函数的实参 -10.0 传递给形参x (x=-10.0),然后执行函数体内部的语句,函数体执行结束之后重新回到第 6行,最后执行第7行的打印语句。

5.3 函数的参数传递

🐮阿ken:函数的参数传递是指将实参传递给形参的过程,Python 中的函数支持以多种方式传递参数,包括位置传递、关键字传递、默认值传递、包裹传递、解包裹传递以及混合传递。本节将针对函数不同的传参方式进行讲解。


5.3.1 参数的位置传递

                                                                                                                    
调用函数时,默认按照位置顺序将对应的实参传递给形参,即将第 1个实参分配给第 1个形参,第 2个实参分配给第 2个形参,以此类推。


假设有个用于判断三角形是否为直角三角形的 is_triangle() 函数, 该函数的定义

具体如下:

def is_triangle(a, b, c):
if a*a + b*b == c*c or a*a + c*c == b*b
or b * b + c * a:
print("是直角三角形")
else:
print("不是直角三角形")


由以上定义可知,is_triangle() 函数需要接收 3个表示三角形各边边长 (大于0)的整型参数。调用 is_triangle()函数, 传入 3个整数,代码如下:

is_triangle(1, 2, 3)


以上代码中的第 1 个实参 "1 " 会被赋给第 1个形参a,第 2个实参 " 2 " 会被赋给第 2个形参b,第 3个实参 3会被赋给第 3个形参c。

通过位置传递方式传参时实参的个数必须与形参的个数保持一致,否则程序会出现异常。


5.3.2 参数的关键字传递

🐮阿ken:如果函数中形参的数目过多,开发者很难记住每个参数的作用,这时可以使用关键字方式传递参数。关键字传递通过 ” 形参变量名 = 实参 " 的形式将形参与实参关联,根据形参的名称进行参数传递、它允许实参和形参的顺序不一致。

例如,有一个构建URL格式序符中的函数 makeup_url(),该函数有两个参数:protocal和 address,分别用于接收协议头和主机地址。它的定义如下所示:

def makeup_url(protocal, address):
print("URL = {}: //{}".format(protocal, address))


通过关键字方式传参时,可以使用如下两种形式:

makeup_url(protocal='http', address='www.baidu.com')
makeup_url(address='www.baidu.com',protocal='http')


这时,我们无须再关心定义函数时参数的顺序,直接在传参时指定对应的名称即可。



5.3.3 参数的默认值传递


函数在定义时可以给每个参数指定默认值,基本形式为:函数名(参数 = 默认值),这样在调用时既可以给带有默认值的参数重新赋值,也可以省略相应的实参,使用参数的默认值。


例如,fun(a=1,b=2,c=3)函数中分别为3个参数a、b、c设置了默认值1、2、3,使用fn(a=7,b=8)调用函数,此时 a和 b的值7和8将覆盖默认值1和2,但是参数 c保持不变,仍然使用默认值3。默认值传递方式并不要求实参与形参的数量相等。

定义 makeup_url() 函数时为参数 protocal 设置默认值,如下所示:

def makeup_url(address, protocal="http"):
print("URL = {}: //{}" .format(protocal, address))


注意,若带有默认值的参数与必选参数同时存在,则带有默认值的参数必须位于必选参数的后面。
调用 makeup_url() 函数可以使用如下两种形式:
makeup_url(address='www.itcast.cn')
makeup_url(protocal="https",address='www.baidu.com')

使用第 1种形式调用函数时,因为没有传值给protocal参数,所以默认会使用该参数的默认值“http";使用第 2种形式调用函数时,因为同时传值给 protocal和 address参数、所以 address参数的新值会替换该参数的默认值。


5.3.4 包裹传递


若定义函数时不确定需要传递多少个参数,可以使用包裹传递。包裹传递的关键在于定义函数时,在相应的参数前添加 “*" 或 ”**":若在某个参数名称的前面加 “*",可以元组形式为该参数传入一组值;若在某个参数名称前加 “**”,可以关键字传递形式为该参数传入一组值。

例如,定义以 “*” 包裹形参 args 的函数test():

def test(*args):
print(args)


调用以上定义的 test()函数时可以传入多个参数,比如传入 5个参数:

test(1,2,3,4,5)
(1,2,3,4,5)


由以上运行结果可知,test()的参数 args接收了一个包含 5个元素的元组。
例如,定义带有 “**” 包裹形参 kwargs 的函数 test():

def test(**kwargs):
print(kwargs)


调用 test()函数时能够以关键字传递的方式传递多个参数,例如:

test(a=1, b=2,c=3,d=4,e=5)
{'a': 1, 'b': 2, 'c': 3, 'd': 4, 'e':5}


由以上运行结果可知,test()的参数 args接收了一个包含 5个键值对的字典。

5.3.5 解包裹传递


在调用函数时,若函数接收的实参为元组或字典类型,可以使用 “*” 和 “**” 对函数参数解包裹,将实参拆分为多个值,并按照位置传递方式或关键字传递方式将值赋给各个形参。

(1) 元组解包裹
下面来看一个对元组解包裹的示例,代码如下:

def func(a, b, c):
print(a, b, c)
args = (1, 2, 3)
func(*args)


以上代码先定义了需要接收 3个参数的 func() 函数,然后调用 func()函数并向该函数传入了一个包含 3个元素的元组 args,由于元组 args的前面添加了 “*”,Python 对 kwargs 进行解包裹操作,将 args元组中的 3 个元素拆分为 3个值,并分别按顺序赋值给形参a、b、c。

(2) 字典解包裹
下面来看一下对字典解包裹的示例,代码如下:

kwargs = {'a':1, 'b': 2,'c':3}
func(**kwargs)


以上代码调用了 func() 函数, 并向该函数中传入了一个包含 3 个键值对的字典 kwargs,由于字典 kwargs 的前面添加了 "**",Python 对 kwargs 进行解包裹操作,将字典 kwargs 中的 3 个键值对拆分为 3 个值,并分别按参数名称赋值给形参a、b、c。


5.3.6 混合传递

前面介绍了函数参数的若干种传递方式,这些方式在调用函数时可以混合使用,但是在使用的过程中要注意前后的顺序。混合使用的基本原则如下:


(1) 先按照参数的位置传递。
(2) 再按照参数的关键字传递。
(3) 最后按包裹的形式传递。

例如,定义一个函数,该函数包含必选参数、默认参数、可变参数和关键字参数:

def func(a,b,c=0, *args, **kw):
print ('a =',a,'b =', b, 'c =', c, 'args =', args, 'kw =',kw)


在调用 func()函数时,Python 解释器按照混合使用的原则传递参数。调用函数的示例如下:

func(1,2)  # 按位置传递方式将1、2赋值给a、b,c采用默认值0
a=1 b=2 c=0 args = () kw = {}
func(1, 2, c=3)  # 按位置传递方式将1、2赋值给a、b,将3赋值给c
a=1 b=2 c=3 args = () kw = {}
 func(1, 2, 3, 'a', 'b')
a=1 b=2 c=3 args = ('a', 'b') kw = {}
 func(1, 2, 3, 'a', 'b', x=99)
a=1 b=2 c=3 args = ('a', 'b') kw = {'x': 99}


调用 func()函数时传入一个元组和字典,可以通过解包裹的形式传递参数。例如:

args = (1, 2, 3, 4)
kw = {'x': 99}
func(*args, **kw)
a = 1 b = 2 c = 3 args = (4,) kw= {'x': 99}


使用混合传递时有两点需要注意:
(1) 若定义函数时参数有默认值,则带有默认值的参数必须跟在必选参数的后面。
(2) 若调用函数时需要混合使用位置传递和关键字传递,则必选参数要出现在关键字参数之前。

 

5.4 函数的返回值


函数中的 return 语句是可选项,可以出现在函数体的任何位置,它的作用是结束当前函数,将程序返回到函数被调用的位置继续执行,同时将函数中的数据返回给主程序。


编写含有自定义函数 is_capital()的程序,实现判断键盘输入的字符串是否以大写字母开头的功能,代码如下:


def is capital(words):
if ord("A")<=ord(words[0])<=ord("z"):
return '首字母是大写的'
else:
return '首字母不是大写的'
result = is_capital("othon") # 将函数返回的结果交给变量
print(result)


执行程序,程序输出的结果:

首字母是大写的


游戏项目通过坐标控制角色位置,角色坐标由 x和 y两个值决定,这要求与位置相关的函数能够同时返回 x和 y两个值。函数可以返回两个值吗? 答案是肯定的,不仅如此,Python 函数中的 return也可以返回多个值。当函数使用 return语句返回多个值时,这些值将以元组形式保存。

例如,定义一个控制游戏角色移动的函数 move(),使用 return语句返回反映角色当前位置的 nx 和 ny,代码如下:

02 control game role. py
import math
def move(x, y, step, angle=0)
nx=x+ step math cos(angle
y =y- step math sin (angle)
return nx, ny
#返回多个值
result move (100, 100, 60, math. pi/6)
#实际上返回的是一个元组
print(result)


以上程序定义了 move()函数、使用变量 result 接收了 move()函数返回的计算结果并将结果打印,打印信息如下:

(151.96152422706632, 70.0)


由以上结果可知,函数返回的其实是一个包含两个元素的元组。

5.5 变量作用域


Python 变量并不是在哪个位置都可以访问的,具体的访问权限取决于变量定义的位置,其所处的有效范围视为变量的作用域。根据作用域的不同,变量可以划分为局部变量和全局变量。


5.5.1 局部变量

在函数内部定义的变量称为局部变量,局部变量只能在定义它的函数内部使用。例如,定义一个包含局部变量 count 的函数 test(),在函数的内部和外部分别访问变量 count,代码如下:

def test():
count = 0  # 局部变量
print(count) # 函数内部访问局部变量
test()
print(count) # 函数外部访问局部变量


执行程序,程序执行的结果如下:

0
Traceback (most recent call last):
File "C:/Users/admin/PycharmProjects/测试/func.py", line 6, in <module>
print(count)
NameError: name 'count' is not defined


以上程序在打印 count的值之后又打印了错误信息 "name 'count' is not defined“,由此可知,函数中定义的变量在函数内部可使用,但无法在函数外部使用。
局部变量的作用域仅限于定义它的代码段内,在同一个作用域内,不允许出现同名的变量。


5.5.2 全局变量


全局变量是指在函数之外定义的变量,它在程序的整个运行周期内都占用存储单元。默认情况下,函数的内部只能获取全局变量,而不能修改全局变量的值。例如,将前面定义的 test()函数进行调整,如下所示:

count = 10 # 全局变量
def test():
count = 11 # 实际上定义了局部变量, 局部变量与全局变量重名
print(count)
test()
print(count)


以上代码中首先在 test() 函数外定义了一个全局变量 count,其次在该函数的内部尝试为 count 重新赋值,然后在函数的内部访问了变量 count,最后在执行完函数后访问变量 count。
执行程序,程序执行的结果如下:

11
10


从以上结果可知,程序在函数 test() 内部访问的变量 count为 1,函数外部访问的变量为10。也就是说,函数的内部并没有修改全局变量的值,而是定义了一个与全局变量同名的局部变量。

 

在函数内部若要修改全局变量的值,需要提前使用保留字 global进行声明,语法格式如下:

global 全局变量


对以上定义的 test() 函数再次进行调整,在该函数中对全局变量 count进行修改,具体代码如下所示:

count = 10  # 全局变量
def test():
global count # 声明 count为全局变量
count += 10 # 函数内修改 count变量
print(count)
test()
print(count)


以上代码首先定义了变量 count并赋值为10,其次在 test() 函数内部使用 global保留字声明 count为全局变量,然后重新给 count变量赋值并将其输出,最后在函数执行完以后再次输出。
执行程序,程序执行的结果如下:

20
20


观察执行结果,程序在函数内部和外部获得的变量 count的值均为20。由此可知,在函数内部使用关键字 global对全局变量进行声明后,函数中对全局变量进行的修改在整个程序中都有效。

 

多学一招:LEGB法则
Python 中的作用域大致可以分为以下 4种
(1) L(local):局部作用域。
(2) E(enclosing):嵌套作用域。
(3) G(global):全局作用域。
(4) B(built-in):内置作用域。
基于 LEGB 法则,搜索变量名的优先级是:局部作用域>嵌套作用域>全局作用域>内置作用域。当函数中使用了未确定的变量名时,Python 会按照优先级依次搜索 4个作用域,以此来确定该变量名的意义。首先搜索局部作用域 (L),其次是上一层函数的嵌套作用域 (E),然后是全局作用域 (G),最后是内置作用域(B)。按照 LEGB原则查找变量,在某个区域内若找到变量,则停止继续查找;若一直没有找到变量,则直接引发 Name Error 异常。


5.6 函数的特殊形式


🐮阿ken:除了前面介绍的普通函数之外,Python 还有两种具有特殊形式的函数:匿名函数和递归函数。

 

5.6.1 匿名函数


匿名函数是一类无须定义标识符的函数,它与普通函数一样可以在程序的任何位置使用,但是在定义时被严格限定为单一表达式。Python 中使用 lambda关键字定义匿名函数,它的语法格式如下:

lambda <形式参数列表>: <表达式>


与普通函数相比,匿名函数的体积更小,功能更单一,它只是一个为简单任务服务的对象。它们的主要区别如下:
(1) 普通函数在定义时有名称,而匿名函数没有名称。
(2) 普通函数的函数体中包含有多条语句,而匿名函数的函数体只能是一个表达式。
(3) 普通函数可以实现比较复杂的功能,而匿名函数可实现的功能比较简单。
(4) 普通函数能被其他程序使用,而匿名函数不能被其他程序使用。

定义好的匿名函数不能直接使用,最好使用一个变量保存它,以便后期可以随时使用这个函数。例如,定义一个计算数值平方的匿名函数,并赋值给一个变量:

temp = lambda x : pow(x, 2) # 定义匿名函数, 它返回的函数对象赋值给变量 temp


此时,变量 temp可以作为匿名函数的临时名称来调用函数,示例如下:

temp(10)
100


5.6.2 递归函数

递归是指函数对自身的调用,它可以分为以下两个阶段:
(1) 递推:递归本次的执行都基于上一次的运算结果。
(2) 回溯:遇到终止条件时,则沿着递推往回一级一级地把值返回来。
递归函数通常用于解决结构相似的问题,其基本的实现思路是将一个复杂的问题转化成若干个子问题,子问题的形式和结构与原问题相似,求出子问题的解之后根据递归关系可以获得原问题的解。递归有以下两个基本要素:
(1) 基例:子问题的最小规模,用于确定递归何时终止,也称为递归出口。
(2) 递归模式:将复杂问题分解成若干子问题的基础结构,也称为递归体。

递归函数的一般形式如下:

def 函数名称 (参数列表):
if 基例:
return 基例结果
else:
return 递归体


由于每次调用函数都会占用计算机的一部分内存,若递归函数未提供基例,函数执行后会返回 " 超过最大递归深度 " 的错误信息。
递归最经典的应用就是阶乘,例如,求n的阶乘,数学中使用函数 fact(n)表示:

fact(n)=n!=1*2*3*…*(n-1)*n=fact(n-1)*n

在程序中定义 fact()函数实现阶乘计算,可以写成如下形式:

def fact(n):
if n == 1: # 基例
return 1
else:
return fact(n-1)*n # 递归体


fact(n) 是一个递归函数,当n大于1时,fact() 函数以 n-1作为参数重复调用自身直到 n为1时调用结束,开始通过回溯得出每层函数调用的结果,最后返回计算结果。

[回头补一个递归过程表]


斐波那契数列也是递归的一个经典案例。斐波那契数列又称黄金分割数列,这个数列从第 3项开始,它的每一项都等于前两项的和。在数学上,斐波纳契数列以递推的方式定义,具体如下所示:

F(1)=1, F(2)=1, F(n)=F(n-1)+F(n-2) (n>=3, n∈N*)


根据以上定义,斐波那契数列的前 9项依次为:1、1、2、3、5、8、13、21、34。
斐波那契数列由数学家列昂纳多·斐波那契以兔子繁殖为例子而引入,故又称为 " 兔子数列 "。兔子繁殖的故事是这样的,一般兔子在出生两个月后就有繁殖能力,对兔子每个月能生出一对小兔子来,如果所有的兔子都不死,那么一年以后一共有多少对兔子? 

 

第1个月, 兔子没有繁殖能力,此时兔子的总数量为1对。
第2个月, 免子拥有了繁殖能力,生下一对小免子,此时兔子的总数量为2对。
第3个月, 兔子又生下一对小兔子,而小兔子没有繁殖能力,此时兔子的总数量为3对。

依此类推,可以得知,经历 0或 1个月份后,兔子的总数量均为1,之后每经历一个月份,兔子的总数量为前两个月份的数量和。例如,经过 3个月时兔子的总数量为1+2=3,经过 4个月时兔子的总数量为 2+3=5,经历 5个月时兔子的总数量为 3+5=8。

使用代码实现计算兔子数列的函数,具体如下所示:

def rabbit(month):
if month <= 1:
return 1
else:
return rabbit(month-1) + rabbit(month-2)


以上代码定义了一个递归函数 rabbit(),该函数接收一个代表经历的月份的参数 month,并在代码段中使用 if-else 语句区分了 1月份和其他月份的不同,若是经过了一个月,则返回总数量为1,代表着递归函数的出口,若是经过了N (大于1)个月,则会重复调用 rabbit()函数,返回 rabbit(month-2) 与 rabbit(month-1) 的和。

在解释器中定义 rabbit()函数,并使用以下语句调用该函数,可计算出经过一年以后,兔子的总数量为:

rabbit(12)
233


5.7 时间处理模块 —— datetime


🐮阿ken:Python 提供了专门操作日期与时间的 datetime模块,该模块提供了很多处理日期与时间的方法,使用这些方法可以从系统中获得时间,并以用户选择的格式进行输出。
datetime 模块以格林尼治时间为基础,将每天用 3600×24 秒精准定义。 datetime 模块中定义了两个常量:datetime.MINYEAR 和 datetime.MAXYEAR,这两个常量分别表示最小年份 (1)和最大年份 (9999) ;datetime 模块还定义了 6个核心的类:

datetime 模块的核心类:

类名说明
date表示具体日期,精确到天
time表示具体的时间,可精确到微秒
datetime表示具体的日期时间,可以理解为 date和 time
timedelta表示具体的时间差
tzinfo表示日期与时间的时区
timezonetzinfo 抽象基类,表示与 UTC的固定偏移量


上表中的前 3个类最为常见,接下来分别对这 3个类中的常用方法进行介绍:

1. date类


date 类表示理想化日历中的日期,由年、月和日组成,比如 1998年1月1日。最简单的创建日期的方式是使用 date类的构造方法,该函数的语法格式如下:

class date(year, month, day)


以上函数中每个参数都只能是整型,它们的含义如下:
(1) year:指定的年份,MINYEAR ≤ year ≤ MAXYEAR。
(2) month:指定的月份,1 ≤ month ≤ 12。
(3) day:指定的日期,1 ≤ day ≤ 给定月份和年份中的天数。
例如,创建一个表示 2019年1月4日的日期对象,代码如下所示:

 from datetime import date
 date(2019, 1, 4)
 date time.date(2019, 1, 4)


2. time 类


time 类是 datetime 模块中用于处理时间的类,表示一天中的(本地)时间,由时、分、秒以及微秒组成,比如 12点 0分 0秒。通过 time类的构造方法可以创建一个时间对象,该函数的语法格式如下:

datetime.time(hour=0, minute=0, second=0, microsecond=0)


以上函数中每个参数的含义如下:
(1) hour:指定的小时,0 ≤ hour < 24。
(2) minute:指定的分钟数,0 ≤ minute < 60。
(3) second:指定的秒数,0 ≤ second < 60。
(4) microsecond:指定的微秒数,0 ≤ microsecond < 1 000 000。

例如,创建一个表示 12时 10分 30秒的时间对象,代码如下所示:

 from datetime import time
 time(12, 10, 30)
 datetime.time(12, 10, 30)


3. datetime 类


datetime 类可以视为 date类与 time类的结合体,它可以同时表示日期和时间,例如 1970年1月1日0时0分0秒。创建 datetime对象的常见方法有以下4个:
(1) datetime():datetime 类的构造方法,用于构造一个指定日期和时间的 datetime对象,可精确到微秒。
(2) today():获取一个表示本地当前日期和时间的 datetime对象。
(3) now():获取一个表示当前时区日期和时间的 datetime对象。
(4) utcnow():获取当前日期和时间对应的 UTC (世界标准时间)对象。


通过 datetime()方法可以直接构造一个日期时间对象,该方法中参数的含义与 date() 和time() 方法中参数的含义相同,此处不再赘述。例如,创建一个表示 2018年 6月 1日 12点 12分 30秒 50微秒的对象,如下所示:

from datetime import datetime
datetime(2018, 6, 1, 12, 12, 30, 50)
datetime.datetime(2018, 6, 1, 12, 12, 30, 50)


通过 today()方法获取本地当前的日期与时间,时间会精确到微秒,如下所示:

datetime.today()
datetime.datetime(2019, 1, 4, 14, 33, 8, 248797)


通过 now()方法可以获取指定时区的日期和时间,时间同样会精确到微秒。若不指定时区,返回本地的日期与时间,作用等同于 today()方法。例如,获取本地当前的日期与时间,如下所示:

datetime.now()
datetime.datetime(2019, 1, 4, 14, 39, 45, 780534)


通过 utcnow() 方法可以获取当前日期和时间对应的 UTC时间 (世界标准时间),时间仍然会精确到微秒。例如,今天的日期是 2019年 1月 4日,所处东八区的具体时间是 14时 45分,当前所对应的世界标准时间为:

datetime.utcnow() 
datetime.datetime(2019, 1, 4, 6, 45, 9, 505050)


创建好 datetime对象以后,可以使用对象的属性和方法进一步控制时间的输出格式。 


 datetime类的常用属性

属性说明
year返回日期包含的年份
month返回日期包含的月份
day返回日期包含的日
hour返回日期包含的小时
minute返回日期包含的分钟
second返回日期包含的秒钟
microsecond返回日期包含的微秒

此外,datetime 类中还提供了常用的格式化日期字符串的 strftime() 方法,可以使用任何通用的格式输出时间。

strftime() 方法控制符

格式控制符说明
%Y四位数的年份表示,取值范围为0001 ~ 9999
%m月份(01 ~ 12)
%d月内中的一天
%B本地完整的月份名称,比如 January
%b本地简化的月份名称,比如 Jan
%a本地简化的周日期
%A本地完整周日期
%H24小时制小时数(0 ~ 23)
%I12小时制小时数(01 ~ 12)
%p本地 A.M. 或 P.M. 等价符
%M分钟数(00 ~ 59)
%S秒(00 ~ 59)


例如,创建一个 datetime对象,以形如 " 时-分-秒  年-月-日 " 的格式进行输出,代码如下:

 date_time = datetime.now()
 date_time
 datetime.datetime(2019, 1, 4, 17, 15, 58, 255314)
 date_time, strftime("%H-%M-%S %Y-%m-%d") # 返回格式化日期
 '17-15-58 2019-01-04'


多学一招:格林尼治时间
我们平时所使用的时间,是以太阳在天空中的方位作标准来计量的。每当太阳转
到夭球子午线的时刻,就是当地正午12时。由于地球自转,地球上不同地点看到太
阳通过天球子午线的时刻是不一样的。例如,当英国伦敦是中午12点时,北京正值
晚上7时45分,上海则是晚上8时06分。
为了使用方便,人们把全球划分成24个时区,毎个时区跨经度为15度。英国原
格林尼治天文台所在的时区叫作零时区,包括西经7.5度到东经7.5度范围内的地区,
在这个时区里的居民都采用原格林尼治天文台的时间。零时区以东第一个时区,叫作
东一区,从东丝75度到225度,是用东经15度的时间作标准的。再往东顺次是东
区、东三区……直到东十二区。每跨过一个时区,时间正好相差1小时。同样地,
零时区以西顺次划分为西一区、西二区……一直到西十二区(西十二区就是东十二
区)世界各地都包括在这24个时区里,每个时区的时间是统一的,称为区时。
中国位于格林尼治东面,使用的是东经120度的标准时间,属于东八区。我们日
常所说的“北京时间**点”就是东八区的标准时间。

 

 


5.8 代码抽象与模块化设计


函数的特点主要体现在两个方面:代码抽象和模块化设计,关于它们的介绍分别如下:

1. 代码抽象

程序由一系列的代码组成,若代码无序且无组织,不仅不利于开发人员的阅读与理解,后期也很难开发与维护。为了形成易于理解的结构,避免编写出面条式代码 (非结构化和难以维护的源代码),需要对代码进行抽象。通常采用函数和对象两种抽象方式抽象代码。
函数将一段代码封装起来并对其命名后供其他程序调用。函数的优点有很多,最直接的优点就是实现代码复用,函数定义之后可以在程序中多次被调用,从而避免重复编写具有相同功能的代码。
对象是程序的一种高级抽象方式,它将一段代码组织成更高级别的类。类是一组具有相同属性和方法的对象集合,描述了属于该对象的所有性质。对象存在于现实世界中,比如大学生、汽车、空调等,对象包括描述特征的属性和描述行为的方法。例如,大学生是—个对象,姓名、年龄等是属性,跑步、学习、思考等是方法。

函数和对象分别是面向过程编程思想和面向对象编程思想的核心。面向过程是种以过程描述为中心的编程方式,它要求开发人员列出解决问题所需要的步骤,然后用函数将这些步骤逐个实现,使用时依次建立调用函数的语句即可;面向对象编程是一种组织程序的新型思维方式,这种思维方式会将数据和操作封装到一起,组成一个相互依存、不可分离的整体 ——对象。
面向对象程序设计的焦点不再是过程,而是对象及对象间的关系。此种思想提取同一类型事物的共性构造出类,在类中设置这一类事物的共同属性,为类定义与外界发生关系的接口 ——方法。
面向过程与面向对象是两种不同的编程方式,它们的抽象级别有所不同,所有能通过面向对象编程实现的功能都可以采用面向过程完成,两者在解决问题上并不存在优劣之分,具体采用哪种方式取决于开发要求。一般在编写大规模程序时建议采用面向对
象的编程方式。
Python 语言同时支持面向对象和面向过程两种编程方式,本系列文采用面向过程的方
式编写程序,但 Python3 内部代码全部采用面向对象方式实现,为降低读者的理解难
度,阿ken 在讲解和使用 Python模块时会涉及面向对象 (调用类的函数创建对象、调
用对象的方法操作对象)

2. 模块化设计

模块化设计是指通过函数或对象的封装功能将程序划分成主程序、子程序、子程
序与子程序间关系的表达,它体现的是分而治之的思想。
针对复杂问题的求解所采用的模块划分通常是从功能的角度进行的,划分后的模块要具备 " 相对独立、功能单一 " 的特征。也就是说,一个好的模块必须具有高度的独立性和较强的功能。在实际应用中,通常会用如下两个指标从不同的角度对模块的划分情况加以度量。
(1) 内聚度:是对模块内各元素之间相互依赖性大小的度量。内聚度越大,模块内各元素之间联系越紧密,其功能越强;反之,低内聚模块内各元素的关系较为松散。
(2) 耦合度:是对模块之间相互依赖程度的度量。耦合度越低,模块的相对独立性越大;耦合性越高,一个模块受其他模块的影响越大。
模块划分时应当尽可能降低不同模块间的关联,提升单一模块自身的功能性,做
到 " 高内聚、低耦合 "。
采用模块结构设计程序的好处在于:整个程序结构清晰,易于分别编写与调试,便于维护与调用,并利于程序功能的进一步扩充与完善。

5.9 本文总结

本章首先介绍了函数的概念、定义和调用,其次介绍了函数参数传递的几种方式,然后介绍了变量作用域和两个具有特殊形式的函数:匿名函数和递归函数,之后介绍了日期时间处理模块 datetime,最后介绍了代码重用与模块化设计的思想。通过对本文的学习,读者应能够理解函数式编程的优越性,可以按照需求灵活定义函数。
 

 

后来你常常会遇到更优秀的人

可能也会跟我一样经常感觉自己是个 loser

但后来想起来Kobe的一句话:如果一定有人会赢,那个人为什么不能是我呢?

而今故人已逝,随着年纪增长,我也渐渐懂了这句话背后的艰辛和努力

也常常用这句话勉励尚在低谷阶段的自己

不管怎样

我一定要赢
 

Peace

 

快速通道:

【保姆级入门系列】阿ken教你学 Python(四)

以上是关于保姆级入门系列阿ken教你学 Python ——函数的主要内容,如果未能解决你的问题,请参考以下文章

保姆级入门系列阿ken教你学Python ——流程控制

保姆级入门系列阿ken的 Python学习笔记数字类型和字符串

保姆级|建议收藏阿ken带你学Java入门及进阶——运算符循环语句,文末有彩蛋鸭✨✨✨

保姆级|建议收藏阿ken带你学Java入门及进阶——运算符循环语句,文末有彩蛋鸭✨✨✨

保姆级|建议收藏阿ken带你学Java入门及进阶——基本数据类型与数组,文末有彩蛋✨✨

保姆级|建议收藏阿ken带你学Java入门及进阶——基本数据类型与数组,文末有彩蛋鸭✨✨✨