Python语言基础04-函数和模块的使用

本文收录在Python从入门到精通系列文章系列html

在分享本章节的内容以前,先来研究一道数学题,请说出下面的方程有多少组正整数解。程序员

事实上,上面的问题等同于将8个苹果分红四组每组至少一个苹果有多少种方案。想到这一点问题的答案就呼之欲出了。编程

能够用Python的程序来计算出这个值,代码以下所示。闭包

"""
输入M和N计算C(M,N)

Version: 0.1
Author: along
"""
m = int(input('m = '))
n = int(input('n = '))
fm = 1
for num in range(1, m + 1):
    fm *= num
fn = 1
for num in range(1, n + 1):
    fn *= num
fmn = 1
for num in range(1, m - n + 1):
    fmn *= num
print(fm // fn // fmn)

轻松得出答案为:35dom

 

1. 函数的做用

  不知道你们是否注意到,在上面的代码中,咱们作了3次求阶乘,这样的代码实际上就是重复代码。编程大师Martin Fowler先生曾经说过:“代码有不少种坏味道,重复是最坏的一种!”,要写出高质量的代码首先要解决的就是重复代码的问题。对于上面的代码来讲,咱们能够将计算阶乘的功能封装到一个称之为“函数”的功能模块中,在须要计算阶乘的地方,咱们只须要“调用”这个“函数”就能够了。编程语言

1.1 定义函数

  在Python中可使用def关键字来定义函数,和变量同样每一个函数也有一个响亮的名字,并且命名规则跟变量的命名规则是一致的。在函数名后面的圆括号中能够放置传递给函数的参数,这一点和数学上的函数很是类似,程序中函数的参数就至关因而数学上说的函数的自变量,而函数执行完成后咱们能够经过return关键字来返回一个值,这至关于数学上说的函数的因变量。函数

在了解了如何定义函数后,咱们能够对上面的代码进行重构,所谓重构就是在不影响代码执行结果的前提下对代码的结构进行调整,重构以后的代码以下所示。spa

def factorial(num):
    """求阶乘"""
    result = 1
    for i in range(1, num + 1):
        result *= i
    return result

m = int(input('m = '))
n = int(input('n = '))
# 当须要计算阶乘的时候不用再写循环求阶乘而是直接调用已经定义好的函数
print(factorial(m) // factorial(n) // factorial(m - n))

  说明: Python的math模块中其实已经有一个factorial函数了,事实上要计算阶乘能够直接使用这个现成的函数而不用本身定义。下面例子中的一些函数在Python中也都是现成的,咱们这里是为了讲解函数的定义和使用才把它们又实现了一遍,实际开发中不建议作这种低级的重复性的工做。设计

 

2. 函数的参数

  函数是绝大多数编程语言中都支持的一个代码的"构建块",可是Python中的函数与其余语言中的函数仍是有不少不太相同的地方,其中一个显著的区别就是Python对函数参数的处理。在Python中,函数的参数能够有默认值,也支持使用可变参数,因此Python并不须要像其余语言同样支持函数的重载,由于咱们在定义一个函数的时候可让它有多种不一样的使用方式,下面是两个小例子。code

from random import randint

def roll_dice(n=2):
    """摇色子"""
    total = 0
    for _ in range(n):
        #print(_)
        total += randint(1, 6)
        #print(total)
    return total

def add(a=0, b=0, c=0):
    """三个数相加"""
    return a + b + c


# 若是没有指定参数那么使用默认值摇两颗色子
print(roll_dice())
# 摇三颗色子
print(roll_dice(3))
print(add())
print(add(1))
print(add(1, 2))
print(add(1, 2, 3))
# 传递参数时能够不按照设定的顺序进行传递
print(add(c=50, a=100, b=200))

  咱们给上面两个函数的参数都设定了默认值,这也就意味着若是在调用函数的时候若是没有传入对应参数的值时将使用该参数的默认值,因此在上面的代码中咱们能够用各类不一样的方式去调用add函数,这跟其余不少语言中函数重载的效果是一致的。

 

2.1 函数的可变参数

  其实上面的add函数还有更好的实现方案,由于咱们可能会对0个或多个参数进行加法运算,而具体有多少个参数是由调用者来决定,咱们做为函数的设计者对这一点是一无所知的,所以在不肯定参数个数的时候,咱们可使用可变参数,代码以下所示。

# 在参数名前面的*表示args是一个可变参数
def add(*args):
    result = 0
    for num in args:
        result += num
    return(result)

# 在调用add函数时能够传入0个或多个参数
print(add())
print(add(1))
print(add(1, 2))
print(add(1, 2, 3))
print(add(1, 3, 5, 7, 9))

 

3. 用模块管理函数

  对于任何一种编程语言来讲,给变量、函数这样的标识符起名字都是一个让人头疼的问题,由于咱们会遇到命名冲突这种尴尬的状况。最简单的场景就是在同一个.py文件中定义了两个同名函数,因为Python没有函数重载的概念,那么后面的定义会覆盖以前的定义,也就意味着两个函数同名函数实际上只有一个是存在的。

def foo():
    print('hello, world!')

def foo():
    print('goodbye, world!')

# 下面的代码会输出什么呢?
foo()

   固然上面的这种状况咱们很容易就能避免,可是若是项目是由多人协做进行团队开发的时候,团队中可能有多个程序员都定义了名为foo的函数,那么怎么解决这种命名冲突呢?答案其实很简单,Python中每一个文件就表明了一个模块(module),咱们在不一样的模块中能够有同名的函数,在使用函数的时候咱们经过import关键字导入指定的模块就能够区分到底要使用的是哪一个模块中的foo函数,代码以下所示。

(1)先在同级目录下,建立2个py文件

module1.py

def foo():
    print('hello world')

module2.py

def foo():
    print('goodbye world')

 

(2)直接使用模块使用函数

test.py

from module1 import foo
foo()

from module2 import foo
foo()

 

(3)也能够按照以下所示的方式来区分到底要使用哪个foo函数。

test.py

import module1 as m1
import module2 as m2

m1.foo()
m2.foo()

 

(4)可是若是将代码写成了下面的样子,那么程序中调用的是最后导入的那个foo,由于后导入的foo覆盖了以前导入的foo。

test.py

from module1 import foo
from module2 import foo

foo()

from module2 import foo
from module1 import foo

# 输出hello, world!
foo()

  须要说明的是,若是咱们导入的模块除了定义函数以外还中有能够执行代码,那么Python解释器在导入这个模块时就会执行这些代码,事实上咱们可能并不但愿如此,所以若是咱们在模块中编写了执行代码,最好是将这些执行代码放入以下所示的条件中,这样的话除非直接运行该模块,if条件下的这些代码是不会执行的,由于只有直接执行的模块的名字才是"__main__"。

module3.py

def foo():
    pass

def bar():
    pass

# __name__是Python中一个隐含的变量它表明了模块的名字
# 只有被Python解释器直接执行的模块的名字才是__main__
if __name__ == '__main__':
    print('call foo()')
    foo()
    print('call bar()')
    bar()

test.py

import module3
# 导入module3时 不会执行模块中if条件成立时的代码 由于模块的名字是module3而不是__main__

 

4. 练习

练习1

实现计算求最大公约数和最小公倍数的函数。

参考答案:

def gcd(x, y):
    """求最大公约数"""
    #(x, y) = (y, x) if x > y else (x, y)
    if x > y:
        (x, y) = (y, x)
    else:
        (x, y)
    for factor in range(x, 0, -1):
        if x % factor == 0 and y % factor == 0:
            return factor

def lcm(x, y):
    """求最小公倍数"""
    return x * y // gcd(x, y)

print(gcd(9,12))
print(lcm(9,12))

 

练习2

实现判断一个数是否是回文数的函数。

参考答案:

def is_palindrome(num):
    """
    判断一个数是否是回文数
    回文数是指将一个正整数从左往右排列和从右往左排列值同样的数
    """
    temp = num
    total = 0
    while temp > 0:
        total = total * 10 + temp % 10
        temp //= 10
    return total == num

 

练习3

实现判断一个数是否是素数的函数。

参考答案:

def is_prime(num):
    """判断一个数是否是素数"""
    for factor in range(2, num):
        if num % factor == 0:
            return False
    return True if num != 1 else False

print(is_prime(1))

 

练习4

写一个程序判断输入的正整数是否是回文素数。

参考答案:

if __name__ == '__main__':
    num = int(input('请输入正整数: '))
    if is_palindrome(num) and is_prime(num):
        print('%d是回文素数' % num)
    else:
        print('%d不是回文素数' % num)

 

  注意:经过上面的程序能够看出,当咱们将代码中重复出现的和相对独立的功能抽取成函数后,咱们能够组合使用这些函数来解决更为复杂的问题,这也是咱们为何要定义和使用函数的一个很是重要的缘由。

 

5. 变量做用域

(1)最后,咱们来讨论一下Python中有关变量做用域的问题

def foo():
    b = 'hello'

    # Python中能够在函数内部再定义函数
    def bar():
        c = True
        print(a)
        print(b)
        print(c)

    bar()
    # print(c)  # NameError: name 'c' is not defined


if __name__ == '__main__':
    a = 100
    # print(b)  # NameError: name 'b' is not defined
    foo()

  上面的代码可以顺利的执行而且打印出100、hello和True,但咱们注意到了,在bar函数的内部并无定义a和b两个变量,那么a和b是从哪里来的。

  咱们在上面代码的if分支中定义了一个变量a,这是一个全局变量(global variable),属于全局做用域,由于它没有定义在任何一个函数中。在上面的foo函数中咱们定义了变量b,这是一个定义在函数中的局部变量(local variable),属于局部做用域,在foo函数的外部并不能访问到它;但对于foo函数内部的bar函数来讲,变量b属于嵌套做用域,在bar函数中咱们是能够访问到它的。bar函数中的变量c属于局部做用域,在bar函数以外是没法访问的。事实上,Python查找一个变量时会按照“局部做用域”、“嵌套做用域”、“全局做用域”和“内置做用域”的顺序进行搜索(由小到大),前三者咱们在上面的代码中已经看到了,所谓的“内置做用域”就是Python内置的那些标识符,咱们以前用过的input、print、int等都属于内置做用域。

  其中,在foo函数中调用bar函数的变量c,会报错;由于读不到此变量。

 

(2)再看看下面这段代码,咱们但愿经过函数调用修改全局变量a的值,但实际上下面的代码是作不到的。

def foo():
    a = 200
    print(a)  # 200

if __name__ == '__main__':
    a = 100
    foo()
    print(a)  # 100

   在调用foo函数后,咱们发现a的值仍然是100,这是由于当咱们在函数foo中写a = 200的时候,是从新定义了一个名字为a的局部变量,它跟全局做用域的a并非同一个变量,由于局部做用域中有了本身的变量a,所以foo函数再也不搜索全局做用域中的a。

 

(3)若是咱们但愿在foo函数中修改全局做用域中的a,代码以下所示。

def foo():
    global a
    a = 200
    print(a)  # 200

if __name__ == '__main__':
    a = 100
    foo()
    print(a)  # 200

  咱们可使用global关键字来指示foo函数中的变量a来自于全局做用域,若是全局做用域中没有a,那么下面一行的代码就会定义变量a并将其置于全局做用域。

  同理,若是咱们但愿函数内部的函数可以修改嵌套做用域中的变量,可使用nonlocal关键字来指示变量来自于嵌套做用域,请你们自行试验。

  在实际开发中,咱们应该尽可能减小对全局变量的使用,由于全局变量的做用域和影响过于普遍,可能会发生意料以外的修改和使用,除此以外全局变量比局部变量拥有更长的生命周期,可能致使对象占用的内存长时间没法被垃圾回收。事实上,减小对全局变量的使用,也是下降代码之间耦合度的一个重要举措,同时也是对迪米特法则的践行。减小全局变量的使用就意味着咱们应该尽可能让变量的做用域在函数的内部,可是若是咱们但愿将一个局部变量的生命周期延长,使其在定义它的函数调用结束后依然可使用它的值,这时候就须要使用闭包,这个咱们在后续的内容中进行讲解。

  说明: 不少人常常会将“闭包”和“匿名函数”混为一谈,但实际上它们并非一回事,若是想了解这个概念,能够看看维基百科的解释。

说了那么多,其实结论很简单,从如今开始咱们能够将Python代码按照下面的格式进行书写,这一点点的改进其实就是在咱们理解了函数和做用域的基础上跨出的巨大的一步。

def main():
    # Todo: Add your code here
    pass

if __name__ == '__main__':
    main()
相关文章
相关标签/搜索