Python函数详解

# ------------------------------------一等函数------------------------------------
# 龟叔: 虽然我把函数定为一等对象,但是我并不把Python当作函数式编程语言.
"""
Python中,所有的函数都是一等对象:
    1.在运行时创建
    2.能赋值给变量或数据结构中的元素
    3.能作为参数传给函数
    4.能作为函数的返回值
"""


# ------------------------------------把函数视作对象------------------------------------
def factorial(n):
    '''return n!'''
    return 1 if n < 2 else n * factorial(n - 1)


print(factorial(42))  # 1405006117752879898543142606244511569936384000000000
print(factorial.__doc__)  # return n!  __doc__是函数对象众多属性中的一个
print(type(factorial))  # <class 'function'>  factorial是function类的实例
"""
__doc__属性用于生成对象的帮助文本.
为了展示函数对象的"一等"本性,我们可以把factorial函数赋值给变量fact,然后通过变量名调用.我们还可以把它作为参数传给map函数.
map函数返回一个可迭代对象,里面的元素是把第一个参数(一个函数)应用到第二个参数(一个可迭代对象)中各个元素上得到的结果.
"""
fact = factorial
print(fact)  # <function factorial at 0x0000020CA4741F28>
print(fact(5))  # 120
print(map(fact, range(11)))  # <map object at 0x0000020CA484B4A8>
print(list(map(fact, range(11))))  # [1, 1, 2, 6, 24, 120, 720, 5040, 40320, 362880, 3628800]

# ------------------------------------高阶函数------------------------------------
"""
有了一等函数,就可以使用函数式风格编程.函数式编程的特点之一是使用高阶函数.
接受函数为参数,或者把函数作为结果返回的函数是高阶函数,例如map函数,sorted函数.
"""
values = ['sda', 'asdasda', 'sadwqedwq', 'sadsa', 'qwwd']
sorted(values, key=len)  # ['sda', 'qwwd', 'sadsa', 'asdasda', 'sadwqedwq']
"""
任何单参数函数都能作为key参数的值.
例如,将各个单词反过来拼写,然后排序.
"""


def reverse(word):
    return word[::-1]


reverse('testing')  # gnitset
sorted(values, key=reverse)  # ['sda', 'asdasda', 'sadsa', 'qwwd', 'sadwqedwq']

"""
在函数式编程范式中,最为人熟知的高阶函数有map,filter,reduce和apply.apply函数在Python3中移除了.
函数式语言通常会提供map,filter和reduce三个高阶函数.在Python3中,map和reduce还是内置函数,但是由于引入了列表推导式和生成器表达式,
它们变得没那么重要了.列表推导式或生成器表达式具有map和filter两个函数的功能,而且更易于阅读.
"""
list(map(fact, range(6)))  # [1, 1, 2, 6, 24, 120]
[fact(i) for i in range(6)]  # [1, 1, 2, 6, 24, 120]
list(map(factorial, filter(lambda n: n % 2, range(6))))  # [1, 6, 120]
[factorial(n) for n in range(6) if n % 2]  # [1, 6, 120]

"""
在Python3中,map和filter返回生成器,因此它们的直接替代品是生成器表达式.(在Python2中这两个函数返回列表.)
在Python2中,reduce是内置函数,但是在Python3中放到了functools模块里了.这个函数最常用于求和,自2003年发布Python2.3开始,
最好使用内置的sum函数.
"""
from functools import reduce
from operator import add

print(reduce(add, range(100)))  #
print(sum(range(100)))  # 4950
"""
sum和reduce的通用思想是把某个操作连续应用到序列的元素上,累计之前的结果,把一个系列值归约成一个值.
all和any也是内置的归约函数:
all(iterable) 如果iterable的每个元素都是真值,返回True;all([])返回True.
any(iterable) 如果iterable中有元素是真值,就返回True;any([])返回False.
"""

# ------------------------------------匿名函数------------------------------------
"""
lambda关键字在Python表达式内创建匿名函数.
然而,Python简单的句法限制了lambda函数的定义体只能使用纯表达式.换句话说,
lambda函数的定义体中不能赋值,也不能使用while和try等Python语句.
在参数列表中最适合使用匿名函数.
"""
print(sorted(values, key=lambda word: word[::-1]))
"""
除了作为参数传给高阶函数之外,Python很少使用匿名函数.由于句法上的限制,非平凡的lambda表达式要么难以阅读,要么无法写出.
lambda句法只是语法糖,与def句法一样,lambda表达式会创建函数对象.
"""

# ------------------------------------可调用对象------------------------------------
"""
除了用户定义的函数,调用运算符(即())还可以应用到其他对象上.如果想判断对象能否调用,可以使用内置的callable()函数.Python列出了7种可调用对象:
1.用户定义的函数----使用def语句或lambda表达式创建.
2.内置函数----使用C语言(CPython)实现的函数,例如len或time.strftime.
3.内置方法----使用C语言实现的方法,如dict.get.
4.方法----在类的定义体种定义的函数.
5.类----调用类时会运行类的__new__方法创建一个实例,然后运行__init__方法,初始化实例,最后把实例返回给调用方.因为Python没有new运算符,
  所以调用类相当于调用函数.
6.类的实例----如果类定义了__call__方法,那么它的实例可以作为函数调用.
7.生成器函数----使用yield关键字的函数或方法.调用生成器函数返回的是生成器对象.

Python种有各种各样可调用的类型,因此判断对象是否可调用,最安全的方法是使用内置的callable()函数.
"""

# ------------------------------------用户定义的可调用类型------------------------------------
"""
不仅Python函数是真正的对象,任何Python对象都可以表现得像函数.为此,只需实现实例方法__call__.
"""
import random


class BingoCage:

    def __init__(self, items):
        # 接受任何可迭代对象;在本地构建一个副本,防止列表参数的意外副作用.
        # shuffle一定能完成工作,因为self._items是列表.
        self._items = list(items)
        random.shuffle(self._items)

    def pick(self):
        try:
            return self._items.pop()
        except IndexError:
            raise LookupError("空的!")

    def __call__(self, *args, **kwargs):
        return self.pick()


bingo = BingoCage(range(3))
print(bingo.pick())  #
print(bingo())  # 1
print(callable(bingo))  # True
"""
实现__call__方法的类是创建函数对象的简便方式,因此必须在内部维护一个状态,让它在调用之间可用.例如bingo中剩余的元素.
"""

# ------------------------------------函数内省------------------------------------
"""
除了__doc__,函数对象还有很多属性.使用dir函数可用探知factorial具有下述属性:
dir(factorial)
['__annotations__', '__call__', '__class__', '__closure__', '__code__', '__defaults__', '__delattr__', '__dict__',
 '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__get__', '__getattribute__', '__globals__', '__gt__',
 '__hash__', '__init__', '__init_subclass__', '__kwdefaults__', '__le__', '__lt__', '__module__', '__name__', '__ne__',
 '__new__', '__qualname__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__str__',
 '__subclasshook__']

其中大多数属性是Python对象共有的.
与用户定义的常规类一样,函数使用__dict__属性存储赋予它的用户属性.这相当于一种基本形式的注解.一般来说,为函数随意赋予属性不是很常见的做法.
但是Django框架这么做了.
"""


class C: pass  # 创建一个空的用户定义的类


obj = C()  # 创建一个实例


def func(): pass  # 创建一个空函数


print(sorted(set(dir(func)) - set(dir(obj))))
# 计算差集,然后排序,得到类的实例没有而函数有的属性方法.
# ['__annotations__', '__call__', '__closure__', '__code__', '__defaults__',
# '__get__', '__globals__', '__kwdefaults__', '__name__', '__qualname__']
"""
__annotations__ dict            参数和返回值的注解
__call__        method-wrapper  实现()运算符;即可调用对象协议
__closure__     tuple           函数闭包,即自由变量的绑定(通常是None)
__code__        code            编译成字节码的函数元数据和函数定义体
__defaults__    tuple           形式参数的默认值
__get__         method-wrapper  实现只读描述符协议
__globals__     dict            函数所在模块中全局变量
__kwdefaults__  dict            仅限关键字形式参数的默认值
__name__        str             函数名称
__qualname__    str             函数的限定名称
"""

# ------------------------------------从定位参数到仅限关键字参数------------------------------------
"""
Python最好的特性之一是提供了极为灵活的参数处理机制,而且Python3进一步提供了仅限关键字参数.与之密切相关的是,
调用函数时使用*和**展开可迭代对象,映射到单个参数.

示例:tag函数用于生成HTML标签;使用名为cls的关键字参数传入class属性,这是一种变通方法,因为class是Python的关键字
"""


def tag(name, *content, cls=None, **attrs):
    if cls is not None:
        attrs['class'] = cls
    if attrs:
        attr_str = ''.join(' %s=%s' % (attr, value) for attr, value in sorted(attrs.items()))
    else:
        attr_str = ''
    if content:
        return '
'.join('<%s%s>%s</%s>' % (name, attr_str, c, name) for c in content)
    else:
        return '<%s%s />' % (name, attr_str)


print(tag('br'))  # <br />  传入单个定位参数,生成一个指定名称的空标签.
print(tag('p', 'hello'))  # <p>hello</p>  第一个参数后面的任意个参数会被*content捕获,存入一个元组.
print(tag('p', 'hello', 'world'))  # <p>hello</p>  <p>world</p>
print(tag('p', 'hello', id=33))  # <p id=33>hello</p>  tag函数签名中没有明确指定名称的关键字参数会被**attrs捕获,存入一个字典.
print(tag('p', 'hello', 'world',
          cls='sidebar'))  # <p class=sidebar>hello</p>  <p class=sidebar>world</p>  cls参数只能作为关键字参数传入.
print(tag(content='testing', name='img'))  # <img content=testing /> 调用tag函数时,即便第一个定位参数也能作为关键字参数传入.
my_tag = {'name': 'img', 'title': 'Sunset Boulevard', 'src': 'sunset.jpg', 'cls': 'framed'}
# 在my_tag前面加上**,字典中的所有元素作为单个参数传入,同名键会绑定到对应的具名参数上,余下的则被**attrs捕获.
print(tag(**my_tag))  # <img class=framed src=sunset.jpg title=Sunset Boulevard />

"""
仅限关键字参数是Python3新增的特性,在上述示例中,cls参数只能通过关键字参数指定,它一定不会捕获未命名的定位参数.
定义函数时若想指定仅限关键字参数,要把它们放到前面有*的参数后面.如果不行支持数量不定的定位参数,但是想支持仅限关键字参数,在签名中放一个*.
"""


def f(a, *, b):
    """
    注意,仅限关键字参数不一定要有默认值.
    """
    return a, b


print(f(1, b=2))  # (1, 2)

# ------------------------------------获取关于参数的信息------------------------------------
"""
下面以一个Web框架中示例为引子.HTTP微框架Bobo中有个使用函数内省的好例子.
"""
import bobo


@bobo.query('/')
def hello(person):
    """
    bobo.query装饰器会把一个普通的函数与框架的请求处理机制集成起来.Bobo会内省hello函数,
    发现它需要一个名为person的参数,然后从请求中获取哪个名称对应的参数,并将其传给hello函数,
    因此程序员根本不用触碰请求对象.
    """
    return 'Hello %s!' % person


"""
安装Bobo,然后启动开发服务器,执行上面的脚本,访问http://localhost:8080/看到的信息是'Missing form variable person',HTTP状态码是403.
这是因为,Bobo知道调用hello函数必须传入person参数,但是在请求中找不到同名参数.

在shell会话中使用curl展开这个行为:
>curl -i http://localhost:8080/
    HTTP/1.0 403 Forbidden
    Date: Sun, 28 Jun 2020 06:56:26 GMT
    Server: WSGIServer/0.2 CPython/3.6.8
    Content-Type: text/html; charset=UTF-8
    Content-Length: 103
    
    <html>
    <head><title>Missing parameter</title></head>
    <body>Missing form variable person</body>
    </html>

>curl -i http://localhost:8080/?person=Jim
    HTTP/1.0 200 OK
    Date: Sun, 28 Jun 2020 06:56:40 GMT
    Server: WSGIServer/0.2 CPython/3.6.8
    Content-Type: text/html; charset=UTF-8
    Content-Length: 10
    
    Hello Jim!

Bobo是怎么知道函数需要哪个参数的呢?它又是怎么知道参数有没有默认值呢?
函数对象有个__default__属性,它的值是一个元组,里面保存着定位参数和关键字参数的默认值.仅限关键字参数的默认值在__kwdefaults__属性中.
然而,参数的名称在__code__属性中,它的值是一个code对象引用,自身也有很多属性.
为了说明这些属性的用途,下面定义一个clip函数,然后再审查它.
"""


def clip(text, max_len=80):
    """
    在max_len前面或后面的第一个空格处截断文本
    """
    end = None
    if len(text) > max_len:
        # 返回字符串最后一次出现的位置,如果没有匹配项则返回-1,第2个参数是开始查找的位置,第3个参数是结束查找位置
        space_before = text.rfind(' ', 0, max_len)
        if space_before >= 0:
            end = space_before
        else:
            space_after = text.rfind(' ', max_len)
            if space_after >= 0:
                end = space_after
        if end is None:  # 没有找到空格
            end = len(text)
        return text[:end].rstrip()


print(clip.__defaults__)  # (80,)
print(clip.__code__)  # <code object clip at 0x0000016531806270, file ..., line 280>
print(clip.__code__.co_varnames)  # ('text', 'max_len', 'end', 'space_before', 'space_after')
print(clip.__code__.co_argcount)  # 2
"""
可以看出,这种组织信息的方式并不是最遍历的.参数名称在__code__.co_varnames中,不过里面还有函数定义体中创建的局部变量.
因此,参数名称是前N个字符,N的值由__code__.co_argcount确定.顺便说一下,这里不包含前缀为*或**的变长参数.
参数的默认值只能通过它们在__defaults__元组中的位置确定,因此要从后面向前面扫描才能把参数和默认值对应起来.
在这个示例中clip函数有两个参数,text和max_len,其中一个有默认值,即80,因此它必然属于最后一个参数,即max_len.这很不舒服.
幸好我们有更好的方式----使用inspect模块
"""
from inspect import signature

sig = signature(clip)
print(str(sig))  # (text, max_len=80)
for name, param in sig.parameters.items():
    print(param.kind, ':', name, '=', param.default)
"""
POSITIONAL_OR_KEYWORD : text = <class 'inspect._empty'>
POSITIONAL_OR_KEYWORD : max_len = 80
"""
"""
这样就好多了,inspect.signature函数返回一个inspect.Signature对象,它有一个parameters属性,这是一个有序映射,
把参数名和inspect.Parameter对象对应起来.各个Parameter属性也有自己的属性,例如name,default和kind.
特殊的inspect._empty值表示没有默认值,考虑到None是有效的默认值.
kind属性的值是_ParameterKind类中的5个值之一:
POSITIONAL_OR_KEYWORD   可以通过定位参数和关键字参数传入的形参
VAR_POSITIONAL          定位参数元组
VAR_KEYWORD             关键字参数字典
KEYWORD_ONLY            仅限关键字参数
POSITION_ONLY           仅限定位参数,目前,Python声明函数的句法不支持.

inspect.Signature对象有个bind方法,它可以把任意个参数绑定到签名中的形参上,所用的规则与实参到形参的匹配方式一样.
框架可以使用这个方法在正真调用函数前验证参数:
"""
sig = signature(tag)
my_tag = {'name': 'img', 'title': 'Sunset Boulevard', 'src': 'sunset.jpg', 'cls': 'framed'}
bound_args = sig.bind(**my_tag)  # 把一个承诺书字典传给.bind()方法.
print(
    bound_args)  # <BoundArguments (name='img', cls='framed', attrs={'title': 'Sunset Boulevard', 'src': 'sunset.jpg'})>
for name, value in bound_args.arguments.items():
    # 迭代bound_args.arguments(一个OrderedDict对象)中的元素,显示参数的名称和值.
    print(name, '=', value)
"""
name = img
cls = framed
attrs = {'title': 'Sunset Boulevard', 'src': 'sunset.jpg'}
"""
# del my_tag['name']  # 把必须指定的参数name从my_tag中删除
# bound_args = sig.bind(**my_tag)
# TypeError: missing a required argument: 'name'  抛出TypeError.
"""
这个示例在inspect模块的帮助下,展示了Python数据模型把实参绑定给函数调用中的形参的机制,这与解释器使用的机制相同.
"""

# ------------------------------------函数注解------------------------------------
"""
Python3提供了一种句法,用于为函数声明中的参数和返回值附加元数据.
"""


def clip(text: str, max_len: 'int > 0' = 80) -> str:
    """
    与原clip函数唯一的区别在第一行
    在max_len前面或后面的第一个空格处截断文本
    """
    end = None
    if len(text) > max_len:
        # 返回字符串最后一次出现的位置,如果没有匹配项则返回-1,第2个参数是开始查找的位置,第3个参数是结束查找位置
        space_before = text.rfind(' ', 0, max_len)
        if space_before >= 0:
            end = space_before
        else:
            space_after = text.rfind(' ', max_len)
            if space_after >= 0:
                end = space_after
        if end is None:  # 没有找到空格
            end = len(text)
        return text[:end].rstrip()


"""
函数声明中的各个参数可以在:之后增加注解表达式.如果参数有默认值,注解放在参数名和=号之间.如果想注解返回值,在)和函数声明末尾的:之间添加->和一个表达式.
那个表达式可以是任何类型.注解中最常见的类型是类(如str或int)和字符串(如'int > 0').
注解不会做任何处理!只是存储在函数的__annotations__属性(一个字典)中.
"""
print(clip.__annotations__)  # {'text': <class 'str'>, 'max_len': 'int > 0', 'return': <class 'str'>}
"""
'return'键保存的是返回值注解,即函数声明中以->标记的部分.
Python对注解所做的唯一的事情是,把它们存储在函数的__annotations__属性里,仅此而已.换句话说,注解对Python解释器没有任何意义.
注解只是元数据,可以供IDE,框架和装饰器等工具使用.
"""
sig = signature(clip)
print(sig.return_annotation)  # <class 'str'>
for param in sig.parameters.values():
    note = repr(param.annotation).ljust(13)
    print(note, ':', param.name, '=', param.default)
"""
<class 'str'> : text = <class 'inspect._empty'>
'int > 0'     : max_len = 80
"""
"""
signature函数返回一个Signature对象,它有一个return_annotation属性和一个parameters属性,后者是一个字典,把参数名映射到Parameter对象上.
每个Parameter对象自己也有annotation属性.
在未来,Bobo等框架可以支持注解,并进一步自动处理请求.例如,使用price:float注解的参数可以自动把查询字符串转换成函数期待的float类型.
"""

# ------------------------------------支持函数式编程的包------------------------------------
"""
虽然Python的目标不是编程函数式编程语言,但是得益于operator和functools等包的支持,函数式编程风格也可以信手拈来.
operator模块:
在函数式编程中,经常需要把运算符当作函数使用.例如,不使用递归计算阶乘.求和可以使用sum函数,但是求积则没有这样的函数.
我们可以使用reduce函数,但是需要一个函数计算序列中两个元素之积.
"""
from functools import reduce


def fact(n):
    return reduce(lambda a, b: a * b, range(1, n + 1))


print(fact(5))  # 120
"""
operator模块为多个算数运算符提供了对应的函数,从而避免编写lambda a, b: a * b这种平凡的匿名函数.
"""
from operator import mul


def fact(n):
    return reduce(mul, range(1, n + 1))


print(fact(5))  # 120
"""
operator模块还有一类函数,能替代从序列中取出元素或读取对象属性的lambda表达式:
itemgetter常见用途: 根据元组的某个字段给元组列表排序.
"""
from operator import itemgetter
metro_areas = [
    ("Tokyo", "JP", 36.933, (31.689722, 139.691667)),
    ("Delhi NCR", "IN", 36.933, (32.689722, 139.691667)),
    ("Mexico City", "MX", 36.933, (33.689722, 139.691667)),
    ("New York-Newark", "US", 36.933, (34.689722, 139.691667)),
    ("Sao Paulo", "BR", 36.933, (35.689722, 139.691667)),
]
# itemgetter(1)的作用与lambda fields: fields[1]一样
for city in sorted(metro_areas, key=itemgetter(1)):
    print(city)

cc_name = itemgetter(1, 0)
# 如果把多个参数传给itemgetter,它构建的函数会返回提取的值构成的元组.
for city in metro_areas:
    print(cc_name(city))
    """
    ('JP', 'Tokyo')
    ('IN', 'Delhi NCR')
    ('MX', 'Mexico City')
    ('US', 'New York-Newark')
    ('BR', 'Sao Paulo')
    """
"""
itemgetter使用[]运算符,因此它不仅支持序列,还支持映射和任何实现__getitem__方法的类.

attrgetter和itemgetter作用类似,它创建的函数根据名称提取对象的属性.如果把多个属性名传给attrgetter,它也会返回提取的值构成的元组.
此外,如果参数名中包含.(点号),attrgetter会深入嵌套对象,获取指定的属性.
"""
from collections import namedtuple
LatLong = namedtuple('LatLong', 'lat long')  # 使用namedtuple定义LatLong
Metropolis = namedtuple('Metropolis', 'name cc pop coord')  # 再定义Metropolis
# 使用Metropolis实例构建metro_areas列表,注意,我们使用嵌套的元组拆包提取(lat, long),然后使用它们构建LatLong,作为Metropolis的coord属性.
metro_areas = [Metropolis(name, cc, pop, LatLong(lat, long)) for name, cc, pop, (lat, long) in metro_areas]
print(metro_areas[0])  # Metropolis(name='Tokyo', cc='JP', pop=36.933, coord=LatLong(lat=31.689722, long=139.691667))
print(metro_areas[0].coord.lat)  # 31.689722
from operator import attrgetter
name_lat = attrgetter('name', 'coord.lat')
for city in sorted(metro_areas, key=attrgetter('coord.lat')):
    print(name_lat(city))
    """
    ('Tokyo', 31.689722)
    ('Delhi NCR', 32.689722)
    ('Mexico City', 33.689722)
    ('New York-Newark', 34.689722)
    ('Sao Paulo', 35.689722)
    """
"""
下面是operator模块中定义的部分函数,省略了以_开头的名称,因为它们基本上是实现细节:
import operator
print([name for name in dir(operator) if not name.startswith('_')])
['abs', 'add', 'and_', 'attrgetter', 'concat', 'contains', 'countOf',
 'delitem', 'eq', 'floordiv', 'ge', 'getitem', 'gt', 'iadd', 'iand',
 'iconcat', 'ifloordiv', 'ilshift', 'imatmul', 'imod', 'imul', 'index',
 'indexOf', 'inv', 'invert', 'ior', 'ipow', 'irshift', 'is_', 'is_not',
 'isub', 'itemgetter', 'itruediv', 'ixor', 'le', 'length_hint', 'lshift',
 'lt', 'matmul', 'methodcaller', 'mod', 'mul', 'ne', 'neg', 'not_', 'or_',
 'pos', 'pow', 'rshift', 'setitem', 'sub', 'truediv', 'truth', 'xor']

这个52个名称中大部分的作用不言而喻.以i开头,后面是另一个运算符的那些名称(如iadd,iand等),对应的是增量赋值运算符(+=,&=).
如果第一参数是可变的,那么这些运算符函数会就地修改它;否则,作用与不带i的函数一样,直接返回运算结果.
"""

"""
methodcaller:
它会自行创建函数.methodcaller创建的函数会在对象上调用参数指定的方法.
"""
from operator import methodcaller
s = 'The time has come'
upcase = methodcaller('upper')
print(upcase(s))  # THE TIME HAS COME
hiphenate = methodcaller('replace', ' ', '_')
print(hiphenate(s))  # The_time_has_come
"""
第二个测试表明,methodcaller还可以冻结某些参数,也就是部分应用.这与functools.parial函数的作用类似.
"""

# ------------------------------------使用functools.partial冻结参数------------------------------------
"""t
functools模块提供了一系列高阶函数,其中最为人熟知的或许是reduce.余下的函数中,最有用的是partial及其变体partialmethod.
functools.partial这个高阶函数用于部分应用一个函数.部分应用是指,基于一个函数创建一个新的可调用对象,把原函数的某些参数固定.
使用这个函数可以把接受一个或多个参数的函数改编成需要回调的API,这样参数更少.
"""
from operator import mul
from functools import partial
triple = partial(mul, 3)  # 使用mul创建triple函数,把第一个定位参数定为3.
print(triple(7))  # 21
print(list(map(triple, range(1, 10))))  # [3, 6, 9, 12, 15, 18, 21, 24, 27]
"""
partial的第一个参数是一个可调用对象,后面跟着任意个要绑定的定位参数和关键字参数.
"""
picture = partial(tag, 'img', cls='pic-frame')
print(picture(src='test.jpg'))  # <img class=pic-frame src=test.jpg />
print(picture)  # functools.partial(<function tag at 0x00000241EF3F6A60>, 'img', cls='pic-frame')
print(picture.func)  # <function tag at 0x00000241EF3F6A60>
print(picture.args)  # ('img',)
print(picture.keywords)  # {'cls': 'pic-frame'}
"""
partialmethod函数的作用与partial一样,不过是用于处理方法的.
"""
原文地址:https://www.cnblogs.com/zyyhxbs/p/13204104.html