outline
函数与函数式编程:
一等函数
把函数视为对象
高阶函数
列表推导 vs (map/reduce)
函数的参数与返回值
方法和装饰器:
一等函数
特点:
可以运行的时候创建;
可以作为变量,传给函数;
可以作为函数的返回结果。
1 2 3 4 5 6 7 def factorial (n ): """ return n! """ return 1 if n < 2 else factorial(n - 1 ) * n print (factorial(5 ))
既然是对象,就有相应的属性,且可以赋值:
1 2 3 4 5 6 7 print (factorial.__doc__)print (type (factorial))fact = factorial print (fact)print (fact(5 ))
输出结果:
1 2 3 4 5 6 return n! <class 'function '> <function factorial at 0x00000184B923F288 > 120
为了更好的理解函数作为对象的特征,可以看看下面这个例子:
1 2 3 4 5 6 7 8 9 def make_averager (): nums = [] def averager (new_value ): nums.append(new_value) total = sum (nums) return total / len (nums) return averager
外层函数返回的是函数的对象,这样一来经过下面的测试可以得到预期结果:
1 2 3 avg = make_averager() print (avg)print (avg(3 ))
输出结果如下:
1 2 <function make_averager.<locals >.averager at 0x00000184B923FDC8 > 3.0
高阶函数
**特点:**接受函数作为参数或者函数作为返回结果。eg:map/reduce,sorted。
map/reduce/filter就是典型的高阶函数:
1 2 3 num_list = [1 , 2 , 3 , 4 , 5 , 7 , 9 ] print (list (map (factorial, num_list)))
输出:[1, 2, 6, 24, 120, 5040, 362880]
reduce和map的区别是,map只允许传入的函数只能接受1个参数,而reduce可以接受两个参数,如:
1 2 3 4 5 from functools import reducelyst = [2 , 3 , 4 , 5 , 6 ] print (list (map (lambda x: x**2 , lyst)))print (reduce(lambda x, y: x + y, lyst))
注意reduce返回的是一个值,而不是一个可迭代对象。
对于常见的加减乘除,还可以使用这种方式来实现:
1 2 3 4 from functools import partialfrom operator import add, mulop_mul = partial(mul, 3 ) op_mul(9 )
这个op_mul就相当于一个乘法运算对象,而且其中有一个值固定为3,现在就传入另外一个值即可。从这里就可以看出来partial的作用:
对于一个函数,如果有几个参数是确定的,但是还有一部分不确定,可以将确定的参数传入,然后借助partial函数,将这个传入了几个确定参数之后的函数作为一个新的函数,接下来只用将剩余的参数传入这个新的函数即可。
字典实现列表推导式:
1 2 3 4 usernames = ['sophia' , 'emma' , 'olivia' , 'ava' ] name_dict = {name: len (name) for name in usernames} print (name_dict)
输出结果如下:
{'sophia': 6, 'emma': 4, 'olivia': 6, 'ava': 3}
嵌套高阶函数:
1 2 3 4 5 6 7 print (list (map (factorial, filter (lambda n: n % 2 , num_list))))result = [factorial(num) for num in num_list if num % 2 ] print (result)
输出:
[1, 6, 120, 5040, 362880]
[1, 6, 120, 5040, 362880]
python单下划线和双下划的区别
python下划线有5种:
模式
样例
含义
单前导下划线
_xxx
约定俗成,仅供类内使用,外部不调用,但是强制调用还是可以的
双前导下划线
__xxx
强制不可外部调用,如果是函数,那么函数所在的类的子类无法轻易重写该函数
单末尾下划线
xxx_
防止和python内置关键字冲突
双前导和末尾下划线
__xxx__
python语义的特殊方法,尽量避免自己使用
单下划线
_
较多的用法是,该变量在下面的作用域内不会使用到
下面就这几种模式来举一下例子:
单前导下划线:
1 2 3 4 5 6 7 class A (): def __init__ (self ): self._name = "julia" pass a = A() print (a._name)
输出:julia
双前导下划线:
1 2 3 4 5 6 7 class A (): def __init__ (self ): self.__name = "julia" pass a = A() print (a.__name)
报错:
1 2 3 4 5 6 7 8 --------------------------------------------------------------------------- AttributeError Traceback (most recent call last) <ipython-input -24 -a75c7bdbf94f> in <module> 4 pass 5 a = A() ----> 6 print (a.__name) AttributeError: 'A' object has no attribute '__name'
如果是带双前导下划线的函数:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 class A (): def __init__ (self ): self.__name = "julia" def __getname (self ): return self.__name class B (A ): def __init__ (self ): pass def __getname (self ): pass print (dir (A))print (dir (B))
运行结果:
1 2 ['_A__getname' , '__class__' , '__delattr__' , '__dict__' , '__dir__' , '__doc__' , '__eq__' , '__format__' , '__ge__' , '__getattribute__' , '__gt__' , '__hash__' , '__init__' , '__init_subclass__' , '__le__' , '__lt__' , '__module__' , '__ne__' , '__new__' , '__reduce__' , '__reduce_ex__' , '__repr__' , '__setattr__' , '__sizeof__' , '__str__' , '__subclasshook__' , '__weakref__' ] ['_A__getname' , '_B__getname' , '__class__' , '__delattr__' , '__dict__' , '__dir__' , '__doc__' , '__eq__' , '__format__' , '__ge__' , '__getattribute__' , '__gt__' , '__hash__' , '__init__' , '__init_subclass__' , '__le__' , '__lt__' , '__module__' , '__ne__' , '__new__' , '__reduce__' , '__reduce_ex__' , '__repr__' , '__setattr__' , '__sizeof__' , '__str__' , '__subclasshook__' , '__weakref__' ]
这里的__getname
函数是带前导双下划线的函数,第一个输出的时候,发现__getname
的名称前面添加了_A
,B类也有一个自己的函数_B__getname
,在python中这种用法是为了避免和子类定义的函数名称冲突。
单末尾下划线:
这种用法是为了避免定义的变量和python的关键字冲突,比如class
,这个时候可以使用class_
来代替。
单下划线:
1 2 for _ in range (10 ): print ("000" )
比如这种情况就没有使用到循环变量,可以使用_来代替。
参考:https://blog.csdn.net/sinat_38682860/article/details/96902828
函数参数的特殊情况
下面需要实现一个html标签,如<p>hello</p>
,<item class='' size='small'>a</item>
。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 def make_element (name, *contents, cls=None , **kwargs ): if cls: kwargs['class' ] = cls pairs = [f"{attr} ={value} " for attr, value in kwargs.items()] attr_str = ' ' .join(pairs) if not contents: return f"<{name} {attr_str} />" elements = [f"<{name} {attr_str} >{content} <{name} />" for content in contents] return '\n' .join(elements) print (make_element("p" , "hello" , "world" , cls="sidebar" , size="small" ))
输出:
1 2 <p size=small class =sidebar >hello <p /> <p size =small class =sidebar >world <p />
因为class是python中的关键字,传入函数的话会冲突,所以将cls特意拿出来接受class参数。现在想设计一个专门生成图片标签的函数,可以利用前面说到的partial函数,代码如下:
1 2 3 4 5 6 7 from functools import partialimage_make = partial(make_element, "img" , cls="pic-frame" ) print (image_make(src="car.png" ))item_make = partial(make_element, "item" , size="small" ) print (item_make("A" , "B" , "C" ))
输出:
<img src=car.png class=pic-frame/>
<item size=small>A<item/>
<item size=small>B<item/>
<item size=small>C<item/>
这种涉及到的特殊参数是*contents
和**kwargs
,传入函数之后,contents是一个元组,kwargs是一个字典,如果需要对元组和字典进行解包,可以使用如下的方式:
1 2 3 4 5 6 7 8 a = (2 , 3 , 4 , 5 ) print (*a)dict_a = {"a" : 1 , "b" : 2 } dict_b = {"c" : 3 , "d" : 4 } new_dict = {**dict_a, **dict_b} print (new_dict)
输出:
2 3 4 5
{'a': 1, 'b': 2, 'c': 3, 'd': 4}
装饰器
装饰器的本质:在不改变函数签名、返回值的情况下,增加额外的功能。
带运行时间的装饰器
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 import timedef build_func_with_time_record (func ): def wrapper (*args, **kwargs ): start = time.time() result = func(*args, **kwargs) end = time.time() print (f"function {func.__name__} cost {end - start} " ) return wrapper def func_foo (a, b ): time.sleep(2 ) print (a + b) new_func = build_func_with_time_record(func_foo) new_func(1 , 3 )
这段代码实现的就是在func_foo
函数的基础上,再“装饰”一下,实现装饰功能的函数是build_func_with_time_record
。
python提供了@语法糖
来简化上述的“装饰”过程,使用@
关键字可以将上述代码简化为:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 import timedef build_func_with_time_record (func ): def wrapper (*args, **kwargs ): start = time.time() result = func(*args, **kwargs) end = time.time() print (f"function {func.__name__} cost {end - start} " ) return result return wrapper @build_func_with_time_record def func_foo (a, b ): time.sleep(2 ) print (a + b) func_foo(1 , 3 )
通过这种方式可以省去显示的将函数传入、赋值等操作。注意,上述的函数有参数a,b。参数不是通过build_func_with_time_record
传入的,而是通过里面的wrapper
函数传入的。
这样,可以将这个装饰器加在上述的make_element
函数上:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 @build_func_with_time_record def make_element (name, *contents, cls=None , **kwargs ): """make HTML element Args: name (label name): such as p cls (str, optional): make a distinction with class. Defaults to None. Returns: str: the results """ if cls: kwargs['class' ] = cls pairs = [f"{attr} ={value} " for attr, value in kwargs.items()] attr_str = ' ' .join(pairs) if not contents: return f"<{name} {attr_str} />" elements = [f"<{name} {attr_str} >{content} <{name} />" for content in contents] return '\n' .join(elements) print (make_element("p" , "hello" , "world" , cls="sidebar" , size="small" ))
输出如下:
1 2 3 function make_element cost 0.0 <p size=small class =sidebar >hello <p /> <p size =small class =sidebar >world <p />
但是,此时我们再输出函数属性:
1 2 3 4 5 print (make_element.__doc__)print (make_element.__name__)from inspect import signaturesignature(make_element)
1 2 3 None wrapper <Signature (*args, **kwargs)>
发现函数的属性 和函数签名 都发生了变化,我们回到不使用@
关键字实现装饰器功能的代码上,发现build_func_with_time_record
函数返回值就是wrapper函数对象,所以最后我们得到的不再是一开始的make_element
函数了,那么有什么方法能解决这个问题呢?
需要将一开始的build_func_with_time_record
做一下修改:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 import timefrom functools import wrapsdef build_func_with_time_record (func ): """ wraps在装饰器函数返回之前 通过获取func的属性 并存储在返回的最终函数中设置上述属性。 """ @wraps(func ) def wrapper (*args, **kwargs ): start = time.time() result = func(*args, **kwargs) end = time.time() print (f"function {func.__name__} cost {end - start} " ) return result return wrapper
然后重新尝试一下:
1 2 3 4 5 print (make_element.__doc__)print (make_element.__name__)from inspect import signaturesignature(make_element)
1 2 3 4 5 6 7 8 9 10 11 make HTML element Args: name (label name): such as p cls (str , optional): make a distinction with class . Defaults to None . Returns: str : the results make_element <Signature (name, *contents, cls=None , **kwargs)>