python笔记——面向对象的细节
Posted 联邦学习小白
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了python笔记——面向对象的细节相关的知识,希望对你有一定的参考价值。
众所周知,面向对象的三大特征就是封装、继承和多态,这是面向对象的语言都具备的特征,只是不同语言在实现上会有所不同。那么python是怎么实现这三个特征的呢?咱这就来唠唠。
封装
- 封装是为了提高程序的安全性
- 将数据(属性)和行为(方法)包装到类对象中。在方法内部对属性进行操作,在类对象的外部调用方法。从而在调用时无需关心方法内部的具体实现细节,从而隔离了复杂度。
- 在Python中没有专门的修饰符用于属性的私有,如果某一属性不希望在类对象外部被访问,可以在定义时往属性名前边加俩下划线。
- 私有属性一般只能通过类中定义的方法来获取,当然也可以进行强制获取(下面代码中有具体方法),那既然可以强制获取,封装又有什么意义呢?实际上这只能靠程序员的自觉,你可以访问,但最好不要。
class Student:
def __init__(self,name,age):
self.name = name
#设置私有属性
self.__age = age
def show(self):
#实例对象可以通过show()方法获得私有属性__age的值
print(self.name,self.__age)
stu1 = Student('张三',20)
stu1.show() #张三 20
#print(dir(stu1)) #通过dir()获取stu1的所有属性和方法,从而得到要获取私有属性的实际属性名
print(stu1._Student__age) #20
继承
- 一个类可以继承其父类的方法和属性,即便某些方法和属性是私有的也可以强制获取,只是不建议这么做,具体参考上一节
- 语法格式(python支持多继承)
class 子类类名(父类1,父类2...):
def __init__(参数1,参数2....)
父类1.__init__(参数)
父类2.__init__(参数)
....
self.属性名 = 参数
- 如果一个类没有继承任何类,则默认继承object
- 定义子类时,必须在其构造方法中调用父类的构造方法
#Person没有定义是继承哪个类,则默认继承object类(object可写可不写)
class Person(object):
def __init__(self,name,age):
self.name = name
self.age = age
def info(self):
print(self.name,self.age)
class Male(object):
def __init__(self,gender):
self.gender = gender
class Student(Person,Male):
def __init__(self,name,age,gender,stu_no):
# 定义子类时,必须在其构造方法中调用父类的构造方法
# 当一个类继承多个类时,就不能用super().__init__来调用父类构造方法了,得具体写出类名,并把括号去掉。
Person.__init__(self,name, age)
Male.__init__(self,gender)
self.stu_no = stu_no
stu = Student('Leo',23,'男',1501)
stu.info() #Leo 23 1501
print(stu.stu_no) #1501
print(stu.gender) #男
- 方法重写
如果子类对继承自父类的某个属性或方法不满意,可以在子类中对其(方法体)进行重写。
子类重写后的方法中可以通过super().xxx()调用父类中被重写的方法。
以上一个继承代码为例,Student所继承的info方法无法输出新定义的性别和学号,那就可以通过重写让其进行完整输出。
class Person(object):
def __init__(self,name,age):
self.name = name
self.age = age
def info(self):
print(self.name,self.age)
class Male(object):
def __init__(self,gender):
self.gender = gender
class Student(Person,Male):
def __init__(self,name,age,gender,stu_no):
Person.__init__(self,name, age)
Male.__init__(self,gender)
self.stu_no = stu_no
#对Person中的info方法进行重写
def info(self):
#还可以再调用父类中要重写的方法
Person.info(self)
print(self.stu_no,self.gender)
stu = Student('Leo',23,'男',1501)
stu.info() #Leo 23 1501 男
- object类
object类是所有类的父类,因此所有类都有object类的属性和方法。
内置函数dir()可以查看指定对象的所有属性。
object有一个__str__()方法,用于返回一个对于“对象的描述”,“对象的描述”默认为地址。print(实例对象)会默认调用__str__()帮我们查看对象信息,所以我们经常会对__str__()进行重写。下面用具体代码举例。
class Student:
def __init__(name,age):
self.name = name
self.age = age
#重写__str__()
def __str__(self):
print("我的名字是0,今年1岁了".format(self.name,self.age))
stu = Student('Leo',23)
#print(实例对象)会默认调用__str__()
print(stu) #我的名字是Leo,今年23岁了
多态
简单地说,多态就是“具有多种形态”,它指的是:即便不知道一个变量所引用的对象到底是什么类型,仍然可以通过这个变量调用方法,在运行过程中根据变量所引用对象的类型,动态决定调用哪个对象中的方法。
有人认为python是没有多态的概念,因为python的变量是没有数据类型的。实际上,虽然变量没有数据类型,但具备多态的特征。因为python是一门动态语言。
静态语言和动态语言关于多态的区别
- 静态语言(如java)实现多态有三个必要条件,分别是继承、父类重写、父类引用指向子类对象
- 动态语言(如python)的多态崇尚”鸭子类型“,比如当看到一只鸟走起来像鸭子、游起来像鸭子,那么这只鸟就可以被称为鸭子。在鸭子类型中,不需要关心对象是什么类型,到底是不是鸭子,只关心对象的行为。
class Animal(object):
def eat(self):
print("动物要吃东西")
class Dog(Animal):
def eat(self):
print('狗吃肉')
class Cat(Animal):
def eat(self):
print('猫吃鱼')
class Person(onject):
def eat(self):
print('人吃五谷杂粮')
def fun(obj):
obj.eat()
fun(Dog()) #狗吃肉
fun(Cat()) #猫吃鱼
fun(Person()) #人吃五谷杂粮
由上述代码演示可以看出,虽然Person类不是Animal的子类,但只要它有eat()方法,那函数fun()就可以和对待Dog()与Cat()一样,去调用Person类的eat()方法。
用”鸭子类型”来解释就是,函数fun()不管Person是不是Animal的子类,只关心Person是否有eat()这个方法。
这就是python作为一门动态语言在多态的实现上与其他语言的不同。
特殊属性和特殊方法
特殊属性和特殊方法指的是那些名字左右都有下划线的属性和方法,例如常见的特殊方法__init__()
名称 | 描述 |
---|---|
__dict__ | 获得类对象或实例对象所绑定的所有属性和方法的字典 |
__class__ | 获得实例对象所属的类 |
__bases__ | 获得类对象的所有父类,不包括默认继承的那些类,比如object |
__base__ | 获得类对象继承的第一个父类 |
__mro__ | 获得类对象层次结构的元组,包括类本身以及类的所有父类,甚至是默认继承的object等 |
class A:
pass
class B:
pass
class C(A,B):
def __init__(self,name,age):
self.name = name
self.age = age
x = C('Jack',20)
#获得实例对象的属性字典,没有方法字典是因为方法是在类对象中的,实例对象只能进行调用。
print(x.__dict__) #'name': 'Jack', 'age': 20
#获得类对象的属性和方法的字典
print(C.__dict__)
''''__module__': '__main__', '__init__': <function C.__init__ at 0x000002730ADB1AE8>, '__doc__': None'''
#输出实例对象所属的类
print(x.__class__) #<class '__main__.C'>
#输出类对象的父类类型的元组
print(C.__bases__) #(<class '__main__.A'>, <class '__main__.B'>)
#输出类对象继承的第一个父类
print(C.__base__) #<class '__main__.A'>
#获得类对象层次结构的元组,包括类本身以及类的所有父类,甚至是默认继承的object等
print(C.__mro__) #(<class '__main__.C'>, <class '__main__.A'>, <class '__main__.B'>, <class 'object'>)
#获得包含类对象所有子类的列表
print(A.__subclasses__()) #[<class '__main__.C'>]
名称 | 描述 |
---|---|
__len__( ) | 通过重写__len__( )方法,让内置函数len( )的参数可以是自定义类型 |
__add__( ) | 通过重写__add__( )方法,可使自定义对象具有“+”功能 |
__new__( ) | 用于创建对象 |
__init__( ) | 对创建的对象进行初始化 |
__subclasses__( ) | 获得包含类对象所有子类的列表 |
__len__( )和__add__( )
class Student:
def __init__(self,name):
self.name = name
#重写__len__()实现求对象的长度
def __len__(self):
return len(self.name)
#重写__add__(),实现两个对象的相加运算
def __add__(self,other):
return self.name+other.name
stu1 = Student('Leo')
stu2 = Student('Jack')
print(len(stu1)) #3
print(stu1+stu2) #LeoJack
__new__( )和 __init__( )是什么关系呢?结合下面的代码进行理解。
当执行语句 p1=Person(‘张三’,20) 时,将Person类对象传给cls,所以可以看到,Person类对象和cls的地址都是9360。
接着又把cls传给父类object去创建对象obj。
接下来又把对象obj传给self,所以self和obj的地址都是7104
当执行完初始化方法后,对象便创建完毕,此时把self返回给了p1,所以p1的地址和self、obj一样都是7104
总的来说就是,__new__( )在前去创建对象, __init__( )在后是为这个对象的实例属性进行赋值,最后将创建的对象返回给p1
以上是关于python笔记——面向对象的细节的主要内容,如果未能解决你的问题,请参考以下文章