欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

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中