python(三):函数

时间:2024-03-26 17:34:15

一、函数、名称空间与作用域

  1.函数的构成

    python有三种层次的抽象:(1)程序可分成多个模块;(2)每个模块包含多条语句;(3)每条语句对对象进行操作。函数大致处于第二层。函数有它的定义格式、参数、逻辑代码块、返回值、以及函数属性五部分组成。

 def foo(name):  # foo: 别名; (),执行规则; name, 形式参数
"""doc""" # 说明文档,可以用foo.__doc__获取
return name # 返回值
print(foo("foo")) # 填写形式参数对应的实体参数,并执行foo函数
f = foo     # 函数别名可以赋值
print(f("foo"))

  2.函数与方法

    函数和方法是有一些区别的,提到方法一般指的是某个对象的方法。因为python自上而下执行,所以函数不可以提前声明。但是方法可以,例如类对象中,可以直接调用后面的方法。但当方法或者函数被调用时,都是函数。

 class Foo:
def pri(self):
return foo()
def foo(self, name):
return name
f = Foo()
f.pri("foo")

  3.名称空间与作用域

    - 名称空间

      名称空间是站在程序的角度对抽象的结构进行划分。

      python使用名称空间(namespaces)的概念在应用程序上储存关于对象的信息和位置。内置函数、语句和对象有特殊的命名空间,函数、模块有单独的命名空间,也就是两者有各自的命名空间,及特定的抽象级别。在特定的抽象级别*问对象或函数时,都将搜寻名称空间表以获得可引用的特定名字,并确定处理存储在该位置的信息的方法。

      函数的参数,加上函数体中绑定的所有变量(通过赋值或者其它绑定语句,如def),功能构成了该函数的本地命名空间(local namespace),也称为本地范围(local space)。所有这些也称为该函数的本地变量(local variable)。

      其它则是全局名称空间,包括可被其它函数或模块直接调用的、存活于整个程序运行期间的变量、函数、模块等。

    - 作用域

      作用域是站在对象的角度查看它的适用范围。在python中,作用域遵循以下规则:

        - 每个模块都有自己的作用域,这意味着多重模块有它们自己的作用域,从而形成自己的名名称空间。

        - 定义新函数时创建新的作用域,作用域仅限于函数体内。

        - 每个未缩进的逻辑代码块,它的作用域是全局的。

  4.全局变量和局部变量

    定义在*别的模块中的变量,被称为全局变量;定义在局部作用域中的变量被称为局部变量。一个变量的作用域和它寄宿的名称空间相关。

    python变量遵循LGB规则:

      - 变量引用依次搜寻3种作用域:局部(Local)、全局(Global)、内置(Built-in)。

      - 在局部作用域内对变量赋值时会创建新对象或更新对象,如果想在局部作用域对全局对象赋值,必须使用关键字global。

      - 全局声明会把赋值名字映射到封装的模块的作用域。这意味着,要么显示地从输入的模块中输入对象,要么使用完全有效的模块/对象名。

    示例一:GLB规则

 a = 20
def func():
print(a) # 在局部作用域找不到a,会继续从全局作用域找a
func()
# 打印ax  

    示例二:全局变量与局部变量的互不干扰

 a = 20  # 全局变量
def func():
b = 25
a = 30
global c
c = 125
print(a)
print(b)
print(a)
func()
print(c)
print(a)
# 打印结果为:
20
30
25
125
20

    也就是说,创建局部变量a,是直接开辟内存生成对象30,然后引用给a。它与全局变量的a毫不相关。global可以修改局部变量为全局变量。

    globals()函数和locals()函数可以直接查看当前文件中的全局变量和局部变量。

  5、参数

    python函数中的传递参数,是传递变量的值本身,而不是创建一个新的引用。

    在定义函数时,有位置参数,默认参数,以及扩展参数;在调用函数时,可以按位置传递相应的变量,也可以按键值对传递相应的参数,也可以以扩展的方式传递参数。

    在定义函数时,位置参数、默认参数与扩展参数的顺序是:位置参数 > 扩展元组 > 默认参数 > 扩展字典。

 def func(a, b, *args, author="jingyu", **kwargs):
print(a)
print(b)
print(args)
print(author)
print(kwargs)
 func(123, 456, 7,8,9, gender="female", age=20)
# 打印结果为:
123
456
(7, 8, 9)
jingyu
{'gender': 'female', 'age': 20}

    在定义函数时,*args会把所有位置参数匹配剩余的变量组合成元组,**kwargs会把关键字参数(键值对)组合成一个字典。

    在执行函数时,*list或者*tuple会把所有的元素展开成单独的位置参数,**dict会把字典内的键值对展开成关键字参数。

    这个功能在写闭包和装饰器的时候非常有用。

 func(
123, 456,
*[7, 8, 9], *[10, 11, 12], *{"name1": "sunbin", "name2": "mengtian"},
**{"name3": "baiqi", "gender": "female", "age": 72},
)
# 打印结果为:
123
456
(7, 8, 9, 10, 11, 12, 'name1', 'name2')
jingyu
{'name3': 'baiqi', 'gender': 'female', 'age': 72}

  6、函数属性

    可以给函数创建一些属性,并通过func.__dict__来查看。函数属性应用在高度专业的应用程序中。

 def say():
print("hello, my man.")
# 接上面定义的func
func.__live__ = 20
func.__create__ = "2018_04_18"
func.say = say
print(func.__dict__)
# 打印结果为:
{'__create__': '2018_04_18', '__live__': 20, 'say': <function __main__.say>}
# 显然它可以执行
func.say()
# 结果显示
hello, my man.
# 这个和js的匿名函数有些类似,但是不支持func.say = function (){console.log("hello, my man.)} 这种写法

二、闭包

  闭包是内层函数对层函数局部变量的引用。简单来说,闭包就是内部包含函数的函数。

  闭包的好处:如果python检测到闭包,它有一个机制,局部作用域不会随着函数的结束而结束。可以在局部作用域添加缓存机制,使得对于计算量较大时能够提高效率。闭包也是装饰器的前提。

  1、创建闭包

 def outer(name):
def inner(age):
return "hello, i'm %s, %s." % (name, age)
return inner
outer("baiqi")(72)
# 打印结果为:
"hello, i'm baiqi, 72."

  可以看到,outer("baiqi")的返回值是一个函数对象,即inner,inner可以接收参数并执行,它的返回值是真正要打印的结果。可以隐隐地感觉到,outer这个外层函数实际是在打印真正结果之前做了一些额外的"修饰"工作。

  2、闭包中的外层变量和内层变量也遵循GLB规则

  def outer():
a = 100
def inner1():
return a * 5
b = inner1()
def inner2():
return b * 10
a = inner2()
def inner3():
b = a
print(a, b)
inner3()
print(a, b)
outer()
# 打印结果为:
5000 5000
5000 500

    闭包里可以写很多逻辑代码和函数,闭包并不一定要返回某个具体的内层函数,闭包有着很好的灵活性和弹性,它相当于一个工作室。

三、装饰器

  1、装饰器函数的推导

    重写一下白起打招呼:

 import time
def hello():
print("Hello, i'm baiqi, 72 years old.") def timer():
t = time.strftime("%Y-%m-%d %X", time.localtime())
print(t)
return hello()

    把它改写成闭包的形式:

 def timer():
t = time.strftime("%Y-%m-%d %X", time.localtime())
print(t)
def fn():
return hello()
return fn
timer()()

    前面说到outer("baiqi")返回的是一个待执行的函数,这里也是,timer()返回的是fn,fn()执行时返回hell()执行的结果。于是正常显示:

 2018-04-18 23:03:54
Hello, i'm baiqi, 72 years old.

    hello这个返回值不够灵活,把hello函数作为对象传进来;并且timer()运行时直接打印了t,它是timer()函数的运行时间而不是hello的,于是改写为:

 def timer(f):
def fn():
t = time.strftime("%Y-%m-%d %X", time.localtime())
print(t)
return f()
return fn
timer(hello)()

    此时如果要只写hello()函数就打招呼,而不是用timer(hello)()这个额外的函数名,可以改写为:

 import time
def hello():
print("Hello, i'm baiqi, 72 years old.")
def timer(f):
def fn():
t = time.strftime("%Y-%m-%d %X", time.localtime())
print(t)
return f()
return fn
hello = timer(hello)
print(hello, hello.__name__)
hello()
# 打印结果为:
<function timer.<locals>.fn at 0x10f201d08> fn
2018-04-18 23:12:57
Hello, i'm baiqi, 72 years old.

    在hello = timer(hello)这一步,把原来的hello函数作为参数传递给timer,那么timer(hello)的返回值就是未执行的fn函数。fn函数在执行时,也就是timer(hello)() = fn() = hello(),会返回print字符串。

    在python中,用@符号来代表hello = timer(hello)这个关系式,并要求def hello()写在它的下面。即:

 import time
def timer(f):
def fn():
t = time.strftime("%Y-%m-%d %X", time.localtime())
print(t)
return f()
return fn
@timer
def hello():
print("Hello, i'm baiqi, 72 years old.")
hello()

    注意:逻辑代码写内层函数里面。上面将t写在外面时,有运行timer()会直接打印调用时间,这个需要避免。可以在fn函数的内部尽情地写if for while 等等代码块,也不一定非要返回f()。

    可以看出装饰器的作用:在尽量保留被装饰函数的基础上,不着痕迹地添加一些额外的功能。“尽量保留”说明它的确修改了原函数的一些内容,如函数名称(fn而不是hello),

 print(hello.__name__)
# 打印结果为:
fn

    “不着痕迹”是指不要在外层函数的局部作用域内写逻辑代码,除非业务需要。

  2、装饰器函数传递参数

 import time
def timer(f):
def fn(name, age):
t = time.strftime("%Y-%m-%d %X", time.localtime())
print(t)
return f(name, age)
return fn
@timer
def hello(name, age):
print("Hello, i'm %s, %d years old." % (name, age))
hello("wangjian", 62)

    因为fn返回和执行hello本身,所以在hello中传递参数,就需要在fn中传递参数。为了使fn和返回执行的f函数(就是hello)具有更大的灵活性,通常会这么写:

 import time
def timer(f):
def fn(*args, **kwargs):
t = time.strftime("%Y-%m-%d %X", time.localtime())
print(t)
return f(*args, **kwargs)
return fn
@timer
def hello(name, age):
print("Hello, i'm %s, %d years old." % (name, age))
hello("limu", 62)

    前面已提到*args和**kwargs在定义函数和执行函数的功能。这里再重申一遍:在定义函数时,*args会将位置参数上传递的元素组合成元组,该元组赋给了变量args;在执行函数时,*args(此时args是一个元组(limu, 62))会把元组展开成元素。于是name还是那个name,age还是那个age。

 class Cla(object):
def __init__(self, *args, **kwargs):
# l1 = *args # SyntaxError can't use starred expression here
print(*args)
# l1 = args # l1 = (1, 2, 3)
# l1, l2, l3 = args # l1 = 1
print(*kwargs)
def __call__(self):
print("hello, world!") def func(*args, **kwargs):
return Cla(*args, **kwargs) func(1,2,3, name=123, age=123)()

    这也说明,fn的参数和f的参数必须保持一致。

  3、装饰器上带参数

 import time
def hello(name, age):
print("Hello, i'm %s, %d years old." % (name, age))
def gender(male=False):
def timer(f):
def fn(*args, **kwargs):
t = time.strftime("%Y-%m-%d %X", time.localtime())
print(t)
f(*args, **kwargs)
if male:
print(" I'm hero.")
else:
print("I'm beautiful girl.")
return fn
return timer
f1 = gender(male=True) # f1就是gender函数执行的返回值,也就是timer
f2 = f1(hello) # f2就是timer(f)函数执行的返回值,也就是fn
f2("limu", 62)

    改写一下丑陋的f1和f2:

 gender = gender(male=True)  # f1就是gender函数执行的返回值,也就是timer
hello = gender(hello) # f2就是timer(f)函数执行的返回值,也就是fn
hello("limu", 62)

    再把gender和hello写到一行:

 hello = gender(male=True)(hello)

    它符合装饰器的写法,即如果有hello=timer(hello),那么有@timer。于是这里可写为@gender(male=True)。最后,杨玉环打招呼了。

 import time
def gender(male=False):
def timer(f):
def fn(*args, **kwargs):
t = time.strftime("%Y-%m-%d %X", time.localtime())
print(t)
f(*args, **kwargs)
if male:
print(" I'm hero.")
else:
print("I'm beautiful girl.")
return fn
return timer
@gender(male=False)
def hello(name, age):
print("Hello, i'm %s, %d years old." % (name, age))
hello("yangyuhuan", 62)

    打印结果为:

 2018-04-19 00:07:25
Hello, i'm yangyuhuan, 62 years old.
I'm beautiful girl.

  4、装饰器的叠加:

    此时,又有一个要求,要打印这个人物的国家。可以在gender里再添加一个参数,在fn函数里去写细节的逻辑代码。现在用另一种方式实现要求:

 import time
def timer(f):
def fn(*args, **kwargs):
t = time.strftime("%Y-%m-%d %X", time.localtime())
print(t)
f(*args, **kwargs)
return args
return fn
def country(f):
def fn(*args, **kwargs):
variables = f(*args, **kwargs)
if args[0] == "yangyuhuan":
print("A beautiful girl, from TangChao.")
else:
print("A hero from ZhanGuo.")
return fn
@country
@timer
def hello(name, age):
print("Hello, i'm %s, %d years old." % (name, age))
hello("yangyuhuan", 24)

    打印结果为:

 2018-04-19 00:17:59
Hello, i'm yangyuhuan, 24 years old.
A beautiful girl, from TangChao

    注意:两个装饰器叠加,第一个装饰器最好写返回值。叠加的装饰器由上而下装饰,由下而上执行。

  5、漏掉的functools

    前面提到hello.__name__是fn,即内层的那个函数名。这在框架里面是不允许出现的,于是有了个functools.wraps()方法能完整保留被装饰函数的完整信息。它的用法也十分简单。

 import time, functools
def timer(f):
@functools.wraps(f)
def fn():
t = time.strftime("%Y-%m-%d %X", time.localtime())
print(t)
return f()
return fn
@timer
def hello():
print("Hello, i'm baiqi, 72 years old.")
print(hello.__name__)
# 打印结果为
hello

  6、类装饰器

    类装饰器写法一:

 import functools, time
class Add(object):
def __call__(self, f):
@functools.wraps(f)
def decorator(*args, **kwargs):
t1 = time.time()
outcome = f(*args , **kwargs)
time.sleep(1)
t2 = time.time()
print("t: %r " % (t2 - t1))
return decorator @Add()
def add(var1, var2):
return var1 * var2
add(4, 5)

    类装饰器写法二:

 class Add(object):
def add(self,f):
@functools.wraps(f)
def fn(var1, var2):
t1 = time.time()
outcome = f(var1 , var2)
t2 = time.time()
print("t: %r " % (t2 - t1))
return outcome
return fn
myAdd = Add()
@myAdd.add
def add(var1, var2):
return var1 * var2
add(4, 5)

    类装饰器写法三:

 class Add(object):
def __init__(self, appname):
self.appname = appname
self.function_dict = {}
def add(self,route):
def decorator(f):
@functools.wraps(f)
def fn(*args, **kwargs):
t1 = time.time()
outcome = f(*args, **kwargs)
t2 = time.time()
print("t: %r " % (t2 - t1))
self.function_dict[route] = fn.__name__
return outcome
return fn
return decorator app = Add("apple") @app.add("/index")
def add(var1, var2):
return var1 * var2
print(add(4, 5))
print(app.function_dict)

    打印结果为:

 t: 1.9073486328125e-06
20
{'/index': 'add'}

四、列表生成式、匿名函数和高阶函数

  1、列表生成式[列表推导式]

    列表生成式是可迭代对象迭代时的一种简便写法。

 table = [
{"name": "baiqi", "attr": "guy", "rank": 1},
{"name": "limu", "attr": "guy", "rank": 2},
{"name": "wangjian", "attr": "guy", "rank": 3},
{"name": "lianpo", "attr": "guy", "rank": 4},
{"name": "xishi", "attr": "girl", "rank": 1},
{"name": "wangzhaojun", "attr": "girl", "rank": 2},
{"name": "diaochan", "attr": "girl", "rank": 3},
{"name": "yangyuhuan", "attr": "girl", "rank": 3},
] name = [person["name"] for person in table] name = [[person["name"], person["attr"], person["rank"]] for _, person in enumerate(table)]
name = [{person["name"]: person["rank"]} for _, person in enumerate(table)] name = {person["name"]: [person["attr"], person["rank"]] for _, person in enumerate(table)} boy = [person["name"] for person in table if person["attr"] == "guy"]
boy = [person["name"] for person in table if person["rank"] >= 3] girl = [person["name"] if person["rank"] >3 and person["attr"] == "girl" else -1 for person in table]

  实际上,列表生成式的使用要比上面的例子中更灵活。比如两层循环或者两个可变序列情形时。

  2、匿名函数

    对于上面的第一个name,可以用lambda函数来重写。同样地,可以用lambda改写上面所有的列表生成式。lambda还支持传入函数,但是没有必要性,它的核心在于简洁。

    lambda params: expression。

 func = lambda lis: [person["name"] for person in lis]
func(table)

  3、三个高阶函数

    高阶指的是该函数的作用域和存在的名称空间,以及它替代一些逻辑代码块的作用。

    apply(FUNCTION, TUPLE)。快要被摒弃的函数。reduce(),逐步叠加计算,在python3中已不存在了。

    map(FUNCTION),遍历,function作用在序列的每个元素上以改变元素。filter(FUNCTION),过滤,function作用在序列的每个元素上,进行条件判断并返回符合条件的元素。这些函数都不改变原序列。

 number = list(range(1, 11, 2))
outcome1 = list(map(lambda x: x**2, number))
outcome2 = list(filter(lambda x: x>4, number))
print(outcome1)
print(outcome2)

    需要注意的是,map函数和filter函数的返回结果是map对象和filter对象,需要list转换一下。这些高阶函数apply函数和map函数也会以方法的形式出现在一些第三方模块中。

 import pandas as pd
df = pd.DataFrame(table)
df["name_map"] = df.name.map(lambda x: x + "_China")
print(df)
# 打印结果为: attr name rank name_map
0 guy baiqi 1 baiqi_China
1 guy limu 2 limu_China
2 guy wangjian 3 wangjian_China
3 guy lianpo 4 lianpo_China
4 girl xishi 1 xishi_China
5 girl wangzhaojun 2 wangzhaojun_China
6 girl diaochan 3 diaochan_China

  4、偏函数

    偏函数的功能和上下文管理器一致。都是管理上下文的开始和结束,即with ... 直到结束。在tensorflow及其它的一些框架中,reader()这个阅读器的功能随处可见,可见它功能还是很强大的。

 f1 = open("homework/user.txt", mode="r", encoding="utf-8")
data = f1.readlines()
f1.close() with open("homework/user.txt", mode="r", encoding="utf-8") as f3:
data = f3.readlines() import functools
reader = functools.partial(open, mode="r", encoding="utf-8")
f3 = reader("homework/user.txt")
f3.readlines()

  5.递归函数

    在函数的内部调用自己本身的函数称为递归函数,递归的最大深度为998。两个例子:

 # 利用递归函数求阶乘
def factorial(number, start=1):
fac = 1 if start in [0, 1] else start
if start < int(number):
return factorial(number, start+1) * fac
else:
return fac

    注意,函数内部的factorial(number, start+1)就相当于fac。这是它的返回结果,递归函数必须存在可以终止的return。另一个特性,递归函数会在当件未满足时一直递归。

 # 二分查找
def func(value, l, index=0):
"""返回被查找值的索引"""
middle = len(l) // 2 # 切片
if middle == 0: # 当middle为0时,说明len(l)的值<=1
return 0 if value == l[0] else None
if value > l[middle]:
index += middle
return func(value, l[middle:], index)
elif value < l[middle]:
return func(value, l[: middle], index)
else:
return l.index(value) + index l = [2,3,5,10,15,16,18,22,26,30,32,35,41,42,43,55,56,66,67,69,72,76,82,83,88, 100]
print(func(2, l))
print(func(100, l))
print(func(14, l))
# 打印结果为
#
#
# None

  6.内置函数

    python3.6.4官方文档中给出的内置函数如下。

    python(三):函数