菜鸟级的Python函数解读,从而以一个电脑初级选手的视角来看待Python编程中的函数。或许作为一个菜鸟,很多地方的理解都是无厘头的,但目的是能够有助于我对它理解和有效掌握,我觉得还是有价值的。
为什么要有函数机制
我们其实可以思考,为什么我们文件多了,要建立文件夹?当然是为了更有序的对我们的文件进行管理。
同样,编程中的函数机制(甚至是以后更大的类、模块、包等概念)都是为了让数据更加有序,有利于程序数据的管理而产生的。
对比一下两种不同的方式:
第一种方式:
a = 4
b =3
c = a2+b2
d = 6
e = 8
f = d2+e2
print(f)
第二种
def gougu(c,d):
c = c2+d2
a = c/2
return c
print(gougu(3,4))
print(gougu(6,8))
所以,我们可以将函数就看作一个文件夹,把数据有效封装起来,以便对外没有干扰,保持自己功能的独立性和完整性,从而通过函数的机制让数据和数据的处理逻辑能够得到重复利用——这也是函数最大的价值所在——重复利用!
函数中的元素解构说明
def——通过这个标识符告诉Python,接下来的代码要“给我”放入一个“文件夹”里面保存起来,从而让下面的代码和外界独立。
gougu——是函数的名称,其实就是一堆组合在一起的代码段的地址而已。只不过我们记不住地址(一堆数据),而我们能记住一些有意义的英文字符串,因此,我们用一个名称来代替了一堆代码段的地址而已。
()——可以看做是元组,用来存放函数的参数用。如果我们将函数(在英文里面就是function,工厂的意思)看成一个工厂,那这个用来存放参数的()元组,其实就相当于工厂加工机器的原料进口。
:——在Python中表明接下来的代码段会归为冒号前的那个函数名称所代表的地址下。
接下来就是在冒号下编写代码了!
由此可见函数也可以看做是一个数据结构,而且是有一定结构和运行机制的数据结构(机制为——先定义、再调用)。
或者说,通过函数,我们能够将运算操作符(加减乘除)和基础数据类型(整数、字符串、元组、列表、集合、字典等)以各种方式组合起来,实现不同的功能。
这一点就像我们用钢铁加工成不同的零件,在将这些零件采用不同的组合,构成具有不同功能的机器一样。
函数的运行机制
其实,如果将函数看做一个去包括基础数据和操作符的文件夹,就不难理解函数的运行机制了。
就如同我们要使用(重复)一个文件或文件夹,实现你最起码要建立一个文件夹吧,然后才能通过复制、粘贴去重复使用一份文档或包含文档的文件夹,是不是。
函数也是这样,你想能够重复使用一个函数,最起码你首先要把这个函数定义出来。
相比使用,定义函数会更加有技术含量。因为你是在创造!
定义一个函数,包括给出关键词def (define),给出能够让你记住代码段放哪里了的函数名称(就像你要给一个文件夹命名一样,不然随着文件夹变多,你怎么找到你要的文件夹呢),接着需要思考给这个函数(工厂)放入哪些参数(原料)让它去加工,最后就是函数主体的代码块,即函数的功能实现代码(包括基础数据结构和相关的操作运算逻辑),小学四年级语文一对一辅导就好比工厂的机器有自己的加工流程(函数其实最像工程里面的机器,而类就像是一个车间——包括很多台机器和原料,模块则就相当于一个生产链条中的一个加工环节的工厂、包则就是整条生产链)。最后就是原料生产完成后,出来什么产品,而这个产品就是我们函数中的return来表达。
接下来就是函数的调用了,对Python来说,调用函数我觉得分为显性调用和隐性调用。看下面的代码
def gougu(c,d):
c = c2+d2
a = c/2
return c
print(gougu(3,4))#隐性调用,直接将函数名称写在了print中,没有任何显式变量指向这个gougu(3,4)
print(gougu(6,8))
i=gougu(2,4)#显式调用,用i这个内存变量指向了这个gougu(2,4)的函数地址
print(i)
函数的调用,其实可以理解为工厂中的机器启动,你只有给它通电了,机器的整个部件才会联动的运转起来。
类似的,i=gougu(2,4)就是用=去启动这台函数机器,也就是通过变量将函数的地址放入运行内存中,让计算机的CPU可以访问到这个函数,而一旦“通电”,通过函数的触发机制(trigger),函数就是自动运算起来。
而函数的这种触发机制,正是通过def这个标识符关键词告诉Python,接下来你遇到的地址gougu所代表的存储地址是需要自动触发运算的——即,它是函数!是一堆逻辑(运算)和一堆数据组合的数学模型!
函数的参数传递
在很久之前学习c++时,最高不明白的就是这个函数里的参数,虽然学过数学,知道参数在数学逻辑算式的作用是什么,也知道编程中的参数其实类似于数学逻辑算式中的参数作用。但不理解的是参数的传递规则和作用域。
而现在,用文件夹的视角,让我很容易理解参数的价值左右和相应的作用域为什么这么设计了。
(1)先展示一下参数的传递正常机制情况
i=3
j=4
def gougu(a,b):
e = a2+b3
return e
print(gougu(i,j))
上述代码中,1、2两行的参数首先传递给了print(gougu(i,j))中的i和j,由于1、2和7行属于函数gougu外部的变量,两者传递需要同名,否则识别不到!
c=3
d=4
def gougu(c,d):
c = c2+d2
a = c/2
return c
print(gougu(a,b))#将c、d改成a、b后会报找不到变量的错误
Traceback (most recent call last):
File “C:/Users/think/Desktop/2.py”, line 7, in
print(gougu(a,b))
NameError: name ‘a’ is not defined
但我们却可以让print(gougu(i,j))和def gougu(c,d)两个gougu里面的参数名称不同。因为一个是函数的定义,而一个是函数的引用。等于我们用print(gougu(i,j))中的i和j与def gougu(c,d)中的c和d的位置地址对应,将i和j获取到的值或者地址赋给def gougu(c,d)中的c和d,进行“通电”,进而启动函数。
正常的机制说完了,为了看清楚传递机制的内涵原理,我们进行下面的变化启发
(2)函数可以不带参数,直接引用外面的数据,但函数内却无法改动外部不可变数据类型(整数、字符串,因为是相对地址传递——也称为值传递)的数据,不过可以改变外部可变类型的数据(因为是绝对地址传递——又称为地址传递)。为什么这样传递?只能“怪罪”于Python的创造人了!
c=3
d=4
def gougu(c,d):
c = c2+d2
a = c/2
return c
print(gougu(c,d))
先解释一下上面的这段程序。首先,我们看到的print(gougu(c,d))中的c和d,与def gougu(c,d)中的c和d全然不同,现在第7行的这个print()从缩进层级就能看出来,属于函数def的外部,那print(gougu(c,d))中的c和d指的是去引用外部这个模块中的参数——即第1行和第2行的两个c=3,d=4的数值。
而def gougu(c,d)中的c和d仅仅是个内存空间,而c和d是这个空间的标识符。或者说这个空间属于def定义后的gougu地址下的内存,与外部是隔了一个gougu所代表的地址,外界是无法直接访问到的。而且这种无法访问,还不像class类那样,可以通过成员寻址运算符“.”点号来访问。
i=3
j=4
def gougu(a,b):
e = a2+b3
d=5
return e
print(gougu(i,j))
print(gougu.d)
Traceback (most recent call last):
File “C:/Users/think/Desktop/2.py”, line 8, in
print(gougu.d)
AttributeError: ‘function’ object has no attribute ‘d’
前面也说过这种传递机制了,这里只是在啰嗦一下,但其实我们可以让传递失效,让函数直接从外部去拿取参数。
i=3
j=4
def gougu(a=0,b=0):#当a=0,b=0 ,也不会影响e的计算结果还是73
e = i2+j3
return e
print(gougu(i,j))
从上面就能看出,gougu在def的时候,e = i2+j3中的i和j直接是从外部拿取的,即使print(gougu(i,j))中的i和j做了将自己传递给def gougu(a=0,b=0)的动作,但def gougu(a=0,b=0)并没有“领情”。因为,在整个功能主体代码块中,a和b这两个参数都没有得到利用,仅仅作为gougu这个函数的某两个参数放在那里了而已。
其次,我想说的是,函数的参数放在括号()里面和函数主体中写的变量,没有什么区别,都是被函数当做自己地址下面的变量了而已。
i=3
j=4
def gougu(a,b):
e = a2+b3
return e
print(gougu(i,j))
上面的代码和下面的得到的结果相同
i=3
j=4
def gougu():#没有参数
e = i2+j3 #直接从外面引用
return e
print(gougu())
结果还是73
同样,我也可以把原来在def gougu(a,b)中的a和b放入函数的主体里面,同样也能传递过去!
i=3
j=4
def gougu():
a=i #道理很简单,既然前面都已经说了,函数内部的变量可以直接去外部引用
b=j #那函数的内部的局部变量当然也可以引用外部的变量值
e = a2+b3
return e
print(gougu())
而下面这段代码向我们展示了参数的传递机制
i=3
j=4
def gougu(a,b):#发现a,b的两个参数根本没有被用过
e = i2+j3 #直接从外部调用i和j
return e
print(gougu(i,j))
既然写在括号里面的参数和放在函数内部的变量没什么两样,都是作为函数内部的变量而已,只不过写在()内部的参数位置,可以有效对应,不会乱掉,而如果你在函数的主体功能区去写传递性,虽然函数具有从外部引用的特征,但这样会大大降低灵活性。比如看下面的代码:
c=3 #把i变成了c
d=4 #把j变成了d
def gougu():
a =i#这个地方也必须同时改动,因为函数直接从外部引用了,要改为a=c
b =j
e = a2+b3
return e
print(gougu())
你会发现,必须要改动def gougu中的e = i2+j3的i和j也要对应修改,因为引用太过于直接了
i=3
j=4
def gougu(a,b):
e = a2+b3
return e
print(gougu(i,j))#通过参数来传递,def gougu(a,b)就不用再改动
即使遇到下面的情况体会会更深
i=3
j=4
def gougu(a,b):
e = a2+b3
return e
print(gougu(i,j))
当另一个人也编写了一个调用def gougu(a,b):有参数形式的传递,
即使有一万个人需要调用def gougu(a,b),也不用像前面那样,因为把引用写在了
函数功能主体直接引用了,导致每次调用都要修改函数的定义,那样就失去了函数的
重复调用的价值了!!!
m=6
n=8
print(gougu(m,n))
参数的可变性或影响性说明
按照很多资料的说明,函数的参数传递,分为值传递和址传递两种,也有说法是不可变类型数据的传递(整数型、字符串型、元组型)和可变变量的传递(list、tuple、dictionary)。对应的,不可变类型数据其实就是值传递,而可变类型数据传递就是址传递。
其实传递的都是地址,但我认为(不是正确的,只是帮助我自己理解)不可变类型数据传递的是相对地址,而可变类型的数据传递的是这些数据结构的绝对地址。
从前面数据类型的说明来看,int、string、tuple在Python中是创建了就不再变化地址了,比如i=2,改成i=3,不是将i赋值为3,而是在类型池中创建一个地址来放3这个值,再把这个地址传递给i这个变量。
这也就是我们说的,变量没有类型,数据才有类型!!!
再回来看待很多资料里面说明的函数参数传递,其实就是想强调当函数发生从外部引用数据后,对从外部引用的数据会有什么影响的问题。
看下面的代码
i=3
j=4
def gougu(a,b):
e = a2+b2
i = 5
print(i)
return e
print(gougu(i,j))
print(i)
结果:
5 #函数内
25
3 #函数外的i变量依然没有什么影响
对比list这些可变影响——就是想警告我们,一定要注意,函数处理后,对函数会对从外界引用的这些类型的数据有影响。
mylist=[1,2]
def yingxiang(mylist):
mylist.append([3,4])
print(“函数内部:”,mylist)
return
yingxiang(mylist)
print(mylist)
结果
函数内部: [1, 2, [3, 4]]
[1, 2, [3, 4]]
在对比一下:
mylist=[1,2]
def yingxiang(mylist):
mylist=[3,4]
print(“函数内部:”,mylist)
return
yingxiang(mylist)
print(“函数外部:”,mylist)
结果:
函数内部: [3, 4]
函数外部: [1, 2]
想一想为什么?因为我们在函数内部用了赋值的运算“=”,这样就会造成看似相同的mylist,但通过赋值运算,函数内部的那个mylist已经不再是外部的那个全局变量了。
对比一下下面的程序
mylist=[1,2]
def yingxiang(mylist):
mylist.append([3,4]) #没有了赋值运算,不会产生新的局部变量
print(“函数内部:”,mylist)
return
yingxiang(mylist)
print(“函数外部:”,mylist)
结果:
函数内部: [1, 2, [3, 4]]
函数外部: [1, 2, [3, 4]]
mylist=[1,2]
def yingxiang(a):#把参数中的mylist换成a,结果不受影响
a.append([3,4])#只要不在功能主体中赋值,增加了新的局部变量
print(“函数内部:”,a)
return
yingxiang(mylist)
print(“函数外部:”,mylist)
函数内部: [1, 2, [3, 4]]
函数外部: [1, 2, [3, 4]]
最后验证一下:
mylist=[1,2]
def yingxiang(a):
a=mylist.append([3,4])#赋值运算后,产生了一个局部变量a
print(“函数内部:”,a)
return
yingxiang(mylist)
print(“函数外部:”,mylist)
结果为:
函数内部: None
函数外部: [1, 2, [3, 4]] #你会发现影响了外部,但内部却是none
mylist=[1,2]
def yingxiang(a):
a=mylist.append([3,4])
print(id(a))
print(“函数内部:”,a)
return
yingxiang(mylist)
print(id(mylist))
print(“函数外部:”,mylist)
结果
140709513211104 #发现赋值运算产生的局部变量地址和mylist不同的
函数内部: None
1414285118344
函数外部: [1, 2, [3, 4]]
Python的函数理解外传
最新推荐文章于 2022-09-25 10:51:13 发布