面向对象的三大特征
1、封装:提高程序的安全性
- 将数据(属性)和行为(方法)包装到类对象中。在方法内部对属性进行操作,在类对象的外部调用方法。这样,无需关心方法内部的具体实现细节,从而隔离了复杂度。
- 在Python中没有专门的修饰符用于属性的私有,如果该属性不希望在类对象外部被访问,前边使用两个“_”。
2、继承:提高代码的复用性
3、多态:提高程序的可扩展性和可维护性
一、封装
一个类就是一个封装,将类的属性、变量、方法全部封装在类里面
代码示例:
'''封装''' class Car: def __init__(self,brand): self.brand=brand def start(self): print('汽车已启动....') car=Car('宝马X5') car.start() print(car.brand)
执行结果:
如果属性不想在类外部使用,则只需要在属性前加"__",示例代码如下:
#对象不想在类外部使用,在属性前使用__进行标记 class Student: def __init__(self,name,age): self.name=name self.__age=age def show(self): print(self.name,self.__age) stu=Student('张三',20) stu.show() #在类之外使用name和age print(stu.name) # print(stu.__age) #直接使用会报错AttributeError: 'Student' object has no attribute '__age' #print(dir(std)) print(stu._Student__age) #此种方法调用,能正确执行并返回
执行结果:
说明:__age可以在外部被使用,此种写法就是告诉开发者不要在外部使用该属性
二、继承及其实现方式
继承
- 语法格式
class 子类类名(父类1,父类2...):
pass
- 如果一个类没有继承任何类,则默认继承Object
- Python支持多继承
- 定义子类时,必须在其构造函数中调用父类的构造函数
代码示例:
'''继承''' class Person(): #父类 def __init__(self,name,age): self.name=name self.age=age def info(self): print(self.name,self.age) class Student(Person): #子类 def __init__(self,name,age,stu_no): super().__init__(name,age) self.stu_no=stu_no class Teacher(Person): #子类 def __init__(self,name,age,tea_no): super().__init__(name,age) self.tea_no=tea_no stu=Student('张三',20,'1001') teacher=Teacher('李四',30,10) stu.info() #info方法是从父类Person类继承过来的 teacher.info()
执行结果:
说明:stu调用的info()方法都是从父类中继承过来的
上面示例中的继承关系图如下:
- Python支持多继承
'''多继承''' class A(): pass class B(): pass class C(A,B): pass
上述代码的示意图:
说明:class C(A,B)表示C类既继承了A类,也继承了B类
三、方法重写
方法重写
- 如果子类对继承父类的某个属性或方法不满意,可以在子类中对齐(方法体)进行重新编写
- 子类重写后的方法中可以通过super().xxx()调用父类中被重写的方法
要输出学生的学生编号和教师的教龄,示例代码:
'''重写 重写父类的方法''' class Person(): #父类 def __init__(self,name,age): self.name=name self.age=age def info(self): print(self.name,self.age) class Student(Person): #子类 def __init__(self,name,age,stu_no): super().__init__(name,age) self.stu_no=stu_no def info(self): #重写了父类的info()方法 super().info() print(self.name+'的学生编号是:'+self.stu_no) class Teacher(Person): #子类 def __init__(self,name,age,teachofyear): super().__init__(name,age) self.teachofyear=teachofyear def info(self): #重写了父类的info()方法 super().info() print(self.name+'的教龄是:',self.teachofyear) stu=Student('张三',20,'1001') teacher=Teacher('李四',30,10) stu.info() print('-----------------------') teacher.info()
执行结果:
说明:重写是子类在自己的类中重新定义父类的方法,即在子类中定义和父类一样的方法名的方法,然后添加自己的业务代码,这段示例代码中,Student类和Teacher类重写了父类的info()方法,重写父类的方法时需要使用super().info()来对父类方法进行调用,学生编号和教龄是通过重写父类方法来输出的
四、object类
object类
- object类是所有类的父类,因此所有类都有object类的属性和方法。
- 内置函数dir()可以查看指定对象所有的属性
- object类有一个__str__()方法,用户返回一个对于“对象的描述”,对应于内置函数str()经常用于print()方法,帮我们查看对象的信息,所以我们经常会对__str__()进行重写
1. 查看object类的所有属性和方法
class Student: pass stu=Student() print(dir(stu))
执行结果:
说明:使用dir()方法可以查看Object类的所有属性和方法,stu是Student类的实例对象,Student类默认继承自Object类,所以执行dir(stu)会将object类的属性和方法全部查询出来,这些属性和方法都不是Student类里定义的
2. __str__()方法,用户返回一个对于“对象的描述”
class Student: pass stu=Student() print(stu)
执行结果:
说明:print(stu) 调用的是object类的 __str__()方法,输出的是stu这个对象的描述,包括对象类型和内存地址
我们可以通过重写__str__()方法,来输出自己的描述
'''重写__str__()方法来返回自己的描述''' class Student: def __init__(self,name,age): self.name=name self.age=age def __str__(self): #重写object类的__str__()方法 return '我的名字是{0},今年{1}岁'.format(self.name,self.age) stu=Student('张三',20) # print(dir(stu)) print(stu) print(type(stu))
执行结果:
说明:__str__()方法默认返回的是对象的描述,在Student类中重写了object的__str__()方法,返回了自己的描述
五、多态的实现
示例代码:
'''多态''' class Animal: def eat(self): print('动物会吃') class Dog(Animal): def eat(self): print('狗吃骨头....') class Cat(Animal): def eat(self): print('猫吃鱼') class Person: def eat(self): print('人吃五谷杂粮') #定义一个函数 def fun(obj): obj.eat() #开始调用函数 fun(Dog()) fun(Cat()) fun(Animal()) print('--------------') fun(Person())
执行结果:
说明:代码中Cat类和Dog类继承了Animal类,重写了Animal中的eat()方法,所以当输入Dog和Cat的对象时,会去执行Dog类和Cat类中的eat方法,但是Person类并没有继承Animal类,只是因为它有eat()方法,所以也会去执行Person类中的eat()方法,这就是和其它变成语言不一样的地方,这叫鸭子类型,Python是一门动态语言,可以动态地去绑定属性和绑定方法。
什么是动态语言
- 动态语言的多态崇尚“鸭子类型”,当看到一只鸟走起来像鸭子,游泳起来像鸭子,收起来像鸭子,那么这只鸟就可以被称为鸭子。在鸭子类型中,不需要关系对象是什么类型,到底是不是鸭子,只关心对象的行为。
静态语言和动态语言的区别:
静态语言实现多态的三个必要条件
- 继承
- 方法重写
- 父类引用指向子类对象
java是静态语言,实现多态必须满足继承、方法重写、父类引用指向子类对象
六、特殊属性和特殊方法
名称 | 描述 | |
特殊属性 | __dict__ | 获得类对象或示例对象所绑定的所有属性和方法的字典 |
特殊方法 | __len__() | 通过重写__len__()方法,让内置函数len()的参数可以是自定义类型 |
__add__() | 通过重写__add__()方法,可使用自定义对象具有“+”功能 | |
__new__() | 用于创建对象 | |
__init__() | 对创建的对象进行初始化 |
1. __add__()
'''__add__''' a=20 b=100 c=a+b #两个整数类型的对象的相加操作 print(c) d=a.__add__(b) print(d)
执行结果:
说明:当计算a+b时,底层其实执行的是a.__add__(b)
练习:使两个字符串相加
class Student: def __init__(self,name): self.name=name stu1=Student('张三') stu2=Student('李四') s=stu1+stu2 print(s)
执行结果:
说明:stu1和stu2是两个字符串,不能直接进行相加操作
如果要使两个字符串相加,则需要自己重新定义相加的方法
class Student: def __init__(self,name): self.name=name def __add__(self, other): return self.name+other.name stu1=Student('张三') stu2=Student('李四') s=stu1+stu2 #实现了两个对象的加法运算(因为在Student类中,编写__add()__特殊的方法) print(s) s1=stu1.__add__(stu2) print(s1)
执行结果:
2.__len()__方法
'''__len()__''' lst=[11,22,33,44] print(len(lst)) #len()是内置函数 print(lst.__len__())
执行结果:
说明:使用len()和__len()__方法的执行结果一样,说明特殊方法__len()__和内置函数len()是对应的
联系:输出字符串的长度
'''__len()__''' class Student: def __init__(self,name): self.name=name def __add__(self, other): return self.name+other.name def __len__(self): return len(self.name) stu1=Student('jack') print(len(stu1)) #会报错,提示TypeError: object of type 'Student' has no len() #如果想要计算string类型的长度,则需要自己的手动写len()方法
执行结果:
3. new和init方法
class Person(object): def __new__(cls, *args, **kwargs): print('__new__被调用执行了,cls的id值为{0}'.format(id(cls))) obj=super().__new__(cls) print('创建的对象的id为:{0}'.format(id(obj))) return obj def __init__(self,name,age): print('__init__被调用了,self的id值为:{0}'.format(id(self))) self.name = name self.age = age print('object这个类对象的id为:{0}'.format(id(object))) print('Person这个类对象的id为:{0}'.format(id(Person))) #创建Person类的实例对象 p1=Person('张三',20) print('p1这个Person类的实例对象的id:{0}'.format(id(p1)))
执行结果:
说明: