【语法篇】函数进阶

60 阅读6分钟

文章一览

函数也是对象

Python中定义的各种函数,实际上function类的一个实例,例如:

from datetime import datetime
def log(String):
    """return [xxxx-xx-xx xx:xx:xx] String"""
    now = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
    return f"[{now}] {String}"
print(type(log))
<class 'function'>

至于为什么要把Python中的函数视为对象,这里主要是以下两个原因:

  • 函数可以赋值给其他变量
  • 函数可以作为参数使用

函数可以赋值给其他变量使用

from datetime import datetime
def log(String):
    """return [xxxx-xx-xx xx:xx:xx] String"""
    now = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
    return f"[{now}] {String}"
f = log
print(f is log)
print(f("hello"))
True
[2024-09-04 15:23:10] hello

函数可以作为参数使用

from datetime import datetime
def log(String):
    """return [xxxx-xx-xx xx:xx:xx] String"""
    now = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
    return f"[{now}] {String}"
f = log
strings = ["hello", "你好", "hi"]
print(list(map(f, strings)))
['[2024-09-04 15:25:23] hello', '[2024-09-04 15:25:23] 你好', '[2024-09-04 15:25:23] hi']

高阶函数

接受函数或者把函数作为结果返回的函数我们称为高阶函数。常用的高阶函数例如map, filter, sorted

map

常用来进行对可迭代序列执行某个函数。例如:

def add(n):
    return n + 5
m = map(add, range(10, 20))
print(m)
print(list(m))

需要注意的是map函数的返回值是一个map对象,这个对象是无法直接打印在控制台查看的,需要通过listtuple来进行序列化后才能成功打印在控制台。
作用:对一个可迭代序列中的所有元素执行同一个函数,返回结果也是一个可迭代序列

filter

def add(n):
    return n > 15
f = filter(add, range(10, 20))
print(f)
print(list(f))
<filter object at 0x0000025035D1CE20>
[16, 17, 18, 19]

作用: 对一个可迭代序列通过某个函数进行判断,保留所有判断为true的内容

map与filter的现代替代

目前常用列表推导式和生成器表达式来实现对可迭代序列的操作。

def add(n):
    return n + 5
def fit(n):
    return  n > 10

ls = range(0, 15)
L1 = [add(i) for i in ls]
print(L1)
L2 = [i for i in L1 if fit(i)]
print(L2)
[5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19]
[11, 12, 13, 14, 15, 16, 17, 18, 19]

匿名函数

俗称lambda表达式,可也编写一些简单的函数,一般常用来作为高阶函数的参数。

def add(x, y):
    return x + y
f = lambda x, y : x + y
print(add(5, 6))
print(f(5, 6))

编写规范lambda 参数1,参数2,...,参数n : 返回表达式,只适合编写一些简单的函数,因为在lambda表达式中不能出现python语句(如=,if, try),所以无法实现复杂的函数,优势在于可以不用费劲心思去编写一个函数名称,直接通过参数的方式传递给高阶函数

l = range(0, 15)
f = filter(lambda x : x > 5, l)
print(list(f))

可调用对象

可调用对象是指通过调用运算符()进行调用的对象,例如常见的函数。可以通过callable()函数检查对象是否可以被调用。

用户定义的函数

def add(x, y):
    return x + y
print(callable(add))
print(add(5, 6))

通过def定义的函数都是可调用对象

内置函数

s = "123456"
print(callable(len))
print(len(s))

python内置的函数也是可调用对象,例如:len,time.time(time不是可调用对象)

内置方法

l = []
print(callable(l.append))
l.append(5)
print(l)

python通过C语言实现的方法也是可调用的,如dict.get,list.append

类方法

class people:
    def run(self):
        print("我是人类")
p = people()
print(callable(p.run))
p.run()

在类中定义的函数(方法)也是可调用对象。

class people:
    def __init__(self):
        print("__init__方法被调用了")
    def __new__(cls, *args, **kwargs):
        print("__new__方法被调用了")
        mcls = super().__new__(cls)
        return mcls
print(callable(people))
p = people()

python中的类也是可调用对象,执行流程是__new____init__(初始化方法,即便两个初始化方式都没有也可以被调用)

类实例

如果类里面有__call__方法,类的实例也是可以被调用的。

生成器函数

主体中有yield关键字的函数或方法,调用会返回一个生成器对象

原生协程函数

使用async def定义的函数或方法,调用原生协程函数返回一个协程对象。

异步生成器函数

使用async def定义,而且主体中有 yield 关键字的函数或方法,调用异步生成器函数返回一个异步生成器,供async for使用

可以调用的类实例

正常的类实例是不允许调用的,能调用的是类方法,例如:

class people:
    def __init__(self):
        print("__init__方法被调用了")
    def __new__(cls, *args, **kwargs):
        print("__new__方法被调用了")
        mcls = super().__new__(cls)
        return mcls
p = people()
print(callable(p))

但是,这并不是绝对的,如果我们在类中添加一个__call__方法,那么我们就可以实现直接调用类实例的操作。

class people:
    def __init__(self):
        print("__init__方法被调用了")
    def __new__(cls, *args, **kwargs):
        print("__new__方法被调用了")
        mcls = super().__new__(cls)
        return mcls
    def __call__(self, *args, **kwargs):
        print("__call__方法被调用了")
        print(kwargs)
        return "hello"
p = people()
print("=====================")
print(callable(p))
print("=====================")
print(p())
print("=====================")
print(p(x = 10))
__new__方法被调用了
__init__方法被调用了
=====================
True
=====================
__call__方法被调用了
{}
hello
=====================
__call__方法被调用了
{'x': 10}
hello

仅限位置参数和仅限关键字参数

def add(*args, **kwargs):
    print("args:", args)
    print("kwargs", kwargs)

add(5, 6, 7, a=1, b=2, c=3)
print("==============")
add(5, 6, z=7, a=1, b=2, c=3)
print("===============")
# add(k =3 , 5, 6)
args: (5, 6, 7)
kwargs {'a': 1, 'b': 2, 'c': 3}
==============
args: (5, 6)
kwargs {'z': 7, 'a': 1, 'b': 2, 'c': 3}
===============

通过***的解包操作,可以自动帮我们分辨拿到位置参数可选参数(可选参数一定要在位置参数之后),在编写函数中,常用到的也是这种解包方式,但是在某些情况下未必合适。

仅限位置参数

def add(a, b, /, c, d):
    print(a, b, c, d)
# add(a=5, b=6, c=7, d=8)
add(5, 6, 7, 8)
print("=============")
add(5, 6, c=7, d=8)
print("=============")
# print(5, b=6, c=7, d=8)

通过/可以规定哪些参数是只能通过位置传递,在/之前的参数只能通过一个位置一个位置的填充,/之后的参数则可以任意。

仅限关键字参数

def add(a, b, *, c, d):
    print(a, b, c, d)
# add(a = 5, b = 6, 7, d = 8)
add(a = 5, b = 6, c = 7, d = 8)
print("===================")
add(5, 6, c = 7, d = 8)

通过*规定了那些参数只能通过x = y进行传递,在*之前的参数传递方式可以任意,但是在*之后的只能通过关键字传递。

测验

def func(a, b , /, c, d, *, e, f):
    print(a, b, c, d, e, f)

分析这个函数中各个参数的传递方式。