函数定义和调用

函数定义

函数是:组织好的,可重复使用的,用来实现单一或相关联功能的代码段。
在程序中,函数的使用能提高应用的模块性、代码的重用率和可读性。
自定义函数的一般格式为:

def 函数名([参数1,参数2,...]):"""函数说明"""函数体

其中:
(1)函数关键字:def 函数名()
(2)函数名:遵循标识符命名规则,最好是有意义的名称,增加可读性
(3)参数:必须放在圆括号中间,称为形式参数(形参)。形参是可选的,可以没有,也可以有多个。
当有多个形参时,各形参之间用逗号隔开
(4)函数说明:函数第一号语句可以选择性的使用文档字符串,用于存放函数说明
(5)函数内容:以冒号起始,并且缩进
(6)返回值:使“return [表达式]”结束函数,返回一个值给调用方。
如果没有“return [表达式]”,函数默认返回None。当函数返回多个值时,其本质是把这些值作为一个元组返回。
如语句“return 2,6,8”,实际上返回的是元组(2,6,8)

a,b,c=2,6,8
a,b,c=(2,6,8)
a,b,c=[2,6,8]
a,b,c={2:1,6:2,8:3}
"以上结果均为:a 2 b 6 c 8"
a,b,c={2,6,8}
"该结果在该进程内随机"
a,b,c=268
"TypeError: cannot unpack non-iterable int object"

定义一个名为myHello()的函数

#定义函数
def myHello():return "Hello, everyone!"a=myHello()
print(a+"\n"+myHello())"""
Hello, everyone!
Hello, everyone!
"""

例:函数成员的使用

def f():a=0print(a,b)
f() # ERROR:no b
b=1
f() # 0 1
del b
f() # ERROR:no b
del a # ERROR:no a

这里其实就是函数变量,话说之前无论是啃书还是啃文档,接触过了很多的函数内容了,这里其实就是函数内局域变量与全局变量的关系,关键在于主体下使用del删除局部变量无效。
其实即便有个语法真能del a,但是除非他并行运算,不然没有意义。
函数就是在调用的时候,才开辟并激活,不调用的时候不存在的东西。所以删除一个不存在的东西,自然不成功。

函数调用

函数定义完成后,就可以在程序中调用了。函数调用时,需要指出函数名称,并传入相应的参数。函数调用时传入的参数称为实际参数(实参)。在默认情况下,函数调用时传入的实参个数、顺序等必须和函数定义时形参的个数、顺序一致。
函数调用时的执行顺序如下:
(1)执行函数调用前的语句
(2)执行函数调用,运行被调用函数内的语句,并返回相应结果
(3)执行函数调用后的语句

PS:这里有个不是很恰当的描述,函数的形参定义,可以是预设数值的,但是预设数值需要在不预设数值的后面,一一对应的是没有预设值的形参,然后预设值的形参可选,可以顺序传入,也可以表达式传入。
python - 官方简易文档篇(1)常用、函数 详见函数部分

例:定义一个求梯形面积函数并调用

def echelonArea(top,bottom,height):area=1/2*(top+bottom)*heightreturn area
if __name__=="__main__":t=3.6;b=6.2;h=4.5area1=echelonArea(t,b,h)print("area1 =",area1)area2=echelonArea                # 这里是将函数的功能赋值给area2,area2就是这个函数的副本print("area2 =",area2(t,b,h))type(area2)                        # function

if __name__=="__main__":
name 是当前模块名,当模块被直接运行时模块名为 main 。这句话的意思就是,当模块被直接运行时,以下代码块将被运行,当模块是被导入时,代码块不被运行。

函数参数

参数传递

Python中的对象可分为不可变对象(数字、字符串、元组等)和可变对象(列表、字典、集合等)。
对应的,Python函数的参数传递有以下两种情况。
(1)实参为不可变对象。
当实参为不可变对象时,函数调用是将实参的值复制一份给形参。在函数调用中修改形参时,不会影响函数外面的实参。
(2)实参为可变对象。
当实参为可变对象时,函数调用是将实参的引用复制给形参。在函数调用中修改形参时,函数外面的实参也会随之变化。

a=0;b=1;l=[0,1]
def f(a,b,l):a,b=b,al[0],l[1]=l[1],l[0]
f(a,b,l)
print(a,b,l)
# 0 1 [1,0]

参数类型

调用函数时可食用的正是参数类型包括:必须参数、关键字参数、默认参数和不定长参数。
1、必需参数
必需参数要求函数调用时传入的实参个数、顺序和函数定义时形参的个数、顺序完全一致。

def f(i,s,l):"""i必须是int型,s必须是str型,l必须是list型"""if type(i)==int:print("int")else:print("ERROR")if type(s)==str:print("str")else:print("ERROR")if type(l)==list:print("list")else:print("ERROR")
f(10,"10",[10])
f(10,10,10)
help(f)
a=f.__doc__
print(a)
type(f.__doc__) # str

书上说:如果调用函数时的实参个数、顺序和函数定义时的形参个数、顺序不一致,运行时可能会得不到预期的结果。
个数不一致,直接报错。
以上我是用模拟的方法去规定了传递的参数的类型,而现实环境下,就是有传递过来后,会需要特定类型的运算,例如我就是要int,结果赋值或者未转化的原因,没有得到正确的运算甚至报错。

注释的调用方法 print(f.doc)

这里help(f)是和print一样打印出来的,但是f.__doc__却不是,他在jupyter中是与help(f)不在一起输出的,是另外一个单独的输出
在ipython中也是如此
在.py下运行,是不显示f.__doc__的
尽在python中正常显示

正常的直接运行.py的效果和IDLE下的python shell是一致的,都无法输出f.__doc__

我明白了,我突然就明白了,f.__doc__很单纯,他就是那串字符串,于是,python、ipython和jupyter都有显示变量、数值的效果,所以他们显示出来就是因为如此,而在显示运行环境下,f.__doc__就是一个单纯的变量。没有print自然就没有效果了!

关键字参数

关键字参数在函数调用时使用形参作为关键字来确定传入的参数值,允许函数调用时实参的顺序与函数定义是形参的顺序不一致。
这里有些无语,我觉得这个功能是为了让调用者知道自己是将什么数值赋值给什么变量。通常用于二次调用函数的请款下,这类函数属于之前写过甚至专门写出的,会有很多的参数,有个别必填的和多个选填的,因为不是这次写出的,所以哪些变量在那个位置上都不确定,所以使用关键字传递,而且某些不是必传的是有默认参数的,等。调入函数、查看函数文档、选择传递参数或关键字参数。如果一个公司里,关键字定义很严格的话,就可以直接用关键字传递参数,而不需要去看参数的先后顺序。

def rectangleArea(length,width):return length*widthprint(rectangleArea(length=10,width=2))

默认参数

调用函数时,如果没有传递实参,则会使用函数定义时赋予参数的默认值。

def CircleArea(radius,pi=3.141592653589793):return 2*radius*piprint(CircleArea(1))
print(CircleArea(1,3.141592654))

谁才是圆周率?π 和 τ 之间的战争

不定长参数

在实际应用中,有可能需要一个函数能处理比当初声明时更多的参数,这种参数称为不定长参数。不定长参数有如下两种形式:
(1)*args:将接受的多个参数放在一个元组中。
(2)**args:将显式赋值的多个参数放入字典中。

def mean(*args):
#   return sum((*args,))s=j=0for i in (*args,):s=s+ij+=1return s/j
print(sums(1,2,3)) # 6def rank(**args):"""变量名就是ID,数值是分数"""l=[*args]l.sort(key=lambda x:{**args}[x],reverse=True) # 这里尝试用lambda去写,之前没有复习lambda来着,但是用法从来不是复习出来的,是用出来了,这里我开始就没有加reverse,但是因为是从小到大的顺序,所以排出来的并非正确名次,所以我让key的结果*-1,刚才才想起来还有reverse这个功能for i in range(len(l)):print("第%d名:%s"%(i+1,l[i]))# print(a) ERROR
rank(a=1,b=2,c=3)

以上是错误演示,详见后面的最下边

平均数、中位数和众数及它们之间的关系
偶尔看到一位大佬的文章,线性回归,最小二乘法

*args的本质就是那些被打包的内容
例如1,2,3被打包成*args,那么*args就是1,2,3,没有引号,没有括号,传入到函数里,就是一个不是元组的元组,真要把他当作元组使用,需要(*args,)
同理,上面**args就是a=1,b=2,c=3,但是他传入函数时,也只是个不是字典的字典,真要当作字典调用,只需要{arges}
那么他们传入函数的作用不是当作赋值,而是当作数据来处理。无法直接调用
args,真的让函数内出现其传递的几个赋值表达式。

python基础之不定长参数

这里面有个例子,我改一下,或者说*与**的反向用法:

t=(1,2,3)
d={"a":4,"b":5,"c":6}
def f(x1,x2,x3,a,b,c):print(x1,x2,x3,a,b,c,sep="\n")
f(*t,**d)
#
1
2
3
4
5
6

*t是打包后的元组,这里有点迷,也就是说?一串数传递给*t,靠!我明白了~ 我一直搞错了!

def f(*t,**d):print(t)print(d)
f(1,2,a=3,b=4)
#
(1, 2)
{'a': 3, 'b': 4}

我思考过这个事,但是没有深入思考过,更加没有看清他的本质。
以*t为例:
t:(1,2)
*t:1,2
他们是可以互换的。

*t的出现条件:
在主体或是函数内:之前要有t,t是iterable。
在函数声明的括号内:他是全新的变量,他接受的数值来自于主体或上级函数的传递数值的打包。

*t的表示内容:传递给他或是t内容的元素,用逗号隔开

t=(1,2)
print(*t)
print(1,2)

*t类似于语法糖、方法等,他是能表示t的内容,但是一闪即逝,而且还真的不能单独存在,会报语法错误。只能存在于数值调用和传递时

t是*t打包,*t是t的解包

同理:
d是**d的打包,**d是d的解包,但因为解包结果是key=value,所以key需要符合变量名的命名规则。

参数传递的序列解包

参数传递的序列解包针对的是实参,有*和**两种形式。实参前加了*或**后会将列表、元组、字典等迭代对象中的元素分别传递给形参中的多个变量。

def f(x,y,z):print(x*5,y*2,z*3)
f(*[1,2,3])
f(**{"x":4,"y":5,"z":6})
f(*{"x":4,"y":5,"z":6})
#
5 4 9
20 10 18
xxxxx yy zzz

若是解包实参,要对应必须赋值的形参名称、个数。

特殊函数

匿名函数

匿名函数是指没有函数名的简单函数,只可以包含一个表达式,不允许包含其他复杂的语句,表达式的结果就是函数的返回值。在Python中,使用关键字lambda创建匿名函数。
创建匿名函数的一般格式为:
lambda [arg1[,arg2,…,argn]]:expression
其中,arg1,arg2,···,argn:形参,可以没有,也可以有一个或多个
expression:表达式
默认情况下,在调用匿名函数时,传入的实参个数、顺序童谣要和匿名函数在定义是的形参个数、顺序一致

匿名函数的创建和使用

sum=lambda x,y:x+y
sum(3,4) # 7
func=lambda a,b=3,c=2:b**2-4*a*c
func(1) # 1
func(1,4) # 8
list1=[1,2,3]
list2=[4,5,6]
sum(list1,list2) # [1,2,3,4,5,6]
list(map(lambda x,y:x+y,list1,list2)) # [5,7,9]
lambda x,y:x+y,list1,list2
# type:function
(<function __main__.<lambda>(x, y)>, [1, 2, 3], [4, 5, 6])map(lambda x,y:x+y,list1,list2)
# type:map
<map at 0x2b5d9b8c7f0>

可以看到map、lambda都是标识、语法糖、方法,没人去调用它,他就不会导出运算结果

递归函数

如果一个函数在函数体中直接或间接调用自身,那么这个函数就成为递归函数。也就是说,递归函数在执行过程中可能会返回以再次调用该函数。
Python允许使用递归函数。如果函数a中调用函数a自身,则称为直接递归。
如果在函数a中调用函数b,在函数b中有调用函数a,则称为间接递归。程序设计中比较常见的是直接递归。
下面,通过1+2+···+n的和来分析递归函数的原理和使用发放。
如果用fac(n)表示1+2+···+n的和,则:
fac(n)=1+2+···+n
fac(n-1)=1+2+···+n
···
fac(2)=1+2
fac(1)=1
因此,fac(n)可以用如下公式表示:
fac(n)={n+fac(n−1)n>11n=1fac(n)=\left\{\begin{matrix}n+fac(n-1) & n>1\\1 & n=1 \end{matrix}\right. fac(n)={n+fac(n−1)1​n>1n=1​
根据fac(n)的公式,可以定义如下的求和函数fac(n):

def fac(n):if n==1:return 1else:return fac(n-1)+n

如果调用求和函数fac(n),并且传入的参数n=3,那么其计算过程如下:
(1)当n=3时,fac(3)=fac(2)+3,用fac(2)去调用函数fac()
(2)当n+2时,fac(2)=fac(1)+2,用fac(1)去调用函数fac()
(3)当n=1时,函数fac(1)以结果1返回
(4)返回到发出调用fac(1)处,继续计算得到fac(2)=fac(1)+2=3的结果返回
(5)返回到发出调用fac(2)处,继续计算得到fac(3)=fac(2)+3=3+3=6的结果返回
这样,就得到了fac(3)的计算结果
从上面的分析可以看出,在使用递归时,需要注意以下两点:
(1)递归函数就是在函数里调用自身
(2)递归函数必须有一个明确的递归结束条件,称为递归出口。否则,会造成程序的死循环

使用递归函数求斐波那契数列前20项之和

#定义求斐波那契数列第n项的函数
def fib(n):if n==1 or n==2:return 1else:return fib(n-1)+fib(n-2)
#求斐波那契数列前20项目之和
sum=0
for i in range(1,21):sum=sum+fib(i)
#7.53 ms
print("前20项之和为:",sum)

与普通函数相比,递归函数具有以下非递归函数难以比拟的优点:
(1)递归函数是代码看起来更加整洁、优雅
(2)可以用递归将复杂任务分解成更简单的子问题
(3)使用递归比使用一些嵌套迭代更容易
但是,过多的递归也会导致递归程序存在以下不足:
(1)递归的逻辑很难调试、跟进
(2)递归调用的代价高昂(效率低),占用了大量的内存和时间
因此,当程序要求递归的层数太多时,就不太适合使用递归函数完成程序。

以上是书上的代码,觉得太过低效,一方面,他是每次都要调用fib()获得固定位置的数值,然后尾巴上一大串东西。

def fib(n,lf): # 这个lf是我之后为了加强%%timeit的准确性加上去,其实可以不用填上if n==1:lf.append(1);returnelif n==2:fib(1,lf);lf.append(1)else:fib(n-1,lf);lf.append(lf[-2]+lf[-1])lf=[] # listfib
fib(20,lf)
suml=0
for i in lf:suml=suml+i
# 10.2 µs
print(suml)

我本来想尝试着将sum加入到fib的函数里面,虽然目前的这个函数可以加,但是觉得函数就该是单一的功能,而且一旦需要的数据被保存,单一的去算sum也是很快的。关键是思路清晰。

def fib(n):if n==1:lf.append(1);returnelif n==2:fib(1);lf.append(1)else:fib(n-1);lf.append(lf[-2]+lf[-1])lf=[]
f(20)
#....

我是习惯没必要传递lf到函数里面的,因为函数会默认读取上层变量名,但是不能改写,这里的改写是指指针级别,而list.append等操作是修改list的内部而非id指针。这样很方便,也很清晰,我觉得非要传入的话,一方面代码长了,内容多了,乱。但这个各有各道,没有对错,只要python把这个机制作为一个硬性标准,几十年后也不会变得,就行了!

嵌套函数

嵌套函数指在一个函数(称为外函数)中定义了另外一个函数(称为内函数)。嵌套函数中的内函数只能在外函数中调用,不能在外函数外面直接调用。

def outfunc():print("hello, there is out!")def infunc():print("here is in!")infunc()outfunc()
infunc()
outfunc()

嘿嘿嘿
嵌套函数有什么意义呢!?个人觉得就是分层次,如果都写在主体下,例如其实仅需要实现两个主要的功能,但是可能需要大大小小的函数模块10个,有的可能这两个功能通用,那就可以写在主体下,有的仅仅是这个函数自己使用,就写在这个函数内,这样就能很好的实现了有效的管理,最好的时候,两个功能,仅需要两个外函数,归主体调用,其他函数是外函数的内函数,这样就省去了主体下没必要的函数管理,而且变量名也更让方便定义,函数空间分配等。如果有个IDE有函数分层管理的功能,也一目了然。
更形象地说,就是硬盘分区根目录下是一堆文件,还是一两个目录的区别!

嵌套函数的参数传递:

#定义外函数
def outerFunc(x):#定义内函数def innerFunc(y):return x*yreturn innerFunc # 这里的这个写法,是默认使用了主体传递给的(4),如果加上括号的话,说明这里要使用自己传递的数,至少需要赋值
print("方法一调用结果:",outerFunc(3)(4))
a=outerFunc(3)
print("方法二调用结果:",a(4))
#定义外函数
def outerFunc(x):#定义内函数def innerFunc(y):return x*yreturn innerFunc(6)
print("方法一调用结果:",outerFunc(3))
a=outerFunc(3)
print("方法二调用结果:",a)

这里外函数调用内函数时,自己去赋值了,这样就会与主体/上一级调用有赋值的冲突
结果就是,当外部连续两个赋值时,外函数内部调用内函数时,就不能再赋值了!

#定义外函数
def outerFunc(x):#定义内函数def ha(z):return 1def innerFunc():return hareturn ha
print("方法一调用结果:",outerFunc(3)(4))
a=outerFunc(3)
print("方法二调用结果:",a(4))

这里我尝试再定义一个内函数,查看参数调用的状态,结论就是!
outerFunc(3)(4)他的第二个数值是赋值给外函数return的函数
所以二级实参传递的条件就是外部函数return一个内部函数,即:
def f(f):
def a(a):
“”
return a
f(1)(2):a(2)

装饰器

装饰器(Decorator)是Python函数中一个比较特殊的功能,是用来包装函数的函数。装饰器可以使程序代码更简洁。
装饰器常用于下列情况:
(1)将多个函数中的重复代码拿出来整理成一个装饰器,对每个函数试用装饰器,从而实现代码的重用
(2)对多个函数的共同功能进行处理。例如,先单独写一个检查函数参数合法性的装饰器,然后在每个需要检查函数参数合法性的函数处调用即可

定义装饰器的一般格式为:

def decorator(func):pass
@decorator
def func():pass
func

这里func调用的时候不要加参数

def decorator(func):def f(x):func(x*2)return f   # 如果这里是return func又会怎样
@decorator
def func(x):print(x)
func("x")

在主函数中,return的func是真正调用func而非被装饰的func,所以这里就有了上述第二条的条件,其实这个是根据下面第二个例子转变来的,第二个例子,还是觉得哪里不对,看来return是没有任何意义的,还是那个例子所属。

于是情况(2)的代码应该是:

def decorator(func):def f(x):print("输入类型是:",type(x))r=func(x*2)print("输出类型是:",type(r))return rreturn f
@decorator
def func(x):print(x)
func("x")
def f():print("f")def f1():print("f1")return f1f()()

而在内嵌函数时,这里要加两个括号,无论有没有数值传入

#定义装饰器
def deco(func):print("I am in deco.")func()return "deco return value"
#使用装时器修饰函数
@deco
def func():print("I am in func")
print(func)

执行原理:
(1)装饰器deco()的参数为一个函数对象func
(2)函数前使用@deco装饰相当于将函数对象func作为参数调用装饰器deco(func)
(3)func的值为调用deco(func)的返回结果

def deco(f):print("I am in deco.")f()return "deco return value"
@deco
def func():print("I am in func")
print(func)
def deco(f):print("I am in deco.")f()return "deco return value"
def func():print("I am in func")
print(deco(func))
def deco():print("I am in deco.")func()return "deco return value"
def func():print("I am in func")
print(deco())

以上算是解释了第一条,装饰器是那个重复的部分,而我们在调用的函数前声明调用装饰器,就是那样子了

第二条,例如一个检查函数参数合法性的装饰器,这个改怎么写呢,目前只想到的是检查被装饰的返回结果的合法性,而非传入参数的合法性

或者说,装饰器才是运算方,被装饰的是传入方,这样传入方就可以算是检查数值合法性,但是调用时却很麻烦,因为调用时是被装饰的函数名。

全网搜索:Python @函数装饰器及用法(超级详细)

里面有一段:

def funA(fn):# 定义一个嵌套函数def say(*args,**kwargs):fn(*args,**kwargs)return say@funA
def funB(arc):print("C语言中文网:",arc)@funA
def other_funB(name,arc):print(name,arc)
funB("http://c.biancheng.net")
other_funB("Python教程:","http://c.biancheng.net/python")

这里的*和**别有风味
在传递时,第一次*是将两个值打包成args,这里获得的参数只是args这么一个元组,第二次则是将args再解包,其实无论怎么理解,*args一直是这两个值,而关键是,被装饰的函数都是要获取准确的变量的,所以*args要分别应对到相应变量的数值,这里数值个数不对应,就ERROR

这篇文章中还提到,有另外三种装饰器,不过是在函数内部的

使用装饰器修改网页文本格式

#定义装饰器
def deco(func):#定义内函数def modify_text(str):return "<strong>"+func(str)+"</strong>"return modify_text
#使用装饰器修饰函数
@deco
def textFunc(str):return str
print(textFunc("text"))

这个参数传递有点绕
看似调用了textFunc(str),其实是调用了deco(str)
看似str是传递给deco,其实是传递给了return的modify_text
传递给了modify_text后,被内部调用,其实只有一次,而且又回到了textFunc(str)

666

带参数的装饰器

在调用装饰器时,除默认的参数(装饰器括号中的函数对象参数)外,还可以定义带参数的装饰器,为装饰器的定义和调用提供更多的灵活性

使用带参数的装饰器检查函数参数合法性

#定义带参数的装饰器
def DECO(args):#定义内部装饰器def deco(func):#定义内函数def call_func(x,y):print("%d %s %d = "%(x,args,y),end="")return func(x,y)return call_funcreturn deco
#传递装饰器参数
@DECO("&")
def andFunc(x,y):   #按位“与”运算return x & y
@DECO("|")
def ofFunc(x,y):    #按位“或”运算return x | y
if __name__=="__main__"print(andFunc(5,6))print(orFunc(5,6))
#
5 & 6 = 4
5 | 6 = 7

这段代码并没有符合题目,至少让我没对题目有了自主码的空间
这段代码的意思,装饰器是三个嵌套函数,最外函数被赋值了,所以当传递参数时,被装饰函数不能传递到最外函数上,就继续往里找,然而最外层的结尾是return是第一个嵌套函数,而他的赋值结构符合装饰器,于是被装饰函数就被赋值到这里,然后就是又调用了最内层的内嵌函数,进行运算。
这代码仍是具有演示意义,但是没有实际意义

接下来尝试写一个真正的验证传递参数的合法性的了!

def checkLegality(types,operators):def getFunc(func):def getinput(strs):for o in operators:if str.count(strs,o)==1:i=str.index(strs,o)typeset=set()if strs[:i].isdecimal():x=int(strs[:i]);typeset.add("int")if strs[i+1:].isdecimal():y=int(strs[i+1:]);typeset.add("int")if typeset<=set(types):i=func(x,o,y)print("这里只是随意检验一下:%g的类型是%s"%(i,str(type(i))[8:-2]))return ireturn "输入不合符规则"return getinputreturn getFunc
@checkLegality(("int",),("+","-","*","/"))
def operator(x,o,y):if o=="+":return x+yelif o=="-":return x-yelif o=="*":return x*yelse:return x/y
strs=input("请输入二元运算,例如1+1,数据类型支持int,运算支持+-*/,回车结束:")
print(operator(strs))

因为这里也算是一种假设状态,只做到二元运算符中的加减乘除,只规定了输入的数字是int型,虽然还是有一些问题,例如如果除的话,被除数是0,它属于内部报错,也就是程序卡死。例如我需要输入一个float,这些都可以照着目前的写法抄写一遍,因为很麻烦的样子,我就不做了!突然觉得,如果这一串写下来,就是一个计算器了吧!再加上优先级什么的。

如果一个函数前有多个装饰器修饰,则称为多重装饰器。多重装饰器的执行顺序是:先执行最后面的装饰器,再执行前面的装饰器。

变量作用域

1、变量类型

Python中,不同位置定义的变量决定了这个变量的访问权限和作用范围。Python中的变量可分为如下4种:
(1)局部变量和局部作用域L(Local):包含在def关键字定义的语句块中,即再函数中定义的变量。局部变量的作用域是从函数内定义他的位置到函数结束。当函数被调用时创建一个新的局部变量,函数调用完成后,局部变量消失。
(2)全局变量和全局作用域G(Global):在模块的函数外定义的变量。在模块文件顶层声明的变量具有全局作用域。从外部看,模块的全局变量就是一个模块对象的属性。全局作用域的作用范围仅限于单个模块文件内。
(3)闭包变量和闭包作用域E(Enclosing):定义在嵌套函数的外函数内,内函数外的变量。闭包变量作用域为嵌套函数内定义它的位置开始的整个函数内。
(4)内建变量和内建作用域B(Built-in):系统内固定模块里定义的变量,一般为预定义在内建模块内的变量。

int(b_x) #内建变量,位于内建函数内
g_x=5.1    #全局变量,位于所有函数外
def outer():e_x=4.8    #闭包变量,位于外函数内、内函数外def inner():l_x=6.3    #局部变量,位于内函数内

在Python中,当程序中要使用一个语句中的变量时,就会以L→E→G→B的规则在程序中查找这个变量的定义,即在局部范围中找不到,便会去局部范围外的局部范围(例如闭包范围)中查找,若找不到就回去全局范围中查找,再去内建范围中查找。
在这4种变量中,程序中使用最多的是局部变量和全局变量,是用最少的是内建变量。因此本节重点讲解局部变量、全局变量及闭包变量的使用方法。

from math import *
pi #内建变量:在math模块中定义的常量
pi=3.14 #全局变量:无论是否有上述导入
def f():pi=3.1415 #局部变量,闭包变量↓def in():pi=3.1425926 #局部变量

(1)pi是math中预设的一个值,从数学概念上来说,ta属于常量,math.pi:3.141592653589793
为了和其他变量同名,这里使用了from math import *,这样没有其他声明的情况下,pi就是指math.pi
在编程的概念上,严格地说ta属于预设变量。

import math
math.pi=6
math.pi
#
6

2、全局变量和局部变量

局部变量只能在其声明的函数内访问,而全局变量可以在整个模块内访问。

def f():a="local"print(a)
def f1():print(a)
a="global"
print(a)
f()
print(a)
f1()
#
global
local
global
global

其实吧:
不是局部变量只能在其声明的函数内访问,准确的说是,局部变量就没有打算在全局变量上使用。
因为函数就是不用时不会调用,用完后完全清除的存在,所以在函数外时,这个函数也是不存在的,自然这个变量也跟着函数消失在了。除非多线程应用,我还没有学到,之前可能会有另一种数据传输方式,和是否全局、局部没有关系。

3、关键字global和nonlocal

global

如果想修改变量作用域,则需要使用关键字global和nonlocal

def f():global aprint("全局变量%g,在函数内使用global时,就可以进行赋值等操作,a=a-1,a:%g"%(a,a-1))
a=100
f()

nonlocal

这个是向上层函数直至最外城函数寻找该变量并增加他的作用域于该函数范围

def outf():a=10def inf():nonlocal aprint("inf nonlocal a:%g"%a)a=a+1inf()print("outf a:%g"%a)
outf()


这两个关键字的意义,global在这本书的前边就用过,就是全局变量会在函数内被操作,优点是真正的把一个长串的代码拆成多个专门功能的函数,调用时才加载,而global就像是不用再传入和return数值了,而是函数内就像全局下一样操作,唯一的变化就是这段代码被函数化/模块化,到时候修改的时候方便,加载的时候理论上也方便。

但在多人多工程下,这个兼容性就要注意了。需要保持global的变量确实存在也确实需要这样的操作。

总的来说,在单人单工程下,他能节省内存应用,大概。但之前看一个视频,说游戏中,代码其实是几乎不占空间的,如果按照这个思路思考,关键还是方便管理的因素吧!但因为操作的是全局一个固定的变量,所以灵活性、兼容性就别提了,理论上是节省一点点的内存空间的。

而闭包的nonlocal就结合了global和闭包的特性,他只向上查找到自内向外的第一个变量直至最外层函数,这种情况下,函数是有可能与外界使用参数传递的方式,也可能使用global,这里要注意,要是内嵌函数要nonlocal,但是外层函数对于该变量使用global,就相当于这串函数内并没有自己声明的该变量,而使用关键字只是将外部的变量作用域扩展到这层函数里,并不是这层函数本身的变量,所以要是global a def nonlocal a,他会说没有找到变量a。

例题

许久没来,都要忘了这章的内容了,而之前刚好做到了内容的结束,之后几个全是例题。回看章节,发现其实很多东西在之前的使用中都接触过了,除了装饰器。它的存在看了一番后才想起来,其实这都只是用法,大家记住概念后,之后码代码,察觉这里应该会用到这个功能,再回来细看就是了。
这章大致的内容都是些进阶方法,即便不知道这些进阶的方法,只需要知道基础的方法,照样能写出来,不过这些进阶的方法省去一些繁杂的代码就是了,都是把常用的一些结构简洁过程,写到系统中!

字符加密解密

因为有利益的存在,所以有盗贼,因为有锁的存在,所以有钥匙,而安全和破解是一个很繁杂的问题,所以这里不在考虑很深,就照着书上要求码一下,毕竟…

要求:密钥key为3;输入明文;定义加密函数,对明文进行加密并返回;定义解密函数,对密文进行解密并返回。

def encrypt(strings):encrypts=""for i in strings:encrypts=encrypts+chr(ord(i)+key)return encrypts
def decrypt(encrypts):decrypts=""for i in encrypts:decrypts=decrypts+chr(ord(i)-key)return decrypts
key=3
strings=input("请输入一串字符串")
print("这串字符串的加密结果:"+(encrypts:=encrypt(strings)))
print("加密字符串的解密结果:"+decrypt(encrypts))

求最大公约数

求约数的优化思路

这里我想到一个问题,如何优化求约数的步骤,例如,如果一个数无法成为2的倍数,自然也就无法成为偶数的倍数,同理,无法成为3的倍数,就无法成为3的倍数的倍数,于是,待求约数的集合就是1~该数,每一个质数无法称为约数的话,其倍数也无法成为约数,此时就使用集合的处理方法,将他的倍数从待求约数中去除,那先写一个代码来获取随一些数的约数,看下结果,验证下想法。

import random
while 1:inputs=input("回车:随机数;数字回车:输入数;eixt回车:退出")if inputs=="exit":breakelif inputs.isalnum():r=int(inputs)else:r=random.randint(1,100)约数=[]质数=[]print(str(r)+"的开方是:"+str(r**0.5))for i in range(1,r+1):if r%i==0:约数.append(i)print(str(r)+"的所有约数:"+str(约数))for i in range(2,r+1):c=0for j in range(2,i):if i%j==0:breakelse:质数.append(i)print(str(r)+"以内的质数:"+str(质数))

以上得出结果:

优化1:约数是成对出现的,且约束中心位置<=目标数值的开方,只需要算开放后的数以内的范围即可,剩下的范围求他的配对即可。

优化2:之前想到的,如果一个数不是他的约数,那么这个数的所有倍数也不是他的约数,所以清除子集即可

疑问:优化2是否会出现反效果,只能试验后才知道,但刚才想了下,问题应该不大

抛弃:我本以为这里面可以利用质数更加优化,但是看来是没有必要的样子

求约数

from collections import deque
def 约数(n):if n<=3:return [1,n]m=int(n**0.5//1)l=deque(range(1,m))y=[]def 求约():nonlocal lll=l.copy()for i in ll:if n%i==0:y.append(i)y.append(int(1000/i))l.popleft()else:s=set(l)s-=set(range(i,n,i))l=deque(s)returnwhile 1:if l:求约()else:breakreturn set(y)
约数(1000)

121 µs ± 11.5 µs per loop (mean ± std. dev. of 7 runs, 10000 loops each)

%%timeit
y=[]
for i in range(1,1000+1):if 1000%i==0:y.append(i)
y

90.9 µs ± 1.06 µs per loop (mean ± std. dev. of 7 runs, 10000 loops each)

吐血,换个更大的数!

100000
18.5ms vs 10.7ms 看来是无望了!?统计下运算次数,看看是否在预期中
94 vs 100000,运算次数是降低了,那就是别的操作占用了大量的时间,关键大概在组合数据的读写操作。

def 约数(n):if n<=3:return [1,n]m=int(n**0.5) #这里之前有个取整除的运算,后来发现int默认就是截断操作y=[]for i in range(1,m+1):if n%i==0:y.append(i)y.append(int(n/i))return set(y)
约数(100000)

35.1 µs ± 1.18 µs per loop (mean ± std. dev. of 7 runs, 10000 loops each)
肯定是哪里搞错了吧!

import time跑了下,还真是这样,我的乖乖!

原来如此,int(100000**0.5),就直接把运算从100000跌倒了137。
而更进一步的优化思路,就像是软件架构,但是完成其功能的硬件架构,即python的数据组合效率跟不上,导致反优化,所以放弃了。但如果不使用取平方根,或者是更大的数,是否能够追回来呢!?

…你的设备内存不足,因为最初优化版使用了列表等结构存储待运算数据,来回颠倒的运算占用了大量的内存,其实一开始就觉得如何标记某批运算不用再运算,除了这个,想不到别的方法,例如规则法等,说到这里,想到分段存储数据如何,例如规则是一个,然后当预备运算数据累积到一定的数量,就先不增加,算完这波再用规则导出下一波数据。但,之前说过,这组合数据的效率不高啊!而且10%3和1000000000000%3的效率是一样的,而组合数据的运算效率要超过这个才行,现在尝试用set运算。

set(range(1,1000))-set(range(1,1000,2))
116 µs ± 915 ns per loop (mean ± std. dev. of 7 runs, 10000 loops each)
相当于每个数字11.6ns,%运算,每个16ns,而且这只是一个规则
set(range(1,1000))-set(range(1,1000,2))-set(range(2,1000,2))
165 µs ± 964 ns per loop (mean ± std. dev. of 7 runs, 10000 loops each)
当,两个规则的时候,这个运算速度已经和单纯的1000次%运算速度相当,无念~

所以简单的观测得到的此项优化是行不通的,结果还是需要深的数学功底,在类似于开方优化的思路下,进一步优化。

def 约数(n):if n<=3:return [1,int(n)]if (n1:=n**0.5)%1==0:y1=list(约数(n1))y2=y1[-2::-1]for i in y2:y1.append(int(n/i))return y1m=int(n**0.5)y=[]for i in range(1,m+1):if n%i==0:y.append(i)y.append(int(n/i))return set(y)
约数(65536)

9.8 µs ± 215 ns per loop (mean ± std. dev. of 7 runs, 100000 loops each)
vs 上一阶的优化
23.4 µs ± 828 ns per loop (mean ± std. dev. of 7 runs, 10000 loops each)

这个,有漏洞,当数值是784时,漏掉了8、16、49、98这几个数。
[1, 2, 4, 7, 14, 28, 56, 112, 196, 392, 784]
[1, 2, 4, 7, 8, 14, 16, 28, 49, 56, 98, 112, 196, 392, 784]
我发现这些数都是由28里面的两个质数2、7累积出来的,而28这个中心位置是没有变的,所以只需要将28的开方到28这个范围内,符合784、2、7的数找出来。因为28无法承载出他的开方到他的,又符合784的数量级,于是无法呈现出来,这也算是高阶优化很常见的问题吧!越是优化,涉及的层次越多,出问题的地方就越多。
[1, 2, 3, 6, 12, 18, 36, 72, 108, 216, 432, 648, 1296]
[1, 2, 3, 4, 6, 8, 9, 12, 16, 18, 24, 27, 36, 48, 54, 72, 81, 108, 144, 162, 216, 324, 432, 648, 1296]

想起了之前看到的那个深度优化的题目,里面提到了质数,我刚才想了下,加入一个数可以被开方成一个整数,那么这个整数到这个数之间,是否存在他的约数是质数?那么这个质数必定是与另一个数相乘得到这个数,除非另一个数也是这个质数,否则无法得到这个数,那么这个目标书的开方就是这个质数,那么他的约数形态只能是1、质数、他。

关于是否存在一个整数,乘以大于他的开方、小于他的质数,得到他。

道理我懂,但是我不知道如何去说

假设这个数是x的平方,x>0,如果有个质数z,x<z<x的平方,符合zy=x的平方,那么就需要zy==xx,但是因为z是质数,所以z无法分解成z和1以外的约数,所以不存在整数yz=x,所以x与x*x之间不存在质数。

求两个数的最大公约数

from random import randint
def 最大公约数(a,b):if a>b:a,b=b,aif b/a%1==0:return az={}def 求质数因子组(a):n=awhile 1:while 1:if (t:=n**0.5)%1==0:n=telse:breakfor i in range(2,int(t)):if n/i%1==0:z[i]=1n=n/iwhile 1:if (t:=n/i)%1==0:n=tz[i]+=1else:breakif n==1:returnbreakelse:if n/(t:=(int(t)+1))%1==0:z[t]=1z[t+1]=1returnelse:z[int(n)]=1return求质数因子组(a)zb={}n=bfor i in z:if n/i%1==0:zb[i]=1n=n/iwhile 1:if (t:=n/i)%1==0:n=tzb[i]+=1else:breakif n==1:breakif zb:zc={}for i in zb:if zb[i]>z[i]:zc[i]=z[i]else:zc[i]=zb[i]x=1for i in zc:x=x*(i**zc[i])return xelse:return 1
a=randint(1,10000)
b=randint(1,10000)
最大公约数(a,b)

原文

使用装饰器检查函数参数合法性

def 约数输入(func):def 检查输入(n):try:t=int(n)except:print("输入无法转换为整数")returnif t>=1 and t%1==0:return func(t)else:print("%d 不是大于1的整数"%n)return 检查输入
@约数输入
def 约数算法(n):print("现在正在计算%d的约数"%n)n=input("求约数,请输入大于1的整数:")
约数算法(n)

如果把装饰器中的return func(t)截取的话,还可以顺便检查返回值的合法性

模拟轮盘游戏

#模拟轮盘抽奖游戏 从1000中抽,每次188元,获奖如代码,假设一天游戏次数1W次,庄家是赚了还是赔了
from random import randintdef game():n=randint(1,1000)if 1<=n<=3:print("您轮得了%d,恭喜您获得特等奖10000元"%n)return 10000elif n<=10:print("您轮得了%d,恭喜您获得一等奖5000元"%n)return 5000elif n<=30:print("您轮得了%d,恭喜您获得二等奖1000元"%n)return 1000elif n<=100:print("您轮得了%d,恭喜您获得三等奖300元"%n)return 300else:print("您轮得了%d,遗憾,继续努力"%n)return 0
z=0
for i in range(10000):z=z+188-game()
print("今日收益%d"%z)

本来想50一次,但是庄家配的血本无归。
最终定为188一次。
但如果数据被人改了的话,那就赔死了~

python - 啃书 第五章 函数相关推荐

  1. python - 啃书 第七章 模块、包和库 (二)

    常用第三方库 Pandas Pandas 中文网 Pandas是基于NumPy库的一种解决数据分析任务的工具库 Pandas库纳入了大量模块和一些标准的数据模型,提供了高效的操作大型数据集所需的工具 ...

  2. python - 啃书 第七章 模块、包和库 (一)

    概述 在Python中,一个模块(Module)是一个以.py结尾的Python文件,包含了Python对象和语句. 在python中,一切皆对象.数字.字符串.元组.列表.字典.函数.方法.类.模块 ...

  3. Python程序开发——第五章 函数

    目录 一.函数的定义 (一)def关键字定义函数 (二)形参和实参 (三)全局变量和局部变量 二.函数的调用 三.函数的参数传递 (一)必需参数 (二)关键字参数 (三)默认参数 (四)不定长参数 1 ...

  4. python - 啃书 第十一章 数据库访问

    概述 与使用文件存储数据相比,使用数据库存储和管理数据更容易实现数据共享.降低数据冗余.保持数据独立性,以及增强数据的一致性和可维护性. 现在数据库技术已经广泛应用于电子邮件.金融业.网站.办公自动化 ...

  5. 【python第五章——函数】

    python第五章--函数 第五章--函数 5.1函数概述 5.2 函数进阶 下面部分之后再写 5.3函数习题(一些小例子) 第五章--函数 5.1函数概述 快速入门: #定义函数 def func( ...

  6. 《Go语言圣经》学习笔记 第五章函数

    <Go语言圣经>学习笔记 第五章 函数 目录 函数声明 递归 多返回值 匿名函数 可变参数 Deferred函数 Panic异常 Recover捕获异常 注:学习<Go语言圣经> ...

  7. DirectX12龙书--第五章笔记

    需要龙书电子版的可联系我,部分图片太大,csdn导入不了,可访问我的github图床github或者在书里面查看. DirectX12龙书第五章笔记 第五章 5.1 3D视觉即错觉? 5.2 模型的表 ...

  8. 第五章 函数和代码复用

    第五章 函数和代码复用 5.1 函数的基本使用 5.1.1 函数的定义 定义:函数是一段具有特定功能的.可重用的语句组,用函数名来表示并通过函数名进行功能调用. 使用函数的目的:降低编程难度和代码重用 ...

  9. stata:stata软件教程(人大十八讲)(5) 第五章 函数与运算符

    第五章 函数与运算符 5.1 运算符 exp 5.1.1 代数运算 5.1.2 字符运算 5.1.3 关系运算 5.1.4 逻辑运算 5.2 函数概览 function 5.3 数学函数 5.3.1 ...

最新文章

  1. GPU高效通信算法-Ring Allreduce
  2. Go协程池设计思路(Task-Job-Worker)
  3. AI基础:Numpy简易入门
  4. WinCE6.0的极速启动
  5. 手写jwt框架SSO
  6. java读取resouces目录下文件
  7. [Abp vNext 源码分析] - 4. 工作单元
  8. 使用命令行的方式,将ini配置文件中的配置信息传递给程序
  9. Jquery取得iframe下内容的方法
  10. python调用dll时路径问题_使用ctypes(python)在带括号的路径中加载dll时出错
  11. KAIOS软件下载-自己做的
  12. CKEditor/CKFinder升级心得
  13. 如何用一个makefile编译多个目标
  14. ERROR: Rosdep experienced an error: Unable to handle package.xml format version ‘3‘
  15. 微信页面触发返回按钮回到聊天界面
  16. R语言量化:使用WindR下载Wind数据
  17. 还在抱怨pandas运行速度慢?这几个方法会颠覆你的看法
  18. Linux 压缩、解压工具
  19. 不同地域的内容偏好性分析
  20. WIKIOI 3163 抄书问题2 题解与分析

热门文章

  1. K3终极折腾记<三> --通过ipv6域名远程访问openwrt、https证书安装配置
  2. 三招!解决消息队列的数据积压问题
  3. 排序算法竟能如此美丽?排序算法可视化大全!
  4. 人生最大的资本是品行
  5. JavaScript高级进阶之设计模式 抽象工厂模式
  6. Java进制转换原理详解
  7. sklearn中的数据预处理方法学习汇总
  8. 基于Spring Boot的在线外卖系统的设计与实现 .rar(毕业论文+程序源码)
  9. (附源码)计算机毕业设计ssm个性化大学生图书推荐系统
  10. IPv4 客户与IPv6服务器_UNP