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笔记——面向对象的细节的主要内容,如果未能解决你的问题,请参考以下文章

Python面向对象编程——多态多态性鸭子类型

python笔记——面向对象的细节

python笔记——面向对象的细节

面向对象-鸭子类型

python 面向对象

Python进阶(十六)----面向对象之~封装,多态,鸭子模型,super原理(单继承原理,多继承原理)