Python

本文深入探讨Python中的函数,包括声明与定义、前向引用、函数属性、内部/内嵌函数、函数应用、变量作用域、全局与局部变量、lambda表达式和高阶函数等概念,结合实例详细阐述了Python函数的高级用法。

声明与定义比较

​ 在某些编程语言里,函数声明和函数定义区分开的,一个函数声明包括提供对函数,参数的名字(传统上还有参数的类型),但不必给出函数的任何代码,具体的代码通常属于函数定义的范畴。

​ 在声明和定义有区别的语言中,往往是因为函数的定义可能和其声明放在不同的文件中python将这两者视为一体,函数的子句由声明的标题以及随后的定义体组成的。

前向引用

和其他高级语言类似,Python也不允许在函数未声明之前,对其进行引用或者调用。

我们下面给出几个例子来看一下:

def foo():
    print("in foo()")
    bar()
def bar():
	print("in bar()")
foo()

猜测运行结果?

如果我们调用函数foo(),肯定会失败,因为函数bar()还没有声明:

>>>foo()
in foo()
Traceback(most recent call last):
  File "<stdin>",line 1,in <module>
  File "前后引用.py",line 3,in foo
  bar()
 NameError:name 'bar' is not defined
 >>>

现在定义函数bar(),咋函数foo()前给出bar()的声明:

def bar():
    print("in bar()")


def foo():
	print("in foo()")
	bar()

现在我们可以安全的调用foo(),而不会出现任何问题:

>>>foo()
in foo()
in bar()

事实上,我们甚至可以在函数bar()前定义函数foo():

def foo():
	print("in foo()")
	bar()

def bar():
	print("in bar()")

Amazing!依然可以很好的运行,不会有前向引用的问题:

>>> foo()
in foo()
in bar()
>>>

​ 这段代码是正确的因为即使(在 foo()中)对 bar()进行的调用出现在 bar()的定义之前,但 foo()本身不是在 bar()声明之前被调用的。换句话说,我们声明 foo(),然后再声明bar(),接着调用 foo(),但是到那时,bar()已经存在了,所以调用成功。

注意 ,foo()在没有错误的情况下成功输出了’in foo()’。名字错误是当访问没有初始化的标识符时才产生的异常 。

函数属性

​ 你可以获得每个 python 模块,类,和函数中任意的名字空间。你可以在模块 foo 和 bar 里都有名为 x 的一个变量,,但是在将这两个模块导入你的程序后,仍然可以使用这两个变量。所以,即使在两个模块中使用了相同的变量名字,这也是安全的,因为句点属性标识对于两个模块意味了不同的命名空间,比如说,在这段代码中没有名字冲突:

import foo, bar
print(foo.x + bar.x) 

​ 函数属性是 python 另外一个使用了句点属性标识并拥有名字空间的领域。

def foo():
    'foo()--properly created doc string'
    
def bar():
	pass
	
bar._doc_='Oops,forgot the doc str above'
bar.version=0.1

​ ​ 上面的 foo()中,我们以常规地方式创建了我们的文档字串,比如, 在函数声明后第一个没有赋值的字串。当声明 bar()时, 我们什么都没做, 仅用了句点属性标识来增加文档字串以及其他属性。我们可以接着任意地访问属性。下面是一个使用了交互解释器的例子。(你可能已经发现,用内建函
数 help()显示会比用__doc__属性更漂亮,但是你可以选择你喜欢的方式)

>>> help(foo)
Help on function foo in module __main__:

foo()
    foo() -- properly created doc string

>>> bar.version 
0.1
>>> foo.__doc__
'foo() -- properly created doc string'
>>> bar.__doc__
'Oops, forgot the doc str above'
>>>

注意我们是如何在函数声明外定义一个文档字串。然而我们仍然可以就像平常一样,在运行时刻访问它。然而你不能在函数的声明中访问属性。换句话说,在函数声明中没有’self‘这样的东西让你可以进行诸如_dict_[‘version’] = 0.1 的赋值。这是因为函数体还没有被创建,但之后你有了函数对象,就可以按我们在上面描述的那样方法来访问它的字典。另外一个自由的名字空间!

内部/内嵌函数

在函数体内创建另一个函数(对象)是完全合法的,这种函数叫做内部/内嵌函数。

最明显的创造内部函数的方法是在外部函数的定义函数(用def关键字),如在:

def foo():
	def bar():
		print("bar() called.")
	print("foo() called")
	bar()

foo()
bar()

我们将以上代码置入一个模块中,如inner.py,然后运行,我们会得到如下输出:

foo().called
bar()called.
Traceback(most recent call last):
   File"内嵌函数.py",line 8,in <module>
NameError: name 'bar' is not defined

内部函数一个有趣的方面在于整个函数体都在外部函数的作用域(即是你可以访问一个对象的区域;稍后会有更多关于作用域的介绍)之内。如果没有任何对 bar()的外部引用,那么除了在函数体内,任何地方都不能对其进行调用,这就是在上述代码执行到最后你看到异常的原因

另外一个函数体内创建函数对象的方式是使用lambda 语句。 稍后讲述。如果内部函数的定义包含了在外部函数里定义的对象的引用(这个对象甚至可以是在外部函数之外),内部函数会变成被称为闭包(closure)的特别之物。

函数应用:打印图形和数学计算

函数的嵌套调用;

程序设计的思路,复杂问题分解为简单问题。

  • 思考1:编程实现:

    • 写一个函数打印一条横线

    • 打印自定义行数的横线

参考代码

#打印一条横线
def line():
	print("-"*5)
#打印多条横线
def lines(n):
	i=0
	#因为line函数已经完成了打印横线的功能
	#只需要多次调用此函数即可
	while i<n:
		line()
		i+=1
	
	lines(3)
  • 思考2:编程实现
    • 写一个函数求三个数的和
    • 写一个函数求三个数的平均值

参考代码

# 求3个数的和
def sum3Number(a,b,c):
    return a+b+c # return 的后面可以是数值,也可是一个表达式

# 完成对3个数求平均值
def average3Number(a,b,c):

    # 因为sum3Number函数已经完成了3个数的就和,所以只需调用即可
    # 即把接收到的3个数,当做实参传递即可
    sumResult = sum3Number(a,b,c)
    aveResult = sumResult/3.0
    return aveResult

# 调用函数,完成对3个数求平均值
result = average3Number(11,2,55)
print("average is %d"%result)

变量作用域

标识符的作用域是定义为其声明在程序里的可应用范围,或者是我们所说的变量可见性,换句话说,就好像在问你自己,你可以在程序里的哪些部分去访问一个制定的标识符,变量可以是局部域或者全局域。

全局变量与局部变量

定义在函数内的变量有局部作用,在一个模块中最高级别的变量有全局作用域。

“声明适用的程序被称为了声明的作用域,在一个过程中,如果名字在过程的声明之内,它的出现即为过程的局部变量;否则的话,出现即为非局部的。”

局部变量的一个特征是除非被删除掉,否者它们的存活到脚本运行结束,且对于所有的函数,他们的值都是可以被访问的,然而局部变量,就像它们存放的钱,暂时地存在,仅仅只依赖于定义它们的函数现阶段是否处于活动。当一个函数调用时,其局部变量就进入声明它们的作用域,在那一刻,一个新的局部变量名为那个对象创建了,一旦函数完成,框架被释放,变量将会离开作用域。

局部变量

先看一个例子:

def foo():
	a=666
	print('foo(),修改前a:\t',a)
	a=888
	print('foo(),修改前a:\t',a)

def bar():
	a=6688
	print('bar(),a:\t',a)

运行结果:

>>> foo()
foo(),修改前a:   666
foo(),修改前a:   888
>>> bar()
bar(),a:         6688
>>>

可以看出:

  • 局部变量,就是在函数内部定义的变量
  • 不同的函数,可以定义相同的名字的局部变量,但是各用个的不会产生影响
  • 局部变量的作用,为了临时保存数据需要在函数中定义变量进行存储,这就是它的作用

全局变量

同样,先看一下例子:

a=6688
def foo():
	print('foo(),a:\t',a)
def bar():
	print('bar(),a:\t',a)

print(foo())
print(bar())

运行结果:

foo(),a:	 6688
None
bar(),a:	 6688
None

全局变量,是能够在所有的函数中进行的变量,那么局部变量可否进行修改编程全局变量呢?

globa语句

如果将全局变量的名字声明在一个函数体内的时候,全局变量的名字能被局部变量给覆盖掉。

a=6688
def foo():
	global a

	print('foo(),修改前a:\t',a)
	a=666
	print('foo(),修改后a:\t',a)

def bar():
	print('bar(),a:\t',a)

foo()
bar()

运行结果:

foo(),修改前a:	 6688
foo(),修改后a:	 666
bar(),a:	 666

通过以上例子,我么可以观察出:

  • 在函数外边定义的变量叫做全局变量
  • 全局变量能够在所有的函数中进行访问
  • 如果在函数中修改全局变量,那么就需要使用,global进行声明,否则出错
  • 如果全局变量的名字和局部变量的名字相同,那么使用的是局部变量的

可变类型的全局变量

先举2个例子:

例1:

a=1
def foo():
	a+=1
	print(a)

foo()

运行结果:

Traceback (most recent call last):
  File "D:\python\函数.py", line 105, in <module>
    foo()
  File "D:\python\函数.py", line 102, in foo
    a+=1
UnboundLocalError: local variable 'a' referenced before assignment

例2:

li=['a','b']
def foo():
	li.append('c')
	print(li)
	
print(foo())
print(li)

运行结果:

['a', 'b', 'c']
None
['a', 'b', 'c']
  • 在函数中不使用global声明全局变量时不能修改全局变量的本质是不能修改全局变量的指向,即不能将全局变量指向新的数据。
  • 对于不可变类型的全局变量来说,因其指向的数据不能修改,所以不使用global时无法修改全局变量。
  • 对于可变类型的全局变量来说,因其指向的数据可以修改,所以不使用global时也可修改全局变量。

Python函数高级话题

递归函数

递归是颇为高级的话题,它在Python中相对少见。然而,它是一项应该了解的有用的技术,因为它允许程序遍历拥有任意的、不可预知的形状的结构。递归甚至是简单循环和迭代的替换,尽管它不一定是最简单的或最高效的一种。

用递归求和

让我们来看一些例子。

要对一个数字列表(或者其他序列)求和,我们可以使用内置的sum函数,或者自己编写一个更加定制化的版本。这里是用递归编写的一个定制求和函数的示例:

def mysum(L)
    #print(L)
    if not L:
        return 0
    else:
        return L[0]+mysum(L[1:])#调用自身
print(mysum[1,2,3,4,5,6])

在每一层,这个函数都递归地调用自己来计算列表剩余的值的和,这个和随后加到前面的一项中。当列表变为空的时候,递归循环结束并返回0。当像这样使用递归的时候,对函数调用的每一个打开的层级,在运行时调用堆栈上都有自己的一个函数本地作用域的副本,也就是说,这意味着L在每个层级都是不同的。如上注释部分的print(L),可以运行查看结果。

正如你所看到的,在每个递归层级上,要加和的列表变得越来越小,直到它变为空——递归循环结束。加和随着递归调用的展开而计算出来。

匿名函数:lambda

lambda表达式

lambda的一般形式是关键字lambda,之后是一个或多个参数(与一个def头部内用括号括起来的参数列表极其相似),紧跟的是一个冒号,之后是一个表达式,即:

lambda para1, para2, .., paraN : expression using paras

由lambda表达式所返回的函数对象与由def创建并赋值后的函数对象工作起来是完全一样的,但是1ambda有一些不同之处让其在扮演特定的角色时很有用。

  • lambda是一个表达式,而不是一个语句。因为这一点,lambda能够出现在Python语法不允许def出现的地方——例如,在一个列表常量中或者函数调用的参数中。此外,作为一个表达式,lambda返回了一个值(一个新的函数),可以选择性地赋值给一个变量名。相反,def语句总是得在头部将一个新的函数赋值给一个变量名,而不是将这个函数作为结果返回。
  • lambda的主体是一个单个的表达式,而不是一个代码块。这个lambda的主体简单得就好像放在def主体的return语句中的代码一样。简单地将结果写成一个顺畅的表达式,而不是明确的返回。因为它仅限于表达式,lambda通常要比def功能要小:你仅能够在lambda主体中封装有限的逻辑进去,连if这样的语句都不能够使用。这是有意设计的——它限制了程序的嵌套:lambda是一个为编写简单的函数而设计的,而def用来处理更大的任务。

除了这些差别,def和lambda都能够做同样种类的工作。例如:

def add(x,y,z):
    return x+y+z
  
print(add("AA","BB","CC"))

add=lambda x,y,z:x+y+z
#add=f(x,y,x)=x+y+Z
print(add(1,2,3))

这里的add被赋值给一个lambda表达式创建的函数对象,这也是def所完成的任务,只不过def的赋值是自动进行的。

默认参数也能能够在lambda参数中使用,就像在def中使用一样。

f=(lambda a="Tom",b="loves",c="python":a+b+c)

print(f())
print(f('LiLei'))
print(f('Lucy','likes','to travel.'))

高阶函数

高阶函数:把一个函数名,以实参的形式,传给这个函数的形参,这个函数就称为高阶函数

比如下面的形参c,对应的实参是一个函数名abs

#函数abs()的功能是取绝对值
def add(a,b,c)
    return c(a)+c(b)
    
add_value=add(-9,1,abs)
print(add_value)

最正确的高阶函数解释

满足下面两个条件之一,就可称之为高阶函数:

  • 1.把一个函数名当做一个实参,传给另外一个函数

  • 2.返回值中包含函数名(不修改函数的调用方式)

示例1:

import time
def bar():
    time.sleep(1)
    print('函数bar')
def test1(func):    #高阶函数(满足条件1)
    start_time=time.time()
    func()
    stop_time=time.time()
    print("这个函数的运行时间是%s"%(stop_time-start_time))
    test(bar)

示例2:

import time
def bar(): #高阶函数(满足了条件2)
    time.sleep(1)
    print("in the bar")
def test2(func):
    print(func)
    return func
bar=test2(bar)
bar()

Python中常见的函数

(1)filter

  • 功能

    • filter的功能是过滤掉序列中不符合函数条件的元素,当序列中要删除的元素可以用某些函数描述时,就应该想起filter函数。
  • 调用

    • filter(function,sequence),

      • function可以是匿名函数或者自定义函数,它会对后面的sequence序列的每个元素判定是否符合函数条件,返回TRUE或者FALSE,从而只留下TRUE 的元素;sequence可以是列表、元组或者字符串

例子:

>>> x=[1,2,3,4,5]
>>> y-filter(lambda x:x%2==0,x)  #找出偶数。
>>>y
<filter object at 0x00000000028BC4E0>
>>>list(y)# py3之后filter函数返回的不再是列表而是迭代器,所有需要用list转换。
[2,4]

②找出奇数
x=[1,2,3,4,5]
def is_odd(n):
    return n%2==1

print(list(filter(is_odd,x)))

(2)map

  • 功能

    • 求一个序列或者多个序列进行函数映射之后的值,就该想到map这个函数,他是python自带的函数,py3返回的是迭代器,同filter,需要进行列表转换
  • 调用

    • map(function,iterable1,iterable2),
      • function中的参数值不一定是一个x,也可以是x和y,甚至多个;后面的iterable表示需要参与function运算中的参数值,有几个参数值就传入几个iterable

例子:

>>> x = {1,2,3,4,5}
>>> y = {2,3,4,5,6}
>>> z = map(lambda x,y:(x*y)+2,x,y)
>>>z
<map object at 0x00000000028BC5C0>
>>> list(z)
[4,8,14,22,32]
>>>
②种方式 def
def f(x,y):
    return x*y+2

x=[1,2,3,4,5]
y=[2,3,4,5,6]

def f(x,y,z):
    return x*y+z

x=[1,2,3,4,5]
z=y=[2,3,4,5,6]
print(list(map(f,x,y,z)))

注:map中如果传入的几个序列的长度不一,那么会依据最短的序列进行计算

(3)reduce

  • 功能

    • 对一个序列进行压缩运算,得到一个值。但是reduce在python2的时候是内置函数,到了python3移到了function模块,所有使用之前需要 from functools import reduce
  • 调用

    • reduce(function,iterable),
      • 其中function必须传入两个参数iterable可以是列表或者元组

例子:

>>> from functools import reduce
>>> y = [2,3,4,5,6]
>>> z = reduce(lambda x,y:x+y,y)
>>>z
20
>>>
 from functools import reduce
 def f(x,y)
     return x+y
x=[2,3,4,5,6]
print(reduce(f,x))

其计算原理:

先计算头两个元素:f(2, 3),结果为5;

再把结果和第3个元素计算:f(5, 4),结果为9;

再把结果和第4个元素计算:f(9, 5),结果为14;

再把结果和第5个元素计算:f(14, 6),结果为20;

由于没有更多的元素了,计算结束,返回结果20。

  • 功能

    ​ 是pandas中的函数,应用对象为pandas中的DataFrame或者Series。大致有两个方面的功能:一是直接对DataFrame或者Series应用函数,二是对pandas中的groupby之后的聚合对象apply函数

  • 调用

    apply(function,axis),function表明所使用的函数,axis表明对行或者列进行运算

    例子:

    import numpy as np
    import pandas as pd
    a=np.random.randint(low=0,high=4,size=(2,4))
    print(a)
    data=pd.DataFrame(a)
    print(data)
    print(data.apply(lambda x: x*10))
    

(5)zip

  • 功能
    • zip()函数用于将可迭代对象作为参数,将对象中对应的元素打包成一个个元组,然后返回由这些元组组成的对象。

如果各个可迭代对象的元素个数不一致,则返回的对象长度与最短的可迭代对象相同

利用*号操作符,与zip相反,进行解压。

  • 调用
    • zip(iterable1,iterable2,…)
      • iterable–一个或多个可迭代对象(字符串、列表、元组、字典)

python2中直接返回一个由元组组成的列表,Python3中返回的是一个对象,如果想要得到列表,可以用list()函数进行转换

例子:

>>> a=[1,2,3] #此处可迭代对象为列表
>>> b=[4,5,6]
>>> c=[4,5,6,7,8]
>>>zipTest=zip(a,b)
>>>zipTest
<zip object at 0x00000000028C3A08> #返回的是一个对象
>>> list(zipTest) #转换为列表
[(1,4),(2,5),(3,6)]
>>>list(zip(a,c))
[(1,4),(2,5),(3,6)]
>>>zipT=zip(a,b)
>>>list(zip(*zipT)) #解压也使用list进行转换
[(1,2,3),(4,5,6)]
>>>
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值