-
和其他编程相比,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))