Python学习笔记(三)面向对象

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中
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值