第七章 函数

“”"
函数的需求背景?

eg. 在控制台上打印五行星星

    for i in range(5):
        print('*' * 6)

意义/作用(解决的问题)
    1. 代码冗余;可以解决代码重复性问题
    2. 代码的可维护性差,不是直接修改参数;提高代码的可维护性
    3. 没有模块化,缺乏可读性;提高程序的模块化程度,细化功能划分,增加可读性

引出定义
    可以重复使用,用来实现一个或者多个功能的代码段

函数的类型
    1. 内建函数
    2. 自定义函数

函数定义要素有哪四点?
    1. 使用def关键字
        def 函数(参数列表):
            函数体

    2. 参数列表 可以为空

    3. 函数定义完之后可以调用

    4. 举例
        def print_star():
            for i in range(5):
                print('*' * 5)
                字符串也是序列,序列重复性操作

写函数的思路
    先搭好框架,用占位符,然后再考虑细化功能

一、内建函数

在内置模块
    print('ddd')
    input('ddd')
    abs()
    li.sort()
    sorted(li)

定义问题
    1. 函数名
    2. 函数的参数:类型、个数
    3. 函数的返回值

函数的调用
    函数被定以后,如果不调用,函数是不执行的
    函数名(参数)

二、自定义函数

计算机书写模块 【】代表队额是可有可无

(一)自定义函数语法

    def 函数名([参数1,参数2,...])
        函数体,函数实现功能
        [return 返回值表达式]

    pass有什么用?
        占位,适用于当前功能还没有细节实现,其他模块的代码已经统筹安排好。
        可以使用pass对当前模块进行占位

    函数名符合哪些条件
        是标识符
        字母、数字、下划线组合
        不能以数字开头,不是关键字

    加功能 转化为 函数的定义 到 函数的执行

        eg. 解决打印星星的需求背景

(二)函数的参数

    定义

        传递参数的,多少个参数,就是客户的需求,我们要完成的功能

        eg.
            def printStar(b):
                for i in range(5):
                    print('*' * b)

            希望通过设置参数来设置打印星星的数量,可以通过参数设置
            printStar(5)
            printStar(6)


    函数的参数分为哪两种?分别适用于设么场景?

        分为 形式参数和实际参数
        在函数调用过程中,实际参数会赋值给形式参数

        x = 8
        printStar(x) 调用函数时,传递的参数是参数的对象
        形式参数会绑定到实际参数指定的对象上

        printStar('a')

    函数提供参数的作用?

        功能是由函数本身决定的,但是功能本身具有一定的细节,具体的细节可以通过参数进行调整控制

    参数参数还分为?

        位置参数(必须参数)
        默认值参数
        命名关键值参数
        可变参数(收集参数)
        关键字参数(收集参数)

    1. 位置参数

        又叫必须参数

        定义
            调用函数时,参数传入的值,必须跟函数定义时,参数的数量、位置保持一致

        2点要求是什么?

            - 位置必须要传入

            - 实际传入的顺序,按照形式参数的顺序进行赋值

        eg. 位置参数 ?

            def printStar(b, line):
                for i in range(line):
                    print('*' * b)

            printStar(5, 2)

            *****
            *****

    2. 默认值参数

        进一步解释

            有一些参数可以传入默认值,那么当函数调用时,就可以无需传入参数

        格式
            定义函数时,形式参数 = 默认值

        要求
            -

        eg. 默认参数

            def printStar(b, line = 5)
                for i in range(line):
                    print('*' * b)

            printStar(4)
            printStar(4, 6)  有问题

            def printStar(line = 5, b):
                for i in range(line):
                    print('*' * b)

            printStar(4) 会报错,b是位置参数必须传值,但是4是传给line,还是传给b,搞不清楚

            引出要求:
                如果既有位置参数,又有默认桉树,需要将位置参数放在默认参数的前面




        def f(a = []):
            a.append(10)
            print(a)

        c = [1, 2]
        d = [1, 2]
        f(c)
        f(d)

        [1, 2, 10]
        [1, 2, 10]

        ??????????
        引出
            参数的默认值必须是不可变类型,如果是可变类型,则程序的结果可能会有问题
        要求会画内存图

        def f(a = []):
            if not a: 等价于 if len(a) == 0: 就是a中没有元素
                a = []
            a.append(10)
            print(a)
        f() 10
        f() 10


        还要注意
            如果调用函数时,给默认参数传递了实际参数,程序会使用实际参数绑定参数值;
            否则会使用默认值给参数进行赋值;
            有了默认参数了,不再传值也是可以的。


    3. 命名关键字参数

        特点
            - 命名关键字参数:在函数调用时,必须制定参数的名字。
            - 定义的方式,使用*,作为分隔符,*后面的参数都是命名关键字参数

        eg. 命名关键字参数

            def printStar(line, *, b):
                for i in range(line):
                    print('*' * b)

            printStar(4, b = 6)
            4行,每行6星

        作用
            - 增加了程序的可读性
            - 调用函数时,可以忽略参数的顺序
            printStar(line = 6, b = 4)
            printStar(b = 4, line = 6)
            - 位置参数放在最前面,默认值参数,命名关键字参数,自己也可以有默认值

        分辨以上三种
            def printStar(*, line, b):
                for i in range(line):
                    print('*' * b)
            printStar(b = 4, line = 6)

            def printStar(b, line = 6):
                for i in range(line):
                    print('*' * b)
            printStar(4)

            def printStar(b, line):
                for i in range(line):
                    print('*' * b)
            printStar(4, 6)

            ===============================

        总结

            已学过的函数中sort
            li = [2, 4, -1, -5]
            reverse 和 key 都是命名关键字参数

            li.sort(reverse = True)

            key指定的是【函数名】。能够将列表中的每一个元素都应用一次key指定的函数,排序按照
            函数的返回值排序

            希望排序的时候,按照每个元素的绝对值进行排序

            li.sort(key = abs, reverse = True)   True   是降序
            [2, 4, 1, 5]
            print(li)
            sorted(key =, reverse =)

            关于位置参数,也可以使用名字传入参数
            好处:增加程序的可读性,忽略传入参数的顺序

            def printStar(b, line):
                for i in range(line):
                    print('*' * b)

            printStar(b = 2, line = 3)
            printStar(line = 3, b = 2)


    4. 可变参数(收集参数) * args

        约定俗称:习惯上的命名方式

        特点
            可以再函数调用时,将所有的位置参数都收集成元组,传递给当前的函数

        定义语法
            * args

        eg. def s(* args):
                args = (1, 2, 3, 4)
                s = 0
                for i in args:
                    s += i ** 2
                print(s)

            调用
            s(1, 2, 3, 4)
            1, 2, 3, 4  是传入的四个参数,不是元组

            a = [1, 2, 3, 4]   证明了可变参数的定义是拆包成元组
            不支持list, TypeError: unsupported operand type(s) for ** or pow(): 'list' and 'int'

            因为a是列表,传入的时候会被打包成一个元素的元组,比那里的时候,是对这一个元素列表进行平方,所以不行

            a = (1, 2, 3, 4, 5)
            s(a)    ((1, 2, 3, 4, 5))

            打包
                函数定义的时候,在形式参数前加*,意味着函数调用时会将所有的实际参数(位置参数)打包成元组

            拆包
                函数执行的时候,在实际参数前面*,意味着将实际参数拆包成序列

            s(* a) 加了星,将a拆包成元组

            s(1, 2)
            s()

    5. 关键字参数(收集参数)

        特点
            收集所有的以名字传入的参数,形成字典,再传递给函数

        语法
            **kwargs

        注册
            必须要提供的name, age, 其他的参数可以提供或者不提供

        打包
            在函数定义的时候,在形式参数前面**,要将所有的以名字传入的参数打包成字典


        eg. def regist(name, age, **kwargs):
                print('注册成功')
                print(name, age)
                print(kwargs)

            regist('张三', 20)
            regist('张三', 20, city = '北京', gender = '男') 单独形成字典

        拆包
            在函数执行的时候,在实际参数前面**,将已存在的字典类型的参数拆包成序列(名=值)

        eg. def regist(name, age, **kwargs):
                print('注册成功')
                print(name, age)
                print(kwargs)

        d = {'city': '南京', 'gender': '女'}
        regist('李四', 30, **d)


    参数的组合,总结

        - 注意参数类型
        - 各个类型的参数位置,函数定义的时候是有顺序的
          位置参数 > 默认参数 > 命名关键字参数/可变参数 > 关键字参数
        - 命名关键字参数和可变参数只能定义一个
        - 万能参数  *args, **kwargs

    命名关键字参数/可变参数,why只能用一个?

        def a(*y, **kwargs):
            print(y)
            print(kwargs)

        def a(*y, *, x):
            pass

        a(1, 2, 3, name = 1, age = 2, c = 8)

        习惯上将 * args, **kwargs 叫做万能参数

        def a(*args, **kwargs):
            print(args)
            print(kwargs)

        a()  被吸收,一个空元组,一个空字典
        a(1)   吸收可变参数,形参元组
         a(1,2)  吸收可变参数,形参元组
         a(1,2,one=3,two=4)  关键字参数必须要命名,只不过不能设置默认值

          在可变参数后面,定义位置参数,自动转换成命名关键字参数。
         def b(*args,y):
             print(args)
             print(y)
         b(1,2,3,4,y=6)  1,2,3,4都被可变参数*args吸收,如果后面还是位置参数,仍然被吸收;所以在这里只能用命名关键字参数

        位置参数> 默认参数 > 命名关键字参数/可变参数 > 关键字参数
          参数的组合,可变参数
         def f1(a,b,c=0,*args,**kwargs):
             print("a={}".format(a))
             print("b={}".format(b))
             print("c={}".format(c))
             print("args={}".format(args))
             print("kwargs={}".format(kwargs))
         f1(1,2)  位置参数,被a和b吸收走了
         f1(1,2,3)  3默认参数,3被c吸收走了,只是默认设置为0
         f1(1,2,3,4)  4可变参数,4被args吸收形成单纯元组
         f1(1,2,3,4,5)  4,和5都被可变参数吸收走
         f1(1,2,3,4,5,x="d")  知道最后指定了5才被关键字参数 吸收走

        位置参数> 默认参数 > 命名关键字参数/可变参数 > 关键字参数
          参数的组合,命名关键字参数
         def f2(a,b,c=0,*,d,**kwargs):
             print("a={}".format(a))
             print("b={}".format(b))
             print("c={}".format(c))
             print("d={}".format(d))
             print("kwargs={}".format(kwargs))
         f2(1,2,3,d=2)
         f2(1,2,3,d=2,f=5,e=6)
         f2(1,2,3,d=2,d=5,e=6)



(三)函数的返回值

    函数总会有返回值,返回给函数的调用端
    通过return来返回,如果不用return,那么返回就是一个None
    遇到return,函数就会中止执行

    def add(a, b):
        sum = a + b
        return sum
        print('还能执行吗?')

        s = add(1, 2)
    # 经返回值重新赋值给变量s
    print(s)

    return不仅是一个变量,可以是表达式,只要是一个值(或者能产生值的变量或者表达式)

    函数还可以返回多个值
    def compute(a, b):
    return a + b
        return a - b

        return a * b
        return a / b

    compute (1, 2)

    3
    Why没有返回?因为函数遇到return就会终止,并将结果返回给函数的调用端

    如何让所有值都输出呢?
    def compute(a, b):
    return [a + b, a – b, a * b, a / b]
    compute (1, 2)

    但我们习惯上返回多个值,用元组,因为元组不可改变
    因为我们的可变对象比如列表会返回给调度端,有可能变动,所以用元组更好


    函数的返回值
     函数是为了完成某一个功能,返回值是完成功能之后给予的结果
     返回值的语法:函数定义的最后   return 返回值表达式
     当函数执行到return的时候, return后面的代码不会再执行
     当函数中有return,return后面的代码就不再执行。


    Exp:
    def add(a,b):
        sums=a+b
         print(sums)
        return sums
         return a+b
         print("ddd")
    a=add(3,4)
    print(add(3,4))

     关于在函数中print还是return?
     习惯上开发时候,在函数的内部没有print,返回值是通过return来获得。

    return3点注意:
    (1) 当程序执行到return时,给返回当前函数的返回值(相当于跳出当前函数)
    (2)return只能有一个,return多个返回值可以通过元组的形式返回
    (3)返回值为None
           Return None
           Return
           没有return


     函数没有返回值:
     (1) return None
    (2) return
     (3) return都不写
    def b():
        print("b执行")
         return None
         return
    print(b())

     在一个函数中,只能执行一个return
    def c(a,b):
        return a+b
        return a-b
        return a*b
        return a/b
    print(c(3,4))
     返回多个值,可以在一个return下返回元组
    def c(a,b):
        return a+b,a-b,a*b,a/b
     可以使用平行赋值的形式接收函数的返回值,一定要注意参数个数要跟返回值一一对应。
    r1,r2,r3,r4=c(3,4)
    print(r1,r2,r3,r4)
    print(c(3,4))

      练习:
      1. 自定义一个求绝对值的函数
     def my_abs(x):
         if x>=0:
             return x
         else:
             return -x
      print(函数()) 相当于在打印函数的返回值
      result=函数()
     print(my_abs(-100))
     print(my_abs(100))
      2. 一元二次方程的解
      ax^2+bx+c=0
      d=b**2-4*a*c  >=0
      x1=(-b+/-math.sqrt(d))/2*a
     import math
     def q(a,b,c):
         d=b**2-4*a*c
         if d>=0:
             x1=(-b+math.sqrt(d))/(2*a)
             x2=(-b-math.sqrt(d))/(2*a)
             return x1,x2
         else:
             return "无解"

     print(q(1,2,3))
     print(q(1,8,3))
     c=q(1,8,3)
     print(c[0],c[1])

      3.计算x的n次方
      pow(x,y)

     def po(x,y=2):
         s=1
         z=abs(y)
         if x!=0 and y==0:
             return 1
         while z>0:
             s=s*x
             z-=1
         if y>0:
             return s
         else:
             return 1/s
     print(po(2,3))
     print(po(2,-3))
     print(po(2,0))
     print(po(2))

三、函数中参数的传递方式

参数:可变类型,不可变类型

(一)不可变类型参数的值传递

    结论:当传入的参数是不可变数据类型的时候,函数外侧定义的数值,不会被函数修改

    def m(a): a不可变数据类型
        a += 2
        print('在m函数中a={}'.format(a))

    a = 10
    m(a)

    print('在m函数的外面a = {}'.format(a))


(二)可变类型的值传递

    结论:当传入的参数是可变数据类型时,函数外侧定义的数据,会被函数修改

    def m(a): a不可变数据类型
        a[0] = 'new'
        print('在m函数中a={}'.format(a))

    a = [1, 2, 3]
    m(a)
    print('在m函数的外面a = {}'.format(a))

四、命名空间和作用域

(一)命名空间和作用域

    命名空间
        可以理解成保存名字的容器,eg. list

    名字
        变量,函数,类等等,都会将名字放到相应的命名空间

        名字在内存空间中是以字典的模式存储

    键
        名字

    值
        对象的内存地址

    每一个名字都有自己的内存空间

    每个命名空间中对于一个名字,只有一个;不同的命名空间可以存储相同的名字

    什么时候定义的名字,放在哪一个命名空间中?是偶发需要确定位置?
        对于变量来说,第一次赋值,就决定了它在哪个命名空间中
        对于函数和类来说,def class就决定了它在哪个命名空间中

    在哪里创建的名字,就属于哪个命名空间,不需要特殊标志
        每一个空间都已经圈定了自己特殊的范围

    命名空间的分类
        1. 内建命名空间
            在Python解释器启动的时候就会被创建
        2. 全局命名空间
            在读取模块定义的时候,被创建,程序执行结束,被销毁
        3. 局部命名空间
            在函数执行的时候创建,在函数执行结束之后被销毁

        注意
            不要把命名空间理解成包含的关系

        不同的命名空间有不同的作用域

        作用域
            起作用的范围

            内建:作用域最大,所有模块
            全局:整个py文件,模块内都有效
            局部:在当前函数内部有效

        注意
            不要将作用域理解成包含关系


(二)访问顺序和原则

    1. 定义个全局变量(不可变类型),在函数内部试图修改这个全局变量
    2. 在函数内部,先输出一个变量,再试图去修改它的值
    3. 在函数内部将全局变量的值+1
    4. 创建变量id,看一下调用id()函数


    先看4大现象:

        定义一个全局变量(不可变类型),在函数内部试图修改这个全局变量
        x=1
        def fun():
             如果需要在局部命名空间下修改全局变量 使用global,如果不用,在函数内部是无法修改全局变量
            global x
            x=2
            print("在fun函数中x={}".format(x))
        fun()
        print("在fun函数的外侧x={}".format(x))


        在函数内部,先输出一个变量,再试图去修改它的值。
         当在命名空间中定义了一个变量,python解释器在编译的时候,就已经编译到了变量
        x=1  即使在外面,也不影响函数内部值得修改
        def fun2():
            x = 2
            print(x)  报的错不一样,不是name erro
              在程序编译的时候,已经向局部命名空间中写入了x
            print(x)
        fun2()
        print(x)

        在函数内部将全局变量的值+1
        x=1
        def fun3():
            global x
            x+=1   x=x+1
        fun3()
        print(x)  x被修改了

        创建变量id,看一下调用id()函数。
         当给变量或者函数起名的时候,不要使用内建函数的名字。
         id=1
         x=1
         print(id(x))  已经不是id函数了,而是变量id

         [访问]顺序:LEGB原则
        当python解释器遇见一个名字x的时候,会到命名空间中查找这个名字,要遵循LEGB原则

        (1)L:Local:先到局部命名空间中查找,如果找到了则停止搜索,否则继续向上查找
        (2)E:enclosing:会到外围命名空间中查找,如果找到了,则停止搜索,否则继续向上查找
        (3)G:global:到全局命名空间中查找,如果找到了,则停止搜索,否则继续向上
        (4)B:buildins:到内建命名空间中查找 ,如果找到了,则停止搜索,否则报错。

        对于一个名称:
        """
        (1) 访问、读取:严格按照LEGB原则的顺序搜索。
        (2) 修改: 只从当前最小的的作用域开始查找,
                  如果找不到不会继续向上搜索,而是直接在当前作用域(命名空间)中新创建一个
        (3) 删除:只能删除自己作用域中名字,无法对其他命名空间中的名字做删除。
        """

        举栗子:
        
        访问
         y=1
         def f():
              print(y)
         f()
        
         修改:想要修改外围命名空间
         y=1
         def f():
             global y
             y=2
             print(y)  里面外面都行
         f()
         print(y)
        
         修改:想要修改局部命名空间
         def outer():
             z=1
             def inner():
                 nonlocal z
                 z=3
             inner()
             print(z)
         outer()

五、lambda表达式(入)

先入为主
 当函数体本身比较简单,又支持是头等函数,可以使用lambda表达式来表示函数。
 lambda表达式,表达的是一个简单的函数,代表函数的名字;
 单纯的lambda表达式是一个匿名函数(也可以有名字)
 语法: lambda 参数:返回值表达式

 背景入门
 
     头等函数
        变量跟函数(类)一样,名字可以被传递、可以被赋值、可以当成返回值。(JS也一样)
        看待一个函数、类、变量,对于命名空间来说,是一样的
        
        头等函数
        
        def x():
            print('x执行')
        y=x
        y()  用y使得x执行

     函数名可以当参数执行
         li=[3,5,-1]
         li.sort(key=x)

     嵌套函数
        def outer():
            def inner():
                return "success"
            return inner()
        print(outer())  获得返回值success

     我们研究的是把函数名字作为返回值
    (函数的闭包)
        def outer():
            def inner():
                print('inner函数执行')
                return "success"
            return inner
          return了一个名字,没有执行,定义也没有执行
        y=outer()
        y()  获得 inner函数执行 这是仿造头等函数,y使得x执行

当函数体本身比较简单,又支持是头等函数,可以使用lambda表达式来表示函数。
    lambda表达式,表达的是一个简单的函数,代表函数的名字,匿名函数
    语法: lambda 参数:返回值表达式
        def add1(k):
            return k+1
        print(add1(20))

    lambda k:k+1
    调用匿名函数(1)给匿名函数起名字,名字()   (2)使用命名关键字参数传入函数名

     调用匿名函数:1.给匿名函数起名字
         y=lambda k:k+1
         print(y(99))
    
     调用匿名函数:2.使用命名关键字参数传入函数名
         li=[-3,1,5]
         li.sort(key=lambda x:x+100)
         print(li)

六、递归函数

递归:函数调用自身的过程
 直接递归  A--A
 间接递归  A-B-A
 所有的递归一定要有出口(给定一个条件,递归终止)。因为无限递归是没有意义,而且达到最大递归深度,程序会报错

 递归 :递推 回归
 递推:希望解决问题,逐层推理
 回归:利用刚才递推取得的终止条件,再一层层返回,最终取得答案
 A(10)  B(11)  C(12)  D(13)  E(14)   F(15)

 递归解决问题的突破点:程序出口

 几乎所有使用递归能够解决的问题,使用循环都可以解决。
 递归:编程思路比较清晰  循环:执行速度快。


递归


(1) 求1+...+100的和

     循环的方法:
    def l(n):
        s=0
        for i in range(1,n+1):
            s+=i
        return s
    print(l(100))
     注意,执行打印操作还是在函数外面比较好



    1+2+3+....+100
                     100+(99的累加和)  最后一个人比较懒,让别人帮忙干
    1+....98         99+98的累加和
                     98+97的累加和
                     97+96的累加和
                     ...
                     2+1的累加和
                     1      =  1
    >>>突破点    n=1     1
                      n>1    n+(n-1)的累加和
    
     Exp:
    def l2(n):
        if n == 1:
            return  1
        else :
            return n+l2(n-1)
    print(l2(100))
    

(2) 的阶乘

     突破点
    n*n-1*n-2....1
    """
    n==1   1
    n>1    n*n-1的阶乘
    """
     Exp:
    def fac(n):
        if n == 1:
            return 1
        else:
            return n*fac(n-1)
    print(fac(3))

(3) 求斐波那切数列的第n项

     每一项是前两项的和
     1 1 2 3  5  8  13....
    
     倒过来想,
     突破点 n==1 和n==2   都是1
     前两项的和是:     n>2          f(n-1)+f(n-2)
    
    
    def fib(n):
        if n == 1 or n == 2:
            return 1
        else:
            return fib(n-1)+fib(n-2)   注意:TM的return别掉了
    a=fib(3)
     print(a)  fib的第三项


(4) 汉诺塔

     懒,我只需要把第n个盘子从A挪到C,至于前n-1个盘子,从A,借助C挪动到B,最后再从A挪到C
    
    def hano(n,A,B,C):
        if n == 1:
            print('{}-{}'.format(A,C))
        else:
            hano(n-1,A,C,B)  A 借助C 挪到B
            print('{}-{}'.format(A,C))  第N 个盘子 A直接挪到C
            hano(n-1,B,A,C)  B 借助A 挪到C
    hano(3,'A','B','C')  共有7步
    
     A-C
     A-B
     C-B
     A-C
     B-A
     B-C
     A-C

 七、高阶函数
     高阶函数:相对于初阶函数来的,至少满足以下一个条件的函数:
     1. 接受一个或者多个函数作为输入
     2. 输出一个函数
    
    跟闭包相关
    
     1. map(fun,iterator)
         fun:函数的名字
         iterator:可迭代对象或者是迭代器
         功能:对迭代对象中的每一个元素调用一次函数,将返回值重新作为迭代对象返回
         返回值是迭代器;(不是列表,是生成器,和range一样,生成器的顶层也是迭代器)
        
         Exp:
        def p(n):
            return n**2
        print(list(map(p,[1,3,4,5,6])))
        
         for循环的形式也可以得到同样的结果,只不过不是列表形式输出
        for i in map(p,[1,3,4,5,6]):
            print(i)
        
         lambda表达式
        print(list(map(lambda n:n**2,[1,2,5,6,7,8,8,9])))
    
     2. filter(fun,iterator) 按照函数进行过滤
        功能:对迭代对象中的每一个元素,调用一次的函数,获得返回值,将返回值为True全部保留
              返回值为False的全部删除。
        
         Exp:
        def p(n):
            if n>5:
                return True
            else:
                return False
        print(list(filter(p,[1,2,5,6,7,8,8,9])))
        
         for循环的形式也可以得到同样的结果,只不过不是列表形式输出
        for i in filter(p,[1,2,5,6,7,8,8,9]):
            print(i)
        
         lambda表达式
        print(list(filter(lambda n:n>5,[1,2,5,6,7,8,8,9])))
        
     3. reduce 按照函数二变一
    
        from functools import reduce
         reduce(fun,iterator,initial)  初始值
         将迭代对象按照fun的规则,二变一,最终返回一个值。
        
         Exp:
         求累加和
         range(1,11)
        
        from functools import reduce
        def a(x1,x2):
            return x1+x2
                         1 2 3 4  5 6 7...
        
        print(reduce(a,range(1,11)))
        print(reduce(lambda x1,x2:x1+x2,range(1,11)))  注意:两个参数的lambda表达式
         x1=1  x2=2  3
         x1=3  x2=3  6
         x1=6  x2=4  10
         x1=10 x2=5  15
        
         求最大值
        print(reduce(lambda x1,x2:x1 if x1>x2 else x2,range(10))) 
         x1=1 x2=2   --- 2
         x1=2 x2=3   --- 3
         x1=3 x2=4   --- 4
        
         复原:
        from functools import reduce
        print(reduce(lambda x1,x2:x1 if x1>x2 else x2,range(10)))
        
        def f(x1,x2):
            if x1>x2:
                return x1
            else:
                return x2
        print(reduce(f,range(10)))
        
         求最小值
        print(reduce(lambda x1,x2:x2 if x1>x2 else x1,range(10)))
        
     4.sort max
    
          sort 也是高阶函数,应为key可以指定一个fun,然后让iterator中的每一个元素都应用这个函数
        
          max(迭代对象,key=fun)
         每个元素按照fun方法的返回值,获取最大值
        
         2000年的最高温度,2001年的最高温度...... 
        
        year_t=[(2000,36),(2001,35),(2002,35.8),(2003,31.2)]
        print(year_t.sort())  原有方法,默认按照元祖的每一个元素排
         返回值 None
        
         不希望按照年份排,希望 按照温度排序,获得最高温度的那一年
         尝试 print(max(year_t)  默认按照最高年份排
        
         所以给max制定规则 不是按照每个元素的第0个元素去比,而是按照每个元素的第1个元素去比
        print(max(year_t,key=lambda x:x[1]))   lambda传入一个参数,每一个参数是一个元祖,然后是元祖的索引为1的元素
         (2000, 36)
        
         还原:
        def f(a):
            return a[1]
        print(max(year_t,key=f))

八、柯里化函数
柯里化的概念:局部函数
局部函数:调用函数时,只希望传入一部分参数,下一次再传入另外一部分参数。

n倍乘的函数
     n是倍数
     def multi(n,x):
         return n*x
     print(multi(5,3))   3的5 倍
    
     柯里化的实现方式:(1)partial  (2)柯里化装饰器
    
    from functools import partial
    语法
     partial内部使用闭包实现
    
    新函数=partial(原函数名,一个参数)
    新函数(另一个参数)
    
    内部的原理:也是用闭包,返回函数的名字
     def outer():
         def inner():
             pass
         return inner
     newfun=outer()
     newfun()
    
     我只想输入一个参数?
    from functools import partial
    def multi(n,x):
        return "{}*{}={}".format(x,n,n*x)
     print(multi(5,3))   3的5 倍
    
     稍微一点你就看不清楚了,sb
    
     进行柯里化,实现n倍的函数
     
    3倍乘的函数
    time5=partial(multi,2)
    print(time5(5))
    
     传入的参数 ,如果没有给名字,默认是按照顺序赋值。
    from functools import partial
    def multi(x,n):
        return "{}*{}={}".format(x,n,n*x)
    time3=partial(multi,n=3)  位置指定好了
    print(time3(9))
    
    函数柯里化装饰器也可以实现函数的柯里化
    from toolz import curry
     需要自己pip安装
     pip install toolz
    
    @curry
    def multi(n,x):
        return "{}*{}={}".format(n,x,x*n)
    time5=multi(x=5)  就少了个partial,只能是第二个  因为特别要注意,都最好指定
    print(time5(n=2))

九、函数的文档和注释

1. 函数的文档

     函数定义的下方使用三引号
    
    def fun():
        """
        功能描述
        :return: 返回值
        """
    print(fun.__doc__)
    help(fun)
    
    
    def fun():
        
    The Zen of Python, by Tim Peters
    Beautiful is better than ugly.
    Explicit is better than implicit.
    Simple is better than complex.
    Complex is better than complicated.
    Flat is better than nested.
    Sparse is better than dense.
    Readability counts.
    Special cases aren't special enough to break the rules.
    Although practicality beats purity.
    Errors should never pass silently.
    Unless explicitly silenced.
    In the face of ambiguity, refuse the temptation to guess.
    There should be one-- and preferably only one --obvious way to do it.
    Although that way may not be obvious at first unless you're Dutch.
    Now is better than never.
    Although never is often better than *right* now.
    If the implementation is hard to explain, it's a bad idea.
    If the implementation is easy to explain, it may be a good idea.
    Namespaces are one honking great idea -- let's do more of those!
        :return:
        
    
     print(fun())
    print(fun.__doc__)
     help(fun)  这个完整一些


2. 函数的注释

     参数的注释  返回值的注释
    
     参数的注释:在参数的后面     :   :参数的类型
     返回值的注释:在def所在行:前面:  ->返回值的类型
    
    综合例子:
    
    def f(a:int,b:int)->float:
    
        """  函数的文档
        函数的功能
        :param a:
        :param b:
        :return:
        """
        return a+b
    print(f.__annotations__)  返回函数的注释
    print(f.__doc__)  返回函数的文档

“”"
第九章 类和对象
“”"
第一章入门
第二章—第七章: 数据类型(数值、布尔类型、字符串、字节、列表、元组、集合、字典)
操作符,流程控制
第八章:函数
第九章、第十章: 类和对象

问题:类和对象?
需要定义数字—数值类型int float 、布尔 bool
需要定义字符串–字符串类型str、字节bytes
需要存储多个元素的数据—列表、元组
需要存储两个数据(键–值),业务访问上比较频繁,加快速度----字典,集合

描述一个操场,多少米,材质,被装修,开运动会。。。。
“操场1000米草地”
“操场500千米塑胶”

所以,数据类型不够用了。我们需要自定义数据类型----创建类(来描述操场,描述学生管理系统)

一、类和对象基本概念

  1. 什么对象:
    万物皆对象。对象可以描述成具有一定的【属性】和【行为】的数据。
    属性----描述当前数据的名词
    行为----描述当前数据的动词

例子:桌子、你、你同桌、电脑、水杯
桌子:颜色、几个腿、周长、高度
人:名字、生日、性别、走、跑
不同的对象具有不同的属性和行为

  1. 什么是类?
    按照需求,将具有相同属性和相同行为的所有对象划分成一个整体,叫做类(有点像物以类聚,人以群分)
    张三:名字,生日,性别,走,跑
    李四:…
    划分成一个 类: 人的类

超市里:生鲜、化妆品、生活用品。。。条件:按照购买的便捷性
在业务中,要看业务需求
例子:学籍管理系统 学生、老师,需要划分成两个类
公交刷卡系统 人 ,车、站点。。。。

颗粒化:划分的粗细程度,一定取决于业务
分析:先有对象,才有类
到底怎么划分类和对象?分析-设计-编程,需要大量的代码量的累积。OOA—OOD—OOP

3.类和对象之间的关系
类—模板,蓝图
对象—类设计出来的实例

分析-编码两个过程
分析————张三、李四、王五…—一个人的类
编码————使用代码创建类,使用类创建对象(对象就是张三、李四、王五)

类创建一个实例,类创建一个对象,实例化一个对象,类产出一个对象。。。。

二、类的定义和创建
1.类的定义
“”"
语法:
class 类名:
类体
“”"
习惯上,自定义的类要将类名首字母大写。函数一般首字母小写。
class Person:
‘注释’
pass

使用类去实例化(创建、产生)对象
语法:对象名= 类名([参数])
创建张三对象
p1=Person()

a=1 也是创建一个对象的过程 使用int这个类创建了一个对象。(type一下就会发现 )
按照语法,创建对象,但是int的话,python社区的人已经创建好了这个类

a=int() 0
 b=list() []
 print(type(a))
 print(type(p1))
 print(p1.doc)
  1. 属性和方法(动态属性和动态行为)
    属性:属性,使用变量的赋值方式实现
    方法:行为,使用函数的定义方式实现 行为–方法—函数
    方法、函数—在类中,习惯将函数称为方法。

动态属性和动态行为—最方便创建。
缺点:只对当前对象有效,对其他对象无效。

(1)定义属性

class Person:
 ‘注释’
 pass
 p1=Person()
 定义语法:对象.属性名=属性值
 p1.name=“张三”
 p1.age=20

调用:对象.属性名

print(p1.name)
 print(p1.age)
 p2=Person()
 print(p2.name)
 print(p2.age)
 “”"


上面对于动态属性的赋值可以理解成两个角度:
(1)使用对象.属性名来对 p1对象创建属性,并且进行属性的赋值
调用的时候,需要通过对象.属性名
(2)直接把 【对象.属性名】直接理解成变量名。只不过变量名要求 对象名.属性名
“”"

(2) 定义动态行为
定义:先定义一个函数(必须要有一个参数self),使用对象名.函数名=原函数名
class Person:

‘注释’
 pass
 p1=Person()
 p1.name=“张三”
 def run(self):
 print(“正在跑步…”)
 p1.p1run=run

调用:对象名.动态行为名(对象)

p1.p1run(p1)
 动态行为只对当前对象有效
 p2=Person()
 p2.p1run(p2)

3.在类中创建实例属性和实例方法
属性:赋值的方式实现(需要在__init__函数中)
行为:在类中定义函数,函数的参数,第一个必修是self
(1)在类中定义实例方法

class Person:
 def run(self):
 print(“正在跑步”)


调用:先有实例(对象),才能调用实例方法
只要对象是由这个类产生的,这个类下定义的实例属性和实例方法,所有的对象在产生的时候,就自然拥有

p1=Person()
 p1.run() 调用实例方法时,不需要传入self参数
 p2=Person()
 p2.run()

(2)在类中定义实例属性

一定要通过__init__方法中定义实例属性
init: 第一个参数是self,是实例方法,
魔法方法(魔法方法是不需要被主动调用,,被自动调是在符合条件的时候用)
__init__在类创建对象时,会被自动调用,
(__init__不是创建对象的方法,是创建好对象之后,实例化属性的方法)

定义实例属性要在__init__中定义
语法:对象名(self).属性名=属性值
在实例方法中self:代表当前对象,调用的时候不需要传入self参数
在init中,参数可以作为实例属性的赋值

如果是创建对象时的必要属性,需要将属性值通过init的参数形式传入——————强依赖关系
如果不是创建对象时必要的属性,可以先在init中进行初始化创建
在客户端创建对象时(或者在其他的实例方法中),再重新赋值————————————弱依赖关系

注意:定义一个类,不一定必须要实现__init__方法,取决于是否需要在创建对象的时候传入强依赖参数。

实例属性也可以在其他的实例方法中进行创建,但是有问题的地方在于:
“”"
(1) 为了创建实例属性,必须主动调用实例方法
(2) 代码的可读性差,实例属性的赋值创建被分散到很多的实例方法中,不好识别代码
“”"

class Person:
弱依赖关系的实例属性,可以不在init中赋值,但是要在init中初始化,在使用之前赋值。

def init(self):
 self.name=None
 self.age=None
def __init__(self,name,age):
    self.name=name
    self.age=age
    self.high=None
    print("执行了init方法")
def run(self,place):
     print("正在跑步")
     在实例方法中调用实例属性和其他实例方法(对象.实例属性名)  因为有self
     在实例方法中使用参数,注意不要当成实例属性,实例属性是带self。
    print("{}正在{}跑步".format(self.name,place))

def walk(self):
    print("正在走步")

def a(self):
    self.gender="男"

p1=Person(“张三”,20)
实例属性的调用:对象名.属性名

p1.high=1.7
 print(p1.name)
 print(p1.age)
 p1.run(“操场”)
 p1.a()
 print(p1.gender)p2=Person(“李四”,30)
 print(p2.name)
 print(p2.age)
 p2.run(“公园”)
 p2.a()
 print(p2.gender)“”"


练习:

  1. 创建一个小狗的类
  2. 定义狗的属性 name age type
  3. 狗的行为:叫、睡觉、跑、吃
  4. 吃的行为,希望显示某个牌子的狗粮
  5. 叫的行为,可以显示狗的名字
1.  “”"
 class Dog:
 def init(self,name):
 self.name=name
 self.age=None
 self.type=None
 def bark(self):
 print("{}{}{}正在汪汪叫".format(self.name,self.age,self.type))
 def sleep(self):
 print(“正在睡觉”)
 def run(self):
 print(“正在跑”)
 def eat(self,x):
 print(“正在吃{}牌子的狗粮”.format(x))
 d1=Dog(“旺财”)
 d1.age=3
 d1.type=“拉布拉多”
 d1.bark()
 d1.run()
 d1.sleep()
 d1.eat(“老北包子”)d2=Dog(“公主”)
 d2.typ=“金毛”
 d2.age=2“”"