声明与定义比较

本文详细介绍了Python中声明与定义的区别,强调Python将两者视为一体。探讨了前向引用的限制,函数属性的使用,包括内部和内嵌函数的概念。深入讲解了函数应用,如打印图形、数学计算以及递归和高阶函数,如lambda表达式、filter、map、reduce和apply。同时,阐述了变量作用域,包括全局变量和局部变量的使用,以及如何通过global语句操作全局变量。最后,举例说明了可变类型全局变量的特性。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

声明与定义比较

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

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

前向引用

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

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

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

结果:

 foo()
in foo()
in bar()

函数属性

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

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

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

def foo():
...     'fo() -- 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()
    fo() -- properly created doc string

>>> bar.version
0.1

bar._doc_
'Oops,forgot the doc str above'

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

内部、内嵌函数

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

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


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

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

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

函数的嵌套调用:

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

  • 思考1:编程实现

    • 写一个函数打印一条横线
    • 打印自定义函数的横线
  • 参考代码

 def line():
...     print("-"*15)
def printOneLine():
    print("-"*30)
    
#打印多行横线
def printSumLine(n):
     for i in sum(n):
  • 求三个数的和

    • 参考代码:
    #求三个数的和
    def sum3Number(a,b,c):
        return a+b+c #return 的后面可以是数值,也可以是一个表达式
        
    #完成对3个数求平均值
    def average3Number(a,b,c):
        
        
        #因为average3Number函数已经完成了3个数的就和,所以只需要调用即可
        #即把接收到的3个数,当做实参传递即可
        sumResult = sum3Number(a,b,c)
        aveResult = sumResult/3.0
        return aveResult
        
        
        #调用函
    

变量作用域

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

全局变量与局部变量

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

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

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

关 于 所 有 变 量 名, 包 括 作 用 域 的 定 义 在 内, 都 是 在 Python 赋 值 的 时 候 生 成 的。 正 如 我 们 所 知, Python 中 的 变 量 名 在 第 一 次 赋 值 时 已 经 创 建, 并 且 必 须 经 过 赋 值 后 才 能 够 使 用。 由 于 变 量 名 最 初 没 有 声 明, Python 将 一 个 变 量 名 被 赋 值 的 地 点 关 联 为( 绑 定 给) 一 个 特 定 的 命 名 空 间。 换 句 话 说, 在 代 码 中 给 一 个 变 量 赋 值 的 地 方 决 定 了 这 个 变 量 将 存 在 于 哪 个 命 名 空 间, 也 就 是 它 可 见 的 范 围。

局部变量

先看一个例子:

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

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

可以看出:

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

全局变量

例子:

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

讨论1:

如果全局变量和局部变量名字相同?

a = 6688
>>> def foo()
  File "<stdin>", line 1
    def foo()
            ^
SyntaxError: invalid syntax
>>>
KeyboardInterrupt
>>> def foo():
...     a = 666
...     print('foo(),修改前a:\t',a)
...     a = 888
...     print('foo(),修改后a:\t',a)
...
>>> def bar():
...     print('bar(),a:\t',a)
...
>>> foo()
foo(),修改前a:   666
foo(),修改后a:   888
>>> bar()
bar(),a:         6688

讨论2:

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

global语句

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

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()
foo(),修改前a:   6688
foo(),修改后a:   666
>>> bar()
bar(),a:         666

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

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

可变类型的全局变量

例子:

例1:

a = 1
>>> def foo():
...     a += 1
...     print(a)
...
>>> foo()
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "<stdin>", line 2, in foo
UnboundLocalError: local variable 'a' referenced before assignment
  • 在函数中不使用global声明全局变量时不能修改全局变量的本质是不能修改全局变量的指向,既不能将全局变量指向新的数据。
  • 对于不可变类型的全局变量来说,因其指向 的数据不能修改,所以不使用global时无法修改全局变量。
  • 对于可变类型的全局变量来说,因其指向的数据

Python函数高级话题

递归函数

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

https://zhidao.baidu.com/question/550718079.html

用递归求和

让我们来看一些例子。

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

def mysum(L):
    if not L:
        return 0
    else:
        return L[0] + mysum(L[1:])#调用自身
print(mysum([1, 2, 3, 4, 5, 6]))
def fibonacci(n):
    if n==1:
        return 1
    if n==2:
        return 1
    else:
        return fibonacci(n-1) + fibonacci(n-2)
fib = []
for i in range(1, 11):
    fib.append(fibonacci(i))
print(fib)

匿名函数:lambda

lambda表达式

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

lambda para1,para2,.....,paraN:expression using paras
  • 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(1,2,3))

使用lambda表达式达到相同的效果,通过明确地将结果赋值给一个变量名,之后就能够通过这个变量名调用这个函数。

add = lambda x, y, z: 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.'))

结果:

 f = (lambda a = "Tom", b = "loves", c = "Python.":a + b + c)
>>> print(f())
TomlovesPython.
>>> print(f('LiLei '))
LiLei lovesPython.
>>> print(f('Lucy ','likes ','to travel.'))
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:

import time
def bar():
    time.sleep(1)
    print('函数bar')

def test1(func):
    start_time = time.time()
    func()
    stop_time = time.time()
    print("这个函数的运行时间是 %s" % (stop_time-start_time))

test1(bar)

结果:
函数bar
这个函数的运行时间是 1.0008063316345215

示例2:

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


结果:
<function bar at 0x00000249C51E3E18>
in the 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)#找出偶数。
print(y)
print(list(y))#py3之后filter函数返回的不再是列表而是迭代器,所以需要用list转换

结果:
<filter object at 0x000001A9DD588198>
[2, 4]

普通函数使用lambda:

x = [1,2,3,4,5]
def is_odd(n):
    return n%2==1
print(list(filter(is_odd,x)))

结果:[1, 3, 5]

(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)
print(z)
print(list(z))

结果:
<map object at 0x00000158EE0F82B0>
[4, 8, 14, 22, 32]

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

(3)reduce

  • 功能
    • 对一个序列进行压缩运算,得到一个值,但是reduce在python2的时候是内置函数,到了Python3移到了functools模块,所以,使用之前需要 form functools import reduce
  • 调用
    • reduce(function, iterable[, initial]),
      • 其中function必须传入两个参数,iterable可以是列表或者元组

例子:

y = [2,3,4,5,6]
z = reduce(lambda x , y : x + y , y , 100)
print(z)

结果:
120
li = [a,b,c,d]
a * 1000 + b * 100 + c * 10 + d
10*(a * 100 + b * 10 + c) + d
10*(10 * (a * 10 + b) + c) + d

(4)apply

  • 功能
    • 是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)

data.apply(lambda x: x*10)

结果:
[[2 2 2 2]
 [1 3 0 1]]
 
   0  1  2  3
   
0  2  2  2  2
1  1  3  0  1

(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)
print(zipTest)
结果:
<zip object at 0x000002987421DE48>#返回的是一个对象

print(list(zipTest))#转换为列表
结果:
[(1, 4), (2, 5), (3, 6)]

print(list(zip(a, b)))
结果:
[(1, 4), (2, 5), (3, 6)]

zipT = zip(a, b)
print(list(zip(*zipT)))#解压也使用list进行转换
结果:
[(1, 2, 3), (4, 5, 6)]

总结:

  1. filter和map都是Python内置函数,可以直接调用,reduce在functools模块,apply在pandas,模块
  2. 要过滤删减序列用filter;要对多个序列做函数运算用map;在pandas里面直接调用apply,reduce用的少。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值