Python学习笔记(三)面向对象
程序员文章站
2022-06-14 22:13:55
...
Python学习笔记(三)面向对象
本节用示例代码做演示
面向对象基础
# 面向对象练习----创建类
# 创建猫类
class Cat:
def eat(self):
# 哪一个对象调用此方法,self就是哪个对象的引用(内存地址)
# 在对象调用这个属性的时候,这个属性才会执行。
print('小猫爱吃鱼')
def drink(self):
print('小猫要喝水')
# 创建对象
tom = Cat()
# 调用Cat的方法
tom.eat()
tom.drink()
# 再创建一个猫对象
lazy_cat = Cat()
lazy_cat.eat()
lazy_cat.drink()
#查看内存地址
print(tom)
lazy_cat2 = lazy_cat
print(lazy_cat)
print(lazy_cat2)
'''
tom和lazy_cat内存地址不同,可见这是两个不同的对象
但lazy_cat和lazy_cat2地址相同,这两个是相同的对象
'''
# 面向对象练习----增加属性_1
# 创建猫类
class Cat:
# 解释器运行到定义类时并不会立即执行,而是在某个对象引用这个类时才会执行
def eat(self):
# 哪一个对象调用此方法,self就是哪个对象的引用(内存地址)
# 在对象调用这个属性的时候,这个属性才会执行。
print('小猫爱吃鱼')
def drink(self):
print('小猫要喝水')
# 创建对象
tom = Cat()
# 再创建一个猫对象
lazy_cat = Cat()
#############################################
# 给对象增加属性的一种方法
# 但是这种方法不推荐使用,因为没有把属性封装在类的内部,属性仅仅添加在对应的对象上。
tom.name = 'tom'
lazy_cat = 'lazy_cat'
# 面向对象练习----增加属性_1.1
# 创建猫类
class Cat:
# 解释器运行到定义类时并不会立即执行,而是在某个对象引用这个类时才会执行
def eat(self):
# 哪一个对象调用此方法,self就是哪个对象的引用(内存地址)
# 在对象调用这个属性的时候,这个属性才会执行。
print('%s爱吃鱼' % self.name)
def drink(self):
print('小猫要喝水')
# 创建对象
tom = Cat()
# 再创建一个猫对象
lazy_cat = Cat()
# 增加一个name属性
tom.name = 'tom'
lazy_cat.name = '大懒猫'
#############################################
# 使用eat方法,可以看到输出中实际上调用了self.name的属性
tom.eat()
lazy_cat.eat()
# 注意!在类外部增加属性的方法有一个问题,如果添加属性的代码在调用方法的后面,那么调用方法时会找不到这个属性。
# 面向对象练习----初始化方法
'''
给对象增加属性的另外一种方法
推荐这种方法,因为这种方法把属性封装在类的内部,其他相应的对象都可以调用。
'''
# 创建猫类
class Cat:
# 创建类时解释器会自动执行两个操作
# 第一个是在内存中为对象分配空间
# 第二个是自动调用初始化方法__init__
# __init__是专门用来定义类的属性的
def __init__(self, new_name):
# 设置name的初始值,将形参new_name传入name属性
self.name = new_name
# 解释器运行到定义类时并不会立即执行,而是在某个对象引用这个类时才会执行
def eat(self):
# 哪一个对象调用此方法,self就是哪个对象的引用(内存地址)
# 在对象调用这个属性的时候,这个属性才会执行。
print('%s爱吃鱼' % self.name)
def drink(self):
print('%s要喝水' % self.name)
# 创建对象
tom = Cat('Tom')
# 再创建一个猫对象
lazy_cat = Cat('大懒猫')
# 调用方法
tom.eat()
tom.drink()
lazy_cat.eat()
lazy_cat.drink()
# 面向对象练习----另外三种内置方法
class Cat:
def __init__(self, new_name):
self.name = new_name
print('%s来了' % self.name)
# __str__方法是用来输出对象的相关信息(自定义字符串)
# __str__方法只能返回字符串
def __str__(self):
return '俺%s是只猫' % self.name
# __del__方法是在对象销毁之前自动调用的,一旦__del__方法被调用,则对象的生命周期结束
def __del__(self):
print('%s走了' % self.name)
tom = Cat('Tom')
# 输出对象(调用__str__)
print(tom)
#输出一条分割线
print('-' * 20)
# 创建一个对象,可以从输出看到自动调用了__init__方法
# __del__方法是在对象被销毁之后才运行的,所以输出在最后面
lazy_cat = Cat('大懒猫')
# 销毁对象
# 一旦销毁对象,__del__方法就会被调用
del lazy_cat
#输出一条分割线
print('*' * 20)
以下是演练
# 面向对象封装练习
'''
面向对象编程的第一步 —— 要将属性和方法分装到一个类中
在创建类时一定要做需求分析
对象方法的细节全部封装在类的内部
外部只用来创建对象和调用方法
'''
##################################################
# 私有属性和方法
'''
对象的某些属性和方法可能只希望在对象内部使用,而不希望在外部访问到
在定义属性或方法时,在属性名或者方法名前加两个下划线,定义的就是私有属性或方法。
但在类内部的方法是可以调用私有属性或私有方法的。
在python中没有真正意义的私有。
'''
class Women:
def __init__(self, name, age):
self.name = name
self.__age = age
def __secret(self):
print(f'俺叫{self.name},俺{self.__age}岁了')
xiaofang = Women('小芳',18)
# 私有方法和属性不能调用
#xiaofang.__secret
#print(xiaofang.__age)
#Python的解释器其实是在私有属性或方法前加上了一个“_类名”,所以其实只要知道这个规则,还是可以调用私有属性和方法的
xiaofang._Women__secret()
print(xiaofang._Women__age)
单继承
# 单继承
# 定义一个动物类
class Animal:
def eat(self):
print('吃')
def drink(self):
print('喝')
def run(self):
print('跑')
def sleep(self):
print('睡')
# 定义一个狗类,继承动物类
class Dog(Animal):
def bark(self):
print('汪汪叫')
#定义一个哮天犬类,继承狗类
class XiaoTianQuan(Dog):
def fly(self):
print("俺能飞")
# 定义对象
wangcai = Dog()
xiaotianquan = XiaoTianQuan()
# 子类的对象可以调用父类的属性和方法
wangcai.eat()
wangcai.drink()
wangcai.bark()
# 继承具有传递性,哮天犬不但有狗类的属性和方法,也有动物类的属性和方法
xiaotianquan.eat()
xiaotianquan.bark()
xiaotianquan.fly()
# 术语为:Dog类是Animal类的子类,Animal类是Dog类的父类,Dog类从Animal类继承
# 继承中方法的重写
# 当父类的方法不能满足子类的需求时,可以对方进行重写(override)
# 重写有两种,1.覆盖父类的方法。2.对父类的方法进行拓展
# 定义一个动物类
class Animal:
def eat(self):
print('吃')
def drink(self):
print('喝')
def run(self):
print('跑')
def sleep(self):
print('睡')
# 定义一个狗类,继承动物类
class Dog(Animal):
def bark(self):
print('汪汪叫')
#定义一个哮天犬类,继承狗类
class XiaoTianQuan(Dog):
def fly(self):
print("俺能飞")
# 哮天犬的犬吠与普通狗不同,可以利用方法重写,这里用覆盖的方法
def bark(self):
print('嗷嗷嗷')
# 哮天犬吃法与普通动物不同,这里用拓展的方法
def eat(self):
# 首先实现哮天犬独有吃饭方式
print('吃的跟神一样')
# 使用super().调用原本在父类中的方法
# super()是在Python中一个特殊的类,在重写方法时super().实际上是调用了一个super()对象,这个对象有父类中的方法和属性
# 在Python 2.x版本中没有super()这个特殊对象,如果想要重写,要调用“父类名.方法()”,不推荐这种方法!
super().eat()
xtq = XiaoTianQuan()
# 如果方法已经重写过,那么就不会调用父类的方法,直接执行子类的方法
xtq.bark()
# 经过拓展的.eat()方法
xtq.eat()
# 注意:子类方法不能调用父类的私有属性和方法,只能使用父类的方法间接调用。
多继承
# 多继承
# 多继承就是一个子类继承多个父类
class A:
def test(self):
print('A')
class B:
def demo(self):
print('B')
# 让C类继承A和B两个类
class C(A, B):
pass
# C就可以继承A和B两个类的属性和方法
c = C()
c.test()
c.demo()
'''
注意!
尽量避免多继承。
尽量不要让不同的父类有同名的方法。
如果有同名,则会使用先继承的那个类。
Python 中的 MRO(method resolution order)————方法搜索顺序,利用“print(子类名.__mro__)”语句来查看
搜索方法时是按照MRO的顺序来查找的,没找到时才会查找下一个类,找到后会直接执行,不再查找
'''
print(C.__mro__)
'''
新式类和旧式类
新式类以object为父类的类,推荐使用
旧式类不以object为父类,不推荐使用
在Python 3.x版本,如果没有指定父类,默认会使用object为父类,Python 2.x则不会。
新式类和旧式类在多继承时会影响方法的搜索顺序。
类属性
# 类属性和类方法 —— 类属性
'''
面向对象开发的第一步是设计类
使用类名()创建对象,创建对象的动作有两步
1.在内存中为对象分配空间
2.调用初始化方法__init__为对象初始化
对象创建后,内存中就有了一个对象的实实在在的存在————实例
对象叫做类的实例
创建对象的动作叫做实例化
每个对象都有自己独立的内存空间,保存各自不同的属性
多各对象的方法在内存中只有一份,在调用方法时,需要把对象的引用传递到方法内部
'''
'''
Python中一切皆对象,定义的类属于类对象,Object属于实例对象
程序运行时,类同样会被加载到内存
类对象在内存中只有一份,一个类可以创建很多个实例对象
除了分账实例的属性和方法外,类对象还可以拥有自己的属性和方法
'''
# 定义一个工具类,如果我们需要有多少个实例时,我们可以定义一个类属性来统计数量
class Tool (object):
# 直接理由赋值语句,定义类属性,创建工具实例的计数器
count = 0
def __init__(self, name):
self.name = name
# 每当实例创建的时候,一定会调用__init__,可以在初始化时完成实例的计数
Tool.count += 1
# 创建工具实例
tool1 = Tool('小刀')
tool2 = Tool('斧子')
tool3 = Tool('锤子')
# 查看工具类的实例个数
print(Tool.count)
'''
属性获取遵循向上查找机制,这个机制分为两步
1.首先会查找实例属性
2.没有对应的实例属性才会查找类属性
因此访问类属性有两种方式 1.类名.类属性 2.对象名.类属性 不推荐第二种方式
注意!!!
如果使用“对象名.类属性 = xxx”这样的赋值语句,是不会改变类属性的,只会创建一个实例属性(与前几节中讲到的语法一致)
'''
# 类属性和类方法 —— 类方法
'''
定义类方法与实例方法类似
不同的是
1.要在开头使用“@classmethod”修饰符
2.第一个参数使用“cls”
'''
class Tool (object):
count = 0
# 创建类方法,用来显示工具计数
@classmethod
def show_tool_count(cls):
print(f'当前工具箱内的工具数量为:{cls.count}')
def __init__(self, name):
self.name = name
Tool.count += 1
# 创建工具实例
tool1 = Tool('小刀')
tool2 = Tool('斧子')
tool3 = Tool('锤子')
# 调用类方法,查看工具类的实例个数
Tool.show_tool_count()
# 类属性和类方法 —— 静态方法
'''
如果封装在类中的一个方法,既不需要访问实例属性或实例方法,也不需要访问类属性和类方法
这样的方法就是静态方法
静态方法的定义也和实例方法类似,不同的是:
静态方法需要用修饰器@staticmethod来标识
'''
class Dog(object):
# 狗对象计数
dog_count = 0
# 定义一个静态方法
@staticmethod
def run():
#这个方法不需要访问任何属性和方法
print('狗在跑')
# 定义一个类方法,用来显示狗的数量
@classmethod
def count(cls):
print(f'一共有{cls.dog_count}只狗')
# 在实例初始化中完成狗的计数
def __init__(self):
Dog.dog_count +=1
# 创建实例
dog1 = Dog()
dog2 = Dog()
# 调用静态方法
Dog.run()
# 调用类方法
Dog.count()
单例
# 单例模式
'''
单例设计模式:让类创建的对象,在系统中只有唯一一个实例
每一次执行类名(),返回的对象内存地址都是相同的
'''
'''
__new__方法
使用类名()创建对象时,Python的解释器首先会调用__new__方法为对象分配空间
__new__是一个由object基类提供的内置静态方法,主要作用有两个:
1.在内存中为兑现分配空间
2.返回对象的引用
Pyhton解释器获得对象的引用后,将引用作为第一个参数传递给__init__
重写__new__方法的代码非常固定,必须用"return super().__new__(cls)"
__new__方法是一个静态方法,在调用时要主动传递cls参数
'''
# 定义一个音乐播放器的类
class MusicPlayer(object):
# 定义类属性,用来记录第一次创建对象的引用。
instance = None
# 定义__new__方法
@staticmethod
def __new__(cls, *args, **kwares):
# 1.判断类属性是否为空对象
if cls.instance is None:
# 2.调用父类方法,为对象分配空间
cls.instance = super().__new__(cls)
# 返回对象的引用
return cls.instance
# __init__方法接受__new__方法返回的引用
def __init__(self):
print('播放器初始化')
# 创建音乐播放器实例
player = MusicPlayer()
print(player)
player2 = MusicPlayer()
print(player2)
# 单例模式(拓展)
'''
在重写__new__后,创建新对象时不会重新分配内存空间,但__init__仍然会被重新调用。
如果希望初始化方法只在第一次创建对象时调用,则可以借鉴以下的例子
'''
'''
思路:
1.定义一个类属性init_flag,用来标记是否执行过初始化函数。
2.在初始化方法中,首先判断init_flag。如果已经执行过初始化,那么将不再执行下面的语句。
'''
# 定义一个音乐播放器的类
class MusicPlayer(object):
# 定义类属性,用来记录第一次创建对象的引用。
instance = None
# 定义类属性,标记是否进行过初始化
init_flag = False
# 定义__new__方法
@staticmethod
def __new__(cls, *args, **kwares):
# 1.判断类属性是否为空对象
if cls.instance is None:
# 2.调用父类方法,为对象分配空间
cls.instance = super().__new__(cls)
# 返回对象的引用
return cls.instance
# __init__方法接受__new__方法返回的引用
def __init__(self):
#首先判断是否进行过初始化,如果进行过,那么直接结束__init__
if MusicPlayer.init_flag:
return
print('初始化。。。')
# 进行初始化之后,修改标记
MusicPlayer.init_flag = True
# 创建音乐播放器实例
player = MusicPlayer()
print(player)
player2 = MusicPlayer()
print(player2)
异常
# 异常
'''
当解释器遇到错误,会停止程序,并提示一些错误信息,这就是异常
程序停止执行并且题是错误信息,这个动作称为:抛出(raise)异常
'''
'''
如果对某些代码的执行不能确定是否正确,可以利用try来捕获异常
'''
#########################最简单的形式#########################
# 不确定是否能正确执行的代码
try:
num = int(input('输入整数:'))
# 如果执行try下的程序出异常,则会执行except下的代码
except:
print('请输入正确的数值(整数)')
# 无论是否错误,都不影响下面代码的执行
print('#' * 50)
#########################已知的多种错误的形式#########################
'''
当解释器抛出异常时,最后一行错误信息的第一个单词就是错误类型
'''
try:
num = int(input('输入整数:'))
result = 7 / num
print(result)
# try中的输入不能是0
except ZeroDivisionError:
print('除0错误')
# try中的输入必须是整型
except ValueError:
print('请输入正确的数值(整数)')
#########################未知的错误的形式#########################
try:
num = int(input('请输入一个整数:'))
# 错误类型很难确定时,利用Exception关键字+as+发生错误的结果
except Exception as num:
print('未知错误 %s' % num)
# 异常
'''
常用的完整的异常捕获的完整语法
'''
try:
num = int(input('输入整数:'))
result = 7 / num
print(result)
# try中的输入必须是整型
except ValueError:
print('请输入正确的数值(整数)')
# 错误类型很难确定时,利用Exception关键字+as+发生错误的结果
except Exception as num:
print('未知错误 %s' % num)
# 只有try下方的代码没有异常时才会执行的代码
else:
print('尝试成功,没有发现异常')
# 无论是否发生异常都会执行的代码
finally:
print('无论是否有异常,都会执行的代码')
# try语法之后的代码
print('之后的代码。。。')
# 异常————异常的传递性
'''
在开发中,可以在主函数中增加异常捕获
这样在子函数中出现异常时,异常会被主程序捕获
这样的好处是无需再子函数中重复增加异常捕获,保持代码的整洁性
'''
def demo1():
return int(input('输入整数'))
def demo2():
return demo1
# 利用异常的传递性,在主程序中捕获异常
try:
print(demo2())
except Exception as result:
print('未知错误 %s' % result)
# 异常————抛出(rsise)异常
'''
捕获异常是在程序内部把异常消化
但在有些需求上,需要主动抛出异常
'''
'''
抛出异常分两步:
1.使用Exception创建异常对象
2.使用raise关键字抛出异常
'''
def input_password():
# 提示用户输入密码
pwd = input('请输入密码:')
# 判断密码长度>=8,返回密码
if len(pwd) >= 8:
return pwd
# 如果<8主动抛出密码
print('主动抛出异常')
# 创建异常对象 - 可以使用错误信息字符串作为参数
ex = Exception('密码长度不够')
# 抛出异常
raise ex
try:
pwd = input_password()
print(pwd)
except Exception as result:
print(result)
模块和包
# 模块
'''
每一个扩展名为.py的源代码文件都是一个模块
模块中定义的全局变量、函数、类都是提供给外界直接使用的工具
想要使用这些工具就必须先导入这个模块
'''
########### 下面做个示例 ##########
# 使用以下两种导入模式,在使用时需要用‘模块名.工具名’的形式
import xxx # 导入某个模块
import xxx as aaa # 导入xxx模块,起个别名aaa。在程序中就可以使用aaa当作xxx的别名
#使用以下两种导入模式,在使用时不需要用‘模块名.工具名’的形式,直接用工具名就好。
from xxxmoudle import xxx # 从xxxmoudle导入xxx工具
from xxx import * # 这个‘*’代表xxx模块的所有工具。不推荐使用这种方法,因为不便于检查重名模块
########### 利用__name__属性兼顾测试和导入 ##########
# 应在代码的最下方加入需要用于测试的代码。格式如下:
def main():
# .......
pass
if __name__ == '__main__':
main()
'''
使用__name__属性的原理:
在测试时__name__属性的值为:__main__
在导入后__name__的值则变为模块的名字了
所以测试是做一个判断就可以执行测试代码了
'''
# 模块和包
'''
包是一个特殊的目录,这个目录里必须有__init__.py文件
要让外界调用这个包,那么必须在__init__.py中指定对外界提供的模块列表
当程序调用一个包,那么这个程序就可以调用在__init__.py中提供的所有py文件
'''
################ 在__init__.py中指定模块列表的代码 ################
from . import xxx # 代码中的‘.’代表当前目录,‘xxx’代表希望对外界提供的模块
################ 制作python压缩包 ################
'''
如果希望将自己的包发布出去,让其他人能够使用这个包,就需要发布压缩包
其他人只需要下载这个压缩包并安装就可以使用你的包
下面是制作压缩包的步骤
'''
# 创建setup.py,文件的内容基本格式如下:
from distutils.core import setup
setup(
name='包名',
version='版本',
description='描述信息',
long_description='完整描述信息',
author='作者',
author_email='作者邮箱',
url='主页',
py_modules=['包名.模块1',
'包名.模块2']
)
# 在终端中(注意,不是在IDE中)执行下面的语句来构建模块:
python setpu.py build # 如果指定python是3.x版本 那么python应该改为python3
# 将构建的模块生成压缩包,在终端中执行下面的语句
python setpu.py sdist # 解释器会自动生成后缀是.tar.gz的压缩包。未知在当前文件夹/dist中