python-变量作用域

变量作用域

  • Python 中,程序的变量并不是在哪个位置都可以访问的,访问权限决定于这个变量是在哪里赋值的。
  • 定义在函数内部的变量拥有局部作用域,该变量称为局部变量。
  • 定义在函数外部的变量拥有全局作用域,该变量称为全局变量。
  • 局部变量只能在其被声明的函数内部访问,而全局变量可以在整个程序范围内访问。
1

【例子】

def discounts(price, rate):
    final_price = price * rate
    return final_price


old_price = float(input('请输入原价:'))  # 98
rate = float(input('请输入折扣率:'))  # 0.9
new_price = discounts(old_price, rate)
print('打折后价格是:%.2f' % new_price)  # 88.20

这段代码是一个简单的计算打折后价格的函数:

def discounts(price, rate):
    final_price = price * rate
    return final_price

这是一个名为discounts的函数定义,它接受两个参数pricerate。函数体内部,将原价price与折扣率rate相乘得到最终价格final_price,然后将其返回。

old_price = float(input('请输入原价:'))  # 98
rate = float(input('请输入折扣率:'))  # 0.9

这里使用input函数分别提示用户输入原价和折扣率,并通过float函数将输入的字符串转换为浮点数类型。用户输入的原价存储在变量old_price中,折扣率存储在变量rate中。

new_price = discounts(old_price, rate)

调用函数discounts,传入old_pricerate作为参数,返回的结果被赋值给变量new_price,即为计算出的打折后价格。

print('打折后价格是:%.2f' % new_price)  # 88.20

使用print函数将打折后的价格格式化输出,保留两位小数,并在字符串中使用占位符%.2f来表示浮点数类型。这样就会将变量new_price的值插入到字符串中进行输出,得到打折后价格的结果。

2
  • 当内部作用域想修改外部作用域的变量时,就要用到globalnonlocal关键字了。

【例子】

num = 1


def fun1():
    global num  # 需要使用 global 关键字声明
    print(num)  # 1
    num = 123
    print(num)  # 123


fun1()
print(num)  # 123

这段代码演示了如何在函数内部修改全局变量的值。让我们逐行解析代码:

num = 1

在全局作用域中定义了一个变量num,初始值为1

def fun1():
    global num  # 需要使用 global 关键字声明
    print(num)  # 1
    num = 123
    print(num)  # 123

定义了一个名为fun1的函数。在函数内部,通过global关键字声明num为全局变量,这样就可以在函数中修改全局变量的值。首先,使用print函数输出全局变量num的值,得到结果1。然后,将num的值修改为123。再次使用print函数输出改变后的num的值,得到结果123

fun1()

调用函数fun1,执行函数内的代码。

print(num)  # 123

使用print函数输出全局变量num的值,得到结果123,即函数内部对全局变量的修改在函数外仍然有效。

3

内嵌函数

【例子】

def outer():
    print('outer函数在这被调用')

    def inner():
        print('inner函数在这被调用')

    inner()  # 该函数只能在outer函数内部被调用


outer()
# outer函数在这被调用
# inner函数在这被调用

这段代码展示了一个内部函数的例子:

def outer():
    print('outer函数在这被调用')

    def inner():
        print('inner函数在这被调用')

    inner()  # 该函数只能在outer函数内部被调用

定义了一个名为outer的函数。在outer函数的函数体内,定义了一个名为inner的内部函数。内部函数inner只能在外部函数outer内部被调用。

outer()

调用函数outer,执行函数内的代码。

输出结果为:

outer函数在这被调用
inner函数在这被调用

首先,打印出字符串"outer函数在这被调用"。然后,在outer函数内部调用了inner函数,因此会打印出字符串"inner函数在这被调用"。

4

闭包

  • 是函数式编程的一个重要的语法结构,是一种特殊的内嵌函数。
  • 如果在一个内部函数里对外层非全局作用域的变量进行引用,那么内部函数就被认为是闭包。
  • 通过闭包可以访问外层非全局作用域的变量,这个作用域称为 闭包作用域

【例子】

def funX(x):
    def funY(y):
        return x * y

    return funY


i = funX(8)
print(type(i))  # <class 'function'>
print(i(5))  # 40

这段代码展示了一个闭包的例子:

def funX(x):
    def funY(y):
        return x * y

    return funY

定义了一个名为funX的函数,它接受一个参数x。在funX函数的函数体内部,定义了一个名为funY的内部函数,该内部函数接受一个参数y,并返回x * y的结果。

i = funX(8)

调用函数funX,将参数8传递给x,返回的结果被赋值给变量i。此时,i成为一个闭包,它包含了函数funYx的值8

print(type(i))  # <class 'function'>

使用type函数输出变量i的类型。结果显示为<class 'function'>,表示i是一个函数。

print(i(5))  # 40

调用变量i,实际上是调用了内部函数funY。将参数5传递给funY的参数y,最终返回的结果是8 * 5 = 40。使用print函数输出结果40

5

【例子】闭包的返回值通常是函数。

def make_counter(init):
    counter = [init]

    def inc(): counter[0] += 1

    def dec(): counter[0] -= 1

    def get(): return counter[0]

    def reset(): counter[0] = init

    return inc, dec, get, reset


inc, dec, get, reset = make_counter(0)
inc()
inc()
inc()
print(get())  # 3
dec()
print(get())  # 2
reset()
print(get())  # 0

这段代码展示了一个使用闭包实现计数器的例子。以下逐行解析代码:

def make_counter(init):
    counter = [init]

    def inc(): counter[0] += 1

    def dec(): counter[0] -= 1

    def get(): return counter[0]

    def reset(): counter[0] = init

    return inc, dec, get, reset

定义了一个名为make_counter的函数,它接受一个参数init。在make_counter函数的函数体内部,首先创建了一个列表counter,将参数init作为列表的第一个元素。然后,定义了四个内部函数incdecgetreset,分别用于增加计数器、减少计数器、获取当前计数器值和重置计数器。

inc, dec, get, reset = make_counter(0)

调用函数make_counter,将参数0传递给init,返回的四个函数分别被赋值给变量incdecgetreset。此时,这些变量成为了闭包,其中包含了计数器列表counter和初始化值init

inc()
inc()
inc()
print(get())  # 3

连续三次调用变量inc,即调用了内部函数inc,每次都会将计数器列表中的第一个元素加1。使用print函数输出调用get()函数的结果,即获取当前计数器的值。结果为3,表示计数器经过三次增加后的值。

dec()
print(get())  # 2

调用变量dec,即调用了内部函数dec,将计数器列表中的第一个元素减1。再次使用print函数输出当前计数器的值。结果为2,表示计数器经过一次减少后的值。

reset()
print(get())  # 0

调用变量reset,即调用了内部函数reset,将计数器列表中的第一个元素重置为初始值。最后,再次使用print函数输出当前计数器的值。结果为0,表示计数器被重置为初始值。

6

【例子】 如果要修改闭包作用域中的变量则需要 nonlocal 关键字

def outer():
    num = 10

    def inner():
        nonlocal num  # nonlocal关键字声明
        num = 100
        print(num)

    inner()
    print(num)


outer()

# 100
# 100

这段代码展示了在函数内部通过使用nonlocal关键字来修改外部嵌套函数中的变量。以下逐行解析代码:

def outer():
    num = 10

    def inner():
        nonlocal num  # nonlocal关键字声明
        num = 100
        print(num)

    inner()
    print(num)

定义了一个名为outer的函数。在outer函数的函数体内部,首先定义了一个变量num并赋值为10。接着,定义了一个内部函数inner。在inner函数内部,使用nonlocal关键字声明了变量num,表示num是外部函数outer中的变量,而不是在inner函数内部重新创建的局部变量。然后,将num赋值为100,并使用print函数输出结果100

接下来,调用inner函数,输出结果为100。再次使用print函数输出外部函数outer中的变量num的值,结果依然为100

outer()

# 100
# 100

调用外部函数outer,执行整个代码块。结果输出为:

100
100

可以看到,通过使用nonlocal关键字,内部函数inner对外部函数outer中的变量num进行了修改,并且这个修改是在外部函数中生效的。

7

递归

  • 如果一个函数在内部调用自身本身,这个函数就是递归函数。

【例子】n! = 1 x 2 x 3 x ... x n

# 利用循环
n = 5
for k in range(1, 5):
    n = n * k
print(n)  # 120

# 利用递归
def factorial(n):
    if n == 1:
        return 1
    return n * factorial(n - 1)


print(factorial(5)) # 120

这段代码展示了计算阶乘的两种方式:使用循环和使用递归。以下逐行解析代码:

n = 5
for k in range(1, 5):
    n = n * k
print(n)  # 120

使用循环的方式计算阶乘。首先,将变量n初始化为5。然后,通过循环遍历从14的范围(不包括5),在每次迭代中,将n与当前迭代变量k相乘,并将结果赋值给n。最后,使用print函数输出最终的结果120,即5!的值。

def factorial(n):
    if n == 1:
        return 1
    return n * factorial(n - 1)


print(factorial(5)) # 120

使用递归的方式计算阶乘。定义了一个名为factorial的函数,接受一个参数n。在函数内部,首先判断n是否等于1,如果是,则返回1作为递归终止条件。如果n不等于1,则执行return n * factorial(n - 1)语句,即将nfactorial(n-1)的返回值相乘,并返回结果。这样就形成了递归调用,每一层递归都会将n减1,直到达到递归终止条件。最后,调用factorial函数,并将参数5传入,使用print函数输出最终的结果120,即5!的值。

这两种方式都可以用来计算阶乘,但是使用循环的方式通常更高效,因为递归需要反复调用函数,并在每一层递归中保存函数调用的状态。在处理大规模的计算时,递归可能会导致栈溢出的问题。而使用循环的方式则没有这个限制,更适合处理大规模的计算。

8

【例子】斐波那契数列 f(n)=f(n-1)+f(n-2), f(0)=0 f(1)=1

# 利用循环
i = 0
j = 1
lst = list([i, j])
for k in range(2, 11):
    k = i + j
    lst.append(k)
    i = j
    j = k
print(lst)  
# [0, 1, 1, 2, 3, 5, 8, 13, 21, 34, 55]

# 利用递归
def recur_fibo(n):
    if n <= 1:
        return n
    return recur_fibo(n - 1) + recur_fibo(n - 2)


lst = list()
for k in range(11):
    lst.append(recur_fibo(k))
print(lst)  
# [0, 1, 1, 2, 3, 5, 8, 13, 21, 34, 55]

这段代码展示了生成斐波那契数列的两种方式:使用循环和使用递归。以下逐行解析代码:

i = 0
j = 1
lst = list([i, j])
for k in range(2, 11):
    k = i + j
    lst.append(k)
    i = j
    j = k
print(lst)  
# [0, 1, 1, 2, 3, 5, 8, 13, 21, 34, 55]

使用循环的方式生成斐波那契数列。首先,定义变量ij分别赋值为01,然后定义一个空列表lst用于存储斐波那契数列。接着,通过循环遍历从210的范围(不包括11),在每次迭代中,计算当前斐波那契数列的值k,即前两个数的和。然后,将k添加到列表lst中,更新变量i为上一个数j,更新变量j为当前计算得到的数k。最后,使用print函数输出最终的斐波那契数列结果。

def recur_fibo(n):
    if n <= 1:
        return n
    return recur_fibo(n - 1) + recur_fibo(n - 2)


lst = list()
for k in range(11):
    lst.append(recur_fibo(k))
print(lst)  
# [0, 1, 1, 2, 3, 5, 8, 13, 21, 34, 55]

使用递归的方式生成斐波那契数列。定义了一个名为recur_fibo的函数,接受一个参数n表示要计算的斐波那契数列的第几个数。在函数内部,首先判断n是否小于等于1,如果是,则返回n作为递归终止条件。如果n大于1,则执行return recur_fibo(n - 1) + recur_fibo(n - 2)语句,即将使用递归调用来计算第n-1和第n-2个斐波那契数的值,并返回它们的和。这样就形成了递归调用,每一层递归都会继续递归调用前两个数。最后,通过循环遍历从010的范围,将每个计算得到的斐波那契数添加到列表lst中。使用print函数输出最终的斐波那契数列结果。

这两种方式分别使用循环和递归实现了斐波那契数列的生成,而且得到的结果是相同的。选择哪种方式取决于特定的情况。循环方式通常更高效,尤其在处理大规模的计算时。而递归方式更简洁直观,更容易理解,但可能在处理大规模的计算时导致栈溢出的问题。因此,在实际应用中,需要根据具体需求和场景综合考虑选择合适的方法。

9

【例子】设置递归的层数,Python默认递归层数为 100

import sys

sys.setrecursionlimit(1000)

这段代码使用sys.setrecursionlimit()函数设置了递归的最大深度限制为1000。sys.setrecursionlimit()是Python标准库sys中的一个函数,用于设置递归调用的最大深度。

在递归算法中,每次递归调用都会占用一定的内存空间,递归的次数过多可能导致栈溢出错误。为了避免栈溢出错误,可以通过设置递归深度的限制来限制递归的最大层数。sys.setrecursionlimit(1000)语句将递归调用的最大深度限制设置为1000,意味着在递归调用超过1000层之后,Python解释器会抛出RecursionError: maximum recursion depth exceeded in comparison异常。

需要注意的是,修改递归深度的设置可能会影响到程序的性能和稳定性。增加递归深度会增加内存的消耗,并且过高的递归深度可能导致程序崩溃。因此,建议在使用sys.setrecursionlimit()函数设置递归深度之前,先仔细评估程序的需求和性能,并确保设置的递归深度不会导致不必要的问题。

如有错误欢迎指正,谢谢!
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 218,525评论 6 507
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 93,203评论 3 395
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 164,862评论 0 354
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,728评论 1 294
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,743评论 6 392
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,590评论 1 305
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,330评论 3 418
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 39,244评论 0 276
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,693评论 1 314
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,885评论 3 336
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 40,001评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,723评论 5 346
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,343评论 3 330
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,919评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 33,042评论 1 270
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 48,191评论 3 370
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,955评论 2 355

推荐阅读更多精彩内容