python基础-函数

接着上周的文档,继续分享python基础相关内容。

1.函数定义

函数:将一段有规律的、可重复使用的代码定义成函数,从而达到一次编写、多次调用的目的

a = [1,5,10,22,33,56,78]
    sum = 0
    for i in a:
        sum = sum + i
    print(sum)

    def sum_list(a):
        sum = 0
        for i in a:
            sum = sum + i
        return sum
    a = [1,5,10,22,33,56,78]
    print(sum_list(a))

2.关键字参数和默认值

2.1关键字参数
  • 位置参数:按顺序为每个参数指定参数值
  • 关键字参数(命名参数):按参数名为参数指定参数值
def  info(name, age, height):
    print('name:', name)
    print('age:', age)
    print('height:', height)

info('w', 25, 175)    # 位置参数

结果:

name: w
age: 25
height: 175

info(age=25, name='w', height=175)    # 关键字参数(命名参数),优势:1.不需要按顺序;2.可读性高

结果:

name: w
age: 25
height: 175

info('w', height=175, age=25)    # 混合使用,位置参数在前,关键字参数在后

结果:

name: w
age: 25
height: 1752

2.2参数默认值
  • 关键字参数(命名参数):按参数名为参数指定参数值

指定默认参数在后

def info(age, name='w'):
    print('name:', name)
    print('age:', age)
    
info(25)

结果:

name: w
age: 25

info(18, 'k')

结果:
name: k
age: 18

def info(name='w', age=25):
    print('name:', name)
    print('age:', age)
info()

结果:

name: w
age: 25

info(age=18)    # 省略前面的参数,则后面的参数需要关键字指定

结果:

name: w
age: 18

3.收集与分配参数(data, *args, **kwargs)

python自定义函数中有两种不定长参数:

第一种是*XXX,在传入额外的参数时可以不用指明参数名,直接传入参数值即可。

第二种是** XXX,这种类型返回的是字典,传入时需要指定参数名

加了一个星号 * 不定长参数会以元组(tuple)的形式导入,存放所有未命名的变量参数。

加了两个星号 ** 的参数会以字典的形式导入,存放已命名的变量参数。

实例一

#coding=utf-8

#第一个是元组形式,第二个是字典形式
def sun(*a, **b):
    print(a)
    print(b)

sun(1, 55258, x=25412, y=5123512)

结果:

(1, 55258)

{‘x’:25412, ‘y’:5123512}

实例二

在a, *b, **c同时出现

#coding=utf-8

#使用不定长参数传固定值,注意,b、c可省略,a不可省略
def fuzhi(a, *b, **c):
    print(a)
    print(b)
    print(c)
fuzhi(853521, 65134, 635263, 45563, 365, x=99, y=999)

结果:


python plot函数怎么画两条线_git

这个实例中,不定长参数b、c可省略,而普通参数a不可省略,假如我们省略a

#coding=utf-8

#使用不定长参数传固定值,注意,b、c可省略,a不可省略
def fuzhi(a, *b, **c):
    print(a)
    print(b)
    print(c)
fuzhi()

结果:


python plot函数怎么画两条线_python plot函数怎么画两条线_02

实例三

普通参数不能写在不定长参数写在后面

#coding=utf-8

def JayChou(a, *b, c):
    print(a)
    print(b)
    print(c)
JayChou(1, 555, 5768, 55451)

运行结果
会出现如下报错,因此,位置不能随意更改


python plot函数怎么画两条线_开发语言_03

!<img src='https://s2.51cto.com/images/blog/202410/20004623_6713e25f5854729436.png?x-oss-process=image/watermark,size_16,text_QDUxQ1RP5Y2a5a6i,color_FFFFFF,t_30,g_se,x_10,y_10,shadow_20,type_ZmFuZ3poZW5naGVpdGk=' align='left'/>

4.递归函数的实现

在python中我们使用函数经常是去调用别的函数,但是递归函数不一样,它是自己去调用自己
递归函数的条件有两个:
1.必须给递归函数一个出口,否则会无限调用,耗费内存
2.对自己的递归条件作大胆假设
下面我们计算数学上经常计算的阶乘,阶加等算法

def scale(s):
    """
    使用递归函数来计算阶乘,阶加等
    :param s:
    :return:
    """
    if s == 1:
        return s
    else:
        return s + scale(s - 1)

print(scale(1))
print(scale(10))
print(scale(100))

scale(3)

3+scale(2)

3+2+scale(1)

3+2+1

结果:


python plot函数怎么画两条线_python_04

递归函数的好处:
1.代码比较少
2.代码界面整洁
递归函数的坏处
1.逻辑有点难理解
2.不写出口时耗费内存

5.函数装饰器的使用(初级班不做要求,感兴趣的自己看一下)

饰器(Decorators)是 Python 的一个重要部分。简单地说:他们是修改其他函数的功能的函数

装饰器的作用:可以增强函数的功能,如插入日志,增加权限校验,事务一致性,缓存等功能,这就是装饰器,使漂亮的姑娘(函数)变得更加漂亮。

5.1初识装饰器


python plot函数怎么画两条线_递归函数_05

python plot函数怎么画两条线_开发语言_06

5.2将函数作为参数传给另一个函数
def hi():
    return "hi yasoob!"

def doSomethingBeforeHi(func):
    print("I am doing some boring work before executing hi")
    print(func())
    print("I am doing some boring work after executing hi")
    
doSomethingBeforeHi(hi)
#outputs:I am doing some boring work before executing hi

#        hi yasoob!
         
#         I am doing some boring work after executing hi
5.3你的第一个装饰器

在上一个例子里,其实我们已经创建了一个装饰器!现在我们修改下上一个装饰器,并编写一个稍微更有用点的程序:

def a_function_requiring_decoration():
    print("I am the function")

a_function_requiring_decoration()
#outputs: "I am the function "

def a_new_decorator(a_func):

    def wrapTheFunction():
        print("before a_func()")
     
        a_func()
     
        print("after a_func()")
     
    return wrapTheFunction

a_function_requiring_decoration = a_new_decorator(a_function_requiring_decoration)
#now a_function_requiring_decoration is wrapped by wrapTheFunction()

a_function_requiring_decoration()
#outputs:before a_func()

#        I am the function

#        after a_func()

这正是 python 中装饰器做的事情!它们封装一个函数,并且用这样或者那样的方式来修改它的行为。现在你也许疑惑,我们在代码里并没有使用 @ 符号?那只是一个简短的方式来生成一个被装饰的函数。这里是我们如何使用 @ 来运行之前的代码:

@a_new_decorator
def a_function_requiring_decoration():
    print("I am the function")

a_function_requiring_decoration()
#outputs: before a_func()

#         I am the function 

#         after a_func()

#the @a_new_decorator is just a short way of saying:
a_function_requiring_decoration = a_new_decorator(a_function_requiring_decoration)

希望你现在对 Python 装饰器的工作原理有一个基本的理解。如果我们运行如下代码会存在一个问题:

print(a_function_requiring_decoration.__name__)
# Output: wrapTheFunction

这并不是我们想要的!Ouput输出应该是"a_function_requiring_decoration"。这里的函数被warpTheFunction替代了。它重写了我们函数的名字和注释文档(docstring)。幸运的是Python提供给我们一个简单的函数来解决这个问题,那就是functools.wraps。我们修改上一个例子来使用functools.wraps:

from functools import wraps

def a_new_decorator(a_func):
    @wraps(a_func)
    def wrapTheFunction():
        print("before a_func()")
        a_func()
        print("after a_func()")
    return wrapTheFunction

@a_new_decorator
def a_function_requiring_decoration():
    print("I am the function")

print(a_function_requiring_decoration.__name__)

# Output: a_function_requiring_decoration
5.4使用场景

应用场景:

1.可以在外层函数加上时间计算函数,计算函数运行时间;

2.计算函数运行次数;

3.可以用在框架的路由传参上;

4.插入日志,作为函数的运行日志;

5.事务处理,可以让函数实现事务的一致性,让函数要么一起运行成功,要么一起运行失败;

6.缓存,实现缓存处理;

7.权限的校验,在函数外层套上权限校验的代码,实现权限校验;

使用场景实例-插入日志

现在我们来看一下装饰器在哪些地方特别耀眼,以及使用它可以让一些事情管理起来变得更简单。

from functools import wraps

def logit(func):
    @wraps(func)
    def with_logging(*args, **kwargs):
        print(func.__name__ + " was called")
        return func(*args, **kwargs)
    return with_logging

@logit
def addition_func(x):
   """Do some math."""
   return x + x


result = addition_func(4)

# Output: addition_func was called
5.5带参数的装饰器

我们回到日志的例子,并创建一个包裹函数,能让我们指定一个用于输出的日志文件。

from functools import wraps

def logit(logfile='out.log'):
    def logging_decorator(func):
        @wraps(func)
        def wrapped_function(*args, **kwargs):
            log_string = func.__name__ + " was called"
            print(log_string)
            # 打开logfile,并写入内容
            with open(logfile, 'a') as opened_file:
                # 现在将日志打到指定的logfile
                opened_file.write(log_string + '\n')
            return func(*args, **kwargs)
        return wrapped_function
    return logging_decorator

@logit()
def myfunc1():
    pass

myfunc1()

# Output: myfunc1 was called

# 现在一个叫做 out.log 的文件出现了,里面的内容就是上面的字符串

@logit(logfile='func2.log')
def myfunc2():
    pass

myfunc2()

# Output: myfunc2 was called

# 现在一个叫做 func2.log 的文件出现了,里面的内容就是上面的字符串
5.6装饰器类

类也可以用来构建装饰器。那我们现在以一个类而不是一个函数的方式,来重新构建logit。

from functools import wraps

class logit(object):
    def __init__(self, logfile='out.log'):
        self.logfile = logfile
        
    def __call__(self, func): # 定义__call__方法,直接实现装饰功能。用@logit修装饰函数时就会执行__call__方法。
        @wraps(func)
        def wrapped_function(*args, **kwargs):
            log_string = func.__name__ + " was called"
            print(log_string)
            # 打开logfile并写入
            with open(self.logfile, 'a') as opened_file:
                # 现在将日志打到指定的文件
                opened_file.write(log_string + '\n')
            # 现在,发送一个通知
            self.notify()
            return func(*args, **kwargs)
        return wrapped_function

    def notify(self):
        # logit只打日志,不做别的
        pass

这个实现有一个附加优势,在于比嵌套函数的方式更加整洁,而且包裹一个函数还是使用跟以前一样的语法:

@logit()
def myfunc1():
    pass

现在,我们给 logit 创建子类,来添加 email 的功能(虽然 email 这个话题不会在这里展开)。

class email_logit(logit):
    '''
    一个logit的实现版本,可以在函数调用时发送email给管理员
    '''
    def __init__(self, email='admin@myproject.com', *args, **kwargs):
        self.email = email
        super(email_logit, self).__init__(*args, **kwargs)
    def notify(self):
        # 发送一封email到self.email
        # 这里就不做实现了
        pass

从现在起,@email_logit 将会和 @logit 产生同样的效果,但是在打日志的基础上,还会多发送一封邮件给管理员。

类当作装饰器的意义

如果装饰器想实现的功能比较少,那我们用普通的装饰器即可。

但如果我们想实现多种功能,会在一个装饰器里写很多代码,显然用普通的装饰器实现起来很费劲,但如果用类装饰器的话,我们可以把各种功能封装在一个个实例方法中,然后__call__方法中调用不同的实例方法