七、学习py:函数

七、学习py:函数

人嘛了,这篇写到一半关机忘记保存了。

就简略写啦o(╥﹏╥)o

1.初识函数

def dhk():
    print("joker!")
#定义函数
dhk()#执行函数

1.1扩充功能,发送邮件

import smtplib
from email.mime.text import MIMEText
from email.utils import formataddr


def send_email():
    # 1.邮件内容配置
    # 邮件文本
    msg = MIMEText("越江才学会用py发送邮件。", "html", "utf-8")
    # 邮件上显示的发信人
    msg['From'] = formataddr(["越江", "自己的邮箱"])


    # 邮件上显示的主题
    msg['Subject'] = "猪哥好"

    # 2.发送邮件
    server = smtplib.SMTP_SSL("smtp.qq.com#发信服务器")
    server.login("#自己的邮箱", "#授权码")
    server.sendmail("#自己的邮箱", "#对面的邮箱", msg.as_string())
    server.quit

send_email()#执行函数

2.参数

在上述发邮件功能中,如果要对多人进行发送操作,一个一个发送略显复杂,所以可以将对方的邮件设置为参数,这样就能极大的提高代码的利用率。

def dhk(a):#这里的a为形式参数
    print("a")
dhk(joker)#传入之后就是实际参数

2.1动态参数

2.1.1*(只能通过地址传递)

def func(*args):
    print(args)
func(1,12,123,123,321)
#输出(1, 12, 123, 123, 321),元组类型

2.1.2**(只能通过名称传递)

def func(**kwargs):
    print(kwargs)
func(n1 = "dhk",n2 = 'joker')#{'n1': 'dhk', 'n2': 'joker'}
#输出字典

2.1.3*&**

def func(*args,**kwargs):
    print(args,kwargs)
func(123,n1="nihao")#(123,) {'n1': 'nihao'}
func(123)#func(123)

2.1.4知识补充

在定义函数时可以用**和 *,其实在执行函数时,也可是使用。

形参固定,实参用**和 *

def func(a1,a2):
    print(a1,a2)
func(11,22)#11 22
func(*[11,22])#11 22

func(a1=1,a2=2)#1 2
func(**{"a1":1,"a2":2})#1 2

形参中用了* ,实参也用了 和 *

2.2默认参数

def func(a,b=123):#这里的b就是默认参数,可以输入可以不输入,如果没有输入就是默认值。
    print(a,b)

2.2.1参数的默认值【面试题】有坑

这个知识点在面试题中出现的概率比较高,但真正实际开发中用的比较少。

def func(a1,a2=18):
    print(a1,a2)

原理:python在创建函数(未执行)时,如果发现函数的参数中有默认值,则在函数内部会创建一块区域并维护这个默认值。

执行函数未传值时,则让a2指向函数维护的那个值的地址。

func("dhk")

执行函数传值时,则让a2指向新传入的值的地址

func("adhk",123)

在特定情况【默认参数的值是可变类型(列表,字典,集合)】&【函数内部会修改这个值】下,参数的默认值 有坑!超级大坑!!!!

小坑

#在内存中会维护一块区域,维护[],而这个地址在每次函数执行的时候不会重新创建,在加载的时候就创建好了。所以每一次执行的时候都是使用的默认值的维护地址,就导致了下面的情况!!!
def func(a1,a2=[]):
    a2.append(666)
    print(a1,a2,id(a2))

func(123)#123 [666] 2791287502016
func(321)#321 [666, 666] 2791287502016
func(33,[77,66])#33 [77, 66, 666] 2791287504768
func(300)#300 [666, 666, 666] 2791287502016

大坑

def func(a1,a2=[1,2]):
    a2.append(a1)
    return(a2)

v1 = func(10)
print(v1)#[1, 2, 10]
v2 = func(20)
print(v2)#[1, 2, 10, 20]
v3 = func(30,[11,22])
print(v3)#[11, 22, 30]
v4 = func(40)
print(v4)#[1, 2, 10, 20, 40]

深坑

#在这里由于先执行的函数,后输出的内容。但是v124都指向的是同一个内存地址,内存地址又发生了一系列的变化,所以就变成了这样。
def func(a1,a2=[1,2]):
    a2.append(a1)
    return(a2)
v1 = func(10)
v2 = func(20)
v3 = func(30,[11,22])
v4 = func(40)

print(v1)#[1, 2, 10, 20, 40]
print(v2)#[1, 2, 10, 20, 40]
print(v3)#[11, 22, 30]
print(v4)#[1, 2, 10, 20, 40]

2.5知识补充

1.**必须放在 *的后面

2.参数和动态参数混合时,动态参数只能放在最后。

3.默认值参数和动态参数同时存在。

2.6参数的补充!

2.6.1参数内存地址相关【面试题】

在开始将参数的内存地址相关之前,我们来学习一个技能:

查看某个值在内存中的地址。

v1 = "dhk"
addr = id(v1)
print(addr)#2780843422256

使用案例,如果两个东西(有些东西是两个东西但是内容是一样的)指向的是同一个地址,那他们就是一个东西。(听君一席话,如听一席话)

注意:函数执行传参时,传递的是内存地址。

v1 = "dhk"
addr = id(v1)
print(addr,id(addr))#1971302903920 1971302979760 内存地址是一样的

2.6.2内存地址的利用

def func(data):
    data.append(666)
data_list = [11,22,33]
func(data_list)
print(data_list)#[11, 22, 33, 666]
#在这个函数内部修改的是data,但是由于data和data_list的内存地址是一样的,所以修改了data的内容其实也就修改了,data_list。
#但是能修改的必须是可以修改的类型

3.函数返回值

def func(a,b):
    return (a+b)

print(func(a=1,b=123))#124

注意:函数在碰到return的时候就会立即停止

3.1函数的返回值是内存地址

def func():
    data = [11,22,33]
    print(id(data))#2831053370624
    return data
v1 = func()
print(id(v1))#2831053370624

但是在第二遍执行这个函数的时候,内存地址是会变的,因为在函数执行完之后,其中参数是会被抹去的,重新执行函数又会重新创建一个新的内存地址。

def func():
    data = [11,22,33]
    return data
v1 = func()
print(id(v1))#1658473428224
v2 = func()
print(id(v2))#1658473713088

4.函数和函数名

函数名其实就是一个变量,这个变量只不过指代的函数而已。

注意:函数必须先定义才能被调用。(解释型语言)(如果是编译型语言就不用。)

4.1函数做元素

既然函数就相当于是一个变量,那么在列表等元素中是否可以把函数当做元素呢?

def func():
    return(123)

data_list = ["dhk","func",func,func()]
print(data_list[0])#dhk
print(data_list[1])#func
print(data_list[2])#<function func at 0x0000023E7A43F040>
print(data_list[3])#123

注意:函数同时也可被哈希,所以函数名同志也可以当做集合的元素、字典的键。

掌握这个只是之后,对后续的开发有很大的帮助。

案例1:要开发一个类似微信的功能。

def send_message():
    '''发送消息'''
    pass
def send_image():
    '''发送图片'''
    pass
def send_file():
    '''发送文件'''
    pass
#把函数名和其对应的数字存入字典中。
func_dict = {
    "1":send_message,#因为input获取的是字符串,所以这里也要是字符串
    "2":send_image,
    "3":send_file}

print("欢迎使用越江系统")
print("请选择:1.发送消息2.发送图片3.发送文件")
choice = input("请输入选择的序号")
func = func_dict.get(choice)
if not func:#如果键不存在返回none,加个not 就是True
    print("输入错误")
else:
    #执行函数
    func()

案例2:某个特定情况需要连续执行发消息,发图片,发文件

def send_message():
    '''发送消息'''
    pass
def send_image():
    '''发送图片'''
    pass
def send_file():
    '''发送文件'''
    pass
#把函数名存入列表中
func_List = [send_message,send_image,send_file]


for func in func_List:#使用for循环
    func()

4.2函数名赋值

如果我们将函数名重新赋值,那么函数名不在指代函数,而是指代你赋值的那个东西。

def func():
    print(123)
func()
func = 1
print(func)

注意:由于函数名被重新定义之后,就会变成新定义的值,所以大家在自定义函数的时候,不要与python内置的函数重合。

4.3函数名做参数和返回值

1.参数

def plus(num):
   return num+100
def handler(func):
    res = func(10)
    msg = f"执行func,并且获取到的结果为:{res}"
    print(msg)
    
headler(plus)

2.返回值

def plus(num):
   return num+100

def handler():
    print("执行handler")
    return plus

result = handler()
data = result(20)
print(data)

5.作用域

作用域,可以理解为一块空间,这块空间的数据是可以共享的。通俗点来说,作用域就类似于一个房子,房子中的东西归里面所有人共享,其他房子的人无法获取。

5.1函数为作用域

python以函数为作用域,所以在函数内创建的所有数据,可以在次函数中被使用,无法在其他函数中被使用。

def func():
    age = "dhk"
    print(name)
    
def func2():
    age = 123
    print(age)
    
func()
func2()

5.2全局和局部

一般来说全局变量的变量名都是大写。

在外面定义的变量为全局变量,一般在函数里面定义的变量为局部变量。

在局部作用域调用变量的时候,首先会在自己的作用域寻找变量,如果找不到才会在上一级作用域中寻找变量。

实例1:在局部作用于中读取全局作用域的变量。

NAME = "dhk"
AGE = 123


def func():
    asd = "dsa"
    AGE = "nihao"
    print(asd)
    print(AGE)
    print(NAME)

func()
print(AGE)
'''
输出
dsa
nihao
dhk
123
'''

5.3global关键字

在局部作用于默认对全局变量只能进行:读取和修改内部元素(可变类型),无法对全局变量进行重新赋值。

但是如果想要在局部作用域中对全局变量重新赋值,则可以基于global关键字实现:

dhk = "joker"
def func():
    global dhk#将全局变量引入局部作用域
    dhk = "动肝"
func()
print(dhk)#动肝

6.函数的嵌套

python中以函数为作用域,在作用域中定义的相关数据只能被当前作用于或子作用域使用。

6.1函数在作用域中

其实,函数也是定义在作用域中的数据,在执行函数的时候,也同样遵循:优先在自己的作用域中寻找,没有则向上寻找

6.2函数定义的位置

def func():
    print("外面")

def inside():
    def func():
        print("里面")
    func()
func()#外面
inside()#里面

其实,大多数情况下我们都会将函数定义在全局,不会嵌套定义函数。不过,当我们定义一个函数去实现某功能,想要将内部功能拆分成N个函数,又担心这n个函数放在全局会与其他函数冲突时(尤其是多人协同开发),可以选择使用函数的嵌套。

6.3嵌套引发的作用域问题

基于内存和执行过程分析作用域

name = "dhk"
def run():
    name = "joker"
    def inner():
        print(name)
    inner()
run()#执行函数就创建了一个作用域.
#输出joker

7.闭包

简而言之就是讲数据封装在一个包中,使用时再去里面取。(本质上,闭包是基于函数嵌套搞出来一个中特殊嵌套)

闭包应用场景1:封装数据防止污染全局

name = "dhk"
def func(age):
    print(name,age)
    def func2():
        print(name,age)
        def func3:
            print(name,age)

闭包应用场景2:封装数据到一个包里,使用时再取

def task(arg):
    def inner():
        print(arg)
    return inner

v1 = task(11)#创建了一个空间储存了arg,这个时候的v1就是inner
v2 = task(22)
v1()#11
v2()#22
def task(arg):
    def inner():
        print(arg)
    return inner

inner_list = []

for val in [11,22,33]:
    inner_list.append(task(val))#虽然返回的都是inner但是每一个inner的包都是不一样的

inner_list[0]()#11
inner_list[1]()#22
inner_list[2]()#33

8.装饰器

现在给你一个函数,在不修改函数源码的前提下,实现在函数执行前和执行后分别输入before和after

def func():
    print("我是一个func函数")
    value = (11,22,33,44)
    return value
result = func()
print(result)

8.1第一回合

def func():
    print("我是一个func函数")
    value = (11,22,33,44)
    return value
#创建了一个闭包,维护了origin内存地址
def outer(origin):
    def inner():
        print("before")
        res = origin()#调用原来func的函数
        print("after")
        return res
    return inner

func = outer(func)#在这里func被重新定义为inner函数,原来的func则被封存在origin的内存地址中
result = func()
print(result)
'''
输出
before
我是一个func函数
after
(11, 22, 33, 44)
'''

8.2第二回合

python中支持特殊语法,在某个函数上方使用:

@函数名
def xxx():
    pass
#python内部会自动执行 函数名(xxx),执行完之后,在将结果赋值给xxx。
#xxx = 函数名(xxx)
def outer(origin):
    def inner():
        print("before")
        res = origin()
        print("after")
        return res
    return inner
@outer #func = outer(func)
def func():
    print("我是一个func函数")
    value = (11,22,33,44)
    return value

reslut = func()
print(reslut)
'''
before
我是一个func函数
after
(11, 22, 33, 44)

'''

8.3第三回合

更改需求,现在有三个fanc函数,func1,func2,func3,都在前面和后面分别输出before 和after

def outer(origin):
    def inner():
        print("before")
        res = origin()
        print("after")
        return res
    return inner
@outer
def func1():
    print("我是一个func1函数")
    value = (11,22,33,44)
    return value
@outer
def func2():
    print("我是一个func2函数")
    value = (11,22,33,44)
    return value
@outer
def func3():
    print("我是一个func3函数")
    value = (11,22,33,44)
    return value

8.4支持优化n个参数

如果函数存在参数

def outer(origin):
    def inner(*args,**kwargs):
        print("before")
        res = origin(*args,**kwargs)
        print("after")
        return res
    return inner
@outer #func = outer(func)
def func(a1):
    print("我是一个func函数")
    print(a1)
    value = (11,22,33,44)
    return value

reslut = func(123)
print(reslut)
'''
before
我是一个func函数
123
after
(11, 22, 33, 44)

'''

总结

装饰器实例

def outer(origin):
    def inner(*args,**kwargs):
        #执行前
        res = origin(*args,**kwargs)
        #执行后
        return res
    return inner
@outer #func = outer(func)
def func(a1):
    pass

func()

在不修改原函数内部和调用方式的情况下,使用装饰器。

9.匿名函数

匿名函数,是基于lambda表达式实现定义一个可以没有名字的函数,例如:

data_list = [lambda x:x+100,lambda x:x+110,lambda]
print(data_list[0])

9.1定义

基于lambda定义的函数格式为:lambda 参数 :函数体

参数,支持任意参数

lambda x:函数体
lambda x1,x2:函数体
lambda *args,**kwargs:函数体

函数体,只能支持单行代码

lambda xx:x + 100

返回值,默认将函数体单行代码执行的结果返回给执行函数的地方。

func = lambda x:x+100
v1 = func()
print(v1)#110

10.生成器

生成器是由函数+yield关键字创造出来的写法,在特定情况下,用它可以帮助我们节省内存。

def func():
    print(111)
    yield 1#可以防止任何类型的数据,出现yield关键字之后,这个函数就变成了生成器函数。

执行生成器函数的时候,函数体默认不会被执行;返回的是一个生成器对象。

下面结合案例说明生成器函数的作用

def func():
    print("1")
    yield 123
    print("2")
    yield 234
    print("3")
    yiled 345
v1 = func()#执行生成器函数时,函数体默认不会被执行,返回的是一个生成器对象。
print(v1)

n1 = next(v1)#在next中执行生成器对象,进入生成器函数并执行其中的代码
print(n1)

n1 = next(v1)
print(n1)

n1 = next(v1)
print(n1)
'''
<generator object func at 0x000001F71903CD60>
1  执行第一次的结果
123返回第一个关键词后面的内容
2   执行第二次的结果
234   返回第二个关键词的内容
3   执行第三次的结果
345   返回第三个关键词的内容
'''

10.1应用场景

假设要让你生成300w个随即的四位数,并打印出来。

1.在内存中一次创建300w个。

2.动态创建,用一个创建一个。

import random
val = random.range(1000,9999)
print(val)
import random
data_list = []
for item in range(3000000):
    val = random.randint(1000,9999)
    data_list.append(val)
#在使用时,从列表中取即可。

而使用生成器的话

import random
def num(max_count):
    counter = 0
    while counter < max_count:
        yield random.randint(1000,9999)
        counter += 1

data_list = num(3000000)
n1 = next(data_list)
n2 = next(data_list)#一边使用,一边生成,节省了大量的内存。

所以在以后遇到,先生成再用,一边生成再用的时候,要想到生成器。

10.2基于for循环

data = func()
for item in data:
    print(item)

11.推导式

推导式,是python中提供的一个非常方便的功能,可以让我们通过一行代码实现创建,list,dict,tuple,set的同时初始化一些值。

列表

num_list = [i for i in range(10)]#创建了零到九的一个列表
print(num_list)
#[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]

字典

num_list = {i:i for in range(10)}
num_list2 = {i:(i,11) for i in range(10)}

推导式的一个拓展(除去末尾的mp4)

image-20220103205854535


转载请注明来源,欢迎对文章中的引用来源进行考证,欢迎指出任何有错误或不够清晰的表达。后续可能会有评论区,不过也可以在github联系我。