1.3面向对象
1.3.1面向对象基础
1.3.1.1面向对象
1.3.1.1.1基本概念
- 过程和函数
- 过程类似于函数,只能执行,但是没有返回值
- 函数不仅能执行而且有返回结果
- 简单来说,面向对象就是相对于函数而言更大的封装,将多个函数封装给一个对象,每个对象具有不同的职责,在开发时根据不同的职责调用不同的对象就可以极大的简化开发的效率
1.3.1.1.2类和对象
- 类是一群具有相同特征或者行为的事物的一个统称,不能直接使用;其中特征被称为属性,行为被称为方法;简单来说,类就相当于制造飞机的图纸,是一个模版是负责创造对象的,类是抽象的是负责创建对象的
- 对象是由类创造出来的一个具体存在,可以直接使用;简单来说,对象就相当于飞机;所以是先有类才有对象;对象中就有相应的属性和方法
- 类和对象的关系:类只有一个,对象可以有多个(可以根据一个图纸制造多个飞机),同一个类的不同对象可能会有所不同;类中定义了什么方法和属性,对象中就有什么方法和属性,不可能多也不可能少
1.3.1.1.3类的三要素
- 类名:一般类名要满足大驼峰命名法(首字母大写)
- 属性:这类事物具有什么特征(通常用名词来表示)
- 方法:这类事物具有什么行为(通常是动词来表示)
1.3.1.1.4内置函数dir查询对象的方法列表
-
在python中对象几乎是无所不在的,变量、数据、函数都是对象
-
python中两种查看对象内所有方法和属性的方法
-
在标识符/数据后输入一个.然后按下tab键,ipython就会提示该对象的所有能够使用的方法列表
-
使用内置函数dir()传入标识符/数据,可以查看对象内的所有属性和方法:其中__方法名__是表示python提供的内置方法/属性
list_temp = [] print(dir(list_temp))
-
1.3.1.2定义简单类
1.3.1.2.1基本语法
-
创建类
class 类名: def 方法1(self, 参数列表): # 类中的方法第一个参数必须是self pass def 方法2(self, 参数列表): pass
-
创建对象
对象变量 = 类名()
-
案例
class Cat: def eat(self): print("eat") def drink(self): print("drink") cat = Cat() cat.drink() cat.eat()
-
拓展
-
如果直接使用print打印对象变量,默认情况下会输入这个变量的引用对象是由哪一个类创建的对象以及在内存中的地址(默认是十六进制表示%x),变量引用的概念同样适用于对象(即对象变量存储的是对象在内存中的地址,注意这里不是类在内存中的地址,即同一个类的不同对象在内存中的地址不同,但如果给对象赋值一个对象则两个对象的地址相同)
class Cat: def eat(self): print("eat") def drink(self): print("drink") cat = Cat() cat.drink() cat.eat() print(cat) addr = id(cat) # id()函数可以得到变量在内存中的地址 print("%x" % addr) # 用十六进制的方式输出cat对象在内存中的地址 print("%d" % addr) # 用十进制的方式输出cat对象在内存中的地址
-
1.3.1.2.2self
-
临时为对象创建一个属性:对象名.属性名=值,由于这种方法没有在类中进行修改,所以在实际开发中不推荐使用
cat.name = "猫1" # 这里就为cat对象添加了一个属性name,值为猫1
-
self:哪一个对象调用了类的方法,self就表示哪一个对象的引用
class Cat: def eat(self): print("%seat" % self.name) # 通过self来调用调用该类的对象的属性 def drink(self): print("drink") cat = Cat() cat.name = "懒猫" cat.drink() cat.eat()
-
在执行程序时,类和函数一样不会被执行,只有当调用时才会被执行
1.3.1.3初始化方法
-
在python中创建一个对象时python会自动做两件事:为对象开辟一个空间;调用初始化方法(不需调用该方法在创建对象的同时就直接执行该方法)
class Cat: def __init__(self): # 初始化方法的名字是固定的 print("初始化") def eat(self): print("%seat" % self.name) # 通过self来调用调用该类的对象的属性 def drink(self): print("drink") cat = Cat() # 在创建对象时,python会自动调用初始化方法 cat.name = "懒猫" cat.drink() cat.eat()
-
如果想要一个类的所有对象在创建的同时就有一些属性,就需要在类中的初始化方法中定义这类属性
class Cat: def __init__(self, new_name): # 初始化方法的名字是固定的,通过使用参数来传递值 print("初始化") self.name = new_name # 定义所有该类的对象都具有都属性 def eat(self): print("%s正在eat" % self.name) # 通过self来调用调用该类的对象的属性 def drink(self): print("drink") cat = Cat("tom") # 在创建对象时,python会自动调用初始化方法并传递相关的参数 cat.drink() cat.eat() print(cat.name)
1.3.1.4内置方法
1.3.1.4.1_del_()方法
-
当一个对象在内存中被销毁前都会自动调用_del_()方法
class Cat: def __init__(self): print("come") def __del__(self): print("back") cat = Cat() del cat # del可以销毁变量,此时自动调用__del__方法 print("*" * 30) # 如果不提前销毁对象,由于cat是全局变量,这里在程序执行完毕后才会被销毁并自动调用__del__方法
1.3.1.4.2_str_()方法
-
在默认的情况下使用print输出对象时会自动打印对象所在的类和对象在内存中的地址,如果希望使用print输出对象时可以输出自己想要的内容就可以使用_str_()方法
class Cat: def __init__(self, new_name): print("come") self.name = new_name def __del__(self): print("back") def __str__(self): # __str__()方法必须有一个返回值 return "我是%s" % self.name cat = Cat("小红") print(cat) # 可以自定义输出内容 del cat # del可以销毁变量,此时自动调用__del__方法 print("*" * 30) # 如果不提前销毁对象,由于cat是全局变量,这里在程序执行完毕后才会被销毁并自动调用__del__方法
1.3.2面相对象练习
-
小明爱跑步案例
class Person: def __init__(self, new_name, new_weight): self.name = new_name self.weight = new_weight def __str__(self): return "我的名字是%s,体重是%.2f斤" % (self.name, self.weight) def run(self): print("%s爱跑步" % self.name) self.weight -= 1 def eat(self): print("%s爱吃零食" % self.name) self.weight += 1 person_boy = Person("小明", 103) person_girl = Person("小美", 90) person_boy.run() # self这个参数不需要传值 print(person_boy) print(person_girl)
-
摆放家具案例
# 通常在实际开发中被使用的类要先被开发(如摆放家具案例中的家具类要比房子类要先定义) # 定义一个家具类 class HouseItem: def __init__(self, new_name, new_area): """家具的默认属性 :param new_name: 家具的名称 :param new_area: 家具的占地面积 """ self.name = new_name self.area = new_area def __str__(self): return "%s占地%.2f" % (self.name, self.area) # 定义一个房子类 class House: def __init__(self, new_house_type, new_area): """房子的默认属性 :param new_house_type: 房子的户型 :param new_area: 房子的占地面积 """ self.house_type = new_house_type self.area = new_area # 剩余面积 self.free_area = new_area # 对于新房子而言剩余面积就是初始面积 # 家具名称列表 self.item_list = [] def __str__(self): # python能够自动将一对括号内部的代码连接在一起 return ("户型:%s\n总面积:%.2f【剩余%.2f】\n家具:%s" % (self.house_type, self.area, self.free_area, self.item_list)) def add_item(self, item): """添加家具 :param item: 要添加的家具名称 :return: """ print("要添加%s" % item) """如果参数是一个对象则输出的内容是print(对象),相当于 输出__str__()函数的内容""" # 判断家具的面积 if item.area > self.free_area: # 在创建家具对象的同时就默认有area和name属性,所以这里可以直接调用 print("%s面积超出无法添加" % item.name) return # 将家具的名称添加到列表中 self.item_list.append(item.name) # 计算剩余面积 self.free_area -= item.area # 添加家具 bed = HouseItem("席梦思", 4) chest = HouseItem("衣柜", 2) table = HouseItem("餐桌", 1.5) print(bed) print(chest) print(table) # 创建房子对象 my_home = House("两室一厅", 60) # 通过add_item()方法将家具放置到房子中 my_home.add_item(bed) my_home.add_item(chest) my_home.add_item(table) print(my_home)
-
士兵突击案例
class Gun: # 枪类的定义 def __init__(self, new_model): """ :param new_model: 枪的型号 """ self.model = new_model self.bullet_count = 0 def add_bullet(self, count): """给枪加子弹 :param count: 添加子弹的数量 :return: """ self.bullet_count += count def shoot(self): """发射 :return: """ if self.bullet_count <= 0: print("%s没有子弹了" % self.model) return else: self.bullet_count -= 1 print("%s发射成功,还剩%d子弹" % (self.model, self.bullet_count)) class Soldier: # 士兵类的定义 def __init__(self, new_name): self.name = new_name # 在定义属性时如果不知道设置什么初始值可以设置为None self.gun = None # 新兵没有枪,这里要设置一个对象 def fire(self): """士兵开枪 :return: """ # 判断士兵是否有枪 if self.gun is None: # python中对None对比较推荐使用is而不是== print("%s还没有枪" % self.name) return else: print("%s冲啊" % self.name) # 装填子弹 self.gun.add_bullet(50) # 调用枪对象的方法 # 发射子弹 self.gun.shoot() # 创建枪对象 ak47 = Gun("ak47") # 创建士兵对象 soldier_boy = Soldier("soldier_boy") # 给士兵一把枪 soldier_boy.gun = ak47 # 这里的ak47是一个对象,就是上面创建的枪对象 # 士兵开火 soldier_boy.fire()
-
身份运算符
- 在python中身份运算符用于比较两个对象所引用的内存地址是否一致,在python中针对None比较时推荐使用is判断(is判断两个对象所引用的地址是否相同,is not用来判断两个对象所引用的地址是否不同)
- is和==的区别:is用来判断两个变量所引用的地址是否相同,==用来判断两个变量所引用的地址中的数值是否相同
-
私有属性和方法
-
在实际开发中,某些对象的属性和方法只希望在对象的内部被使用,而不希望在外部被调用,这些属性和方法就被称为私有属性和方法
-
定义:在定义私有属性和私有方法时,在私有属性和私有方法的名字前面添加两个下划线就表示这个属性和方法就是私有属性和私有方法
class Women: def __init__(self, new_name, new_age): self.name = new_name self.__age = new_age def __secret(self): print("我的姓名是%s,我的年龄是%d" % (self.name, self.__age)) # 对象的内部可以直接访问私有属性和私有方法 girl = Women("xiaofang", 18) girl.__secret() # 对象的外部不可以直访问对象的私有属性和私有方法 """个人理解:这里的对象内部和外部可以理解为类的内部和外部"""
-
-
伪私有属性和方法
-
在python中没有真正意义的私有(其实python的私有属性和私有方法是伪私有属性和伪私有方法,其实在对象的外部可以通过特定的方式来调用私有属性和私有方法)
class Women: def __init__(self, new_name, new_age): self.name = new_name self.__age = new_age def __secret(self): print("我的姓名是%s,我的年龄是%d" % (self.name, self.__age)) girl = Women("xiaofang", 18) # 在对象外部只需在私有方法或私有属性的前面加上_类名(私有方法或私有属性所在的类)就可以调用私有方法或私有属性 girl._Women__secret()
-
如何在外部调用私有属性和私有方法(在实际开发中不要这么使用,私有的方法和属性就不要在外部调用)
-
1.3.3单继承和方法的重写
1.3.3.1单继承
1.3.3.1.1问题的抛出
- 面向对象的三大特性:继承、封装、多态
- 继承:实现代码的重用,相同的代码不需要在重复的编写
- 封装:根据职责将属性和方法封装到一个抽象的类中
- 多态:不同的对象调用相同的方法,产生不同的执行结果,增加代码的灵活度
1.3.3.1.2继承的概念和语法
-
继承的语法
class 类名(父类名): # 子类就具有父类的属性和方法而不需要重新定义 pass
1.3.3.1.3继承的相关术语:继承和派生
- 如果一个Dog类继承Animal类,那么可以说Dog类是Animal类的子类,Animal类是Dog类的父类,Dog类从Animal类继承;也可以说Dog类是Animal类的派生类,Animal类是Dog类的基类,Dog类从Animal类派生;也就是会说:基类就是父类,派生类就是子类
1.3.3.1.4继承的传递性
- A类是B类的子类,C类是A类的子类,则C类可以调用B类的方法和属性
1.3.3.2方法的重写
1.3.3.2.1覆盖父类方法,重写子类方法实现
-
当父类的方法不能满足子类需求时就需要对方法进行重写(在子类中定义一个和父类同名的方法,这种重写只有在调用子类才会被使用(调用子类的方法时会调用子类中重写后的方法而不会调用父类的方法),但是父类的同一个方法不会改变,也就是说父类的其它子类调用该方法时仍然是调用父类原先的方法)
class Father: def run(self): print("father run") class Boy(Father): def run(self): print("boy run") class Girl(Father): pass boy = Boy() boy.run() girl = Girl() girl.run()
1.3.3.2.2拓展父类方法,super对象调用父类方法
-
如果在开发中,子类的方法实现中包含父类的方法实现(也就是子类的方法所实现的功能比父类的方法多,父类的方法时子类方法的一部分),就可以通过拓展的方法拓展子类的方法
-
在需要的位置使用super().父类方法来调用父类方法的执行,其它位置就编写子类的其它功能代码
-
在python中super是一个特殊的类,super()就是使用super类创建出来的对象,最常使用的场景就是在重写父类时调用父类中封装的方法实现
-
简单来说,super()方法就是在子类重写父类方法时能够调用父类原本的方法(而不需要在重写方法时将父类原本的功能在写一遍才能调用父类原有的功能)
-
代码理解
class Father: def run(self): print("father run") class Boy(Father): def run(self): # 针对子类特殊的需求编写代码 print("boy run") # 使用super()在重写父类方法的同时调用父类中原本的方法 super().run() # 增加其它的方法 print("boy walk") class Girl(Father): pass boy = Boy() boy.run() girl = Girl() girl.run()
1.3.3.2.3使用父类名调用父类方法
-
在python2.x中是没有super()方法的,所有还有一种调用父类方法的方法(在python3.x不推荐使用该方法,python3中推荐使用super()方法来调用父类原有的方法功能)
-
语法:父类名.方法(self)
-
代码理解
class Father: def run(self): print("father run") class Boy(Father): def run(self): # 针对子类特殊的需求编写代码 print("boy run") # 使用父类名.方法(self),这里的self不能省略 Father.run(self) # 增加其它的方法 print("boy walk") class Girl(Father): pass boy = Boy() boy.run() girl = Girl() girl.run()
1.3.4私有方法和属性
1.3.4.1子类对象不能直接访问
- 子类的对象(子类中也是不可以直接访问父类的私有方法和私有属性)是不能直接访问父类的私有方法和私有属性
1.3.4.2通过父类的方法间接访问
- 在父类中定义共有方法,在该方法中访问父类的私有方法和私有属性,则子类可以通过父类的公有方法来调用父类的私有方法和私有属性
1.3.5多继承
1.3.5.1概念、语法
-
子类可以拥有多个父类并且拥有父类的所有属性和方法,这就称为多继承(一个子类只有一个父类则称单继承)
-
多继承语法
class 子类(父类1, 父类2, ……): pass
-
注意事项:如果一个子类要继承多个父类注意要保证父类的方法名不同否则会出现歧义(子类到底是调用哪个父类的方法),也就是说如果不同的父类拥有同名的方法应该避免使用多继承
1.3.5.2MRO方法搜索顺序
-
method resolution order的简称是MRO(方法解决顺序)
-
使用类的内置属性__mro__可以查看方法的搜索顺序(如果父类出现同名的现象可以通过该内置属性来查看子类调用的是哪一个父类的方法)
class A: def a(self): print("a") class B: def a(self): print("b") class C(A, B): pass c = C() c.a() print(C.__mro__) # 输出的顺序就是方法调用的顺序
1.3.5.3新式类和经典类(旧式类)
-
object是python为所有对象提供的基类,提供一些内置方法和属性,可以使用dir()函数查看,在python3中所有类的最终基类都是object
-
以object为基类的类就是新式类,推荐使用;不以object为基类的类是经典类,不推荐使用;在python3中如果定义一个类时不指定父类就会默认使用object作为该类的基类,也就是说在python3中所有类的最终基类都是object;在python2中定义的类不会以object为基类
-
为了保证编写的代码能够同时在python2和python3上运行,今后在定义类时,如果没有父类都统一使用object来作为该类的父类
class 类名(object): pass
1.3.6多态
1.3.6.1基本概念
- 不同的子类对象条用相同的父类方法产生不同的执行结果
- 多态可以增加代码的灵活度,多态以继承和重写父类方法为前提,多态是调用方法的技巧不会影响到类的内部设计
1.3.7类属性、类方法、静态方法
1.3.7.1类属性
1.3.7.1.1实例
- 概念:为类创建的对象就叫做实例,通俗来说对象就是实例
1.3.7.1.2类是一个特殊的对象
-
在python中一切皆对象,class 类名:定义的类属于类对象,对象名 = 类名():定义的对象是一个实例对象
-
在程序运行时类同样会被加载到内存中
-
类对象可以拥有自己的属性和方法,这种属性和方法叫类属性和类方法;可以通过类名.的方式可以访问类的属性或者调用类的方法
-
类名.属性可以访问到类属性;类名.方法名()可以访问到类方法;类名()可以访问自定义的实例属性(__init__定义的实例属性);对象名.方法名可以访问实例方法
-
总结:类方法、类属性、实例方法、实例属性的区别
class Test: count = 0 # count是一个类属性而不是实例属性 def __init__(self): # __init__(self)方法就是一个实例方法而不是类方法 self.sum = 1 # 这里的sum就是一个实例属性而不是一个类属性 @classmethod # 使用该标志的方法就是类方法 def test(cls): pass
1.3.7.1.3类属性的定义和应用
-
类属性就是在类中定义的属性,通常用来记录与这个类相关的特征,类属性不会用于记录具体对象的特征
class Tool(object): count = 0 # 定义一个类属性count用来统计调用类的对象个数 def __init__(self): Tool.count += 1 # 使用类名.属性可以方法类属性 tool_01 = Tool() tool_02 = Tool() tool_02 = Tool() print(Tool.count)
1.3.7.1.4属性查找机制-向上查找
- 除了通过类名来访问类属性还可以通过对象名来访问类属性,访问的语法同类一样
- 在python中如果用对象名的方式访问类属性,python会先在对象中查找是否有该属性如果有就调用对象中的属性如果没有就向上查找对象所属的类中是否有该属性,如果没有就报错
- 在实际开发中一般推荐使用类名来访问类属性
- 使用对象名.变量名=值的方式会在对象的内存中开辟一个变量的空间,并给这个变量赋值
1.3.7.2类方法
1.3.7.2.1基本语法
-
语法格式:
@ classmethod def 类方法名(cls): # 哪一个类调用该方法(类名.方法),cls就是哪一个类的引用 pass # 可以使用cls.方法名来访问当前类的其它类方法,使用cls.属性名来访问当前类的其它类属性
1.3.7.3静态方法
-
在开发中如果一个方法既不需要访问实例属性或者实例方法也不需要访问类属性和类方法那么就可以将该方法封装成一个静态方法
-
格式:
@staticmethod def 静态方法名(): # 静态方法不需要指定第一个参数 pass
-
静态方法的调用:类名.静态方法名()
1.3.7.4方法综合
-
案例:
class Game(object): # 定义一个类属性top_score用来记录历史最高分 top_score = 0 # 定义一个实例属性player_name用来记录当前游戏玩家的姓名 def __init__(self, player_name): self.player_name = player_name # 定义一个静态方法show_help用来显示游戏的帮助信息(一般在实际的开发中将不需要访问类属性和实例属性的方法定义为静态方法) @staticmethod def show_help(): print("帮助信息") # 定义一个类方法show_top_score用来显示历史最高分 @classmethod def show_top_score(cls): print("历史记录是%d" % cls.top_score) # 定义一个实例方法start_game用来开始当前玩家的游戏 def start_game(self): print("%s开启游戏" % self.player_name)
-
方法定义的技巧
- 一般而言实例属性是和对象相关的,不同的对象实例属性一般不同(即不同对象的同一属性的值不同那么该属性就定义为实例属性);而类属性一般和对象无关(即无论对象为随该属性的值都不变那么这类属性就可以定义为类属性)
- 如果一个方法内部既需要访问实例属性有要访问类属性则定义为实例方法
- 如果一个方法内部需要访问到实例属性就定义为实例方法,实例方法内部可以通过类名来访问类属性
- 如果一个方法只需要访问类属性就定义为类方法
- 如果一个方法不需要访问类属性和实际属性就定义为静态方法
1.3.8单例模式
1.3.8.1设计模式和单例设计模式的概念
- 设计模式是前人工作的总结和提炼,通常被人们广泛流传的设计模式都是针对某一个问题的成熟解决方案;使用设计模式可以更好的保证代码的可读性
- 单例设计模式的目的是让类创建的对象在系统中只有一个唯一的实例;每一次执行类名()返回的对象的内存地址都是相同的(例如一个音乐播放器一次只能播放一首音乐,每台电脑只有一个回收站,一个打印机一次只能打印一份文件)
1.3.8.2__new__方法
- _new__方法是有object基类提供的一个内置的静态方法,主要作用有两个:在内存中为对象分配空间;返回对象的引用,在实例化对象时python会自动调new方法(也就是说new方法负责在对象创建时为其开辟一个内存然后返回该内存的地址)_
1.3.8.3重写__new__方法
class MusicPlayer(object):
def __new__(cls, *args, **kwargs): # 重写new方法,在创建对象时该方法会被自动调用
return super().__new__(cls)
def __init__(self):
print("播放器初始化")
player = MusicPlayer()
print(player)
1.3.8.4单例设计模式实现
class MusicPlayer(object):
# 记录第一个类对象的引用
isstance = None
def __new__(cls, *args, **kwargs):
# 判断类属性是否时空对象
if cls.isstance is None: # 一般当保存的内容为对象时可以使用None
# 调用父类的方法,为第一个对象分配空间
cls.isstance = super().__new__(cls)
# 返回类属性保存的对象引用
return cls.isstance
player1 = MusicPlayer()
player2 = MusicPlayer()
print(player1)
print(player2)
# 要向让一个类的不同对象的引用相同就需要重写类的new方法,来保证返回值都是一样的
1.3.8.5单例模式下让初始化方法只调用一次
class MusicPlayer(object):
# 记录第一个类对象的引用
isstance = None
# 记录是否执行过初始化
init_flay = False
def __new__(cls, *args, **kwargs):
# 判断类属性是否时空对象
if cls.isstance is None: # 一般当保存的内容为对象时可以使用None
# 调用父类的方法,为第一个对象分配空间
cls.isstance = super().__new__(cls)
# 返回类属性保存的对象引用
return cls.isstance
def __init__(self):
# 判断是否执行过初始化
if MusicPlayer.init_flay == False:
print("初始化")
MusicPlayer.init_flay = True
else:
return
# 这里的调用一次是指可以让初始化内部的特定语句只调用一次并不是说初始化方法只调用一次(只要对象被创建初始化方法就会被调用)
player1 = MusicPlayer()
player2 = MusicPlayer()
print(player1)
print(player2)
# 要向让一个类的不同对象的引用时相同的就需要重写类的new方法,来保证返回值都是一样的
1.3.9异常
1.3.9.1捕获异常
-
在日常的程序开发中如果对某些代码的执行不能确定是否正确就需要捕获异常(当特定代码执行错误时程序不会中断),捕获异常最简单的语法格式:
try: 尝试执行的代码 # 不能确定正确执行的代码 except: 出现错误的处理 # 出现错误时执行的代码
1.3.9.2错误类型捕获
-
当实际开发中错误的类型不止一个时就需要对错误的类型进行捕获,针对不同的类型错误得到不同的错误提示
-
代码演示:
try: num = int(input("请输入一个整数:")) result = 8 / num print(result) except ValueError: # ValueError表示特定的错误,获取的方式就是不写捕获异常,报错后的第一个报错类型就是错误类型 print("值错误") except ZeroDivisionError: print("除零错误")
-
捕获未知错误,在实际开发中如果有些错误是事先无法捕获到的(就是有些错误类型无法提前预知到的),就需要捕获未知错误
except Exception as result: print("未知错误%s" % result)
try: num = int(input("请输入一个整数:")) result = 8 / num print(result) except ValueError: # ValueError表示特定的错误,获取的方式就是不写捕获异常,报错后的第一个报错类型就是错误类型 print("值错误") except Exception as result: # 未知错误捕获 print("未知错误 %s" % result) # 系统会输出未知错误的错误提示
1.3.9.3异常捕获的完整代码
-
完整代码实例
try: # 尝试执行的代码 except 错误类型1: # pass except 错误类型2: # pass except Exception as result: # 捕获未知错误类型 print(result) # 系统会输出未知错误的错误提示 else: # 没有异常才会执行的代码 finally: # 无论是否有异常都会执行的代码
1.3.9.4异常的传递
-
当函数/行为执行出现异常时,会将异常传递给函数/方法的调用一方,如果传递到主程序时仍然没有异常处理程序才会终止
-
在实际开发中,可以在主函数中添加异常捕获;而在主函数中调用其它函数只要出现异常都会传递到主函数的异常捕获中;这样就可以保证在编写代码时不需要增加大量的异常捕获保证代码的整洁
-
代码实例
def demo1(): b = int(input("请输入除数:")) sum = 8 / b return sum def demo2(): return demo1() # 利用异常的传递性,在主程序中捕获异常 try: temp = demo2() print(temp) except Exception as result: print(result)
1.3.9.5主动抛出异常
-
抛出raise异常:创建异常对象;抛出异常;捕获创建的异常对象
def input_password(): # 提示用户输入密码 pwd = input("请输入密码:") if len(pwd) >= 8: return pwd else: # 创建异常对象,可以使用错误信息字符串作为参数 ex = Exception("你所输入的密码长度不够") # 主动抛出异常 raise ex try: print(input_password()) except Exception as result: # 这里会抛出上面所创建的异常对象中的错误信息 print(result)
1.3.10模块和包
1.3.10.1模块
-
模块名也是一个标识符,因此在给程序命名时要符合标识符的命名规则
-
给模块起别名:import 原模块名 as 原模块别名(模块别名要符合大驼峰命名法)
-
在使用from import导入模块中的方法时,如果两个模块拥有同名的方法则后导入的方法会覆盖原先导入的方法(即实际调用的是后导入模块中的方法)
-
给模块中的方法起别名:from 模块名 import 方法名 as 方法别名
-
注意:在实际开发中from 模块名 import *这种导入所有方法的形式不推荐使用,因为当模块出现同名就很难处理
-
模块的搜索顺序:python的解释器在导入模块时会先搜索当前目录下的指定文件,如果有就直接导入如果没有就在搜索系统目录;在python中每一个模块都有一个内置属性__file__可以查看模块的完整路径
import random print(random.__file__) ran = random.randint(0,10) print(ran)
-
导入一个模块时,该模块中所有没有被缩进的代码都会被自动执行(不需要被调用),也就是说被导入模块中所有没有缩进的代码都会在调用他时直接被执行
-
__name__属性可以做到,测试模块的代码只有在测试情况下被运行,而在被导入时不会被执行;name属性是一个内置属性,记录着一个字符串;如果是被其它文件导入的,name属性就是一个模块名,如果是当前执行的程序,name就是一个main
import test_09
import random print(random.__file__) ran = random.randint(0,10) print(ran) if __name__ == "__main__": # 使用这种形式判读表明本部分的代码就是测试代码,在被导入时不会直接被执行 print(__name__) # 如果执行当前程序,__name__就是一个固定的__main__
import test_09 test_09.main() # 可以通过调用的方式调用模块中的测试代码
def main(): print("这是测试代码") def test(): print("这是被导入代码") test() if __name__ == "__main__": main()
1.3.10.2包
-
包是一个包含多个模块的特殊目录,包名的命名方式和变量名一致,一般使用小写字母+的方式命名,在一个包下必须有一个特殊的文件__init_.py
-
实例代码
-
_init_.py
from . import send # 该部分表明要把该包下哪些模块提供给其它程序使用,不在该部分的模块其它程序无法调用内部的方法
-
send.py
def test(): print("测试代码")
-
test.py
import test_001 # 导入包名 test_001.send.test() # 调用包中send方法的test()方法
-
1.3.10.3制作模块
1.3.10.3.1将包制作成压缩包并发布压缩包的步骤
-
创建setup.py文件
from distutils.core import setup setup(name="包名", version="版本", description="描述信息", long_description="完整描述信息", author="作者", author_email="作者邮箱", url="主页", py_modules = ["要导出的模块1,格式为包名.模块名", "要导出的模块2"])
-
构建模块
$ python3 setup.py build
-
生成发布压缩包
$ python3 steup.py sdist
1.3.10.3.2安装模块压缩包
$ tar -zxvf 压缩包名
$ sudo python3 setup.py install
1.3.10.3.3删除模块
- 使用模块的内置属性__file__查看模块的具体安装位置
- 在命令行下切换到模块的安装位置
- 使用命令删除模块即可
1.3.10.4使用pip安装第三方模块
-
第三方模块的安装和卸载
$ sudo pip3 install 模块名 # 将模块安装到python3中 $ sudo pip3 uninstall 模块名 # 将模块卸载
-
在Mac下安装ipython
sudo easy_install pip sudo pip install ipython
-
在Linux下安装ipython
sudo apt install ipython3
1.3.11文件操作
1.3.11.1文件的概念
- 在计算机中,文件是以二进制的方式保存在磁盘上的
- 文本文件
- 可以使用文本编辑软件查看
- 本质上还是二进制文件
- 例如:python源程序
- 二进制文件
- 保存的内容不是给人直接阅读的,而是提供给其它软件使用的
- 例如:图片文件、音频文件、视频文件等
- 二进制文件不能直接用文本编辑器查看
1.3.11.2文件操作
-
在计算机中文件的操作的步骤非常固定:打开文件、操作文件(读写)、关闭文件
-
操作文件的函数/方法
函数或方法 说明 open 打开文件并且返回问价操作对象 read 将文件内容追取到内存 write 将指定文件写入内存 close 关闭文件 -
read/write/close都需要通过文件对象来调用
-
读取文件内容
-
open函数第一个参数是要打开的文件名,其中如果文件存在,返回文件操作对象,如果文件不存在会抛出异常
-
read函数可以一次性的读入和返回文件的所有内容
-
如果忘记关闭文件会导致资源消耗,而且会影响后续对文件的访问
-
代码实例
# 打开文件 file = open("test") # 读取文件内容 text = file.read() print(text) # 关闭文件 file.close()
-
-
读取文件后文件的指针会发生变化
- 文件指针标记从哪个位置开始读取数据,第一次打开文件时,通常文件指针会指向文件开始的位置,当执行完read方法后文件指针会移动到读取内容的末尾(也就是说,当执行一次read方法读取完一个文件后在执行一次read方法就无法读取文件中的内容)
-
打开文件和写入数据
-
打开文件的方式:f = open(“文件名”, “访问形式”)
-
文件的访问形式
访问方式 说明 r 以只读方式打开文件,文件的指针会放在文件的开头,这是默认的形式,如果文件不存在,抛出异常 w 以只写方式打开文件,如果文件存在则会被覆盖,如果文件不存在则创建新文件 a 以追加方式打开文件,如果文件已经存在则文件指针会放在文件的末尾,如果文件不存在创建新文件重新写入 r+ 以读写方式打开文件,文件的指针会放在文件的开头,如果文件不存在则会抛出异常 w+ 以读写方式打开文件,如果文件存在则会覆盖原文件,如果文件不存在则会创建新文件 a+ 以读写方式打开文件,如果文件存在则指针放在文件的结尾,如果文件不存在则会创建新文件 -
在实际开发中为避免频繁的使用指针导致文件的写入混乱通常只使用只读和只写
-
代码实例
# 打开文件 file = open("test", "a+") # 读取文件内容 text = file.write("def") # 关闭文件 file.close()
-
-
使用readline分行读取大文件:realline可以一次读取一行内容,方法执行后文件指针会移动到下一行
-
代码实例
# 打开文件 file = open("test") # 读取文件内容 while True: text = file.readline() # 判断是否读取,同时实现读完结束读取的功能 if not text: break print(text) # 关闭文件 file.close()
-
-
小文件复制
file_read = open("test") file_write = open("test_temp", "w") text = file_read.read() file_write.write(text) file_read.close() file_write.close()
-
大文件复制
file_read = open("test") file_write = open("test_temp", "w") while True: text = file_read.readline() file_write.write(text) if not text: # text为空时视为False break file_read.close() file_write.close()
1.3.11.3目录管理操作
-
在python中如果要实现对文件的删除、移动、重命名等操作时就需要导入模块os
-
文件操作相关命令
方法名 说明 实例 rename 重命名文件 os.rename(源文件名, 目标文件名) remove 删除文件 os.remove(文件名) -
目录操作相关命令
方法名 说明 实例 listdir 目录列表 os.listdir(目录名) mkdir 创建目录 os.mkdir(目录名) rmdir 删除目录 os.rmdir(目录名) getcwd 获取当前目录 os.getcwd() chdir 修改工作目录 os.chdir(目标目录) path.isdir 判断是否是文件 os.path.isdir(文件路径) -
以上的目录名与文件名都需要加括号
1.3.12文本编码
1.3.12.1文本文件的编码方式ASCII和UTF8
- python2默认使用ASCII编码方式(所以不支持中文),python3默认使用UTF8编码方式
- ASCII编码:计算机中一共有256个ASCII字符,一个ASCII占用一个字节的空间(汉字数以万计无法使用)
- UTF8编码:计算机中使用1-6个字节来表示一个UTF8字符,涵盖了地球上几乎所有的 文字,大多数汉字会使用3个字节表示;UTF-8是UNICODE编码的一种编码格式
1.3.12.2如何在python2中使用中文
-
在python2的源文件的第一行增加一行代码
# *_* coding:utf8 *_*
1.3.12.3python2处理中文字符串
-
遍历输出中文字符串时,python2中仍然是以ASCII编码的方式输出的(即使写了# _ coding:utf8 *_*也一样),而中文在UTF-8中一般使用3个字节,所以输出中文时会出现乱码
-
可以在有中文的字符串前面加上一个u,告诉解释器这是一个utf-8编码格式的字符串
hel_str = u"你好世界" for c in hel_str: print(c)
1.3.13内建函数eval
1.3.13.1基本使用
-
在实际开发中不要使用eval()函数来转换input()的输出结果(因为在输入时如果输入一些特殊的语句会导致文件发生改变)
-
eval函数可以将字符串当成有效的表达式来求值并返回计算结果
a = eval("1+1") # 相当于eval函数可以将引号去掉然后计算引号内部的结果并返回结果 print(a) b = type(eval("[1,2,3]")) print(b) input_str = input("请输入算术:") print(eval(input_str))