Python学习笔记(十五):python 中的面向对象


  • 和其他编程相比,python 在尽可能不增加新的语法和语义的情况下加入了类机制;

  • python 中的类提供了面向对象编程的所有基本功能:封装,继承,多态;


类的定义:

python 中定义类的语法如下:

class 类名:
    属性列表
    方法列表
# 定义一个类:以 class 关键字开始,后跟类名,然后以冒号结尾
class Cat:

    # 属性
    name = ""
    age = 0

    # 方法(self 后面再讲)
    def eat(self):
        print("===== 猫吃鱼")

 

类的对象:

类表示一类事物,比如 猫,是一类生物的统称;

而对象是类的实例,表示一个具体的事物,比如 汤姆猫,就是一个具体的生物;

类的实例化,意思就是创建一个类的对象;

python 中实例化一个对象的语法如下:

对象名 = 类名()

注意:在 python 中,没有 new 这个关键字,所以创建对象的时候,直接写类名即可;例如:

# 定义一个类:以 class 关键字开始,后跟类名,然后以冒号结尾
class Cat:
    pass    # pass 是一个空语句,用于保持程序结构的完整性

# 实例化一个对象
tom = Cat()
print(tom)

输出结果:

 

类属性 和 对象属性:

在类中直接定义的属性,叫做类属性;

通过对象定义的属性,叫做对象属性(对象属性也可以叫做实例属性);

# 定义一个类
class Cat:
    # 类属性
    name = "汤姆猫"

# 输出类属性的值:通过 类名.属性名 调用;
# 类属性和对象没有关系,不管实例化多少对象,
# 也不管在对象中怎么操作类属性,类属性及其值都是固定不变的
print("类属性 name:", Cat.name)

# 实例化一个类的对象
cat = Cat()

# 输出对象属性的值:通过 对象名.属性名 调用;
# 如果对象属性名称和类属性名称一致,并且对象属性并没有设置新的值时,
# 对象属性的值 就是类属性的值;
print("对象属性 name:", cat.name)

print("==================")

# 设置对象属性:
# 即使对象属性名称和类属性名称一样,为对象属性设置新值时,也不会改变类属性的值;
cat.name = "蓝猫"
# 为对象添加新的属性,该属性只在当前对象中有用;类或者别的对象,都无法操作该属性
cat.age = 22

print("对象属性 name:", cat.name) 	# 输出的是为对象属性设置的新值 	 
print("对象属性 age:", cat.age)		# age 属性只有当前 cat 对象能够访问

print("==================")

# 再次输出类属性的值:可以发现,虽然对象属性 name 设置了新值,但是类属性 name 的值依然不变
print("类属性 name:", Cat.name)

输出结果:

总结:调用类属性的时候,最好通过 类名.属性名 调用;而调用对象属性的时候,只能通过 对象名.属性名 调用;

 

类方法、对象方法 和 静态方法:

类方法需要用 @classmethod 进行修饰,并且需要声明一个参数,用于接收类本身;

对象方法不需要用注解进行修饰,对象方法和普通方法的唯一区别,就是对象方法必须有一个参数,用于接收对象;而且该参数必须放在第一位;对象方法也可以叫做实例方法;

静态方法需要用 @staticmethod 进行修饰,该方法可以没有参数;

# 定义一个类
class Cat:

    # 类属性
    name = "大脸猫"

    # 对象方法:
    # 对象方法和普通的函数有一个区别:就是对象方法必须有一个额外的第一个参数,通常用 self 表示;
    # self 不是 python 的关键字,把它换成其他任意一个字符串都可以;
    # self 参数用于接收 类的对象,而 self.__class__ 则指向类本身;
    # 由于 self 相当于对象,那么 self.name 获取的就是对象属性的值,而不是类属性的值;
    def sleep(self):
        print(self)
        print(self.__class__)
        print(self.name + "爱睡觉")

    # 类方法:必须用 @classmethod 注解进行修饰;
    # 类方法和对象方法一样,也必须有一个额外的第一个参数,通常用 cls 表示;
    # cls 也不是 python 的关键字,把它换成其他任意字符串也可以;
    # cls 参数用于接收类的本身,那么 cls.name 获取的就是类属性的值;
    # 注意:类方法中不能获取到对象的属性;
    @classmethod
    def eat(cls):
        print(cls)
        print(cls.name + "爱吃鱼")

    # 静态方法:必须用 @staticmethod 进行修饰,其他用法和普通函数一样;
    @staticmethod
    def catch():
        print("猫会抓老鼠")

# 实例化一个对象
tom = Cat()
tom.name = "汤姆猫"    # 设置对象属性的值

print(tom)  # 直接输出对象

# 通过对象调用类中的对象方法:
# 相当于 tom.sleep(tom),即将 tom 对象传入到对象方法中;
# 此时对象方法中的 self 参数接收到的实参就是 tom 对象;
# 通过对象调用对象方法,不需要手动传入对象参数,因为已经知道了是哪个对象调用的;
tom.sleep()

# 如果通过类名去调用对象方法,必须传入一个对象参数,
# 因为对象方法中的 self 参数不知道接收的是哪个对象,所以需要手动指定对象;
Cat.sleep(tom)

print("=========================")

print(Cat)  # 直接输出类

# 通过类名调用类方法:
# 相当于 Cat.eat(Cat),即将 Cat 类本身传入到类方法中,
# 此时类方法中的 cls 参数接收到的实参就是 Cat 类本身;
Cat.eat()
tom.eat()   # 类方法也可以通过对象来调用

print("=========================")

# 调用静态方法,就当成一个普通方法去调用即可;
# 静态方法通过类名或者对象去调用都可以;
Cat.catch()
tom.catch()

输出结果:

 

初始化方法 __init__():

初始化方法用于初始化类的对象;

# 定义一个类
class Cat:

    # 初始化方法,在类实例化的时候,会自动调用,用于初始化对象
    # 默认的初始化方法,只有一个 self 参数,用于接收对象
    def __init__(self):
        print("调用了默认的初始化方法")

    # 多参数的初始化方法
    def __init__(self, name, age):
        print("调用了三个参数的初始化方法")
        # 初始化对象属性
        self.name = name
        self.age = age

    # 对象方法
    def eat(self):
        print("%s爱吃鱼" %self.name)

# 实例化类:调用三个参数的初始化方法,并将参数传入到初始化方法中;
tom = Cat("汤姆猫", 33)
print("%s的年龄是%d" %(tom.name, tom.age))
tom.eat()   

# 一个类可以有多个对象
lanmao = Cat("蓝猫", 22)
print("%s的年龄是%d" %(lanmao.name, lanmao.age))
lanmao.eat()

 

类中的 __str__() 方法:

如下代码,输出一个对象的名字:

# 定义一个类
class Cat:
    pass	# pass 是空语句,只是为了保持程序结构的完整性

# 创建一个对象
tom = Cat()
# 输出对象
print(tom)

输出结果为:

输出的信息表示 Cat 类的对象(object),以及在内存中的地址;

如果我们想在输出对象的时候,输出我们自己想要的内容,就可以使用 __str__() 方法实现:

# 定义一个类
class Cat:

    # 注意:__str__() 方法必须要有返回值
    def __str__(self):
        return "自定义输出信息"

# 创建一个对象
tom = Cat()
# 此时输出对象时,输出的就是 __str__() 方法返回的数据
print(tom)

 

类中的 __del__() 方法:

当对象的引用计数归 0 的时候调用(即对象被删除或释放的时候调用);

# 定义一个类
class Cat:

    # 当对象的引用计数归 0 的时候调用(即对象被删除或释放的时候调用)
    def __del__(self):
        print("===== over =====")

# 创建一个对象,tom1 是该对象的引用,即 tom1 指向该对象
tom1 = Cat()

# tom2 指向 tom1 所表示的对象,即 tom2 和 tom1 是同一个对象的引用
tom2 = tom1

del tom1    # 删除 tom1 引用,tom2 不受影响
print("****** del *****")

输出结果:

可以看到,先输出的是 del,后输出的是 over;这是因为 Cat 类在实例化对象以后,有两个引用,tom1 和 tom2,虽然删除了一个 tom1,但是 tom2 不受影响,所以对象还存在;最后程序在运行结束的时候,所占用的内存要全部释放,此时才会调用 __del__() 方法;

如果在程序运行结束前,就把对象的引用全部删除,就会先调用 __del__() 方法了,如下所示:

# 定义一个类
class Cat:

    # 当对象的引用计数归 0 的时候调用(即对象被删除或释放的时候调用)
    def __del__(self):
        print("===== over =====")

# 创建一个对象,tom1 是该对象的引用,即 tom1 指向该对象
tom1 = Cat()

# tom2 指向 tom1 所表示的对象,即 tom2 和 tom1 是同一个对象的引用
tom2 = tom1

del tom1    # 删除 tom1 引用,tom2 不受影响
del tom2    # 删除 tom2 引用,此时对象的引用计数归 0,对象被释放,调用 __del__() 方法
print("****** del *****")

输出结果:

获取对象的引用个数:

import sys	# 导入模块

# 定义一个类
class Cat:

    # 当对象的引用计数归 0 的时候调用(即对象被删除或释放的时候调用)
    def __del__(self):
        print("===== over =====")

# 创建一个对象,tom1 是该对象的引用,即 tom1 指向该对象
tom1 = Cat()

# 获取对象引用的个数(要比实际引用个数多1)
print("a....", sys.getrefcount(tom1))

tom2 = tom1 # tom2 指向 tom1 所表示的对象,即 tom2 和 tom1 是同一个对象的引用

print("b....", sys.getrefcount(tom1))

del tom2    # 删除 tom2 引用,tom1 不受影响

print("c....", sys.getrefcount(tom1))

del tom1    # 删除 tom1 引用,此时对象的引用计数归 0,对象被释放,调用 __del__() 方法

print("d....", sys.getrefcount(tom1))   # tom1 删除后,再获取 tom1 的引用个数会报错

print("****** del *****")

输出结果:

 

私有属性 和 私有方法:

私有属性:

# 定义一个类
class Cat:

    # 初始化方法:
    # 会覆盖掉默认的初始化方法(默认的初始化方法只有一个 self 参数),
    # 即在实例化对象的时候,不能不传参数了,必须传入两个参数
    def __init__(self, name, age):
        # 普通的对象属性
        self.name = name
        # 以 __ 开头的属性称为私有属性
        self.__age = age

    # 获取私有属性
    def getAge(self):
        return self.__age

    # 设置私有属性的值
    def setAge(self, age):
        if (age < 0):
            print("动物年龄不能为负数")
        elif (age > 100):
            print("你想成精啊。。")
        else:
            self.__age = age

# 创建对象,并为对象属性设置初始值
tom = Cat("汤姆猫", 10)

# 获取对象属性的值
print("name = ", tom.name)  		# 普通属性可以直接通过对象调用
print("getAge():", tom.getAge()) 	# 通过方法获取私有属性的值
print("age = ", tom.__age)  		# 私有属性不能直接通过对象调用

通过对象调用私有属性会报如下错误:AttributeError: 'Cat' object has no attribute '__age'

私有方法:

# 定义一个类
class Cat:

    # 以 __ 开头的方法称为私有方法
    def __catchMouse(self):
        print("你已经长大了,可以抓老鼠了")

    # 普通方法
    def checkAge(self, age):
        if (age < 0):
            print("不可以负生长哦")
        elif (age > 100):
            print("你已经成精了,不需要抓老鼠了")
        elif (age > 10):
            self.__catchMouse()
        else:
            print("你还小,打不过老鼠")

# 创建对象
tom = Cat()
tom.checkAge(33)    # 通过对象调用普通方法
tom.__catchMouse()  # 对象不能直接调用私有方法,否则报错

输出结果:

 

继承:

# 定义一个父类 Animal
class Animal:
    # 父类中的方法
    def eat(self):
        print("==== 吃饭 ====")
    def sleep(self):
        print("==== 睡觉 ====")

# 定义一个子类 Cat,继承父类 Animal;
# 只需在子类后面加一个小括号,括号中写上父类即可;
class Cat(Animal):
    def catch(self):
        print("==== 抓老鼠 ===")

# 定义一个子类 Dog,继承父类 Animal;
class Dog(Animal):
    def bite(self):
        print("==== 咬人 ====")

# 实例化类的对象
tom = Cat()
tom.eat()       # 子类对象可以直接调用父类中的方法
tom.sleep()
tom.catch()

wangcai = Dog()
wangcai.eat()
wangcai.sleep()
wangcai.bite()

 

私有属性 和 私有方法 不能继承:

# 定义一个父类
class Animal:
    def __init__(self, name, age):
        self.name = name    # 公有属性
        self.__age = age    # 私有属性

    # 公有方法
    def eat(self):
        print("==== 吃饭 ====")

    # 私有方法
    def __sleep(self):
        print("==== 睡觉 ====")

    # 父类的公有方法,可以访问父类的私有属性和私有方法
    def test1(self):
        print(self.__age)
        self.__sleep()

# 定义一个子类继承父类
class Cat(Animal):
    # 子类的公有方法,不能够访问父类中的私有属性和私有方法
    def test2(self):
        print(self.__age)
        self.__sleep()

# 实例化类的对象
tom = Cat("汤姆猫", 22)

print("name = ", tom.name)  # 公有属性会被继承
# print("age = ", tom.__age)  # 私有属性不会被继承(此句会报错)

tom.eat()       # 公有方法会被继承
# tom.__sleep()   # 私有方法不会被继承(此句会报错)

tom.test1() 	# 此句可成功
tom.test2()		# (此句会报错)

 

多继承:

# python 中的 object 是所有类的基类,不管你写不写,都会继承于它
class Base(object):
    def test(self):
        print("======= base")

# 定义一个子类 A,继承于 Base
class A(Base):
    def test1(self):
        print("====== test1")

# 定义一个子类 B,继承于 Base
class B(Base):
    def test2(self):
        print("======= test2")

# 多继承:一个子类继承多个父类,多个父类用逗号分隔
# C 既会继承 A 的所有公有属性和公有方法,
# 也会继承 B 的所有公有属性和公有方法;
class C(A, B):
    pass

# 实例化类对象
c = C()
c.test()    # 子类对象调用父类的父类的方法
c.test1()   # 子类对象调用父类 A 的方法
c.test2()   # 子类对象调用父类 B 的方法

问题:如果多继承时,多个父类中有同名的方法,那么子类调用的是哪一个父类中的方法???

关于子类调用父类中方法的顺序有一个算法,叫做 C3算法,如果按照该算法表示的顺序搜索到了指定的方法,那么就停止搜索;

有一个方法可以查看搜索的顺序,该方法为:类名.__mro__(),如下代码所示:

# 基类中有一个 test 方法
class Base(object):
    def test(self):
        print("======= Base")

# 父类 A 中也有一个 test 方法 
class A(Base):
    def test(self):
        print("====== A")

# 父类 B 中也有一个 test 方法
class B(Base):
    def test(self):
        print("======= B")

# 多继承:一个子类继承多个父类,多个父类用逗号分隔
class C(A, B):
    pass

# 实例化类对象
c = C()
c.test()    # 那么子类调用的是哪个父类的 test 方法??

# 输出类 C 调用 test 方法时的搜索顺序
print(C.__mro__)

输出结果:

 

重写:


# 定义一个父类 Animal
class Animal:
    def eat(self):
        print("==== 吃饭 ====")

# 定义一个子类 Cat,继承父类 Animal;
class Cat(Animal):
    # 重写父类的方法,会覆盖掉父类的方法
    def eat(self):
        print("==== 猫吃鱼 ===")

# 定义一个子类 Dog,继承父类 Animal;
class Dog(Animal):
    def eat(self):
        print("==== 狗吃肉 ===")

        # 在重写的方法中调用父类的同名方法:方法一
        Animal.eat(self)	# 类名.方法名(self),self 参数必须要有
        # 在重写的方法中调用父类的同名方法:方法二
        super().eat()		# 不需要传 self 方法

# 实例化类的对象
tom = Cat()
tom.eat()

print("**************")

dog = Dog()
dog.eat()

 

多态:

简单点理解就是,定义一个方法的时候,并不知道调用的是哪个类中的方法;只有在执行该方法的时候,通过传入的对象,来自动识别调用的是哪个类中的方法,如下代码所示:

# 定义一个基类
class Animal(object):
    def eat(self):
        print("======= 吃饭")

# 定义一个 Cat 类,继承于 Animal 类
class Cat(Animal):
    def eat(self):
        print("====== 猫吃鱼")

# 定义一个 Dog 类,继承于 Animal 类
class Dog(Animal):
    def eat(self):
        print("====== 狗吃肉")

# 定义一个方法,有一个用于接收对象的参数
def eat(temp):
    # 因为传入的对象不确定,所以具体调用哪个类中的 eat 方法,现在并不知道
    temp.eat()

# 实例化对象
lanmao = Cat()
wangcai = Dog()

# 调用普通的 eat 方法,根据传入的对象不同,调用不同类中的方法
eat(lanmao)
eat(wangcai)

 

对象的创建方法 __new__():

# 定义一个类
class Cat(object):

    # __new__() 方法用于对象的创建,但并不是调用当前类自己的 __new__() 方法创建对象,
    # 而是调用其父类的 __new__() 方法创建子类的对象;
    # 在父类的 __new__() 方法被调用的时候,子类对象还没有被创建,对象的创建是在该方法中完成的;
    # 在子类对象创建成功之后,__new__() 方法会返回对象的引用,然后再调用 __init__() 方法,
    # 将对象的引用传入进去,对对象进行初始化操作;
    # __new__() 方法必须声明一个额外的第一个参数,通常用 cls 表示,用于接收需要创建对象的类;
    # 如果在子类中重写了 __new__() 方法,就不能调用父类的该方法了,也就无法创建对象了;
    def __new__(cls):
        print("===== new")

    # 初始化方法,用于初始化对象,对象创建成功之后调用
    def __init__(self):
        print("===== init")

# 实例化对象:
# 因为子类重写了 __new__() 方法,所有无法创建对象了,
# 也就不能调用 __init__() 方法初始化对象了
tom = Cat()

# 因为对象创建失败,所有输出为 None
print(tom)

输出结果:

如果想在子类中重写 __new__() 方法,又想创建子类对象,可以在子类中显示调用父类的方法,如下所示:

# 定义一个类
class Cat(object):

    # 创建对象方法
    def __new__(cls):
        print("===== new")
        # 显示调用父类的 __new__() 方法,并将子类通过 cls 参数传入,
        # 对象创建之后,返回对象的引用;
        return super().__new__(cls)

    # 初始化方法
    def __init__(self):
        print("===== init")

# 实例化对象:
tom = Cat()
# 输出对象
print(tom)

输出结果:

 

创建单例对象:

创建单例对象就是只创建一个对象,如果后面再需要对象的时候,不再创建新的,而是把之前创建好的对象拿出来用;

因为创建对象的方法是 __new__() 方法,所以可以重写该方法,实现创建单例对象:

# 定义一个类
class Cat(object):

    # 私有属性:用来存储实例对象
    __instance = None

    # 重写父类的 创建对象方法
    def __new__(cls):
        # 如果实例对象不存在,则创建
        if cls.__instance == None:
            # 显示调用父类的方法创建对象
            cls.__instance = super().__new__(cls)
        # 返回之前创建好的实例对象
        return cls.__instance

# 实例化对象
cat1 = Cat()
print(id(cat1))     # 两个对象的地址一样,说明是一个对象

cat2 = Cat()
print(id(cat2))

 

  • 2
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值