Python(3)—— 列表推导式,文件操作,类和对象

一、列表推导式:用来创建列表

> 所谓的列表推导式,就是指的轻量级循环创建列表

格式:
列表推导式的常见形式: my_list = [ item    for item in iterable]
my_list: 列表名 (变量名,  属于标识符)
item: 将要存放到列表中的内容
for item in iterable:  非常标准的for循环表达式

[expr for iter in iterable if cond_expr]
expr: 将要存放到列表中的内容
iter: 遍历的每一项内容
    iterable: 遍历的对象
    if cond_expr: 条件表达式, 只有满足当前条件的,才能存放到列表中.

例如:
my_list = [x*x for x in range(10)]
print(my_list)
my_list1 = [x**2 for x in range(6)]
print(my_list1)

结果:
[0, 1, 4, 9, 16, 25, 36, 49, 64, 81]
[0, 1, 4, 9, 16, 25]

使用if的情况:

例如:
list = [x for x in range(3, 10) if x % 2 == 0]
print(list)
结果: [4, 6, 8]

例如:
list = [11, 10, 9, 8, 7, 6]
[x for x in list if x % 2]
结果:[11, 9, 7]

例如:
llist = [x for x in 'hello python' if x != ' ' and x != 'l']
print(llist)
结果: ['h', 'e', 'o', 'p', 'y', 't', 'h', 'o', 'n']

例如:
llist = ['哈哈' for x in range(3)]
print(llist)
结果: ['哈哈','哈哈' ,'哈哈']

例如:
llist = ['%d' % (i+1) for i in range(3)] #等同于llist = [str(i+1) for i in range(3)]
print(llist)
结果: ['1', '2' , '3' ] 

二、文件操作

> 什么是文件?      能够保存数据的集合. JPEG的压缩性能太强,压缩后,展开,压缩,展开...10次打开就很模糊,压缩时很多像素点剔除了,效果不好。所以网络传输中,传jpeg的话,压缩后展开还原不到原来的程度。所以我们常用png用于传输,它的压缩性能不好。

1. 文件操作介绍

在操作文件的整体过程
> 1. 打开文件,或者新建立一个文件
> 2. 读/写数据
> 3. 关闭文件

2.  文件的打开与关闭

1)  打开文件

在python,使用open函数,可以打开一个已经存在的文件,或者创建一个新文件.

说明:
* 使用open的时候,如果文件已经存在,则直接打开.
* 如果不存在,就创建一个新文件.

打开的方式: open(文件名,访问模式)

例如: f = open('test.txt', 'w') #如果是r的权限可以省略不写

| 访问模式: |                                                              |
| r        | 以只读方式打开文件。文件的指针将会放在文件的开头。这是默认模式。如果文件不存在会报错 |
| w        | 打开一个文件只用于写入。如果该文件已存在则将其覆盖。如果该文件不存在,创建新文件。 |

| a        | 打开一个文件用于追加。如果该文件已存在,文件指针将会放在文件的结尾。也就是说,新的内容将会被写入到已有内容之后。如果该文件不存在,创建新文件进行写入。 |
| rb        | 以二进制格式打开一个文件用于只读。文件指针将会放在文件的开头。这是默认模式。 |
| wb        | 以二进制格式打开一个文件只用于写入。如果该文件已存在则将其覆盖。如果该文件不存在,创建新文件。 |
| ab        | 以二进制格式打开一个文件用于追加。如果该文件已存在,文件指针将会放在文件的结尾。也就是说,新的内容将会被写入到已有内容之后。如果该文件不存在,创建新文件进行写入。 |
| r+        | 打开一个文件用于读写。文件指针将会放在文件的开头。          |
| w+        | 打开一个文件用于读写。如果该文件已存在则将其覆盖。如果该文件不存在,创建新文件。 |
| a+        | 打开一个文件用于读写。如果该文件已存在,文件指针将会放在文件的结尾。文件打开时会是追加模式。如果该文件不存在,创建新文件用于读写。 |
| rb+      | 以二进制格式打开一个文件用于读写。文件指针将会放在文件的开头。 |
| wb+      | 以二进制格式打开一个文件用于读写。如果该文件已存在则将其覆盖。如果该文件不存在,创建新文件。 |
| ab+      | 以二进制格式打开一个文件用于追加。如果该文件已存在,文件指针将会放在文件的结尾。如果该文件不存在,创建新文件用于读写。 |

2)  关闭文件

关闭格式:close()

例如:
# 新建一个文件,文件名为:test.txt
f = open('test.txt', 'w')
# 关闭这个文件
f.close()

3. 文件的读写

1) 写数据    ------>  write

> 使用write()可以完成向文件写入数据

例如:
f = open('test.txt', 'w', encoding='utf-8')
#写入有中文时,要加入这个编码方式,防止乱码,内存编码方式bgk,文件读取到内存就会# 变成gbk,写入到文件中时又会从bgk变成utf-8
str = '''既然你诚心诚意的问了,'''
f.write(str)
f.close()

我们会发现, 不但创建了一个新的文件,而且文件中还添加上了上面的语句.

**注意**:

- 如果文件不存在那么创建,如果存在那么就先清空,然后写入数据

2) 读数据  ---->  read

> 使用read(num)可以从文件中读取数据,num表示要从文件中读取的数据的长度(单位是字节)

> 如果没有传入num,那么就表示读取文件中所有的数据

例如:
f = open('test.txt', 'r', encoding='utf-8')
content = f.read(5)  # 最多读取5个数据,换行符也占1个位
print(content)
print("-"*30)  # 分割线,用来测试
content = f.read()  # 从上次读取的位置继续读取剩下的所有的数据
print(content)
f.close()  # 关闭文件,这个可以是个好习惯哦

结果:
竟然你诚心
------------------------------
诚意的发问了,

注意:

- 如果用open打开文件时,如果使用的"r",那么可以省略,即只写 `open('test.txt')`

3) 读数据 ------>  readlines,读取多行

> 就像read没有参数时一样,  readlines可以按照行的方式把整个文件中的内容进行一次性读取,并且返回的是一个列表,其中每一行的数据为一个元素

例如:
f = open('test.txt', 'r', encodint='utf-8')
content = f.readlines()
print(type(content))
i=1
for temp in content:
    print("%d:%s" % (i, temp))
    i += 1
f.close()

结果:
<class 'list'>
1:竟然你诚心诚意的发问了,
2:纳我就大发慈悲的告诉你,

4)  读数据 -----> readline,读取单行

f = open('test.txt', 'r', encoding='utf-8')
content = f.readline()
print("1:%s" % content)
content = f.readline()
print("2:%s" % content)
f.close()

结果:
1:竟然你诚心诚意的发问了,
2:纳我就大发慈悲的告诉你,

4. 应用: 制作文件的备份

给文件做备份的步骤:
1. 打开文件
2. 读取文件数据
3. 创建一个复制文件
4. 把读取的数据写入复制文件中
5. 关闭文件

代码:
# # 创建一个原文件
# # 打开文件
old_f = open("hm.txt", "w")
# # 写入数据
old_f.write("helloworld")
# # 关闭文件
old_f.close()

# 做备份文件 -> 伪代码
# 01 打开hm.txt文件
# 02 读取hm.txt文件的数据
# 03 创建一个hm[复件].txt文件
# 04 把从hm.txt读取的数据写入到hm[复件].txt文件中
# 05关闭文件

# 01 打开hm.txt文件
old_f = open("hm.txt", "r")
# 02 读取hm.txt文件的数据
result = old_f.read()
# 03 创建一个hm[复件].txt文件
new_f = open("hm[复件].txt", "w")
# 04 把从hm.txt读取的数据写入到hm[复件].txt文件中
new_f.write(result)
# 05关闭文件
old_f.close()
new_f.close()

5. 文件  文件夹的相关操作

>  有些时候,需要对文件进行重命名、删除等一些操作,python的os模块中都有这么功能

1) 文件重命名

> os模块中的rename()可以完成对文件的重命名操作

格式:
import os
os.rename(需要修改的文件名, 新的文件名)

例如:
import os
os.rename("毕业论文.txt", "毕业论文-最终版.txt")

2) 删除文件

> os模块中的remove()可以完成对文件的删除操作

格式:
import os
os.remove(待删除的文件名)

例如:
import os
os.remove("毕业论文.txt")

3) 创建文件夹

> os模块中的mkdir()可以完成创建文件夹
import os
os.mkdir(文件夹名)

例如:
import os
os.mkdir("张三")

4) 获取当前目录

> 获取文件所在的目录地址

# 获取的是绝对路径(可以看到盘符) 
import os
path = os.getcwd()
print(path)

5)  改变默认目录

>  改变当前文件的默认路径

# ../ 上一级目录 ./ 或者../ 都是相对路径
# ./ 当前目录
# ../../上一级的上一级目录

import os
os.chdir("../")

6)  获取目录列表

# .idea python项目中个一个隐藏文件
# 文件名以.开头的就是一个隐藏文件
import os
os.listdir("./")

7)  删除文件夹

import os
os.rmdir(文件夹名)

6. 应用: 批量修改文件名

import os
# 准备工作
# 01 当前目录下创建一个文件夹
os.mkdir("黑马文件夹")

# 02 指定默认目录
os.chdir("黑马文件夹")
print(os.getcwd())

# 03 在黑马文件夹下面创建10个文件
for i in range(1, 11):
    # 打开文件
    f =open("hm%d.txt" % i, "w")
    # 关闭文件
    f.close()

# 实际工作
hmx.txt -> hmx[中国].txt
# 01 指定默认目录
os.chdir("黑马文件夹")
# 02 获取当前目录下的目录列表
my_list = os.listdir()
# 03 遍历my_list
for file_name in my_list:
    # 得的新的文件名
    new_file_name = file_name.replace(".txt", "[中国].txt")
    # 对文件重命名
    os.rename(file_name, new_file_name)

六.面向对象基础,面向对象的语言特征:封装,继承,多态

1. 面向对象编程介绍

**请用程序描述如下事情:**
- A同学报道登记信息
- B同学报道登记信息
- C同学报道登记信息
- A同学做自我介绍
- B同学做自我介绍
- C同学做自我介绍

# 面向过程编程
stu_a = {
    "name":"A",
    "age":21,
    "gender":1,
    "hometown":"河北"}

stu_b = {
    "name":"B",
    "age":22,
    "gender":0,
    "hometown":"山东"}

stu_c = {
    "name":"C",
    "age":20,
    "gender":1,
    "hometown":"安徽"}

def stu_intro(stu):
    """自我介绍"""
    for key, value in stu.items():
    print("key=%s, value=%d"%(key,value))

stu_intro(stu_a)
stu_intro(stu_b)
stu_intro(stu_c)

考虑现实生活中,我们的思维方式是放在学生这个个人上,是学生做了自我介绍。而不是像我们刚刚写出的代码,先有了介绍的行为,再去看介绍了谁。

用我们的现实思维方式该怎么用程序表达呢?

# 面向对象的编程的话,我们会创建一个Student的类,这个类会接收一些个人信息的参数
stu_a = Student(个人信息)
stu_b = Student(个人信息)
stu_c = Student(个人信息)

stu_a.intro()
stu_b.intro()
stu_c.intro()

- 面向过程:根据业务逻辑从上到下写代码

- 面向对象:将数据与函数绑定到一起,进行封装,这样能够更快速的开发程序,减少了重复代码的重写过程

面向过程编程最易被初学者接受,其往往用一长段代码来实现指定功能,开发过程的思路是将数据与函数按照执行的逻辑顺序组织在一起,数据与函数分开考虑。

今天我们来学习一种新的编程方式:面向对象编程(Object Oriented Programming,OOP,面向对象程序设计)

1)解决菜鸟买电脑的故事

第一种方式:

> 1)在网上查找资料

> 2)根据自己预算和需求定电脑的型号 MacBook 15 顶配 1W8

> 3)去市场找到苹果店各种店无法甄别真假 随便找了一家

> 4)找到业务员,业务员推荐了另外一款 配置更高价格便宜,也是苹果系统的 1W

> 5)砍价30分钟 付款9999

> 6)成交

> 回去之后发现各种问题

第二种方式 :

> 1)找一个靠谱的电脑高手

> 2)给钱交易

需要了解的定义性文字:

面向对象(object-oriented ;简称: OO) 至今还没有统一的概念 我这里把它定义为: 按人们 认识客观世界的系统思维方式,采用基于对象(实体) 的概念建立模型,模拟客观世界分析、设 计、实现软件的办法。

面向对象编程(Object Oriented Programming-OOP) 是一种解决软件复用的设计和编程方法。 这种方法把软件系统中相近相似的操作逻辑和操作 应用数据、状态,以类的型式描述出来,以对象实例的形式在软件系统中复用,以达到提高软件开发效率的作用。

2. 类和对象

面向对象编程的2个非常重要的概念:类和对象

对象是面向对象编程的核心,在使用对象的过程中,为了将具有共同特征和行为的一组对象抽象定义,提出了另外一个新的概念——类

类就相当于制造飞机时的图纸,用它来进行创建的飞机就相当于对象

1)  类

人以类聚 物以群分。

具有相似内部状态和运动规律的实体的集合(或统称为抽象)。

具有相同属性和行为事物的统称

类是抽象的,在使用的时候通常会找到这个类的一个具体的存在,使用这个具体的存在。一个类可以找到多个对象

2)  对象

某一个具体事物的存在 ,在现实世界中可以是看得见摸得着的。

可以是直接使用的

3)  类和对象之间的关系

小总结:类就是创建对象的模板

4) 类的构成

类(Class) 由3个部分构成
- 类的名称:类名
- 类的属性:一组数据
- 类的方法:允许对进行操作的方法 (行为)

5)  类的抽象

拥有相同(或者类似)属性和行为的对象都可以抽像出一个类

3* 定义类

定义一个类,格式如下:
class 类名:
    方法列表 —— 方法就是函数

定义一个Hero类
# 经典类(旧式类)定义形式:
# class Hero:
# class Hero():

# 新式类定义形式
class Hero(object): ——这里的object是所有类的父类
    # 这是一个实例方法,或者称为对象方法
    def info(self):
        print("英雄各有见,何必问出处。")

说明:

- 定义类时有2种形式:新式类和经典类,上面代码中的Hero为新式类,前两行注释部分则为经典类;
- object 是Python 里所有类的最顶级父类;
- 类名 的命名规则按照"大驼峰命名法";
- info 是一个实例方法,第一个参数一般是self,表示实例对象本身,当然了可以将self换为其它的名字,其作用是一个变量 这个变量指向了实例对象

4*  创建对象

python中,可以根据已经定义的类去创建出一个或多个对象。

创建对象的格式为:
对象名1 = 类名()
对象名2 = 类名()
对象名3 = 类名()

class Hero(object):  # 新式类定义形式
    """info 是一个实例方法,类对象可以调用实例方法,实例方法的第一个参数一定是self"""
    def info(self):
        """当对象调用实例方法时,Python会自动将对象本身的引用做为参数,
            传递到实例方法的第一个参数self里"""
        print(self)
        print("self各不同,对象是出处。")

# Hero这个类 实例化了一个对象  taidamier(泰达米尔)
taidamier = Hero()

# 对象调用实例方法info(),执行info()里的代码
# . 表示选择属性或者方法
taidamier.info()

print(taidamier)  # 打印对象,则默认打印对象所在类的内存的地址,16进制的地址,以ox或oX开头的一长串,结果等同于info里的print(self)
print(id(taidamier))  # id(taidamier) 则是内存地址的十进制形式表示

说明:

* 用类创建一个对象时,类似于用一个模子,来制造实物

> 对象既然有实例方法,也可以有自己的属性

5*  添加和获取对象的属性

类创建出来的对象,可以添加属性

例如:

class Hero(object):
    """定义了一个英雄类,可以移动和攻击"""
    def move(self):
        """实例方法"""
        print("正在前往事发地点...")

    def attack(self):
        """实例方法"""
        print("发出了一招强力的普通攻击...")

# 实例化了一个英雄对象 泰达米尔
taidamier = Hero()

# 给对象添加属性,以及对应的属性值
taidamier.name = "泰达米尔"  # 姓名
taidamier.hp = 2600  # 生命值
taidamier.atk = 450  # 攻击力
taidamier.armor = 200  # 护甲值

# 通对.成员选择运算符,获取对象的属性值
print("英雄 %s 的生命值 :%d" % (taidamier.name, taidamier.hp))
print("英雄 %s 的攻击力 :%d" % (taidamier.name, taidamier.atk))
print("英雄 %s 的护甲值 :%d" % (taidamier.name, taidamier.armor))

# 通过.成员选择运算符,获取对象的实例方法
taidamier.move()
taidamier.attack()

> 对象创建并添加属性后,可以在类的实例方法里获取这些属性

6*  在方法内通过self获取对象属性

class Hero(object):
    """定义了一个英雄类,可以移动和攻击"""
    def move(self):
# self是个形参,谁(taidamier )调用了move这个方法,谁就会传到self里去
        """实例方法"""
        print("正在前往事发地点...")
    def attack(self):
        """实例方法"""
        print("发出了一招强力的普通攻击...")
    def info(self):
        """在类的实例方法中,通过self获取该对象的属性"""
        print("英雄 %s 的生命值 :%d" % (self.name, self.hp))
        print("英雄 %s 的攻击力 :%d" % (self.name, self.atk))
        print("英雄 %s 的护甲值 :%d" % (self.name, self.armor))

# 实例化了一个英雄对象 泰达米尔
taidamier = Hero()

# 给对象添加属性,以及对应的属性值
taidamier.name = "泰达米尔"  # 姓名
taidamier.hp = 2600  # 生命值
taidamier.atk = 450  # 攻击力
taidamier.armor = 200  # 护甲值

# 通过.成员选择运算符,获取对象的实例方法
taidamier.info()  # 只需要调用实例方法info(),即可获取英雄的属性
taidamier.move()
taidamier.attack()

> 创建对象后再去添加属性有点不合适,更简单的,可以在创建对象的时候,就已经拥有这些属性

7*  魔法方法 `__init__()`

class Hero(object):
    """定义了一个英雄类,可以移动和攻击"""
    # Python 的类里提供的,两个下划线开始,两个下划线结束的方法,就是魔法方法,__init__()就是一个魔法方法,通常用来做属性初始化 或 赋值 操作。
    # 如果类里面没有写__init__方法,Python会自动创建,但是不执行任何操作,
    # 如果为了能够在完成自己想要的功能,可以自己定义__init__方法,
    # 所以一个类里无论自己是否编写__init__方法 一定有__init__方法。
    # 魔法方法,又叫构造方法
    # 类在创建对象的时候,它内部会执行的一些方法,就是魔法方法,它会把内部的魔法方法走完,才会把这个对象创建出来

    # init的self不是对象
    def __init__(self):
        """ 方法,用来做变量初始化 或 赋值 操作,在类实例化对象的时候,会被自动调用"""
        self.name = "泰达米尔" # 姓名
        self.hp = 2600 # 生命值
        self.atk = 450  # 攻击力
        self.armor = 200  # 护甲值

    def move(self):
        """实例方法"""
        print("正在前往事发地点...")
    def attack(self):
        """实例方法"""
        print("发出了一招强力的普通攻击...")

# 实例化了一个英雄对象,并自动调用__init__()方法
taidamier = Hero()

# 通过.成员选择运算符,获取对象的实例方法
taidamier.info() # 只需要调用实例方法info(),即可获取英雄的属性
taidamier.move()
taidamier.attack()

说明:

- `__init__()`方法,在创建一个对象时默认被调用,不需要手动调用

- `__init__(self)`中的self参数,不需要开发者传递,python解释器会自动把当前的对象引用传递过去。

总结:

* `__init__`方法也被成为构造方法

* 在python中,以`__`开头,以`__`结尾的方法,我们成为魔法方法

* 这个方法是由python提供给我们的,也可以理解为是由python中的object类,提供给我的.

* python可以通过这些魔法方法监听我们创建对象的进度

* 程序员也可以在自定义的类中,  重写该魔法方法,  在方法中做自己的事情.

* 注意. 当进入`__init__`这个魔法方法的时候, 对象已经创建成功了.

> 在类的方法里定义属性的固定值,则每个对象实例变量的属性值都是相同的。

> 一个游戏里往往有很多不同的英雄,也可以让实例化的每个对象,都有不同的属性值

8*  有参数的`__init__()`方法

class Hero(object):
    """定义了一个英雄类,可以移动和攻击"""
    def __init__(self, name, skill, hp, atk, armor):
    """ __init__() 方法,用来做变量初始化 或 赋值 操作"""
        # 英雄名
        self.name = name
         # 技能
        self.skill = skill
        # 生命值:
        self.hp = hp
        # 攻击力
        self.atk = atk
        # 护甲值
        self.armor = armor
        # 在类里面调用实例方法,用self
        self.move()

    def move(self):
        """实例方法"""
        print("%s 正在前往事发地点..." % self.name)

    def attack(self):
        """实例方法"""
        print("发出了一招强力的%s..." % self.skill)

    def info(self):
        print("英雄 %s 的生命值 :%d" % (self.name, self.hp))
        print("英雄 %s 的攻击力 :%d" % (self.name, self.atk))
        print("英雄 %s 的护甲值 :%d" % (self.name, self.armor))

# 实例化英雄对象时,参数会传递到对象的__init__()方法里
taidamier = Hero("泰达米尔", "旋风斩", 2600, 450, 200)
gailun = Hero("盖伦", "大宝剑", 4200, 260, 400)
# print(gailun)
# print(taidamier)

# 不同对象的属性值的单独保存
print(id(taidamier.name))
print(id(gailun.name))

# 同一个类的不同对象,实例方法共享
print(id(taidamier.move()))
print(id(gailun.move()))

说明:

- 通过一个类,可以创建多个对象,就好比 通过一个模具创建多个实体一样

- `__init__(self)`中,默认有1个参数名字为self,如果在创建对象时传递了2个实参,那么`__init__(self)`中出了self作为第一个形参外还需要2个形参,例如`__init__(self,x,y)`

注意:

1. 在类内部获取 属性 和 实例方法,通过self获取;

2. 在类外部获取 属性 和 实例方法,通过对象名获取。

3. 如果一个类有多个对象,每个对象的属性是各自保存的,都有各自独立的地址;

4. 但是实例方法是所有对象共享的,只占用一份内存空间。类会通过self来判断是哪个对象调用了实例方法。

9*  魔法方法: `__str__()`方法

class Hero(object):
    """定义了一个英雄类,可以移动和攻击"""
    def __init__(self, name, skill, hp, atk, armor):
        """ __init__() 方法,用来做变量初始化 或 赋值 操作"""
        # 英雄名
        self.name = name  # 实例变量
        # 技能
        self.skill = skill
        # 生命值:
        self.hp = hp  # 实例变量
        # 攻击力
        self.atk = atk
        # 护甲值
        self.armor = armor

    def move(self):
        """实例方法"""
        print("%s 正在前往事发地点..." % self.name)

    def attack(self):
        """实例方法"""
        print("发出了一招强力的%s..." % self.skill)

    # def info(self):
    #    print("英雄 %s 的生命值 :%d" % (self.name, self.hp))
    #    print("英雄 %s 的攻击力 :%d" % (self.name, self.atk))
    #    print("英雄 %s 的护甲值 :%d" % (self.name, self.armor))

    def __str__(self):
        """
            这个方法是一个魔法方法 (Magic Method) ,用来显示信息
            没有参数,只有一个返回值,而且返回值必须是一个字符串
            该方法需要 return 一个数据,并且只有self一个参数,当在类的外部 print(对象) 则打印这个数据
        """
        return "英雄 <%s> 数据: 生命值 %d, 攻击力 %d, 护甲值 %d" % (self.name, self.hp, self.atk, self.armor)

taidamier = Hero("泰达米尔", "旋风斩", 2600, 450, 200)
gailun = Hero("盖伦", "大宝剑", 4200, 260, 400)

# 如果没有__str__ 则默认打印 对象在内存的地址。
# 当类的实例化对象 拥有 __str__ 方法后,那么打印对象则打印 __str__ 的返回值
print(taidamier)
print(gailun)

# 查看类的文档说明,也就是类的注释
print(Hero.__doc__)

说明:

- 在python中方法名如果是`__xxxx__()`的,那么就有特殊的功能,因此叫做“魔法”方法

- 当使用print输出对象的时候,默认打印对象的内存地址。如果类定义了`__str__(self)`方法,那么就会打印从在这个方法中 `return` 的数据

- `__str__`方法通常返回一个字符串,作为这个对象的描述信息

10* 魔法方法: `__del__()`方法(了解)

创建对象后,python解释器默认调用`__init__()`方法;
当删除对象时,python解释器也会默认调用一个方法,这个方法为`__del__()`方法

class Hero(object):
    # 初始化方法
    # 创建完对象后会自动被调用
    def __init__(self, name):
        print('__init__方法被调用')
        self.name = name

    # 当对象被删除时,会自动被调用
    def __del__(self):
        print("__del__方法被调用")
        print("%s 被 GM 干掉了..." % self.name)

# 创建对象
taidamier = Hero("泰达米尔")

# 删除对象
print("%d 被删除1次" % id(taidamier))
del(taidamier) # 只有在del这个对象的时候,才会走__del__

#引用计数
print("--" * 10)
gailun = Hero("盖伦")
gailun1 = gailun
gailun2 = gailun
print("%d 被删除1次" % id(gailun))
del(gailun)
print("%d 被删除1次" % id(gailun1))
del(gailun1)
print("%d 被删除1次" % id(gailun2))
del(gailun2) —— 引用计数为0时,这个类才会被释放, 走__del__

总结

- 当有变量保存了一个对象的引用时,此对象的引用计数就会加1;

- 当使用del() 删除变量指向的对象时,则会减少对象的引用计数。如果对象的引用计数不为1,那么会让这个对象的引用计数减1,当对象的引用计数为0的时候,则对象才会被真正删除(内存被回收)。

七、 继承

1. 继承的概念

- 在程序中,继承描述的是多个类之间的所属关系。

- 如果一个类A里面的属性和方法可以复用,则可以通过继承的方式,传递到类B里。

- 那么类A就是基类,也叫做父类;类B就是派生类,也叫做子类。

# 父类
class A(object): —— object是所有类的父类
    def __init__(self):
        self.num = 10
    def print_num(self):
        print(self.num + 10)

# 子类
class B(A):
    pass

b = B()
print(b.num)
b.print_num()

计算结果:10,20

2*  单继承

> 单继承:子类只继承一个父类

# 定义一个Master类
class Master(object):
    def __init__(self):
        # 属性
        self.kongfu = "古法煎饼果子配方"
    # 实例方法
    def make_cake(self):
        print("按照 <%s> 制作了一份煎饼果子..." % self.kongfu)

# 定义Prentice类,继承了 Master,则Prentice是子类,Master是父类。
class Prentice(Master):
    #子类可以继承父类所有的属性和方法,哪怕子类没有自己的属性和方法,也可以使用父类的属性和方法。
    pass               

laoli = Master()
print(laoli.kongfu)
laoli.make_cake()
damao = Prentice()  # 创建子类实例对象
print(damao.kongfu) # 子类对象可以直接使用父类的属性
damao.make_cake()  # 子类对象可以直接使用父类的方法

说明:

- 虽然子类没有定义`__init__`方法初始化属性,也没有定义实例方法,但是父类有。所以只要创建子类的对象,就默认执行了那个继承过来的`__init__`方法

总结:

- 子类在继承的时候,在定义类时,小括号()中为父类的名字

- 父类的属性、方法,会被继承给子类

3*  多继承

>  多继承:子类继承多个父类

class Master(object):
    def __init__(self):
    # 实例变量,属性
        self.kongfu = "古法煎饼果子配方"
    # 实例方法,方法
    def make_cake(self):                   
        print("[古法] 按照 <%s> 制作了一份煎饼果子..." % self.kongfu)
    def dayandai(self):
        print("师傅的大烟袋..")

class School(object):
    def __init__(self):
        self.kongfu = "现代煎饼果子配方"
    def make_cake(self):
        print("[现代] 按照 <%s> 制作了一份煎饼果子..." % self.kongfu)
    def xiaoyandai(self):
        print("学校的小烟袋..")

# 多继承,继承了多个父类(School在前)
class Prentice(School, Master):
    pass

damao = Prentice()
print(damao.kongfu) ——执行School的配方
damao.make_cake()
damao.dayandai()
damao.xiaoyandai()

class Prentice(Master, School):  # 多继承,继承了多个父类(Master在前)
    pass

damao = Prentice()
# 执行Master的属性
print(damao.kongfu)
# 执行Master的实例方法
damao.make_cake()
# 子类的魔法属性__mro__决定了属性和方法的查找顺序
print(Prentice.__mro__)

# 不重名不受影响
damao.dayandai()
damao.xiaoyandai()

说明:

- 多继承可以继承多个父类,也继承了所有父类的属性和方法

- 注意:如果多个父类中有同名的 属性和方法,则默认使用第一个父类的属性和方法(根据类的魔法属性**mro**的顺序来查找)

- 多个父类中,不重名的属性和方法,不会有任何影响。

4*  子类重写父类的同名属性和方法

class Master(object):
    def __init__(self):
        self.kongfu = "古法煎饼果子配方"

    def make_cake(self):
        print("[古法] 按照 <%s> 制作了一份煎饼果子..." % self.kongfu)

class School(object):
    def __init__(self):
        self.kongfu = "现代煎饼果子配方"

    def make_cake(self):
        print("[现代] 按照 <%s> 制作了一份煎饼果子..." % self.kongfu)

class Prentice(School, Master):  # 多继承,继承了多个父类
    def __init__(self):
        self.kongfu = "猫氏煎饼果子配方"

    def make_cake(self):
        print("[猫氏] 按照 <%s> 制作了一份煎饼果子..." % self.kongfu)

# 如果子类和父类的方法名和属性名相同,则默认使用子类的
# 叫 子类重写父类的同名方法和属性
damao = Prentice()
print(damao.kongfu) # 子类和父类有同名属性,则默认使用子类的
damao.make_cake() # 子类和父类有同名方法,则默认使用子类的
# 子类的魔法属性__mro__决定了属性和方法的查找顺序
print(Prentice.__mro__)

5*  子类调用父类同名属性和方法

class Master(object):
    def make_cake(self):                 
        print("按照 [古法] 制作了一份煎饼果子...")

class School(object):
    def make_cake(self):
        print(" 按照 [现代] 制作了一份煎饼果子...")

# 多继承,继承了多个父类
class Prentice(School, Master):
    # 实例方法
    def make_cake(self):
        print("按照 [猫氏] 制作了一份煎饼果子...")

    # 调用父类方法格式:父类类名.父类方法(self)
    def make_old_cake(self):
        # 调用父类Master的实例方法
        Master.make_cake(self)

    def make_new_cake(self):
        # 调用父类School的实例方法
        School.make_cake(self)

# 实例化对象,自动执行子类的__init__方法
damao = Prentice()
damao.make_cake() # 调用子类的方法(默认重写了父类的同名方法)
print("--" * 10)
damao.make_old_cake() # 进入实例方法去调用父类Master的方法
print("--" * 10)
damao.make_new_cake() # 进入实例方法去调用父类School的方法
print("--" * 10)
damao.make_cake() # 调用本类的实例方法

执行结果:
按照 [猫氏] 制作了一份煎饼果子...
--------------------
按照 [古法] 制作了一份煎饼果子...
--------------------
按照 [现代] 制作了一份煎饼果子...
--------------------
按照 [猫氏] 制作了一份煎饼果子...

6. super()的使用

class Master(object):
    # 实例方法,方法
    def make_cake(self):
        print("按照 [古法] 制作了一份煎饼果子...")

# 父类是 Master类
class School(Master):
    # 实例方法,方法
    def make_cake(self):
        print("按照 [现代] 制作了一份煎饼果子...")
        # 执行父类的实例方法
        super().make_cake()

# 父类是 School 和 Master
# 多继承,继承了多个父类
class Prentice(School, Master):
    # 实例方法,方法
    def make_cake(self):
        print("按照 [猫氏] 制作了一份煎饼果子...")

    def make_all_cake(self):
        # 方式1. 指定执行父类的方法(代码臃肿)
        # School.make_cake(self)
        # Master.make_cake(self)

        # 方法2. super() 带参数版本,只支持新式类
        # super(Prentice, self).make_cake()
        # self.make_cake()

        # 方法3. super()的简化版,只支持新式类
        # 执行父类的 实例方法
        super().make_cake() —— super()一般指第一个父类

damao = Prentice()
damao.make_cake()
damao.make_all_cake()
# print(Prentice.__mro__)

知识点:

> 子类继承了多个父类,如果父类类名修改了,那么子类也要涉及多次修改。而且需要重复写多次调用,显得代码臃肿。
> 使用super() 可以逐一调用所有的父类方法,并且只执行一次。调用顺序遵循 **mro** 类属性的顺序。
> **注意:如果继承了多个父类,且父类都有同名方法,则默认只执行第一个父类的(同名方法只执行一次,目前super()不支持执行多个父类的同名方法)**
> super() 在Python2.3之后才有的机制,用于通常单继承的多层继承。

八.  面向对象

>  面向对象三大特性:封装、继承、多态

1. 私有权限  ---  私有方法和私有属性

封装的意义:

1. 将属性和方法放到一起做为一个整体,然后通过实例化对象来处理;

2. 隐藏内部实现细节,只需要和对象及其属性和方法交互就可以了;

3. 对类的属性和方法增加 访问权限控制。

私有权限:在属性名和方法名 前面 加上两个下划线 __

> 1. 类的私有属性 和 私有方法,都不能通过对象直接访问,但是可以在本类内部访问;

> 2. 类的私有属性 和 私有方法,都不会被子类继承,子类也无法访问;

> 3. 私有属性 和 私有方法 往往用来处理类的内部事情,不通过对象处理,起到安全作用。

class Master(object):
    def __init__(self):
        self.kongfu = "古法煎饼果子配方"

    def make_cake(self):         
        print("[古法] 按照 <%s> 制作了一份煎饼果子..." % self.kongfu)

class School(object):
    def __init__(self):
        self.kongfu = "现代煎饼果子配方"

    def make_cake(self):
        print("[现代] 按照 <%s> 制作了一份煎饼果子..." % self.kongfu)

class Prentice(School, Master):
    def __init__(self):
        self.kongfu = "猫氏煎饼果子配方"
        # 私有属性,可以在类内部通过self调用,但不能通过对象访问
        self.__money = 10000 

    # 私有方法,可以在类内部通过self调用,但不能通过对象访问
    def __print_info(self):
        print(self.kongfu)
        print(self.__money)

    def make_cake(self):
        self.__init__()
        print("[猫氏] 按照 <%s> 制作了一份煎饼果子..." % self.kongfu)

    def make_old_cake(self):
        Master.__init__(self)
        Master.make_cake(self)

    def make_new_cake(self):
        School.__init__(self)
        School.make_cake(self)

class PrenticePrentice(Prentice):
    pass

damao = Prentice()
# 对象不能访问私有权限的属性和方法
print(damao.__money)
damao.__print_info()
pp = PrenticePrentice()

# 子类不能继承父类私有权限的属性和方法
print(pp.__money)
pp.__print_info()

总结

- Python中没有像C++中 public 和 private 这些关键字来区别公有属性和私有属性。

- Python是以属性命名方式来区分,如果在属性和方法名前面加了2个下划线'__',则表明该属性和方法是私有权限,否则为公有权限。

2.  修改私有属性的值

- 如果需要修改一个对象的属性值,通常有2种方法

  > 1. 对象名.属性名 = 数据 ----> 直接修改

  > 2. 对象名.方法名() ----> 间接修改

- 私有属性不能直接访问,所以无法通过第一种方式修改,一般的通过第二种方式修改私有属性的值:定义一个可以调用的公有方法,在这个公有方法内访问修改。

class Master(object):
    def __init__(self):
        self.kongfu = "古法煎饼果子配方"

    def make_cake(self):         
        print("[古法] 按照 <%s> 制作了一份煎饼果子..." % self.kongfu)

class School(object):
    def __init__(self):
        self.kongfu = "现代煎饼果子配方"

    def make_cake(self):
        print("[现代] 按照 <%s> 制作了一份煎饼果子..." % self.kongfu)

class Prentice(School, Master):
    def __init__(self):
        self.kongfu = "猫氏煎饼果子配方"
        # 私有属性,可以在类内部通过self调用,但不能通过对象访问
        self.__money = 10000 

    # 现代软件开发中,通常会定义get_xxx()方法和set_xxx()方法来获取和修改私有属性值。
    # 返回私有属性的值

    def get_money(self):
        return self.__money

    # 接收参数,修改私有属性的值
    def set_money(self, num):
        self.__money = num

    def make_cake(self):
        self.__init__()
        print("[猫氏] 按照 <%s> 制作了一份煎饼果子..." % self.kongfu)

    def make_old_cake(self):
        Master.__init__(self)
        Master.make_cake(self)

    def make_new_cake(self):
        School.__init__(self)
        School.make_cake(self)

class PrenticePrentice(Prentice):
    pass

damao = Prentice()
# 对象不能访问私有权限的属性和方法
print(damao.__money)
damao.__print_info()
# 可以通过访问公有方法set_money()来修改私有属性的值
damao.set_money(100)
# 可以通过访问公有方法get_money()来获取私有属性的值
print(damao.get_money())

3. 多态

所谓多态:定义时的类型和运行时的类型不一样,此时就成为多态 ,多态的概念是应用于Java和C#这一类强类型语言中,而Python崇尚“鸭子类型”。

> 鸭子类型:虽然我想要一只"鸭子",但是你给了我一只鸟。 但是只要这只鸟走路像鸭子,叫起来像鸭子,游泳也像鸭子,我就认为这是鸭子。

Python的多态,就是弱化类型,重点在于对象参数是否有指定的属性和方法,如果有就认定合适,而不关心对象的类型是否正确。

- Python伪代码实现Java或C#的多态

class F1(object):
    def show(self):
        print('F1.show')

class S1(F1):
    def show(self):
        print('S1.show')

class S2(F1):
    def show(self):
        print('S2.show')

# 由于在Java或C#中定义函数参数时,必须指定参数的类型
# 为了让Func函数既可以执行S1对象的show方法,又可以执行S2对象的show方法,
# 所以在def Func的形参中obj的类型是 S1和S2的父类即F1
# 而实际传入的参数是:S1对象和S2对象

def Func(F1 obj): 这是java的写法,Python应该是def Func(object)
    """Func函数需要接收一个F1类型或者F1子类的类型"""
   print(obj.show())

#当看到形参是object的函数时,可理解为这个函数可以接收所有对象

s1_obj = S1()
Func(s1_obj) # 在Func函数中传入S1类的对象 s1_obj,执行 S1 的show方法,结果:S1.show
s2_obj = S2()
Func(s2_obj) # 在Func函数中传入Ss类的对象 ss_obj,执行 Ss 的show方法,结果:S2.show

>  通俗点理解:定义obj这个变量是说的类型是:F1的类型,但是在真正调用Func函数时给其传递的不一定是F1类的实例对象,有可能是其子类的实例对象, 这种情况就是所谓的多态

>  python是弱类型,即无论传递过来的是什么,obj变量都能够指向它,这也就没有所谓的多态了(弱化了这个概念)

4. 类属性和实例属性

在前面的例子中我们接触到的就是实例属性(对象属性),顾名思义,类属性就是`类对象`所拥有的属性,它被所有`类对象`的`实例对象`所共有,在内存中只存在一个副本,这个和C++中类的静态成员变量有点类似。对于公有的类属性,在类外可以通过`类对象`和`实例对象`访问

类属性

class People(object):
    name = 'Tom'  # 公有的类属性
    __age = 12  # 私有的类属性

p = People()
print(p.name)  # 正确
print(People.name)  # 正确
print(p.__age)  # 错误,不能在类外通过实例对象访问私有的类属性
print(People.__age) # 错误,不能在类外通过类对象访问私有的类属性

实例属性(对象属性)

class People(object):
    address = '山东'  # 类属性
    def __init__(self):
        self.name = 'xiaowang'  # 实例属性
        self.age = 20  # 实例属性

p = People()
p.age = 12  # 实例属性
print(p.address)  # 正确
print(p.name)  # 正确
print(p.age)  # 正确
print(People.address)  # 正确
print(People.name)  # 错误
print(People.age)  # 错误

不能通过实例(对象)去修改类属性,要通过类改变类属性

class People(object):
    country = 'china' #类属性

print(People.country)
p = People()
print(p.country)
p.country = 'japan' # 这是错误的做法,不能实际改变类属性,要通过类改变类属性
print(p.country)  # 实例属性会屏蔽掉同名的类属性,打印p.country会使japan
print(People.country)# 此时打印People.country 仍然是china
del p.country  # 删除实例属性
print(p.country)

总结

- 如果需要在类外修改`类属性`,必须通过`类对象`去引用然后进行修改。如果通过实例对象去引用,会产生一个同名的`实例属性`,这种方式修改的是`实例属性`,不会影响到`类属性`,并且之后如果通过实例对象去引用该名称的属性,实例属性会强制屏蔽掉类属性,即引用的是`实例属性`,除非删除了该`实例属性`。

5.  静态方法和类方法

1)  类方法

是类对象所拥有的方法,需要用修饰器`@classmethod`来标识其为类方法,对于类方法,第一个参数必须是类对象,一般以`cls`作为第一个参数(当然可以用其他名称的变量作为其第一个参数,但是大部分人都习惯以'cls'作为第一个参数的名字,就最好用'cls'了),能够通过实例对象和类对象去访问。

class People(object):
    country = 'china'
    #类方法,用classmethod来进行修饰
    @classmethod
    def get_country(cls): —— cls的意思就是当前类
        return cls.country

p = People()
print(p.get_country())    #可以用过实例对象引用
print(People.get_country())    #可以通过类对象引用

类方法还有一个用途就是可以对类属性进行修改:

class People(object):
    country = 'china'
    #类方法,用classmethod来进行修饰
    @classmethod
    def get_country(cls):
        return cls.country

    @classmethod
    def set_country(cls,country):
        cls.country = country

p = People()
print(p.get_country())  #可以用过实例对象访问
print(People.get_country())    #可以通过类访问
p.set_country('japan')
print(p.get_country())
print(People.get_country())

结果显示在用类方法对类属性修改之后,通过类对象和实例对象访问都发生了改变

2*)  静态方法

需要通过修饰器`@staticmethod`来进行修饰,静态方法不需要多定义参数,可以通过对象和类来访问。

class People(object):
    country = 'china'
    @staticmethod
    #静态方法
    def get_country():
        return People.country

p = People()
# 通过对象访问静态方法
p.get_contry()
# 通过类访问静态方法
print(People.get_country())

总结

1. 从类方法和实例方法以及静态方法的定义形式就可以看出来,类方法的第一个参数是类对象cls,那么通过cls引用的必定是类对象的属性和方法;

2. 实例方法的第一个参数是实例对象self,那么通过self引用的可能是类属性、也有可能是实例属性(这个需要具体分析),不过在存在相同名称的类属性和实例属性的情况下,实例属性优先级更高。

3. 静态方法中不需要额外定义参数,因此在静态方法中引用类属性的话,必须通过类实例对象来引用,静态方法内存空间是一直占用,只用项目代码走完才会释放,而其他方法在用完这个方法以后就会释放
classPeople(object):
    country = 'china'
     @staticmethod
     def getCountry():
         return People.country

python中的方法总结:
1.实例方法(对象方法) --> 场景很多

    调用格式: 对象名.实例方法名()
    使用场景: 在方法中需要self
2.类方法 --> 对私有类属性取值或者赋值
    定义格式: @classmethod
                    def 类方法名(cls):
    调用格式: 类名.类方法名()  或者 对象名.类方法名()
    使用场景: 在方法中需要cls类名
3.静态方法 --> 一般不用
    定义格式: @staticmethod
                    def 静态方法名():
    调用格式: 类名.类方法名() 或者 对象名.类方法名()
    使用场景: 在方法中不需要self 也不需要cls     

6. `__new__`方法

`__new__和__init__`的作用:

class A(object):
    def __new__(cls):        
        print("这是 new 方法")        
        return object.__new__(cls)
    def __init__(self):
        print("这是 init 方法")

A()

总结:new在ini之前创建一个对象

- `__new__`至少要有一个参数cls,代表要实例化的类,此参数在实例化时由Python解释器自动提供
- `__new__`必须要有返回值,返回实例化出来的实例,这点在自己实现`__new__`时要特别注意,可以return父类`__new__`出来的实例,或者直接是object的`__new__`出来的实例
- `__init__`有一个参数self,就是这个`__new__`返回的实例,`__init__`在`__new__`的基础上可以完成一些其它初始化的动作,`__init__`不需要返回值
- 我们可以将类比作制造商,`__new__`方法就是前期的原材料购买环节,`__init__`方法就是在有原材料的基础上,加工,初始化商品环节

补充: *args:  表示  将位置参数中的剩余实参存放到 args 中, 且以元组的形式保存

def foo(x,*args):
    print(x)
    print(args)
foo(1,2,3,4,5)#其中的2,3,4,5都给了args

运行结果:
1
(2, 3, 4, 5)

def foo(x,y=1,*args): # *args一定要在缺省参数以后
    print(x)
    print(y)
    print(args)
foo(1,2,3,4,5)#其中的x为1,y=1的值被2重置了,3,4,5都给了args

结果:
1
2
(3, 4, 5)

def foo(x,*args,y=1):
    print(x)
    print(args)
    print(y)
foo(1,2,3,4,5)#其中的x为1,2,3,4,5都给了args,y按照默认参数依旧为1

结果:
1
(2, 3, 4, 5)
1

** kwargs: 表示  形参中按照关键字传值 把多余的值以字典呈现
def foo(x,**kwargs):
    print(x)
    print(kwargs)
foo(1,y=1,a=2,b=3,c=4)#将y=1,a=2,b=3,c=4以字典的方式给了kwargs

执行结果是:
1
{'y': 1, 'a': 2, 'b': 3, 'c': 4}

def foo(x,*args,**kwargs):
    print(x)
    print(args)
    print(kwargs)
foo(1,2,3,4,y=1,a=2,b=3,c=4)
#将1传给了x,将2,3,4以元组方式传给了args,y=1,a=2,b=3,c=4以字典的方式给了kwargs

结果:
1
(2, 3, 4)
{'y': 1, 'a': 2, 'b': 3, 'c': 4}

位置参数、默认参数、**kwargs三者的顺序必须是位置参数、默认参数、**kwargs,不然就会报错:
def foo(x,y=1,**kwargs):
    print(x)
    print(y)
    print(kwargs)
foo(1,a=2,b=3,c=4)#将1按照位置传值给x,y按照默认参数为1,a=2,b=3,c=4以字典的方式给了kwargs

结果:
1
1
{'a': 2, 'b': 3, 'c': 4}

7*  单例模式

1. 单例是什么

举个常见的单例模式例子,我们日常使用的电脑上都有一个回收站,在整个操作系统中,回收站只能有一个实例,整个系统都使用这个唯一的实例,而且回收站自行提供自己的实例。因此回收站是单例模式的应用。
确保某一个类只有一个实例,而且自行实例化并向整个系统提供这个实例,这个类称为单例类,单例模式是一种对象创建型模式。

2. 创建单例-保证只有1个对象

# 单例模式: 在程序中这个类创建出来的对象 只有一个(也就是占用一分内存地址)
# 单例模式 也只会走一次__init__方法(保证这个单例对象的属性也是唯一的)(name=小明
#  age = 20)
# 合理使用内存 避免浪费(避免浪费内存)

class Person(object):
    # 定义一个私有类属性, 初始化是该类为None
    __instance = None
    def __new__(cls, *args, **kwargs):
        # 如果不存在, 则进入if语句
        if not cls.__instance:
            # 不存在时进入  我们就创建一个
            cls.__instance = object.__new__(cls)
        # 创建出来之后, return返回
        return cls.__instance

# 创建对象,类里面的方法是引用哪个方法,走哪个,不会每次都从头开始走
xiaoming = Person('小明', 20)
xiaohong = Person('小红', 32)
xiaozhang = Person('小孙', 31)
print(xiaoming)
print(xiaohong)
print(xiaozhang)

运行结果:
# 我们可以看到三个值都是一样的,代表对象没有创建多个,而是只有一个,达到我们的目的
<__main__.Person object at 0x00000000023CD2E8>
<__main__.Person object at 0x00000000023CD2E8>
<__main__.Person object at 0x00000000023CD2E8>

3. 创建单例-保证只执行1次__init__方法

class Person(object):
    __instance = None
    # 创建一个私有类属性, 记录是否是第一次进入
    # 只有第一次进入的时候, 当前初始化的值(True)被使用
    # 其他时候进入的都将该参数变为False
    __isFirst = True
    def __new__(cls, *args, **kwargs):
        if not cls.__instance:
            cls.__instance = object.__new__(cls)
        return cls.__instance

    # 对init方法加工, 使其只能被调用一次, 保证对象的属性只能赋值一次
    def __init__(self, name, age):
        if Person.__isFirst:
            self.name = name
            self.age = age
            Person.__isFirst = False

# 创建对象
xiaoming = Person('小明', 20)
xiaohong = Person('小红', 32)
xiaozhang = Person('小孙', 31)
print(xiaoming)
print(xiaohong)
print(xiaozhang)
print(xiaozhang.name, xiaoming.name, xiaohong.name)

运行结果:
<__main__.Person object at 0x00000000023CD2E8>
<__main__.Person object at 0x00000000023CD2E8>
<__main__.Person object at 0x00000000023CD2E8>
小明 小明 小明

总结:

- 单例: 单独的实例,  即只能有一个实例

- 这样可以节约内存,  例如针对公共的一些对象,我们只需要指定一个即可, 需要使用时, 直接拿去用.

- 内存有限, 请珍惜.

- 既然该类的对象都只有一个, 那么该对象的属性也只有一份, 不存在别人也有该对象属性的情况.

九.  异常 & 模块(了解)

1.  异常

看如下示例:
    print '-----test--1---'
    open('123.txt','r')
    print '-----test--2---'

运行结果:
-----test--1---
Traceback (most recent call last):
File "demo.py", line 2, in <module>
    open('123.txt', 'r')
FileNotFoundError: [Errno 2] No such file or directory: '123.txt'

说明:

>  打开一个不存在的文件123.txt,当找不到123.txt 文件时,就会抛出给我们一个IOError类型的错误,No such file or directory:123.txt (没有123.txt这样的文件或目录)

> 当Python检测到一个错误时,解释器就无法继续执行了,反而出现了一些错误的提示,这就是所谓的"异常"

2.  捕获异常

1)  单个异常捕获(try...except...)

使用规则:
try:
    可能发生异常的代码01
    可能发生异常的代码02
    ...
except 要捕获的异常类型名:
    如果发生异常,进行的后续处理01
    如果发生异常,进行的后续处理02
    ...

看如下示例:
try:
    print(num)
except NameError:
    print('发生异常了')
print('go on continue')

运行结果:
发生异常了
go on continue

说明:

- 如果我们在可能发生异常的代码外, 添加 try....except.... 方法, 这样就可以捕获发生的异常,可以对异常进行实时跟踪, 而且程序不会崩溃, 可以继续运行后续的代码
- 我们通过运行结果可以看到, 虽然异常发生了, 但是对程序的破坏性不是很大, 有利于程序的继续执行.
- 其中, try:  里面的代码是有可能发生异常的内容
- except  后面紧跟的是发生异常的类型名称, 如果异常名称弄错, 那么当前异常照样捕获不到, 就相当于:  except  鸭子类型异常:    但是发生的是天鹅类型异常,  这样的话except照样捕获不到该异常.
- 使用try...except...能够使我们在异常发生的情况下正常执行程序, 这一点非常非常好. 对于公司来说,这个功能非常有用.

2) 多种异常捕获

使用 except 捕获多种异常
使用规则:
try:
    可能发生异常的代码01
    可能发生异常的代码02
    ...
except (要捕获的异常类型名01,要捕获的异常类型名02, 要捕获的异常类型名03...):
    如果发生异常,进行的后续处理01
    如果发生异常,进行的后续处理02
    ...

例如:
try:
    print(num)
    open('hm.txt')
except (FileNotFoundError, NameError):
    print('捕获到异常了')

运行结果:
捕获到异常了

说明:
* except后面可以跟 多种类型的异常名, 并且把这些异常名用元组包裹起来, 就可以同时捕获多种异常.
* 其他使用方式不变

3) 异常的描述信息获取

总结:
* except 后面可以用括号包含多种异常的名字
* except后面可以连接 as 关键字, as 关键字能够把异常信息赋给一个变量, 我们可以通过打印变量的形式来获取有关该异常的描述信息
* 我们也可以简单的理解为:  as 关键字  能够把异常描述信息 保存在变量中.

4)  捕获所有异常

我们有两种方式可以捕获所有的异常:
第一种 :
使用规则:
try:
有可能产生异常的代码01
有可能产生异常的代码02
..
except:
    捕获到异常后的操作01
    捕获到异常后的操作02
    ...

> 说明: 虽然用这样的形式可以捕获异常,但是不建议大家使用, 原因是这样的方式获取的异常不能够查看    聚的异常信息. 

第二种 :
使用规则:
try:
    有可能产生异常的代码01
    有可能产生异常的代码02
    ...
except Exception:
    捕获到异常后的操作01
    捕获到异常后的操作02
    ...

> 说明:
> * 使用这样的形式可以捕获所有类型的异常, 原因是: Exception是所有异常类的父类.
> * 这样的书写形式后面可以跟 as 关键字, 从而可以存储并打印输出异常的具体信息.
> * 推荐使用这样的形式, 当然, 如果你不需要异常信息, 只想知道代码是否有异常, 可以使用上面的形式

举例:
try:
    open('hm.txt','r')
except:
    print('获取异常...')

运行结果:
获取异常...

try:
    print(num)
    open('hm.txt')
except Exception as info:
    print('捕获到异常了', info)

运行结果:
捕获到异常了 name 'num' is not defined

> **注意**:
> 上面代码中: 因为异常代码有两行, 所以捕获到的是先执行的第 2 行的异常.

总结:
* 捕获所有异常和正常的捕获行为很像, 只是捕获类名有所变化
* 此处学习捕获所有异常后, 在公司如无其他需求, 一般都是使用上面的两种方法, 获取代码的异常.
* 此处学习的两种方法均可使用, 希望大家牢记, 多多联系.

5)  与else的搭配使用

> 在 if 中,它的作用是当条件不满足时执行的代码;
> 同样在 try...except...else 中也是如此,即如果没有捕获到异常,那么就执行else中的事情
> 如果产生异常, 就不执行 else 中的语句.

try:
    num = 100
    print(num)
except NameError as errorMsg:
    print('产生错误了:%s'%errorMsg)
else:
    print('没有捕获到异常,真高兴')

运行结果如下:
100
没有捕获到异常,真高兴

try:
    # num = 100
    print(num)
except NameError as errorMsg:
    print('产生错误了:%s'%errorMsg)
else:
    print('没有捕获到异常,真高兴')

运行结果如下:
产生错误了:name 'num' is not defined

6)  与finally的搭配

finally语句用来表达这样的情况:
> 在程序中,如果一个段代码必须要执行,即无论异常是否产生都要执行,那么此时就需要使用finally。 比如文件关闭,释放锁,把数据库连接返还给连接池等

try:
    num = 100
    print(num)
except NameError as errorMsg:
    print('产生错误了:%s'%errorMsg)
finally:
    print('finally方法')

结果:
100
finally方法

try:
    # num = 100
    print(num)
except NameError as errorMsg:
    print('产生错误了:%s'%errorMsg)
finally:
    print('finally方法')

结果:
产生错误了:name 'num' is not defined
finally方法

总结:
* finally 如果添加了, 那么无论是否发生异常,  finally里面的程序都会执行
* finally一般不写, 除非公司有这方面的需求.
* finally 和 try.... except.... 可以搭配使用, 组成:  try.... except.... finally.....
* 其中,  try 中出错, 那么执行 except 和 finally 的代码
* 如果,  try  中没有错误, 那么执行  try  和 finally 中的代码
*  finally 和 try .... except... else .... 可以搭配使用, 组成 try.....except....else.....finally....
* 其中, 如果 try 中出错, 那么执行 except  和 finally 的代码
* 如果, try 中如果没有错误, 那么执行  try 和 else 和 finally 的代码

  try:
    num = 100
    print(num)
except NameError as errorMsg:
    print('产生错误了:%s'%errorMsg)
else:
    print('else')
finally:
    print('finally方法')

结果:
100
else
finally方法

* 如果出错,则:
try:
    # num = 100
    print(num)
except NameError as errorMsg:
    print('产生错误了:%s'%errorMsg)
else:
    print('else')
finally:
    print('finally方法')

结果:
产生错误了:name 'num' is not defined
finally方法

7)  异常的传递

try嵌套

> 两个try嵌套, 如果内部的异常没有捕获到, 则异常会往外部传递, 外部会进行捕获:

try:
    num = 100
    print(num)
    try:
        open('text.haha')
    except NameError as error:
        print('嵌套内部的异常捕获:', error)
except Exception as result:
    print('嵌套外围的异常捕获: 产生错误了', result)

运行结果:
100
嵌套外围的异常捕获: 产生错误了 [Errno 2] No such file or directory: 'text.haha'

说明:

上例中,内部没有捕获到的原因是:  open()方法出错的异常类型应该是IOName, 而内部我们捕获的类型是NameError.

> 两个try嵌套, 如果内部的异常, 内部已经捕获到, 则不会往外部传递, 外部不会捕获:

try:
    num = 100
    print(num)
    try:
        open('text.haha')
    except Exception as error:
        print('嵌套内部的异常捕获:', error)
except Exception as result:
    print('嵌套外围的异常捕获: 产生错误了', result)

运行结果:
100
嵌套内部的异常捕获: [Errno 2] No such file or directory: 'text.haha'

**说明**:

​ 这次内部能够捕获异常的原因是: 内部捕获的是所有的异常类型,使用的是: Exception.

函数嵌套

def demo1():

    print("----demo1-1----")

    print(num)

    print("----demo1-2----")

def demo2():

    try:

        print("----demo2-1----")

        demo1()

        print("----demo2-2----")

    except Exception as result:

        print("捕获到了异常,信息是:%s" % result)

    print("----demo2-3----")

demo2()

运行结果:

----demo2-1----

----demo1-1----

捕获到了异常,信息是:name 'num' is not defined

----demo2-3----

或者:

def demo1():

    print("----demo1-1----")

    try:

        print(num)

        print("----demo1-2----")

    except Exception as result:

        print("捕获到了异常,信息是:%s" % result)

    print("----demo1-3----")

def demo2():

    print("----demo2-1----")

    demo1()

    print("----demo2-2----")

demo2()

结果:

----demo2-1----

----demo1-1----

捕获到了异常,信息是:name 'num' is not defined

----demo1-3----

----demo2-2----

**总结**:

* 如果try嵌套,那么如果里面的try没有捕获到这个异常,那么外面的try会接收到这个异常,然后进行处理,如果外边的try依然没有捕获到,那么再往外进行传递。。。
* 如果异常是在函数嵌套中产生的:
* 例如函数A---->函数B---->函数C,而异常是在函数C中产生的,那么如果函数C中没有对这个异常进行处理,那么这个异常会传递到函数B中,如果函数B有异常处理那么就会按照函数B的处理方式进行执行;如果函数B也没有异常处理,那么这个异常会继续传递,以此类推。。。如果所有的函数都没有处理,那么此时就会进行异常的默认处理,程序最终会崩溃.
* 第一个案例中:  当demo1中发生了异常, 此异常被传递到demo2函数中处理,当处理完成后, 并没有返回demo1中进行执行, 而是在demo2中继续执行.

3.  模块

1). Python中的模块

>  在Python中有一个概念叫做模块(module),这个和C语言中的头文件以及Java中的包很类似,比如在Python中要调用`randint()函数`,必须用import关键字引入 random 这个模块,下面就来了解一下Python中的模块。

> 模块就好比是工具包,要想使用这个工具包中的工具(就好比函数),就需要导入这个模块

2)  import

>  在Python中,  一般用关键字`import`来导入模块

>  比如要引用模块 random,就可以在文件最开始的地方用 import random 来导入。

形如:

import module1,mudule2...

当解释器遇到 import 语句,我们添加的模块就会被导入。

在调用 random 模块中的函数时,必须这样引用:

模块名.函数名

例如:

random.randint()

> 因为可能存在在多个模块中含有相同名称的函数,此时如果只是通过函数名来调用,解释器无法知道到底要调用哪个函数。所以如果像上述这样引入模块的时候,调用函数必须加上模块名

    import math

    # 这样会报错

    # 求4的非负平方根

    print sqrt(4)

    #这样才能正确输出结果

    print math.sqrt(4)

3)  from…import

有时候我们只需要用到模块中的某些个函数,只需要引入该函数即可,此时可以用下面方法实现:

from 模块名 import 函数名1,函数名2....

不仅可以引入函数,还可以引入一些全局变量、类等

**注意**:

> - 通过这种方式引入的时候,调用函数时只能给出函数名,不能给出模块名,但是当两个模块中含有相同名称函数的时候,后面一次引入会覆盖前一次引入。也就是说假如模块A中有函数function( ),在模块B中也有函数function( ),如果引入A中的function在先、B中的function在后,那么当调用function函数的时候,是去执行模块B中的function函数。

> - 如果想一次性引入某个模块中所有的东西,还可以通过 from 模块名 import * 来实现

例如,要导入模块 random 的 randint 方法,使用如下语句:

    from random import randint

**注意**:

- 这样的方式不会把整个模块的内容导入到当前文件中,  它只会将模块中的单个方法引入进来

4). from … import *

把一个模块的所有内容全都导入到当前的文件中也是可行的,只需使用如下声明:

from 模块名 import *

**注意**:

- 这提供了一个简单的方法来导入一个模块中的所有项目。然而这种声明不该被过多地使用。

- 这个方法最好不要用的太多, 因为这样的方式是把模块中的内容一次性都引入, 会对内存造成很大的压力.

5). as

> as 关键字可以给导入的模块起一个别名

使用格式:import 模块名 as 别名

说明:

* 需要说明的是 一旦使用 as 给某个模块起别名以后, 原来模块的名字就不可以使用了, 用了会报错

import random as tt

tt.randint(0, 10)

random.randint(0, 10)

运行结果为:

Traceback (most recent call last):

  File "demo.py", line 3, in <module>

    random.randint(0, 10)

NameError: name 'random' is not defined

6). 定位模块  (linux系统)

当你导入一个模块,Python解析器对模块位置的搜索顺序是:

-. 当前目录

-. 如果不在当前目录,Python则搜索在shell变量PYTHONPATH下的每个目录。

-. 如果都找不到,Python会察看默认路径。UNIX下,默认路径一般为/usr/local/lib/python/

-. 模块搜索路径存储在system模块的sys.path变量中。变量里包含当前目录,PYTHONPATH和由安装过程决定的默认目录。

7). 模块制作

-定义自己的模块

> 在Python中,每个Python文件都可以作为一个模块,模块的名字就是文件的名字。

> 比如有这样一个文件 demo.py,在 demo.py 中定义了函数add

# demo.py文件中:

def add(a, b):

    return a + b

- 调用自己定义的模块

>  那么在其他文件中就可以先import demo,然后通过demo.add(a,b)来调用了,当然也可以通过from demo import add来引入

main.py文件中:

# main.py

import demo

result = demo.add(11,22)

print(result)

-. 测试模块

>  在实际开中,当一个开发人员编写完代码后,会检测这个模块是否达到了想要的效果, 往往会在当前项目中添加一些检测代码, 例如:

demo.py文件中:

# demo.py文件中

def add(a,b):

    return a+b

# 用来进行自测:

ret = add(12,22)

print('demo:  12+22=%d'%ret)

如果此时,在其他py文件中引入了此文件的话,想想看,测试的那段代码是否也会执行呢!

main.py文件中:

import demo

result = demo.add(11,22)

print(result)

运行现象:

demo:  12+22=34

33

至此,可发现 demo.py 中的测试代码,应该是单独执行 demo.py 文件时才应该执行的,不应该是其他的文件中引用而执行

为了解决这个问题,python在执行一个文件时有个变量`__name__`

##### demo.py文件中运行的结果为:

# demo.py文件中

def add(a,b):

    return a+b

# 用来进行自测:

print('demo中__name__的值是: %s' % __name__)

结果:

demo中__name__的值是: __main__

在别的文件中导入demo.py文件,然后运行:

在main.py文件中运行后:

import demo

运行结果:

demo中__name__的值是: demo

# 所以在demo.py文件中,自测一般先判断: if __name__ == '__main__':

def add(a,b):

    return a+b

# 用来进行自测:

def main():

    print(add(10,20))

if __name__ == '__main__':

    main()

 总结:

- 可以根据__name__变量的结果能够判断出,是直接执行的python脚本还是被引入执行的,从而能够有选择性的执行测试代码

8).模块中的`__all__`

我们可以在模块中添加`__all__`属性.  这个属性的作用是:

> 如果一个模块中有`__all__`属性,则只有`__all__`内指定的属性, 方法, 类可被导入

> 但是这样做有一个限制:  必须用 from 模块名 import *  的形式导入

没有`__all__`

demo01.py:

# 全局变量

name = 'itcast'

def hello():

    print('hello')

class Person(object):

    def eat(self):

        print('eat')

demo02.py:

from demo01 import *

print(name)

hello()

per = Person()

per.eat()

运行demo02.py ,  可得结果:

itcast

hello

eat

模块中有`__all__`

demo01.py:

__all__ = ['name', 'hello']

# 全局变量

name = 'itcast'

def hello():

    print('hello')

class Person(object):

    def eat(self):

        print('eat')

demo02.py:

from demo01 import *

print(name)

hello()

per = Person()

per.eat()

运行demo02.py, 可得:

itcast

hello

Traceback (most recent call last):

  File "demo02.py", line 5, in <module>

    per = Person()

NameError: name 'Person' is not defined

上例中,如果我们把 Person 添加到`__all__`中,可得:

demo01.py:

__all__ = ['name', 'hello', 'Person']

# 全局变量

name = 'itcast'

def hello():

    print('hello')

class Person(object):

    def eat(self):

        print('eat')

demo02.py:

from demo01 import *

print(name)

hello()

per = Person()

per.eat()

运行demo02.py, 可得:

itcast

hello

eat

总结

- 如果一个模块中有`__all__`属性,则只有`__all__`内指定的属性, 方法, 类可被导入, 当然前提是使用from 模块名 import * 的导入形式

- 这样做有利于我们决定是否将模块中的所有内容都展示出去.  可以有选择性的进行保留

- 在程序中一般定义接口的时候, 可能用到. 这里知道即可, 不做过多讲解.

4. python中的包

>  包是什么?      其实:  包就是带有`__init__.py`文件的文件夹

1). 引入包

> 在程序中, 我们往往会把有联系的模块放在一个包中  (即把有联系的多个.py文件放在一个特殊文件夹下)

> 特殊在哪里呢?  这个文件夹下会有一个`__init__.py`的文件

我们在创建一个新的模块(文件), 在新建的模块中, 导入包里面的两个文件:   

这两个文件在包中, 所以我们导入的时候,也要把包名带上:

2).包中的`__init__.py`文件

> `__init__.py` 控制着包的导入行为

总结:

- 包将有联系的模块组织在一起,即放到同一个文件夹下,并且在这个文件夹创建一个名字为`__init__.py` 文件,那么这个文件夹就称之为`包`

- 有效避免模块名称冲突问题,让应用组织结构更加清晰

- `__init__.py`文件有什么用?

  - `__init__.py` 控制着包的导入行为

- `__init__.py`如果为空:

  * 仅仅是把这个包导入,不会导入包中的模块

- `__all__`

  * 在`__init__.py`文件中,定义一个`__all__`变量,它控制着 from 包名 import *时导入的模块

- 也可以在`__init__`.py文件中定义一些方法,那么这些方法能在包导入后调用

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 194,457评论 5 459
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 81,837评论 2 371
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 141,696评论 0 319
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,183评论 1 263
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 61,057评论 4 355
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,105评论 1 272
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,520评论 3 381
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,211评论 0 253
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,482评论 1 290
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,574评论 2 309
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,353评论 1 326
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,213评论 3 312
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,576评论 3 298
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 28,897评论 0 17
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,174评论 1 250
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,489评论 2 341
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,683评论 2 335