一、变量的作用域LEGB

1.1、变量的作用域

在Python中,程序的变量并不是在哪个位置都可以访问的,访问权限决定于这个变量是在哪里赋值的。变量的作用域决定了在哪一部分程序可以访问哪个特定的变量名称。

在Python程序中创建、改变、查找变量名时,都是在一个保存变量名的空间中进行,我们称之为命名空间,也被称之为作用域。python的作用域是静态的,在源代码中变量名被赋值的位置决定了该变量能被访问的范围。即Python变量的作用域由变量所在源代码中的位置决定。

1.2、变量作用域的产生

在Python中并不是所有的语句块中都会产生作用域。只有当变量在Module(模块)、Class(类)、def(函数)中定义的时候,才会有作用域的概念。

举个栗子:

def greet_people():
name = 'jack'
print("Hello, "+name) # 在函数内打印变量 print(name) # 在函数外打印变量 ===》 直接出错 greet_people()

上述代码的运行结果为:

C:\Python37\python3.exe D:/pythoncode/Exercise/Exer8/Exer8-13.py
Traceback (most recent call last):
File "D:/pythoncode/Exercise/Exer8/Exer8-13.py", line 5, in <module>
print(name)
NameError: name 'name' is not defined Process finished with exit code 1

分析:在作用域中定义的变量,一般只在作用域中有效,上面是在greet_people() 函数中定义的变量 name ,在函数外调用就会出错,因为函数外就不是变量name的作用域了。

注意:在if-elif-else、for-else、while、try-except\try-finally等关键字的语句块中并不会产成作用域 ,只要是定义的变量,都可以调用。

1.3、变量作用域的分类

Python中变量的作用域可分为以下四种:

  • L (Local) 局部作用域
  • E (Enclosing) 闭包函数外的函数中
  • G (Global) 全局作用域
  • B (Built-in) 内建作用域

注意:上述变量作用域是按照 L –> E –> G –> B 的规则查找,即:在局部找不到,便会去局部外的局部找(例如闭包),再找不到就会去全局找,再者去内建中找。

举个栗子:

x = int(2.9)         # 内建作用域

g_count = 0          # 全局作用域

def outer():
o_count = 1 # 闭包函数外的函数中,enclosing
def inner():
i_count = 2 # 局部作用域

详细介绍上面的四种作用域:

L(local)局部作用域

局部变量:包含在def关键字定义的语句块中,即在函数中定义的变量。每当函数被调用时都会创建一个新的局部作用域。在函数内部的变量声明,除非特别的声明为全局变量,否则均默认为局部变量。有些情况需要在函数内部定义全局变量,这时可以使用global关键字来声明变量的作用域为全局变量,Python中也有递归,即自己调用自己,每次调用都会创建一个新的局部命名空间。

注意:如果需要在函数内部对全局变量赋值,需要在函数内部通过global语句声明该变量为全局变量。

E(enclosing)嵌套作用域

E也包含在def关键字中,E和L是相对的,E相对于更上层的函数而言也是L。与L的区别在于,对一个函数而言,L是定义在此函数内部的局部作用域,而E是定义在此函数的上一层父级函数的局部作用域。主要是为了实现Python的闭包,而增加的实现。

G(global)全局作用域

即在模块层次中定义的变量,每一个模块都是一个全局作用域。也就是说,在模块文件顶层声明的变量具有全局作用域,从外部开来,模块的全局变量就是一个模块对象的属性。

注意:全局作用域的作用范围仅限于单个模块文件内

B(built-in)内置作用域

系统内固定模块里定义的变量,比如int, bytearray等

搜索变量的优先级顺序依次是:作用域局部 > 外层作用域 > 当前模块中的全局 > python内置作用域,也就是LEGB

举个栗子:

globalVar = 100               #globalVar是全局变量,作用于全局作用域

def test_scope():
enclosingVar = 200 #enclosingVar是嵌套变量,作用于test_scope函数以及其包含的func函数 def func():
localVar = 300 #localVar局部变量,作用于func函数
print(localVar) #==> localVar=300,局部变量作用域作用于本地函数func
print(globalVar) #====> globalVar=100,说明全局变量作用域群局作用域
print(enclosingVar) #===> enclosingVar=200,说明嵌套变量作用域包含其内部包含的变量
func() print(localVar) #==> 直接出错,出了func函数的作用域局部变量就不能引用了
print(globalVar) #====> globalVar=100,说明全局变量作用域群局作用域
print(enclosingVar) #===> enclosingVar=200,嵌套函数在自己顶层函数test_scope的作用域内有效 test_scope()
print(localVar) #==> 直接出错,出了func函数的作用域局部变量就不能引用了
print(globalVar) #====> globalVar=100,说明全局变量作用域群局作用域
print(enclosingVar) #==> 直接出错,说明相出整个顶层函数test_scope的作用域,嵌套变量也是无效的, 相当于出了局部变量的函数作用域 print(__name__) #内置变量,直接调用

上面这个例子基本上就是各种变量作用域的测试。

下面再看两个例子:

variable = 300           # 定义全局变量variable

def test_scopt():
print(variable) ====> 输出variable=300 直接调用全局变量variable test_scopt()
print(variable) ====> 输出variable=300 直接调用全局变量variable
variable = 300          # 定义全局变量variable

def test_scopt():
print(variable) ====> 直接报错 #错误的原因在于print(variable)时,解释器会在局部作用域找,会找到 variable = 300(函数已经加载到内存),但variable使用在声明前了,所以报错;
variable = 200 #定义局部变量 test_scopt()
print(variable) ====> 输出variable=300 直接调用全局变量variable

上述代码的运行结果为:

C:\Python37\python3.exe D:/pythoncode/Exercise/Exer8/Exer8-13.py
Traceback (most recent call last):
File "D:/pythoncode/Exercise/Exer8/Exer8-13.py", line 36, in <module>
test_scopt()
File "D:/pythoncode/Exercise/Exer8/Exer8-13.py", line 33, in test_scopt
print(variable)
UnboundLocalError: local variable 'variable' referenced before assignment Process finished with exit code 1

分析:上述代码在函数 test_scopt 中打印变量variable时直接程序出错,因为在执行程序时的预编译能够在test_scopt()中找到局部变量variable(对variable进行了赋值)。在局部作用域找到了变量名,所以不会升级到嵌套作用域去寻找。但是在使用print语句将变量variable打印时,局部变量variable并有没绑定到一个内存对象(没有定义和初始化,即没有赋值)。本质上还是Python调用变量时遵循的LEGB法则和Python解析器的编译原理,决定了这个错误的发生。所以,在调用一个变量之前,需要为该变量赋值(绑定一个内存对象)。

1.4、变量作用域的修改(不建议使用)

变量性质不同其作用域也是不同的,当内部作用域想修改外部作用域的变量时,就要用到global和nonlocal关键字了,当修改的变量是在全局作用域(global作用域)上的,就要使用global先声明一下;global语句是一个命名空间的声明,它告诉Python解释器打算生成一个或多个全局变量,也就是说,存在于整个模块内部作用域(命名空间)的变量名。

举个栗子:

variable = 300             # ====> 定义全局变量
def test_scopt():
global variable # ====> 通过global语句声明变量variable,使其在函数test_scopt内可以被直接调用
print(variable) ====> 输出值为:300
variable = 200 # ====> 定义局部变量
print(variable) ====> 输出值为:200 test_scopt()
print(variable) ====> 输出值为:200,因为global语句将variable变量变成全局变量了

注意:global语句包含了关键字global,其后跟着一个或多个由逗号分开的变量名。当在函数主题被赋值或引用时,所有列出来的变量名将被映射到整个模块的作用域内。

但是在实际工作中,尽量不要使用global改变局部变量作用域,因为如果代码量非常多的时候不好调试,不知道哪里函数作用域范围出错,尽量不要改变变量作用域以免导致出错。

也在某些环境中,局部变量可以修改全局变量

举个栗子:

names = ['Rose','Jcak']
def change_name():
names[0] = 'kitter'
print("Idide function",names) # ====》 Idide function ['kitter', 'Jcak'] change_name()
print(names) # ====》 ['kitter', 'Jcak'] 全局变量被修改了

注意:在当全局变量是数字或者是字符串的时候是不能修改的,修改全局变量只在全局变量是列表、字典、集合等可变数据类型中才可以修改

二、递归函数

定义:在函数内部,可以调用其他函数。如果一个函数在内部调用自身本身,这个函数就是递归函数。

递归函数特性:

  1. 必须有一个明确的结束条件;
  2. 每次进入更深一层递归时,问题规模相比上次递归都应有所减少
  3. 相邻两次重复之间有紧密的联系,前一次要为后一次做准备(通常前一次的输出就作为后一次的输入)。
  4. 递归效率不高,递归层次过多会导致栈溢出(在计算机中,函数调用是通过栈(stack)这种数据结构实现的,每当进入一个函数调用,栈就会加一层栈帧,每当函数返回,栈就会减一层栈帧。由于栈的大小不是无限的,所以,递归调用的次数过多,会导致栈溢出)

举个栗子:计算1到100之间相加之和

运用之前的for 循环实现:

def sum_add(n):
sum = 0
for i in range(1,n+1): # for循环相加
sum += i
return sum print("循环求和:",sum_add(100))

运用递归函数实现:

def sum(n):
if n > 0:
return n + sum(n - 1) # 通过对函数的多次调用
else:
return 0 print("递归求和:",sum(100))

递归函数的优点是定义简单,逻辑清晰。理论上,所有的递归函数都可以写成循环的方式,但循环的逻辑不如递归清晰。

注意:使用递归函数需要注意防止栈溢出。在计算机中,函数调用是通过栈(stack)这种数据结构实现的,每当进入一个函数调用,栈就会加一层栈帧,每当函数返回,栈就会减一层栈帧。由于栈的大小不是无限的,所以,递归调用的次数过多,会导致栈溢出,比如上面的递归求和,计算sum(1000)。

C:\Python37\python3.exe D:/pythoncode/Exercise/Exer8/Exer8-14.py
Traceback (most recent call last):
File "D:/pythoncode/Exercise/Exer8/Exer8-14.py", line 16, in <module>
print("递归求和:",sum(1000))
File "D:/pythoncode/Exercise/Exer8/Exer8-14.py", line 12, in sum
return n + sum(n - 1)
File "D:/pythoncode/Exercise/Exer8/Exer8-14.py", line 12, in sum
return n + sum(n - 1)
File "D:/pythoncode/Exercise/Exer8/Exer8-14.py", line 12, in sum
return n + sum(n - 1)
[Previous line repeated 994 more times]
File "D:/pythoncode/Exercise/Exer8/Exer8-14.py", line 11, in sum
if n > 0:
RecursionError: maximum recursion depth exceeded in comparison Process finished with exit code 1

解决递归调用栈溢出的方法是通过尾递归优化,事实上尾递归和循环的效果是一样的,所以,把循环看成是一种特殊的尾递归函数也是可以的。

尾递归是指,在函数返回的时候,调用自身本身,并且,return语句不能包含表达式。这样,编译器或者解释器就可以把尾递归做优化,使递归本身无论调用多少次,都只占用一个栈帧,不会出现栈溢出的情况。

三、匿名函数

匿名函数的命名规则,用lamdba 关键字标识冒号(:)左侧表示函数接收的参数(a,b) ,冒号(:)右侧表示函数的返回值(a+b)。因为lamdba在创建时不需要命名,所以,叫匿名函数

举个栗子:

# 普通函数
def add(a,b):
return a + b
print( add(2,3) ) # 匿名函数
add = lambda a,b : a + b
print( add(2,3) )

注意:匿名函数lambda后面可以跟的表达式不能复杂,可以是三元运算符,但是不能是for循环语句

num = lambda n : 3 if n > 5 else n
print(num(3)) ====》 3
res = filter(lambda n : n > 5 ,range(10))  # filter 用于基于条件过滤
for i in res:
print(i) ====》 6 7 8 9

四、内置函数

python内置了一系列的常用函数,以便于我们使用:

具体每个内置函数的用法还得多看多练习。

五、函数式编程

函数式编程是一种编程范式,我们常见的编程范式有命令式编程(Imperative programming),函数式编程,常见的面向对象编程是也是一种命令式编程。

命令式编程是面向计算机硬件的抽象,有变量(对应着存储单元),赋值语句(获取,存储指令),表达式(内存引用和算术运算)和控制语句(跳转指令),一句话,命令式程序就是一个冯诺依曼机的指令序列。

而函数式编程是面向数学的抽象,将计算描述为一种表达式求值,一句话,函数式程序就是一个表达式。函数式编程关心数据的映射,命令式编程关心解决问题的步骤,这也是为什么“函数式编程”叫做“函数式编程”。

函数式编程有什么好处呢?

1)代码简洁,易懂。

2)无副作用

由于命令式编程语言也可以通过类似函数指针的方式来实现高阶函数,函数式的最主要的好处主要是不可变性带来的。没有可变的状态,函数就是引用透明(Referential transparency)的和没有副作用(No Side Effect)。

函数式编程的一个特点就是,允许把函数本身作为参数传入另一个函数,还允许返回一个函数!

参考链接:

https://www.jianshu.com/p/17a9d8584530

Python函数初识二的更多相关文章

  1. python函数(二)

    python函数(二) 变量的作用域 1.局部变量与全局变量 在函数内创建的变量被称为局部变量,这类变量的生命周期与函数相同,当函数执行完毕时,变量也就随之消失. 此类变量只能在函数内部调用,函数外不 ...

  2. 从入门到自闭之Python函数初识

    函数初识 定义:def--关键字 ​ 将某个功能封装到一个空间中就是一个函数 功能: ​ 减少重复代码 函数的调用 ​ 函数名+():调用函数和接收返回值 函数的返回值 return 值 == 返回值 ...

  3. python 函数初识和文件操作

    文件操作  打开文件:文件句柄 = open('文件路径', '模式')  打开文件的模式 w #以写的方式打开 (不可读,不存在则创建,存在则删除内容) a #以追加的模式打开(可读, 不存在则创建 ...

  4. 《转》Python学习(19)-python函数(二)-关于lambda

    转自http://www.cnblogs.com/BeginMan/p/3178103.html 一.lambda函数 1.lambda函数基础: lambda函数也叫匿名函数,即,函数没有具体的名称 ...

  5. Python函数初识

    一.函数是什么 ​ 计算机语言中的函数是类比于数学中的函数演变来的,但是又有所不同.前面的知识中我们学会了运用基础语法(列表.字典)和流程控制语句貌似也能处理一些复杂的问题,但是相对于相似的大量重复性 ...

  6. Python 函数初识 (1)

    一.今日主要内容 认识函数 函数:对功能或者动作的封装(定义) 语法: def 函数名字(形参) 函数体 函数的调用格式:函数名(实参) 函数的返回值 关键字:return 终止函数的运行 1.函数内 ...

  7. Python 函数(二)

    Python 3 函数(匿名函数.偏函数 and 变量作用域:全局变量.局部变量) 一.匿名函数:没有名字,也不再使用 def 语句这样标准的形式定义的一个函数. OCP培训说明连接:https:// ...

  8. 《转》Python学习(18)-python函数(二)

    转自 http://www.cnblogs.com/BeginMan/p/3173328.html 一.装饰器(decorators) 装饰器的语法以@开头,接着是装饰器函数的名字.可选参数. 紧跟装 ...

  9. Python 学习笔记(十三)Python函数(二)

    参数和变量 >>> def foo(a,b): #函数是一个对象 return a+b >>> p =foo #对象赋值语句.将foo函数赋值给p这个变量 > ...

随机推荐

  1. virtualbox+vagrant学习-2(command cli)-9-vagrant Plugin命令

    Plugin 格式: vagrant plugin <command> [<args>] 这是用来管理插件的命令. 1)Plugin Expunge 格式: vagrant p ...

  2. C++矩阵库 Eigen 简介

    最近需要用 C++ 做一些数值计算,之前一直采用Matlab 混合编程的方式处理矩阵运算,非常麻烦,直到发现了 Eigen 库,简直相见恨晚,好用哭了. Eigen 是一个基于C++模板的线性代数库, ...

  3. 关于2.4G芯片中 CC2500的相关资料

    CC2500芯片,是TI(原Chipcon被TI收购)推出的一款超低功耗.低成本的无线收发模块,其载频范围在2.400GHz-2.483GHz内可调,可用来实现多信道通信.它支持多种调制方式,包括FS ...

  4. ZOJ3202-Second-price Auction(根据输入输出判断是队列还是栈)

    A Stack or A Queue? Time Limit:1000MS     Memory Limit:32768KB     64bit IO Format:%lld & %llu S ...

  5. 串口调试助手vc源程序及其详细编写过程

    串口调试助手vc源程序及其详细编写过程   目次: 1.建立项目 2.在项目中插入MSComm控件 3.利用ClassWizard定义CMSComm类控制变量 4.在对话框中添加控件 5.添加串口事件 ...

  6. 开源 免费 java CMS - FreeCMS1.9 全文检索

    项目地址:http://code.google.com/p/freecms/ 全文检索 从FreeCMS 1.7開始支持 仅仅有创建过索引的对象才干被lucene类标签查询到. 信息类数据会在信息更新 ...

  7. 【Dubbo源码阅读系列】之 Dubbo XML 配置加载

    今天我们来谈谈 Dubbo XML 配置相关内容.关于这部分内容我打算分为以下几个部分进行介绍: Dubbo XML Spring 自定义 XML 标签解析 Dubbo 自定义 XML 标签解析 Du ...

  8. 时间序列分析工具箱——tidyquant

    目录 时间序列分析工具箱--tidyquant tidyquant 的用途 加载包 tq_get:获得数据 从 Yahoo! Finance 获得股票数据 从 FRED 获得经济数据 使用 tq_tr ...

  9. 2017-2018-1 20155320第十周课下作业-IPC

    2017-2018-1 20155320第十周课下作业-IPC 研究Linux下IPC机制:原理,优缺点,每种机制至少给一个示例,提交研究博客的链接 共享内存 管道 FIFO 信号 消息队列 共享内存 ...

  10. WPF 学习笔记-设置属性使窗口不可改变大小

    原文:WPF 学习笔记-设置属性使窗口不可改变大小 调整Windows下的ResizeMode属性: ResizeMode = NoResize Resize属性是控制Windows是否可以改变大小, ...