Python学习笔记 六:函数和模块的使用,及变量作用域的探讨

1. 函数使用

今天学习骆昊的博文,并把他的相关知识点进行了总结,希望对你有所帮助。
GitHub原文连接

  • 求解: C M N = M ! N ! ( M − N ) ! , (M=7, N=3) C_M^N =\frac{M!}{N!(M-N)!}, \text{(M=7, N=3)} CMN=N!(MN)!M!,(M=7, N=3)
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("求得值为 = {}".format(fm/fn/fmn))

m = 7
n = 3
求得值为 = 35.0

1.1 函数的作用

  • 对于上面的例子,我们使用了三次for循环,这样使得程序重复次数过多,编程大师Martin Fowler先生曾经说过:“代码有很多种坏味道,重复是最坏的一种!”
  • 我们需要建立函,将这些相同的功能封装到其中

1.2 定义函数

  • 使用def关键字来定义函数
  • 函数执行完成后我们可以通过return关键字来返回一个值,这相当于数学上说的函数的因变量
def factorial(num): # factorial:阶乘
    result = 1
    for n in range(1, num + 1):
        result *= n
    return result

m = int(input("m = "))
n = int(input("n = "))

print(factorial(m)/factorial(n)/factorial(m - n))

# 在Python的math模块中已经有一个factorial函数
# 就不需要自己定义函数了

import math

m = int(input("M = "))# 注意要将str强制转换为int
n = int(input("N = "))

c = math.factorial(m)/ \
    math.factorial(n)/ \
    math.factorial(m - n)
print(c)

m = 7
n = 3
35.0
M = 7
N = 3
35.0

使用上面的函数来定义,将使得程序更加简洁和简单

1.3 函数的参数

  • 函数的参数可以有默认值,也支持使用可变参数
  • 参数的设置是灵活多变的,有多种使用的方式
from random import randint

def rollDice(n = 2):
    """摇色子"""
    total = 0
    for i in range(n):
        total += randint(1, 6)
    return total

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

# 如果没有指定参数那么使用默认值摇两颗色子
print(rollDice()) # 4

# 摇三颗色子
print(rollDice(3)) # 14
print(add())       # 0
print(add(1))      # 1
print(add(1, 2))   # 3
print(add(1, 2, 3))# 6

# 传递参数时可以不按照设定的顺序进行传递
print(add(c=50, a=100, b=200))   # 350


1.4 函数的重载(用同一个函数完成不同的功能)

  • 我们给上面两个函数的参数都设定了默认值,这也就意味着如果在调用函数的时候如果没有传入对应参数的值时将使用该参数的默认值,所以在上面的代码中我们可以用各种不同的方式去调用__add__函数,这跟其他很多语言中函数重载的效果是一致的
  • 上面的__add__函数中,我们已经给出来参数的个数,但是如果参数个数不知道,我们又应该怎么做呢?
    • 可在参数前加一个*,表示此参数是可变的
def add(*args):
    total = 0
    for i in args:
        total += i
    return total


# 在调用add函数时可以传入0个或多个参数
print(add())          	# 0
print(add(1))			# 1
print(add(1, 2))		# 3		
print(add(1, 2, 3))		# 6


2.用模块管理函数

  • 对于任何一种编程语言来说,给变量、函数这样的标识符起名字都是一个让人头疼的问题,因为我们会遇到命名冲突这种尴尬的情况。最简单的场景就是在同一个.py文件中定义了两个同名函数,由于Python没有函数重载的概念,那么后面的定义会覆盖之前的定义,也就意味着两个函数同名函数实际上只有一个是存在的
  • 简单的理解就是前后重复的函数名会被后一个覆盖掉
def sayHi():
    print("hello")
    return None
    
def sayHi():
    print('hello,world!')
    return None

sayHi()

hello,world!

2.1 解决上面问题的方法

  • 为每个模块建立一个文件,如module1.py、module2.py…
  • 所有,在不同的文件(模块)内,允许有同名的函数
  • 使用时,使用__import__关键字导入指定的模块即可
from module1 import sayHi

sayHi()

from module2 import sayHi

sayHi()


# 也可以使用下面的方法来区分使用了哪个 sayHi 函数
import module1 as m1
import module2 as m2

m1.sayHi()
m2.sayHi()
  • 如果将代码写成了下面的样子,那么程序中调用的是最后导入的那个sayHi,因为后导入的sayHi覆盖了之前导入的sayHi
from module1 import sayHi
from module2 import sayHi

sayHi()# 输出hello

from module2 import sayHi
from module1 import sayHi

sayHi()# 输出hello, world!

注意:
  • 如果我们导入的模块除了定义函数之外还中有可以执行代码,那么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()

call foo()
call bar()

import module3

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

案例1:实现计算求最大公约数和最小公倍数的函数

def gcd(x, y):
    
    # 求最大公约数:两个或多个整数共有约数中最大的一个
    # (x, y) = (y, x) if x > y else (x, y)
    for factor in range(y, 0 , -1):
        if x % factor == 0 and y % factor == 0:
            return factor

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


gcd(200, 45)
lcm(7, 7)

案例2 :实现判断一个数是不是回文数的函数

  • 例如:1234321
def isPal(num):
    temp = num
    total = 0
    while temp > 0:
        total = total * 10 + temp % 10
        temp //= 10
    return total == num

isPal(1234321)


案例3:实现判断一个数是不是素数的函数

def isPrime(num):
    # 判断一个数是不是素数
    for factor in range(2, num):
        if num % factor == 0:
            return False
    return True if num != 1 else False
注意:
  • 通过上面的程序可以看出,当我们将代码中重复出现的和相对独立的功能抽取成函数后,我们可以组合使用这些函数来解决更为复杂的问题,这也是我们为什么要定义和使用函数的一个非常重要的原因。

3. Python中有关变量作用域的问题

  • 查找变量时的顺序:
    • 局部作用域 : c
    • 嵌套作用域 : b
    • 全局作用域 : a
    • 内置作用域,如一些内置的标识符,input、print、int等
def foo():
    b = 'hello'  # 局部变量,在foo函数的外部并不能访问到它
                 # 对于foo函数内部的bar函数来说,变量b属于嵌套作用域
                 # 在bar函数中,可以访问到它
    
    # Python中可以在函数内部再定义函数
    def bar():
        
        #nonlocal b
        #b = "nonlocal" # 修改嵌套变量的值
        
        c = True # 局部作用域,在bar函数之外是无法访问
        print(a)
        print(b)
        print(c)

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

if __name__ == '__main__':
    a = 100      # 全局变量,属于全局作用域
    foo()
    # print(b)   # NameError: name 'b' is not defined

100
hello
True
hello
35.0

# 案例1:
def foo():
    a = 200
    print(a)         # 200


if __name__ == '__main__':
    a = 100
    foo()
    print(a)         # 100
通过上面的例子,我们知道最后输出的a还是为100,因为,按照python的搜索顺序,当找到局部变量之后,将不再寻早其他的变量
3.1.如果希望将局部变量的值用到全局变量中,可以使用:global关键字
3.2.如果希望函数内部的函数能够修改嵌套作用域中的变量,可以使用:nonlocal关键字
# 案例2:
def foo():
    global a
    a = 200
    print(a)  # 200


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

总结:

  • 在实际开发中,我们应该尽量减少对全局变量的使用,因为全局变量的作用域和影响过于广泛,可能会发生意料之外的修改和使用,除此之外全局变量比局部变量拥有更长的生命周期,可能导致对象占用的内存长时间无法被垃圾回收
  • 事实上,减少对全局变量的使用,也是降低代码之间耦合度的一个重要举措,同时也是对迪米特法则的践行。
  • 减少全局变量的使用就意味着我们应该尽量让变量的作用域在函数的内部,但是如果我们希望将一个局部变量的生命周期延长,使其在定义它的函数调用结束后依然可以使用它的值,这时候就需要使用闭包

猜你喜欢

转载自blog.csdn.net/amyniez/article/details/104415925