Python零基础-中【详细】

接上篇继续:

Python零基础-上【详细】-CSDN博客

目录

十、函数式编程

1、匿名函数lambda表达式

(1)匿名函数理解

(2)lambda表达式的基本格式

(3)lambda表达式的使用场景

(4)def 与 lambda 函数对比

2、map函数

(1)map的基本格式

(2)示例

(3)函数中带两个参数的map函数格式

3、reduce函数

(1)reduce函数的基本格式

(2)示例

4、filter函数

(1)filter理解

(2)filter函数基本格式

(3)filter函数使用

5、sorted函数

(1)sorted理解

(2)sorted函数基本格式

(3)应用

# 对序列做升序排序

# 对序列做降序排序

# 对存储多个列表的序列做排序

6、闭包

7、闭包经典问题

8、装饰器

(1)装饰器理解

(2)示例

十一、Python核心知识点

1、Python工程组织结构之包、模块

(1)模块

(2)包

(3)包与子包

2、命名空间

(1)命名空间理解

(2)命名空间查找顺序

(3)当函数嵌套时的查找规则

(4)命名空间的生命周期

(5)命名空间的访问

# 局部命名空间的访问

# 全局命名空间的访问

(6)locals 与 globals 之间的区别

3、在Python工程中导入模块

4、在Python工程中导入变量

(1)导入一个变量

(2)导入多个变量

5、python中的导包机制

(1)Python导入机制

(2)模块导入示例

6、__init__.py的作用及用法

(1)作用

(2)用法

7、__all__、 __name__的作用及其用法

(1)__all__ 的作用及其用法

(2)__name__ 的作用及其用法

十二、错误处理

1、Python中异常的捕获与处理

(1)错误

(2)异常

(3)警告

(4)异常处理

2、自定义异常与异常的抛出

(1)自定义异常

(2)异常抛出

3、如何通过debug分析问题

(1)简单问题分析

(2)复杂问题分析

4、为代码编写单元测试

(1)什么是单元测试

(2)单元测试的优缺点

(3)如何编写单元测试

十三、IO操作

1、输入输出

2、文件的读取

(1)打开文件

(2)读取文件

3、文件内容的写入

4、操作文件夹

(1)创建文件夹

(2)创建多层级文件夹

(3)获取当前所在目录

(4)改变当前的工作目录

(5)删除空文件夹

(6)删除多层空文件夹

5、操作文件夹示例

(1)文件夹下仅有文件的删除

(2)文件夹下有子文件夹的删除

6、StringIO和BytesIO

(1)理解

(2)用法

十四、面向对象编程

1、面向对象及其三大特性

(1)面向对象理解

(2)面向对象的三大特性

2、类和对象

(1)类和对象的理解

(2)类和对象的关系

(3)定义类以及实例化对象

3、类的构造函数

(1)什么是构造函数

(2)构造函数的用途

4、类变量与实例变量

(1)类变量

(2)实例变量

(3)访问实例变量和类变量

(4)修改实例变量和类变量的值

(5)实例变量查找规则

(6)类变量存储

5、实例方法与self关键字

(1)实例方法

(2)self关键字作用

(3)self名称统一

6、类方法与静态方法

(1)类方法

(2)静态方法

7、Python中的访问限制

8、打破Python中的访问限制

十五、面向对象高级特性

1、python中的继承

(1)继承的理解

(2)python中如何继承

2、super的作用及其用法

(1)super函数理解

(2)super函数的基本语法

(3)super函数应用

3、抽象方法与多态

(1)抽象方法理解

(2)定义抽象类

(3)多态理解

(4)多态的好处

4、python中的多重继承

5、多重继承所带来的的问题

(1)新式类和旧式类

(2)菱形继承(钻石继承)

(3)解决菱形继承问题

6、枚举类

(1)枚举理解

(2)枚举实例

(3)枚举访问

(4)枚举类的特性

(5)枚举成员的比较


十、函数式编程

1、匿名函数lambda表达式

(1)匿名函数理解

匿名函数,即没有名字的函数,在程序中不用使用 def 进行定义,可以直接使用 lambda 关键字编写简单的代码逻辑,lambda 本质上是一个函数对象,可以将其赋值给另一个变量,再由该变量来调用函数,也可以直接使用

(2)lambda表达式的基本格式

lambda入参:表达式

入参可有多个,如下:

power = lambda x, n: x ** n
print('%d的%d次方的结果是:%d' % (2, 3, power(2, 3)))

(3)lambda表达式的使用场景

一般适用于创建一些临时性的、小巧的函数,比如上面的power函数,我们可以使用 def 来定义,但使用 lambda 来创建会显得很间接,尤其是在高阶函数的使用中

(4)def 与 lambda 函数对比

示例:定义一个函数,传入一个list,将list每个元素的值加1(另一个地方调用将所有元素的值加2)

# 定义函数,把需要增加的地方抽出来,让调用者自己传

def add(func, l=[]):
    return [func(x) for x in l]


def add1(x):
    return x + 1


def add2(x):
    return x + 2


print(add(add1, [1, 2, 3]))
print(add(add2, [1, 2, 3]))

# 直接使用lambda函数调用

def add(func, l=[]):
    return [func(x) for x in l]


print(add(lambda x: x + 1, [1, 2, 3]))
print(add(lambda x: x + 2, [1, 2, 3]))

2、map函数

(1)map的基本格式

map() 函数接收两个以上的参数,开头是一个函数,剩下的是序列,将传入的函数依次作用到序列的每个元素,并把结果作为新的序列返回,也就是类似于 map(func,[1,2,3])

map(func, *iterables)

(2)示例

这里还是以列表值+1为例:传入一个list,将list每个元素的值加1

# 定义函数,然后使用map()调用

def add(x):
    return x + 1


result = map(add, [1, 2, 3, 4])
print(type(result))
print(list(result))

# 使用 lambda表达式简化操作

result = map(lambda x: x + 1, [1,2,3,4])
print(type(result))
print(list(result))

(3)函数中带两个参数的map函数格式

使用map()函数,将两个序列的数据对应位置求和,之后返回,也就是对[1,2,3]、[4,5,6]两个序列进行操作之后,返回结果[5,7,9],如下:

print(list(map(lambda x, y: x + y, [1, 2, 3], [4, 5, 6])))

若对于两个序列元素个数不一样的,序列各元素想加,查看返回结果,结果以个数少的为准

print(list(map(lambda x, y: x + y, [1, 2, 3], [4, 5])))

3、reduce函数

(1)reduce函数的基本格式

reduce把一个函数作用在一个序列上,这个函数必须接收两个参数,reduce函数把结果继续和序列的下一个元素做累积计算,和递归类似,reduce函数会被上一个计算结果应用到本次计算中

reduce(function, sequence, initial=None)
reduce(func, [1, 2, 3]) = func(func(1, 2), 3)

(2)示例

# 使用reduce函数,计算一个列表的乘积

from functools import reduce


def func(x, y):
    return x * y


print(reduce(func, [1, 2, 3, 4]))

# 使用lambda表达式,计算一个列表的乘积

from functools import reduce

print(reduce(lambda x, y: x * y, [1, 2, 3, 4]))

4、filter函数

(1)filter理解

filter顾名思义是过滤的意思,不需要的数据经过filter处理之后,就被过滤掉

(2)filter函数基本格式

filter()接收一个函数和一个序列,把传入的函数依次作用于每个元素,然后根据返回值是 True 还是 False 决定保留还是丢弃该元素

filter(functin_or_None, iterable)

(3)filter函数使用

使用filter函数对给定序列进行操作,最后返回序列中所有偶数

print(list(filter(lambda x: x % 2 == 0, [1, 2, 3, 4, 5])))

5、sorted函数

(1)sorted理解

sorted 从字面意思就可以看出这是一个用来排序的函数,sorted 可以对所有可迭代的对象进行排序操作

(2)sorted函数基本格式

sorted(iterable, key=None, reverse=False)
  • iterable:可迭代对象
  • key:主要是用来进行比较的元素,只有一个参数,具体函数的参数就是取自于可迭代对象中,指定可迭代对象中的一个元素来进行排序
  • reverse:排序规则,reverse = True 降序 ; reverse = False 升序(默认)

(3)应用

# 对序列做升序排序
print(sorted([1, 6, 4, 5, 9]))

# 对序列做降序排序
print(sorted([1, 6, 4, 5, 9],reverse=True))

# 对存储多个列表的序列做排序
data = [["Python", 99], ["c", 88]]
print(sorted(data, key=lambda item: item[1]))

6、闭包

在万物皆对象的Python中,函数也可作为函数的返回值进行返回

def my_power():
    n = 2

    def power(x):
        return x ** n

    return power


p = my_power()
print(p(4))

从下面的代码段可以看出:my_power函数在返回的时候,也将其引用的值(n)一同带回,n 的值被新的函数所使用,这种情况被称为闭包

def my_power():
    n = 2

    def power(x):
        return x ** n

    return power


n = 3
p = my_power()
print(p(4))
print(p.__closure__)

下面的例子:不是闭包,未返回环境变量,只返回了函数

n = 2

def my_power():
    def power(x):
        return x ** n

    return power


n = 3
p = my_power()
print(p(4))
print(p.__closure__)

7、闭包经典问题

下面的程序不是闭包,且执行报错,闭包在引用变量时,不能直接修改引用变量的值

def my_power():
    n = 2

    def power(x):
        n += 1
        return x ** n

    return power


p = my_power()
print(p(3))

将上面的程序修改为闭包,如下:

def my_power():
    n = 2

    def power(x):
        nonlocal n
        n += 1
        return x ** n

    return power


p = my_power()
print(p(3))
print(p.__closure__)

Python函数只有在执行时,才会去找函数体里的变量的值,形参不确定,目标i的值就不确定;在形参未确定前,Python就只会记住最后一个值,因此i都等于2

def my_power():
    n = 2
    L = []
    for i in range(1, 3):
        def power():
            return i ** n

        L.append(power)
    return L


f1, f2 = my_power()
print(f1())
print(f2())
print('-----------------')
print(f1.__closure__[0].cell_contents)
print(f1.__closure__[1].cell_contents)
print(f2.__closure__[0].cell_contents)
print(f2.__closure__[1].cell_contents)

8、装饰器

(1)装饰器理解

装饰器允许想一个现有对象添加新的功能,同时又不改变其结构,这种类型的设计模式属于结构型模式,它是作为现有的类的一个包装

这种模式创建了一个装饰类,用来包装原有的类,并在保持类方法签名完整性的前提下,提供了额外的功能

import time
start = time.time()
time.sleep(4)
end = time.time()
print(end - start)

(2)示例

打印【这是一个函数】

def my_fun():
    print("这是一个函数")


my_fun()

如果想要获取函数执行的时间,这里改动到了原来的函数,则如下

import time


def my_fun():
    begin = time.time()
    time.sleep(2)          # 这里让结果看起来更直观点,睡眠了2秒
    print("这里一个函数")
    end = time.time()
    print(end - begin)


my_fun()

若不想修改原有函数,想要获取函数执行的时间,如下(第二种):

第一种:该方法不可取,因为要增加功能会导致所有的业务调用方都得进行修改

import time

def my_fun():
    print("这是一个函数")


def my_time(func):
    begin = time.time()
    time.sleep(2)
    func()
    end = time.time()
    print(end - begin)


my_time(my_fun)

第二种:

import time


def print_cost(func):
    def wrapper():
        begin = time.time()
        time.sleep(2)
        func()
        end = time.time()
        print(end - begin)

    return wrapper


@print_cost
def my_fun():
    print("这里一个函数")

my_fun()

十一、Python核心知识点

1、Python工程组织结构之包、模块

(1)模块

当我们新建一个python file,这个时候形成一个 .py 后缀的文件,这个文件就称之为模块

(2)包

在pycharm中,我们右键可以创建一个目录,也可以创建一个包,两者看起来差不多,唯一的区别在于,创建包的时候,包下面会有一个__init__.py的文件,这也是python为了区分目录和包所做出的界定

(3)包与子包

包下面,还能新建包,称之为子包

2、命名空间

(1)命名空间理解

命名空间是变量到对象的映射集合,一般都是通过字典来实现的。主要可以分为三类:

  • 每个函数都有着自已的命名空间,叫做局部命名空间,它记录了函数的变量,包括函数的参数和局部定义的变量
  • 每个模块拥有它自已的命名空间,叫做全局命名空间,它记录了模块的变量,包括函数、类、其它导入的模块、模块级的变量和常量
  • 内置命名空间,任何模块均可访问它,它存放着内置的函数和异常

通俗点讲:命名空间就是为了确定全局的唯一,比如模块A中有变量c,模块B中也有一个变量c,此时,通过A.c来确定引用A中的变量c,比如在class2模块中要引用class1中的变量a,在导入class1模块之后,可以使用class1.a访问class1中的变量

(2)命名空间查找顺序

当一行代码要使用变量 x 的值时,Python 会到所有可用的命名空间去查找变量,按照如下顺序:

  • 局部命名空间:特指当前函数或类的方法,如果函数定义了一个局部变量 x,或一个参数x,Python 将使用它,然后停止搜索
  • 全局命名空间:特指当前的模块,如果模块定义了一个名为 x 的变量、函数或类,Python将使用它然后停止搜索
  • 内置命名空间:对每个模块都是全局的,作为最后的尝试,Python 将假设 x 是内置函数或变量
  • 如果 Python 在这些命名空间找不到 x,它将放弃查找并引发一个 NameError 异常,如 NameError: name 'xxx' is not defined

(3)当函数嵌套时的查找规则

  • 先在当前 (嵌套的或 lambda) 函数的命名空间中搜索
  • 然后是在父函数的命名空间中搜索
  • 接着是模块命名空间中搜索
  • 最后在内置命名空间中搜索
def my_func():
    name = "小北 "

    def func_son():
        name = "小南 "  # 此处的name变量,覆盖了父函数的name变量
        print(name)

    # 调用内部函数
    func_son()
    print(name)


my_func()

(4)命名空间的生命周期

  • 内置命名空间在 Python 解释器启动时创建,会一直保留,不被删除
  • 模块的全局命名空间在模块定义被读入时创建,通常模块命名空间也会一直保存到解释器退出
  • 当函数被调用时创建一个局部命名空间,当函数返回结果或抛出异常时,被删除,每一个递归调用的函数都拥有自己的命名空间
a = 1


def my_func(str):
    if a == 1:
        print(str)
        a = 24


my_func("file")

上面的程序会报错,在python的函数中和全局同名的变量,如果你有修改变量的值就会变成局部变量,在修改之前对该变量的引用自然就会出现没定义这样的错误了,如果确定要引用全局变量,并且要对它修改,就必须加上global关键字,如下:

a = 1


def my_func(str):
    global a
    if a == 1:
        print(str)
        a = 24


my_func("file")
print(a)

(5)命名空间的访问

# 局部命名空间的访问

局部命名空间可以用 locals() 来访问

def my_func():
    a = 1
    b = 2
    print(locals())


my_func()

locals 返回一个名字/值对的 dictionary ,这个 dictionary 的键是字符串形成的变量名字,dictionary 的值是变量的实际值

# 全局命名空间的访问

全局命名空间可以通过 globals() 来访问

a = 1
b = 2

print(globals())

(6)locals globals 之间的区别

locals 是只读的,但 globals 是可读写的

def my_func():
    x = 123
    print(locals())
    locals()["x"] = 456
    print("x=", x)


y = 123
my_func()
globals()["y"] = 111
print("y=", y)

3、在Python工程中导入模块

在平时的开发中,一般会有多模块,为了软件的复用性,我们通常在模块之间相互引用,以达到复用的目的

在Python中,可以使用 import 关键字进行模块的导入,语法如下:

import module_name

例如,在模块 class3 中要引用同目录下 test 中的变量 a ,此时可以如下:

import test

print(test.a)

这个时候,需要使用命名空间来访问相应的变量,这个导入似乎很轻松,但是如果模块名太长,代码写起来就显得非常拖沓,这个时候想让代码看起来简短些,就可以使用别名,uti语法如下:

import module_name as alias

import 导入时,要做的操作:

  • 查找一个模块,如果有必要还会加载并初始化模块
  • 在局部命名空间中为 import 语句位置所处的作用域定义一个或多个名称

当一个模块首次被导入时,Python 会搜索该模块,如果找到就创建一个 module 对象并初始化它;如果置顶名称的模块未找到,则会引发 ModuleNotFoundError ,当发起调用导入机制时,Python 会实现多种策略来搜索指定名称的模块

注意:当模块首次被导入时,会执行模块里面的代码

使用 import 关键字导入模块是最常用的方式,但是还有另外的方法可以导入模块,即使用 importlib 模块进行导入,基本语法如下:

import importlib
importlib.import_module("module_name")

若想要导入另一个包中的模块,语法如下:

from package import module

示例:

from pythonProject.package.class2 import class2
print(class2.a)

若想要导入多层包中的模块,语法如下(可参考上面示例):

from package.son import module

# 【package.son】是多层包路径

4、在Python工程中导入变量

(1)导入一个变量

导入一个变量时,基本语法如下:

from module import variable

示例:从 test 中导入变量 a

from test import a

print(a)

注意:当模块首次被调用时,会执行模块里面的代码

修改 test 中的内容,再次运行 class4 文件,就会发现,先输出 hello world ,再打印 a 的值

a = 1111
print("hello world")

(2)导入多个变量

如果要导入多个变量,可以使用逗号分隔

from class3 import a, b
print(a)
print(b)

若要导入的变量非常多,则可以使用【*】进行导入

from test import *
print(a)
print(b)
print(c)

注意:虽然支持【*】通配符进行导入,但是不建议过多使用,因为使用【*】导入,阅读代码时会难以理清其含义

5、python中的导包机制

(1)Python导入机制

导入期间,会在 sys.modules 查找模块名称,如存在则其关联的值就是需要导入的模块,导入过程完成,如果值为 None ,则会引发 ModuleNotFoundError;如果找不到指定
模块名称,Python 将继续搜索该模块
如果指定名称的模块在 sys.modules 找不到,则将发起调用 Python 的导入协议以查找和加载该模块。此协议由两个概念性模块构成,即查找器和加载器

  • 查找器的任务是确定是否能使用其所知的策略找到该名称的模块
  • 同时实现这两种接口的对象称为导入器,它们在确定能加载所需的模块时会返回其自身

以【from class5_import import a】为例,来理解Python导入机制:

在sys.modules中查找符号"class5_import"
# 如果符号存在,则获得符号class5_import对应的module对象<module class5_import>
    @ 从<module class5_import>的dict中获得符号"a"对应的对象,如果"a"不存在,则抛出异常
# 如果符号class5_import不存在,则创建一个新的module对象<module class5_import>,注意,这时,module对象的dict为空
# 执行class5_import.py中的表达式,填充<module class5_import>的dict
# 从<module class5_import>的dict中获得"a"对应的对象,如果"a"不存在,则抛出异常

(2)模块导入示例

【模块 class5.py】

from class5_import import a

b = 22
print(a)

【模块 class5_import.py】

from class5 import b

a = 11

执行过程如下:

class5.py会执行报错

# 执行class5.py中的from class5_import import a,由于是执行的python class5.py,所以在sys.modules中并没有<module class5_import>存在,首先为B.py创建一个module对象(<module class5_import>),【注意,这时创建的这个module对象是空的,里边啥也没有】,在Python内部创建了这个module对象之后,就会解析执行class5_import.py,其目的是填充<module class5_import>这个dict


# 执行class5_import.py中的 from class5 import b,在执行class5_import.py的过程中,会碰到这一句,首先检查sys.modules这个module缓存中是否已经存在<module class5>了,由于这时缓存还没有缓存<module class5>,所以类似的,Python内部会为class5.py创建一个module对象(<module class5>),然后,同样地,执行class5.py中的语句


# 再次执行class5.py中的from class5_import import a,这时,由于在第1步时,创建的<module class5_import>对象已经缓存在了sys.modules中,所以直接就得到了<module class5_import>【注意,从整个过程来看,我们知道,这时<module class5_import>还是一个空的对象,里面啥也没有】,所以从这个module中获得符号"a"的操作就会抛出异常;如果这里只是import class5_import,由于"class5_import"这个符号在sys.modules中已经存在,所以是不会抛出异常的

流程图如下:

6、__init__.py的作用及用法

(1)作用

  • 标志所在目录是一个模块包
  • 本身也是一个模块
  • 可用于定义模糊导入时要导入的内容

(2)用法

我们使用【from package import *】时会报错误,如果想使用该语法不报错,可以在__init__.py 中定义要导入的模块,即使用 __all__=['module_name1','module_name2'] 定义【*】号匹配时要导入的模块,之后再导入的时候,就可以使用【*】通配符进行模糊导入

  • 导入一个包的时候,包下的 __init__.py 中的代码会自动执行
  • 用于批量导入模块

当我们的许多模块汇总,都需要导入某些公共的模块,此时可以在__init__.py 中进行导入,之后直接导入该包即可

7、__all____name__的作用及其用法

(1)__all__ 的作用及其用法

__all__是list的结构,其作用如下:

  • 在普通模块中使用时,表示一个模块中允许哪些属性可以被导入到别的模块中
  • 在包下的__init__.py中,可用于标识模糊导入时的模块

在class6.py中导入包test,即可调用test包下的内容

class6.py

from pythonProject.package.class6.test import *
print(test_a.a)

(2)__name__ 的作用及其用法

  • __name__这个系统变量显示了当前模块执行过程中的名称,如果当前程序运行在这个模块中,__name__ 的名称就是__main__,如果不是,则为这个模块的名称
  • __main__一般作为函数的入口,类似于C语言,尤其在大型工程中,常常有【if __name__ == "__main__"】来表明整个工程开始运行的入口

示例:

def my_fun():
    if __name__ == "__main__":
        print("this is main")
    else:
        print(__name__)


my_fun()

十二、错误处理

1、Python中异常的捕获与处理

(1)错误

即还没开始运行,在语法解析的时候就发现语法存在问题,这个时候就是错误,例如:

print("hello world"

上面这句少了个括号,这个时候编辑器已经告诉我们这句语法有问题,如果继续运行,就会报相应的错误

(2)异常

代码写好之后,无明显语法错误(编辑器不知道有错,语法解析时也不知道有错),但是运行的时候会发生错误,这个时候称之为异常,例如:

print(10 / 0)

(3)警告

import warnings


def fxn():
    print("hello")
    warnings.warn("deprecated", DeprecationWarning)

fxn()

(4)异常处理

异常的处理格式如下:

try:
    你要做的可能会发生异常的事
except 可能会发生的异常:
    发生异常之后要做的事
except 可能会发生的异常2:
    发生异常之后要做的事2
finally:
    最终要做的事情

示例如下:

try:
    print(10 / 0)
except ZeroDivisionError:
    print("除数不能为0")

在平时的开发中,也会使用预定义清理的操作,来避免因为异常而导致程序崩溃,比如在进行IO操作的时候,可以使用如下代码,这样一旦运行时发生异常,程序会自动帮你关闭文件,避免整程序崩溃

with open("myfile.txt") as f:
    for line in f:
        print(line, end="")

2、自定义异常与异常的抛出

(1)自定义异常

虽然Python中提供了非常多的内置异常类,但是在平时开发中,针对特定业务,可能需要自定义异常,此时通过自定义集成Exception类的类,可以实现异常的自定义

class MyException(Exception):
    def __init__(self, parameter):
        err = '非法入参{0},分母不能为0'.format(parameter)
        Exception.__init__(self, err)
        self.parameter = parameter

(2)异常抛出

当我们代码中碰到某种特殊业务情况,需要向调用方抛出自定义异常,可以使用【raise】关键字

from class12.my_exception import MyException

def my_fun(x):
    if x == 0:
        raise MyException(x)
    return 12 / x


print(my_fun(0))

我们在捕获异常后,也可直接将异常抛出,此时直接使用【raise】关键字即可

def my_func():
    try:
        print(10 / 0)
    except ZeroDivisionError:
        print("除数不能为0")
        # 此处直接将捕获的异常抛出
        raise


my_func()

3、如何通过debug分析问题

(1)简单问题分析

首先,我们编写一个函数,有两入参 x,y,函数的返回值是 x/y 的值

def my_fun(x, y):
    result = x / y
    return result


print(my_fun(12, 0))

如上图,当入参变成[12,0]的时候,就会发生异常,这时候去定位问题:

  • 看错误信息,提取关键部分
  • 看错误发生的地方,并找出对应的位置(PyCharm中有行数显示)

这个时候可以看到,这个错误发生的地方是在第2行,当第6行调用第2行时发生错误,对于这种问题结合上面的推断,就可以解决问题了

(2)复杂问题分析

第一步:在对应出错的地方打下断点

第二步:右键选择debug

此时,程序运行至断点处就会停住,在下面的控制台可以看到此时对应的值,至此,可以确定异常产生的原因,从而解决问题

4、为代码编写单元测试

(1)什么是单元测试

单元测试(英语:Unit Testing)又称为模块测试,是针对程序模块(软件设计的最小单位)来进行正确性检验的测试工作。程序单元是应用的最小可测试部件,在过程化编程中,一个单元就是单个程序、函数、过程等;对于面向对象编程,最小单元就是方法,包括基类(超类)、抽象类、或者派生类(子类)中的方法。


简而言之:就是写一段代码,用来验证另一段代码在特定情况下的正确性

(2)单元测试的优缺点

  • 好处:减少bug、提高代码质量、可以放心重构(在未来修改实现的时候,可以保证代码的行为仍旧是正确的)
  • "坏处":占用开发时间,尤其是在起步阶段

(3)如何编写单元测试

第一步:新建Python文件,编写具体业务代码

class MyTest():


    def my_add(self, a, b):
        return a + b

第二步:右键类名,选择【Go To】---> 【test】,或者直接【ctrl+shift+t】

第三步:填写相应的模块名及测试类名,点击OK,此时PyCharm 会帮我们自动创建测试模块及类

第四步:编写测试代码,并执行单元测试

import unittest
from unittest import TestCase
from pythonProject.异常.单元测试 import MyTest


class TestMyTest(TestCase):
    def test_add(self):
        s = MyTest()
        self.assertEqual(s.my_add(1, 5), 6)


if __name__ == "__main__":
    unittest.main()

十三、IO操作

1、输入输出

Python提供了 input() 内置函数从标准输入读入一行文本,默认的标准输入是键盘

print("用户输入的内容是:",input())

当用户在控制台输入内容之后按回车,此时我们可以看到控制台输出如下:

优化内容,实现用户的友好交互,如下:

print("请输入内容,按回车结束:")
str = input()
print("用户输入的内容是:", str)

2、文件的读取

(1)打开文件

在Python中,可以使用 open 函数来打开文件,具体语法如下:

open(filename, mode)
  • filename:文件名,一般包括该文件所在的路径
  • mode:模式,如果读取时读取中文文本,需要在打开文件的时候使用 encoding 指定字符编码为 utf-8

常见的打开文件的模式如下:

模式描述
t文本模式(默认)
x写模式,新建一个文件,如果该文件已存在则会报错
b二进制模式
+打开一个文件进行更新(可读可写)
r以只读方式打开文件,文件的指针将会放在文件的开头,这是默认模式
rb以二进制格式打开一个文件用于只读,文件指针将会放在文件的开头,这是默认模式,一般用于非文本文件如图片等
r+打开一个文件用于读写,文件指针将会放在文件的开头
rb+以二进制格式打开一个文件用于读写,文件指针将会放在文件的开头,这是默认模式,一般用于非文本文件如图片等
w打开一个文件只用于写入,如果该文件已存在则打开文件,并从头开始编辑,即原有内容会被删除;如果该文件不存在,则创建新文件
wb
以二进制格式打开一个文件只用于写入,如果该文件已存在则打开文件,并从开头开始编辑,即原有内容会被删除。如果该文件不存在,创建新文件。一般用于非文本文件如图片等
w+
打开一个文件用于读写,如果该文件已存在则打开文件,并从开头开始编辑,即原有内容会被删除。如果该文件不存在,创建新文件
wb+
以二进制格式打开一个文件用于读写,如果该文件已存在则打开文件,并从开头开始编辑,即原有内容会被删除。如果该文件不存在,创建新文件。一般用于非文本文件如图片等
a
打开一个文件用于追加。如果该文件已存在,文件指针将会放在文件的结尾。也就是说,新的内容将会被写入到已有内容之后。如果该文件不存在,创建新文件进行写入
ab
以二进制格式打开一个文件用于追加。如果该文件已存在,文件指针将会放在文件的结尾。也就是说,新的内容将会被写入到已有内容之后。如果该文件不存在,创建新文件进行写入
a+
打开一个文件用于读写。如果该文件已存在,文件指针将会放在文件的结尾。文件打开时会是追加模式。如果该文件不存在,创建新文件用于读写
ab+
以二进制格式打开一个文件用于追加。如果该文件已存在,文件指针将会放在文件的结尾。如果该文件不存在,创建新文件用于读写

(2)读取文件

读取文件的内容,使用 read 相关方法

使用 read 方法,读取文件的全部内容(如果文件较大,一次性读取可能会导致内存不足,此时需要进行行数指定)

使用 readline() 方法,读取文件的一行

使用 readlines() 方法,一次读取所有内容并按行返回 list

示例:

file = open("E:\\Desktop\\test.txt", "r", encoding="utf-8")
print(file.readline())
print(file.read())

特别注意:

每次打开文件完成相应操作之后,都必须关闭该文件,且因为文件在读写过程中可能会出现 IOError 而导致文件不能正常关闭,所以每次读写文件时,必须使用 try finally 语法包裹,使其最终都能正常关闭文件

try:
    file = open("E:\\Desktop\\test.txt", "r", encoding="utf-8")
    print(file.readline())
    print(file.read())
finally:
    file.close()

如果觉得 try finally 语法有点繁琐,也可以使用 Python 提供的另外一种方式:

with open("E:\\Desktop\\test.txt", "r", encoding="utf-8") as file:
    print(file.readline())
    print(file.read())

使用 with 这种方式,无须显示去关闭文件,该语法在使用完文件后,会自动帮我们关闭文件

3、文件内容的写入

写入文件内容时,需要使用 open 打开文件,相应的 mode 指定为追加写入,之后可以使用 write 函数进行文件的写入

try:
    file = open("E:\\Desktop\\test.txt", "a", encoding="utf-8")
    file.write("hello world123")

finally:
    file.close()

4、操作文件夹

(1)创建文件夹

可以使用 os.mkdir(dir_name) 来在当前目录下创建一个目录

import os

os.mkdir("test")

(2)创建多层级文件夹

import os

os.makedirs("test\\my")

(3)获取当前所在目录

import os

print(os.getcwd())

(4)改变当前的工作目录

import os

os.chdir("test")
print(os.getcwd())

(5)删除空文件夹

import os

os.rmdir("test")

(6)删除多层空文件夹

import os

os.removedirs("test\\my")

5、操作文件夹示例

(1)文件夹下仅有文件的删除

问题:如果一个文件夹下仅有文件,自定义一个函数,用于删除整个文件夹

思路:获取文件夹中所有的文件,遍历删除,之后删除空的文件夹

import os

def my_rmdir(dir):
    files = os.listdir(dir)
    os.chdir(dir)
    # 删除文件夹中所有的文件
    for file in files:
        os.remove(file)
        print("删除成功!", file)
    # 删除空的文件夹
    os.chdir("..")
    os.rmdir(dir)


my_rmdir("E:\Desktop")


def remove_dir(dir):
	dir = dir.replace('\\', '/')
	if (os.path.isdir(dir)):
		for p in os.listdir(dir):
			remove_dir(os.path.join(dir, p))
		if (os.path.exists(dir)):
			os.rmdir(dir)
	else:
		if (os.path.exists(dir)):
			os.remove(dir)

(2)文件夹下有子文件夹的删除

问题:如果一个文件夹下有子文件夹,自定义一个函数,用于删除整个文件夹

思路:获取文件夹下所有文件及文件夹,文件删除,文件夹递归处理

import os

def my_rmdir(dir):
    # 判断是不是文件夹,如果是,递归调用my_rmdir
    if (os.path.isdir(dir)):
        for file in os.listdir(dir):
            my_rmdir(os.path.join(dir, file))
        # 如果是空的文件夹,直接删除
        if (os.path.exists(dir)):
            os.rmdir(dir)
            print("删除文件夹成功===>", dir)
    # 只是文件,直接删除
    else:
        if (os.path.exists(dir)):
            os.remove(dir)
            print("删除文件成功===>", dir)


my_rmdir("E:\\Desktop")

其实删除文件夹,直接调用 python 提供的 api即可

import shutil

shutil.rmtree("E:\\Desktop")

6、StringIO和BytesIO

(1)理解

在前面,我们更多接触到IO操作,都是在文件层面上进行操作,但是,在平时的开发中,某些场景下,我们只是要缓存相应的文本内容,方便后续处理,这时候我并不需要新建文件并写入,我只想直接在内存中缓存这些文本,此时StringIo,BytesIo就派上用场了


StringIo,BytesIo均属于io包下(3.7环境),类似于操作文件,临时在内存中缓存文本,两者api与直接进行文件io的操作相似,StringIO跟ByteIo的区别在于前者写入字符串,后者写入二进制

(2)用法

可以在一开始就赋值内容 每次使用完 StringIO 之后,都必须显式进行关闭,也可使用 with 语法
from io import StringIO

str_io = StringIO("hello world")
print(str_io.readline())
str_io.close()
from io import StringIO

str_io = StringIO()
str_io.write("hello")
str_io.write(" world")
print(str_io.getvalue())
str_io.close()
from io import StringIO

with StringIO() as str_io:
    str_io.write("hello")
    str_io.write(" world")
    print(str_io.getvalue())

十四、面向对象编程

1、面向对象及其三大特性

(1)面向对象理解

面向对象程序设计(Object Oriented Programming)作为一种新方法,其本质是以建立模型体现出来的抽象思维过程和面向对象的方法。模型是用来反映现实世界中事物特征的,任何一个模型都不可能反映客观事物的一切具体特征,只能对事物特征和变化规律的一种抽象,且在它所涉及的范围内更普遍、更集中、更深刻地描述客体的特征。通过建立模型而达到的抽象是人们对客体认识的深化。

(2)面向对象的三大特性

面向对象的三大特性分别为封装、继承和多态

  • 封装:把对象的属性私有化,同时提供可以被外界访问这些属性的方法
  • 继承:使用已存在的类的定义,作为建立新类的基础技术,新类可以增加新的属性或新的方法,也可以用父类的功能,但不能选择性的继承;通过使用继承,能够非常方便的复用这些代码
  • 多态:python中的多态,本质也是通过继承获得多态的能力(不同的子类调用相同的父类方法,产生不同的执行结果,可以增加代码的外部调用灵活度)

2、类和对象

(1)类和对象的理解

类和对象(class)是两种以计算机为载体的计算机语言的合称。对象是对客观事物的抽象,类是对对象的抽象,类是一种抽象的数据类型
客观事物:张三、李四、王五这几个同事,都是客观存在的,可抽象成对象,将这些对象进一步抽取共同特征:名字、性别、年龄,就形成了类

(2)类和对象的关系

类就像工厂里生产产品的模具,它负责对象的创建,决定了对象将被塑造成什么样,有什么属性、做出来之后能做什么
比如:有个女娲造人的模具(类),定义了名字、性别、身高(属性),能自我介绍(方法)

(3)定义类以及实例化对象

# 定义person类
class Person():

    # 所有类方法,无论是否要用到,都必须有self参数
    def introduce_self(self):
        print("my name is cendy")


# 实例化对象的时候 使用类名(),即可完成实例化
person = Person()
# 调用对象方法可以使用对象名.方法名()
person.introduce_self()

注意:定义类的时候,类名需使用驼峰的命名方式(即单词首字母大写,如果类名由多个单词组成,同样每个单词的首字母都大写)

虽然说都是通过同一个模板(类)刻出来来的(多个对象),但是,每个对象都是独立的个体,都不一样,如下:person跟person2看起来几乎一样,但是,通过id函数可以很明确的知道,两者不同

# 定义Person类
class Person():

    # 所有类方法,无论是否要用到,都必须有self参数
    def introduce_self(self):
        print("my name is cendy")


# 实例化多个对象
person = Person()
person2 = Person()
# 通过id函数证明实例化出来的两个对象都是不同的
print("person对象的ID值为:", id(person))
print("person2对象的ID值为:", id(person2))

3、类的构造函数

(1)什么是构造函数

构造函数,是一种特殊的方法,主要用来在创建对象的时候去初始化对象,即为对象成员变量赋初始值

注意:python中不支持多个构造函数

在python中,使用__init__作为构造函数,如果不显式写构造函数,python会自动创建一个什么也不干的默认构造函数,如下:

# 定义person类
class Person():
    pass

# 实例化对象的时候,使用类名(),即可完成实例化
person = Person()
person1 = Person()
person2 = Person()

每次实例化对象的时候,均会帮我们自动执行构造函数里面的代码,如下:

# 定义Person类
class Person():

    def __init__(self):
        print("hello world")


# 实例化对象的时候 使用类名(),即可完成实例化
person = Person()
person1 = Person()
person2 = Person()

(2)构造函数的用途

# 定义person类
class Person():

	# 作为模板,在构造函数中,为必须的属性绑定对应的值
	def __init__(self, name, age, height):
		self.name = name
		self.age = age
		self.height = height

	def introduce_self(self):
		print("hello,my name is %s,my age is %d and i'm %d height" % (self.name, self.age, self.height))

# 实例化对象的时候 使用类名(),即可完成实例化
person = Person('cendy', 25, 175)
# 访问对象属性可以使用对象实例.属性名的方式为属性赋值

# 调用对象方法可以使用对象名.方法名()
person.introduce_self()

特别注意:当使用了构造函数,且构造函数式带参的且无默认参数,此时,实例化对象的时候,必须显式入参,否则会报错。若要避免错误,可以在构造函数使用默认参数
# 定义person类
class Person():

	# 作为模板,在构造函数中,为必须的属性绑定对应的值
	def __init__(self, name, age, height):
		self.name = name
		self.age = age
		self.height = height

	def introduce_self(self):
		print("hello,my name is %s,my age is %d and i'm %d height" % (self.name, self.age, self.height))

# 实例化对象的时候 使用类名(),即可完成实例化
person = Person()
# 访问对象属性可以使用对象实例.属性名的方式为属性赋值

# 调用对象方法可以使用对象名.方法名()
person.introduce_self()

4、类变量与实例变量

(1)类变量

类变量定义在类中且在函数体之外,类变量通常不作为实例变量使用;类变量在整个实例化的对象中是公用的

(2)实例变量

实例变量是定义在方法中的变量,用 self 绑定到实例上,只作用于当前实例的类

(3)访问实例变量和类变量

访问实例变量可以使用【对象名.属性】,访问类变量可以使用【类名.属性】

# 定义person类
class Person():
    total = 0

    def __init__(self, name, age, height):
        self.name = name
        self.age = age
        self.height = height


person = Person('cendy', 25, 175)
# 访问实例变量
print(person.name)
# 访问类变量
print(Person.total)

(4)修改实例变量和类变量的值

如果要修改实例变量的值,可以使用【对象名.属性=新值】的方式进行赋值;

如果要修改类变量的值,可以使用【类名.属性=新值】的方式进行赋值,在实例方法里,可以使用 self.__class__

# 定义person类
class Person():
    total = 0

    def __init__(self, name, age, height):
        self.name = name
        self.age = age
        self.height = height


person = Person('cendy', 25, 175)
# 修改实例变量的值
person.name = 'summer'
# 访问实例变量
print(person.name)
# 修改类变量的值
Person.total = 1
# 访问类变量
print(Person.total)

下面的代码段显示如下:该代码不会报错,实例变量的查找规则优先从实例对象中查找,若找不到会去类变量里查找

# 定义person类
class Person():
    total = 0

    def __init__(self, name, age, height):
        self.name = name
        self.age = age
        self.height = height


person = Person('cendy', 25, 175)
print(person.total)

(5)实例变量查找规则

先从实例变量里面找,找不到的话再看看类变量,若没有,就去父类找,如果还是找不到,就报错

在对象里,有一个__dict__对象,存储着对象相关的属性

# 定义person类
class Person():
    name = 0

    def __init__(self, name, age, height):
        self.name = name
        self.age = age
        self.height = height


person = Person('cendy', 25, 175)
print(person.name)
print(person.__dict__)

(6)类变量存储

类也有对应的__dict__,存储着与类相关的信息

# 定义person类
class Person():
	total = 0

	def __init__(self, name, age, height):
		self.name = name
		self.age = age
		self.height = height

person = Person('cendy', 25, 175)
print(Person.__dict__)

案例:定义一个动物类,有name、age的属性,动物有eat的行为,每次调用该行为,直接打印具体动物 is eating,如 老虎 is eating

class Animal():
    def __init__(self, name, age):
        self.name = name
        self.age = age

    def eat(self):
        print(self.name, "is eating")


animal = Animal("dog", 5)
animal.eat()

5、实例方法与self关键字

(1)实例方法

与实例变量类似,属于实例化之后的对象,定义实例方法至少要有一个self入参,由实例对象调用

(2)self关键字作用

self指向实例本身,我们在实例方法中药访问实例变量,必须使用【self.变量名】的形式

(3)self名称统一

self名称不是必须的,在python中,self不是关键字,你可以定义成 a 或者 b 或者其他名字都可以,但是约定俗成,为了和其他编程语言统一,减少理解难度,最好还是不要另起名称

# 定义person类
class Person():
    total = 0

    # 构造函数,用于为成员变量赋初始值
    def __init__(aa, name, age, height):
        aa.name = name
        aa.age = age
        aa.height = height

    def introduce_self(self):
        print("hello,my name is %s,my age is %d and i'm %d height" % (self.name, self.age, self.height))


# 实例化对象的时候 使用类名(),即可完成实例化
person = Person('cendy', 25, 175)
person.introduce_self()

在实例方法中,如果不使用【self.属性名】的方式去访问对应的属性,会发生报错

# 定义person类
class Person():

    # 构造函数,用于为成员变量赋初始值
    def __init__(self, nick, age, height):
        self.name = nick
        self.age = age
        self.height = height
        # 不使用self.属性名的方式去访问对应的属性
        print(name)

    def introduce_self(self):
        print("hello,my name is %s,my age is %d and i'm %d height" % (self.name, self.age, self.height))


# 实例化对象的时候 使用类名(),即可完成实例化
person = Person('cendy', 25, 175)
person.introduce_self()

如果不实例化对象,直接使用【类名.实例方法名】的方式调用方法,则会报错

# 定义person类
class Person():
    # 构造函数,用于为成员变量赋初始值
    def __init__(self, nick, age, height):
        self.name = nick
        self.age = age
        self.height = height


def introduce_self(self):
    print("hello,my name is %s,my age is %d and i'm %d height" % (self.name, self.age, self.height))


Person.introduce_self()

6、类方法与静态方法

(1)类方法

类方法使用装饰器@classmethod。第一个参数必须是当前类对象,该参数名一般约定为“cls”(不是cls也没关系,但是,约定俗成的东西不建议变动),通过它来传递类的属性和方法(不能传实例的属性和方法);其调用对象可以是实例对象和类

# 定义Person类
class Person():
    total = 1

    @classmethod
    def print_total(cls):
        cls.total += 1
        print(cls.total)


# 类调用
Person.print_total()
# 实例对象调用(不建议)
person = Person()
person.print_total()

(2)静态方法

使用装饰器@staticmethod。参数随意,没有“self”和“cls”参数,但是方法体中不能使用类或实例的任何属性和方法
静态方法是类中的函数,不需要实例,静态方法主要是用来存放逻辑性的代码,逻辑上属于类,但是和类本身没有关系,也就是说在静态方法中,不会涉及到类中的属性和方法的操作,可以理解为:静态方法是个独立的、单纯的函数,它仅仅托管于某个类的名称空间中,便于使用和维护

# 定义person类
class Person():


    @staticmethod
    def print_hello():
        print("hello world")


Person.print_hello()

小总结:

实例方法:与具体实例相关,用于操作实例变量
类方法:与类相关,用于操作类变量,虽然实例可以直接调用,但是不建议通过实例调用
静态方法:与类和具体的实例无关,相当于一个独立的函数,只是依托于类的命名空间内

7、Python中的访问限制

如下我们定义了一个类,实例化了一个对象,给其年龄赋值-1,程序正确运行,但是,这不符合现实生活中的场景,当我们不加限制的将属性暴露出去,也就是意味着我们的类失去了安全性

# 定义person类
class Person():

    # 构造函数,用于为成员变量赋初始值
    def __init__(self, name, age, height):
        self.name = name
        self.age = age
        self.height = height

    def introduce_self(self):
        print("hello,my name is %s,my age is %d and i'm %d height" % (self.name, self.age, self.height))


person = Person("cendy", 25, 175)
person.age = -1
person.introduce_self()

当我们不希望外部可以随意更改类内部的数据,可以将变量定义为私有变量,并提供相应的操作方法,为了保证外部不可以随意更改实例内部的数据,可以在构造函数中,在属性的名称前加上两个下划线__,这样该属性就变成了一个私有变量,只有内部可以访问,外部不能访问

# 定义person类
class Person():

	# 构造函数,用于为成员变量赋初始值
	def __init__(self, name, age, height):
		self.name = name
		self.__age = age
		self.height = height

	def set_age(self, age):
		if age < 0 or age > 150:
			raise Exception("非法入参,年龄不合法")
		self.__age = age

	def introduce_self(self):
		print("hello,my name is %s,my age is %d and i'm %d height" % (self.name,self.age, self.height))

person = Person("cendy", 25, 175)
print(person.age)

方法私有化,同样也是在方法名前面加上__

# 定义person类
class Person():

	# 构造函数,用于为成员变量赋初始值
	def __init__(self, name, age, height):
		self.name = name
		self.__age = age
		self.height = height

	def set_age(self, age):
		if age < 0 or age > 150:
			raise Exception("非法入参,年龄不合法")
		self.__age = age

	def __introduce_self(self):
		print("hello,my name is %s,my age is %d and i'm %d height" % (self.name,self.age, self.height))


person = Person("cendy", 25, 175)
person.__introduce_self()

8、打破Python中的访问限制

问题1:下面的程序依旧可以运行,可以打印实例的__dict__来看下:我们可以看到,在实例里面,自动帮我们整了一个__age的属性

# 定义person类
class Person():

	# 构造函数,用于为成员变量赋初始值
	def __init__(self, name, age, height):
		self.name = name
		self.__age = age
		self.height = height

	def set_age(self, age):
		if age < 0 or age > 150:
			raise Exception("非法入参,年龄不合法")
		self.__age = age

	def introduce_self(self):
		print("hello,my name is %s,my age is %d and i'm %d height" % (self.name,self.age, self.height))


person = Person("cendy", 25, 175)
person.__age = -1
print(person.__age)
print(person.__dict__)

问题2:在python中,仅属性或方法前面加上__时,表示私有;如果后面再加上__,此时含义就发生改变,变成了普通的属性或方法

# 定义person类
class Person():

	# 构造函数,用于为成员变量赋初始值
	def __init__(self, name, age, height):
		self.name = name
		self.__age__ = age
		self.height = height

	def set_age(self, age):
		if age < 0 or age > 150:
			raise Exception("非法入参,年龄不合法")
		self.__age__ = age

	def introduce_self(self):
		print("hello,my name is %s,my age is %d and i'm %d height" %(self.name, self.age, self.height))


person = Person("cendy", 25, 175)
print(person.__age__)

问题3:通过问题1,我们看出对象的__dict__打印出来的结果,多了个_Person__age,其值刚好对应私有属性的值,因此,可以如下访问:

# 定义person类
class Person():

	# 构造函数,用于为成员变量赋初始值
	def __init__(self, name, age, height):
		self.name = name
		self.__age = age
		self.height = height

	def set_age(self, age):
		if age < 0 or age > 150:
			raise Exception("非法入参,年龄不合法")
		self.__age = age

	def introduce_self(self):
		print("hello,my name is %s,my age is %d and i'm %d height" % (self.name,self.age, self.height))


person = Person("cendy", 25, 175)
#打破访问限制
print(person._Person__age)

十五、面向对象高级特性

1、python中的继承

(1)继承的理解

继承可以使得子类具有父类的属性和方法或者重新定义、追加属性和方法等

(2)python中如何继承

# 子类直接继承

# 定义animal类
class Animal():

    def __init__(self, name):
        self.name = name

    def walk(self):
        print(self.name + " is walking")


# Dog继承animal
class Dog(Animal):
    pass


# cat继承animal
class Cat(Animal):
    pass


# Dog和cat均因为继承animal而获得walk的能力和属性
dog = Dog("Dog")
dog.walk()
print('当前的小动物是:', dog.name)
print('---------------------')
cat = Cat("Cat")
cat.walk()
print('当前的小动物是:', cat.name)

# 子类可以有自己的属性与方法

# 定义animal类
class Animal():

    def __init__(self, name):
        self.name = name

    def walk(self):
        print(self.name + " is walking")


# Dog继承animal
class Dog(Animal):
    # Dog有吠叫的功能
    def bark(self):
        print(self.name + " is bark")


dog = Dog("Dog")
print(dog.name)
dog.walk()
dog.bark()

# 子类具备父类所有的属性与功能,但是父类并不具备子类的属性与功能

# 定义animal类
class Animal():
	def __init__(self, name):
		self.name = name
	def walk(self):
		print(self.name + " is walking")

# Dog继承animal
class Dog(Animal):
	def bark(self):
		print(self.name + " is bark")


animal = Animal("animal")
dog = Dog("Dog")
print(isinstance(dog, Animal))
print(isinstance(animal, Dog))

# 当子类有自己的构造方法时,将会覆盖父类的构造方法

# 定义animal类
class Animal():
    def __init__(self):
        print("Animal")


# Dog继承animal
class Dog(Animal):
    def __init__(self):
        print("Dog")


dog = Dog()

# 子类重写父类方法

# 定义animal类
class Animal():
    def __init__(self):
        print("Animal")

    def eat(self, name):
        print("Animal is eating")


# Dog继承animal
class Dog(Animal):
    def __init__(self):
        print("Dog")

    def eat(self, name):
        print("Dog is eating \n" + "it's name is:" + name)


dog = Dog()
dog.eat("aimy")

注意:当且仅当子类方法与父类同名,入参相同,才可称之为重写父类的方法

不要为了获得某些属性或功能而继承,比如:因为在Dog类中写了一个 eat 方法,觉得 Person 也该有这样的方法,就直接继承 Dog,这种不可取

问题:如下所示,程序会报错

原因:之所以会报错,是因为子类自己写了init方法,程序运行的时候就不会去调用父类的构造方法,所以,实例中就不存在name这样一个属性

# 定义animal类
class Animal():
	def __init__(self,name):
		self.name = name

# Dog继承animal
class Dog(Animal):
	def __init__(self):
		print("Dog")


dog = Dog()
print(dog.name)

2、super的作用及其用法

(1)super函数理解

super() 函数是用于调用父类(超类)的一个方法,一般是用来解决多重继承问题的,直接用类名调用父类方法在使用单继承的时候没问题,但是如果使用多继承,会涉及到查找顺序(MRO)、重复调用(钻石继承)等种种问题

(2)super函数的基本语法

在python3中,可以直接使用super() 而无需任何参数来调用父类

super(类名, self)

(3)super函数应用

第一节出现一个问题,子类自己写了init方法,因此无法调用父类init方法中的name属性,如果想要调用父类的该属性,有两种方法:

方法1:直接通过父类名.__init__来调用,虽然可以达到目的,但是不建议直接通过类名去调用

# 定义animal类
class Animal():
    def __init__(self, name):
        self.name = name


# Dog继承animal
class Dog(Animal):
    def __init__(self, name):
        Animal.__init__(self, name)
        print("Dog")


dog = Dog("tomcat")
print(dog.__dict__)
print(dog.name)

方法2:通过super去进行调用

# 定义animal类
class Animal():
    def __init__(self, name):
        self.name = name


# Dog继承animal
class Dog(Animal):
    def __init__(self, name):
        super(Dog, self).__init__(name)
        print("Dog")


dog = Dog("tomcat")
print(dog.__dict__)
print(dog.name)

3、抽象方法与多态

(1)抽象方法理解

在面向对象编程语言中抽象方法指一些只有方法声明,而没有具体方法体的方法;抽象方法一般存在于抽象类或接口中,抽象类的一个特点是它不能直接被实例化,子类要么是抽象类,要么必须实现父类抽象类里定义的抽象方法

在python3中可以通过使用abc模块轻松的定义抽象类

(2)定义抽象类

from abc import ABCMeta, abstractmethod

# 定义抽象类时,使用metaclass=ABCMeta
class Animal(metaclass=ABCMeta):

	@abstractmethod
	def eat(self):
		pass

class Dog(Animal):
	def eat(self):
		print("dog is eating")


dog = Dog()
dog.eat()

抽象类的子类必须实现抽象类中所定义的所有方法,否则,程序不能正确运行

from abc import ABCMeta, abstractmethod

# 定义抽象类时,使用metaclass=ABCMeta
class Animal(metaclass=ABCMeta):

	@abstractmethod
	def eat(self):
		pass

class Dog(Animal):
	pass


dog = Dog()

即使是实现部分抽象方法也是不行的

class Animal(metaclass=ABCMeta):
	
	@abstractmethod
	def eat(self):
		pass

	@abstractmethod
	def run(seft):
		pass

class Dog(Animal):
	
	def eat(self):
		print("Dog is eating")


dog = Dog()

(3)多态理解

不同的子类对象调用相同的父类方法,产生不同的执行结果,可以增加代码的外部调用灵活度

# 定义animal类
from abc import ABCMeta, abstractmethod

# 定义抽象类时,使用metaclass=ABCMeta
class Animal(metaclass=ABCMeta):

	@abstractmethod
	def eat(self):
		pass

	@abstractmethod
	def run(seft):
		pass

	def activity(self):
		self.eat()
		self.run()

class Dog(Animal):
	def eat(self):
		print("Dog is eating")
	
	def run(self):
		print("dog is running")

class Cat(Animal):
	def eat(self):
		print("cat is eating")

	def run(self):
		print("cat is running")


# 不同的子类对象,调用父类的activity方法,产生不同的执行结果
dog = Dog()
cat = Cat()
dog.activity()
cat.activity()

(4)多态的好处

  • 增加了程序的灵活性
  • 增加了程序的可扩展性

4、python中的多重继承

python里面支持多重继承,也就是一个子类能有多个父类

class Father:
    def power(self):
        print("拥有很大的力气")


class Mother:
    def sing(self):
        print("唱歌")


class Me(Father, Mother):
    pass


me = Me()
me.power()
me.sing()

当继承多个父类时,如果父类中有相同的方法,那么子类会优先使用最先被继承的方法

class Father:
    def power(self):
        print("拥有很大的力气")

    def sing(self):
        print("唱草原歌")


class Mother:
    def sing(self):
        print("唱情歌")


# 改变继承的顺序
class Me(Mother, Father):
    pass


me = Me()
me.power()
me.sing()

5、多重继承所带来的的问题

(1)新式类和旧式类

新式类都从object继承(python3中,默认都继承自object),经典类不需要
新式类的MRO(method resolution order 基类搜索顺序)算法采用C3算法广度优先搜索,而旧式类的MRO算法是采用深度优先搜索
新式类相同父类只执行一次构造函数,经典类重复执行多次

class Father:
	def my_fun(self):
		print("Father")


class Son1(Father):
	def my_fun(self):
		print("Son1")


class Son2(Father):
	def my_fun(self):
		print("Son2")


class Grandson(Son1, Son2):
	def my_fun(self):
		print("Grandson")


d = Grandson()
d.my_fun()

(2)菱形继承(钻石继承)

之前学习继承的时候说过,要执行父类构造方法有两种方式,一种是直接使用父类名字调用构造,一种是使用super(),普通的继承中,这两种方法没啥大问题,但是,如果在多重继承里,使用父类名字调用构造可能会发生问题

class Father:
	def __init__(self):
		print("this is Father")

	def my_fun(self):
		print("Father")

class Son1(Father):
	def __init__(self):
		Father.__init__(self)

	def my_fun(self):
		print("Son1")


class Son2(Father):
	def __init__(self):
		Father.__init__(self)
	
	def my_fun(self):
		print("Son2")


class Grandson(Son1, Son2):
	def __init__(self):
		Son1.__init__(self)
		Son2.__init__(self)

	def my_fun(self):
		print("Grandson")


d = Grandson()
d.my_fun()

(3)解决菱形继承问题

要解决菱形继承多次调用构造的问题,可以使用super()

class Father:

    def __init__(self):
        print("this is Father")

    def my_fun(self):
        print("Father")


class Son1(Father):

    def __init__(self):
        print("Son1 init")
        super().__init__()

    def my_fun(self):
        print("Son1")


class Son2(Father):

    def __init__(self):
        print("son2 init")
        super().__init__()

    def my_fun(self):
        print("Son2")


class Grandson(Son1, Son2):

    def __init__(self):
        super().__init__()

    def my_fun(self):
        print("Grandson")


d = Grandson()
d.my_fun()

6、枚举类

(1)枚举理解

在数学和计算机科学理论中,一个集的枚举是列出某些有穷序列集的所有成员的程序,或者是一种特定类型对象的计数。这两种类型经常(但不总是)重叠

(2)枚举实例

假设现在开发一个程序,大概内容是记录车子在通过路口时红绿灯的颜色,并判断司机是否违规,程序普通的写法如下:

# 1 红色 2:黄 3:绿
def judge(color):
    if color == 1 or color == 2:
        print("司机闯红灯")
    else:
        print("司机正常行驶")

judge(1)

上面的程序存在一个很明显的问题,程序是自己写的,自己能理解。但是,如果别人来看我的程序,会无法理解1跟2这两个数字的意义,这里有注释还好,但是还有一个问题,可能另外的同事1是表示绿灯,这个时候双方理解就会产生很大的问题。
这个时候,我希望在系统里统一一下这些定义,枚举类的的作用就提现出来了,在枚举类里将有限个可能取值都列出来,大家统一用枚举类里的定义,如下:

from enum import Enum


class TrafficLight(Enum):
    RED = 1
    YELLOW = 2
    GREEN = 3


def judge(color):
    if color == TrafficLight.RED or color == TrafficLight.YELLOW:
        print("司机闯红灯")
    else:
        print("司机正常行驶")

judge(TrafficLight.GREEN)

在枚举类中, RED = 1 这样的是枚举成员, RED 是名字, 1 是值

(3)枚举访问

通过名字获取枚举成员

from enum import Enum


class TrafficLight(Enum):
	RED = 1
	YELLOW = 2
	GREEN = 3


print(TrafficLight.RED)

# 通过枚举成员获取名字以及值

from enum import Enum


class TrafficLight(Enum):
	RED = 1
	YELLOW = 2
	GREEN = 3


# 通过枚举成员获取名字
print(TrafficLight.RED.name)
# 通过枚举成员获取值
print(TrafficLight.RED.value)

# 通过值获取枚举成员

from enum import Enum


class TrafficLight(Enum):
	RED = 1
	YELLOW = 2
	GREEN = 3


# 通过枚举成员获取名字
print(TrafficLight(1))

(4)枚举类的特性

# 定义枚举时,其枚举成员的名称不允许相同

from enum import Enum, unique


class TrafficLight(Enum):
	RED = 0
	RED = 1
	YELLOW = 2
	GREEN = 3


# 1 红色 2:黄 3:绿
def judge(color):
	if color == TrafficLight.YELLOW or color == TrafficLight.RED:
		print("司机闯红灯")
	else:
		print("司机正常行驶")


judge(TrafficLight.YELLOW)

默认情况下,不同的成员值允许相同。但是两个相同值的成员,其第二个成员名称是第一个成员名称的别名;因此在访问枚举成员时,只能获取第一个成员

from enum import Enum, unique


class TrafficLight(Enum):
	RED = 1
	BLACK = 1
	YELLOW = 2
	GREEN = 3


print(TrafficLight(1))

如果要限定枚举里面所有的值必须唯一,可以在定义枚举类时,加上@unique

from enum import Enum, unique


@unique
class TrafficLight(Enum):
	RED = 1
	BLACK = 1
	YELLOW = 2
	GREEN = 3


print(TrafficLight(1))

(5)枚举成员的比较

from enum import Enum


class TrafficLight(Enum):
    RED = 1
    YELLOW = 2
    GREEN = 3


# 通过名字获取枚举成员
print(TrafficLight.RED == 1)
# 通过枚举成员获取值
print(TrafficLight.RED.value == 1)

  • 30
    点赞
  • 22
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值