类(二)继承(父类方法重载/重写)、多态、封装

Python 类的三大特性 :继承、多态、封装

一、继承

Python 是多继承机制(和 C++ 一样),即一个子类可以同时拥有多个直接父类
子类继承父类时在定义子类时,将父类(可以是多个)放在子类之后的圆括号里即可

继承是相对子类来说的,即子类继承自父类
派生是相对父类来说的,即父类派生出子类

class 类名(父类1, 父类2, ...):
    类定义部分

如果没有显式指定继承哪个类,默认继承 object 类(object 类是 Python 中所有类的父类,即要么是直接父类,要么是间接父类)

class P:
    p_name='ppp'
    def __init__(self,name):
        self.name=name
    def eat(self):
    	#self.own='own'
        return "fish"
    def drink(self,drp):
        self.drp='drp'
    	return 'drp_water'
    

class E(P):
    pass

class E1(P):
    def __init__(self):
        self.dr='dr'
  

class E2(P):
	def drink(self):
		self.dr='water'
		return 'water'
		
class D(P):
    d_name='ddd'
    def __init__(self,age):
        self.age=age

e=E('zhangshan')
e1=E1()
e2=E2('lisi')
p=P('parent')
d=D('dog')

print(dir(p))
print(dir(e))
print(dir(e1))


print(dir(e2))
print(e2.drink())
print(dir(e2))

print(dir(p))
print(p.drink())
print(dir(p))
print(dir(e2))

print(dir(p))
print(dir(P))
print(dir(d))
print(d.name)



运行结果(所有输出省略了内置的属性和方法)
['__class__', '__delattr__', '__dict__', '__dir__', '__doc__', '__eq__', 
'__format__', '__ge__', '__getattribute__', '__gt__', '__hash__', '__init__', 
'__init_subclass__', '__le__', '__lt__', '__module__', '__ne__', '__new__', 
'__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', 
'__str__', '__subclasshook__', '__weakref__']

['drink', 'eat', 'name', 'p_name']                p
['drink','eat', 'name', 'p_name']                 e
['dr','drink', 'eat', 'p_name']                   e1

['drink', 'eat', 'name', 'p_name']                e2调用drink前
water
['dr', 'drink', 'eat', 'name', 'p_name']          e2调用drink后

['drink', 'eat', 'name', 'p_name']                p调用drink前
water_drp
['drink', 'drp', 'eat', 'name', 'p_name']         p调用drink后

['dr', 'drink', 'eat', 'name', 'p_name']          e2
['drink', 'drp', 'eat', 'name', 'p_name']         p
['drink', 'eat', 'p_name']                 		  P(大写)
['age','d_name', 'drink', 'eat', 'p_name']        d
Exception has occurred: AttributeError
'D' object has no attribute 'name'   


注意区分dir(object) dir(class)的区别
dir(object)函数:查看某个对象拥有的所有的属性和方法,该函数会返回一个
包含有所有属性名和方法名的有序列表 
dir(class)类只能得到类的方法 

dir() 函数的内部实现是在调用参数对象 __dir__() 方法的基础上
对该方法返回的属性名和方法名做了排序。所以,除了使用 dir() 函数
我们完全可以自行调用该对象具有的 __dir__() 方法 只是输出的顺序不不同
p=P('clangs)
print(clangs.__dir__())

同时dir()函数,不仅仅输出本类中新添加的属性名和方法还会输出从父类(这里为 object 类)继承得到的属性名和方法名
如果父类不是object,而是我们自己写的一个类则是我们现在讨论的情况
除了内置的全部继承以外 我们自己写的不一定全都会继承



子类会继承父类p的所有类变量p_name  因此e e1 e2 d d1都有p_name

关于子类是否会继承父类的实例变量:
1、初始构造方法中的实例变量:
  1.1若子类e1,重写了构造函数 子类e1不会继承父类初始构造函数中定义的实例变量name 
     而是创建自己出事构造方法中的实例变量dr  参见e1的输出
     同样
     子类d继承父类p,且子类新定义了自己的实例属性age 子类对象d没有继承到父类的name属性
     只有自己的age实例属性 但是父类的方法eat还是继承下来了的,出现该问题的原因是 
     子类写了和父类同名(只看函数名是否相同 不看参数列表)的方法__init__,
     子类会覆盖/重写父类的方法

	 注意弱类型的语言这里只看函数名 即使参数个数不一样 只要函数名一样都会覆盖
	 在Python弱类型中类成员在__init__中通过赋值来定义的(赋值即定义)
	 
     若子类重写了构造方法仍想要继承父类出事构造方法中的实例变量就是我们接下来要讨论的
     两种实现子类继承父类构造方法的手段
  1.2若子类e e2没有重写父类构造函数,子类e,e2会继承父类初始构造函数中定义的实例变量name
  	 参见e e2 的所有输出都有name 
2、普通方法中的实例变量
  2.1即使在不存在继承关系时类p的普通方法drink中定义的实例变量drp
     也只有在类p的类对象调用该方法后该实例变量才会被创建drp
     且该实例变量只作用于调用它的对象p 参见p、e2调用drink前后的输出
  2.21)若子类e2重写了父类普通方法drink 
    子类e2不会继承父类父类普通方法drink中定义的实例变量drp
    不管是父类p的对象在调用drink之前还是之后 参见e2在p调用drink前后之后e2的输出没变都没有drp
    这是因为类p的实例变量drp只影响当前这个类P的一个实例p不会影响该类其它类对象(包括P的子类对象) 
    
    但若是子类e2的实例对象调用了drink(这个drink是自己的,父类那个drink被覆盖)之后
    e2对象会有自己的dr 但是依然不会有drp 若不调用dr也不会有
    在覆盖了父类方法之后子类仍想要访问该父类方法也是可以的 在本文稍后会提到
    
    (2)若子类没有重写父类的普通方法eat 
    子类会继承该方法 可以看到所有子类都由eat输出
    但是子类在没有调用该父类的普通方法之前是不会继承该方法中的实例变量own的
    参见若我们去掉p类中eat内的注释引入own实例变量 只有p在eat之后所有p的输出中会多一个own
    其余都不会有own输出 
    但若子类调用该没变重写的普通方法 这之后子类的实例对象就会有这个own了
    
    一般来说被重写的普通方法是中实例变量的继承没多少意义 因为普通方法一般是进行功能性开发 
    子类若重写该普通方法说明功能需求变化继承下来也没啥用 不如直接在子类中定义自己的实例变量
    
   
     
总结 
继承是说子类继承了父类的所有的类变量(实例变量是否继承看情况)和方法(所有方法)
至于方法重写了以后则方法覆盖父类的方法,自热也就不会继承该方法内的实例属性
若仍要想实现重写方法内实例变量的继承有如下两种方法

若在子类中还要继续在父类中被覆盖/重写的方法采用如下方法
方法一:
在子类重写的方法的参数中加入父类的参数
并在子类重写方法中用 父类.父类同名方法(self,父类参数列表)来实现父类被覆盖方法的继承

class P:
    def __init__(self,name):
        self.name=name
    def eat(self):
        return "fish"

class D1(P):
    d1_name='d1d1d1'
    def __init__(self,name,age):
        self.age=age
        P.__init__(self,name)

d1=D1('dog',5)
print(dir(d1))
print(d1.name,d1.age)

运行结果
['age', 'd1_name', 'drink', 'eat', 'name', 'p_name']
dog 5


首先 在子类重写的方法的参数中加入父类的参数name
并在子类重写方法中通过 父类.父类同名方法(父类参数列表)来实现父被覆盖方法的继承

可以通过‘.’运算符访问类中的变量 会因为python中类的方法和属性默认是类似于c c++中的public
具体见本文最后封装相关内容

方法二:super()
子类重写函数参数列表添加父类函数的参数列表
并在子类重写方法中用 super().父类同名方法(父类参数列表)来实现父类被覆盖方法的继承

区别第一种方法 采用super()时不用在参数列表中写self

class P:
    def __init__(self,name):
        self.name=name
    def eat(self):
        return "fish"

class D1(P):
    def __init__(self,name,age):
        self.age=age
        super().__init__(name)
       
d1=D1('dog',5)
print(dir(d1))
print(d1.name,d1.age)

运行结果
['age', 'd1_name', 'eat', 'name', 'p_name']
dog 5

更推荐采用super()实现 但是只适合单继承super(),多继承不适合

实例

关于是否同名覆盖
class Person:
	def __init__(self,name,age):
		self.name=name
		self.age=age
	
	def get_name(name):
		return self.name
	
	def get_age(age):
		return self.age

class Student:
	def __init__(self,school,name,age):
		self.school=school
		super().__init__(name,age)
	def grade(self,n):
		pring("{0}'s grade is {1}".format()self.name,str(n))

     
stu1=Student("BUPT","xiaoming",27)
stu1.grade(99)
print(stu1.get_name())

Person和Student的__init__方法会出现覆盖问题 参数个数一个是3个 一个是2个
还是封装了一下成员属性

如果想要在子类中调用父类中被重写的方法采用如下办法

class Bird:
    #鸟有翅膀
    def isWing(self):
        print("鸟有翅膀")
    #鸟会飞
    def fly(self):
        print("鸟会飞")
class Ostrich(Bird):
    # 重写Bird类的fly()方法
    def fly(self):
        print("鸵鸟不会飞")
        
# 创建Ostrich对象
ostrich = Ostrich()

#调用 Ostrich 类中重写的 fly() 类方法
ostrich.fly()

#调用 Bird 类中的 fly() 方法
Bird.fly(ostrich)

需要注意的一点是,使用类名调用其类方法Python 不会为该方法的第一个 self 参数自定绑定值
因此采用这种调用方法,需要手动为 self 参数赋值。

尽量少用或者不用多继承
Python 是一门支持多继承的面向对象编程语言,如果子类继承的多个父类中包含同名的类实例方法,则子类对象在调用该方法时,会优先选择排在最前面的父类中的实例方法,显然构造方法也是如此。
多继承传送门(http://c.biancheng.net/view/2290.html)

二、多态

python语言作为弱类型语言,天然具有多态特性

def add(x,y):
	return x+y

我们并没有指定x y的对象类型,但只要x,y引用的对象可以可执行+运算就可以 这就是天然多态性
add(3+4)
add('adc','def')
add([1,2],[3,4,5])

最明显的特征是在使用变量时,无需为其指定具体的数据类型,这会导致一种情况,即同一变量可能会被先后赋值不同的类对象,但这并不是多态

类的多态特性,还要满足以下 2 个前提条件:
继承:多态一定是发生在子类和父类之间;
重写:子类重写了父类的方法。

class CLanguage:
    def say(self):
        print("调用的是 Clanguage 类的say方法")
class CPython(CLanguage):
    def say(self):
        print("调用的是 CPython 类的say方法")
class CLinux(CLanguage):
    def say(self):
        print("调用的是 CLinux 类的say方法")
a = CLanguage()
a.say()
a = CPython()
a.say()
a = CLinux()
a.say()

运行结果
调用的是 Clanguage 类的say方法
调用的是 CPython 类的say方法
调用的是 CLinux 类的say方法

CPython 和 CLinux 都继承自 CLanguage 类,且各自都重写了父类的 say() 方法
从运行结果可以看出,同一变量 a 在执行同一个 say() 方法时,由于 a 实际表示不同的类实例对象
因此 a.say() 调用的并不是同一个类中的 say() 方法,这就是多态。

多态进阶

class WhoSay:
    def say(self,who):
        who.say()
class CLanguage:
    def say(self):
        print("调用的是 Clanguage 类的say方法")
class CPython(CLanguage):
    def say(self):
        print("调用的是 CPython 类的say方法")
class CLinux(CLanguage):
    def say(self):
        print("调用的是 CLinux 类的say方法")
a = WhoSay()

#调用 CLanguage 类的 say() 方法
a.say(CLanguage())     

#调用 CPython 类的 say() 方法
a.say(CPython())

#调用 CLinux 类的 say() 方法
a.say(CLinux())


运行结果
调用的是 Clanguage 类的say方法
调用的是 CPython 类的say方法
调用的是 CLinux 类的say方法

以a.say(CLanguage()) 为例
这行代码进入WhoSay类执行def say(self,who):
        				who.say()
传入的是CLanguage(),因此调用 CLanguage 类的 say() 方法

通过给 WhoSay 类中的 say() 函数添加一个 who 参数,其内部利用传入的 who 调用 say() 方法
这意味着,当调用 WhoSay 类中的 say() 方法时,
我们传给 who 参数的是哪个类的实例对象,它就会调用那个类中的 say() 方法

三、封装

和其它面向对象的编程语言(如 C++、Java)不同,Python 类中的变量和函数,不是公有的(类似 public 属性),就是私有的(类似 private),这 2 种属性的区别如下:
public:公有属性的类变量和类函数,在类的外部、类内部以及子类中都可访问
private:私有属性的类变量和类函数,只能在本类内部使用,类的外部以及子类都无法使用

Python 并没有提供 public、private ,为了实现类的封装,Python 采取了下面的方法:
默认情况下,Python 类中的变量和方法都是公有(public)的,它们的名称前没有下划线(_)因此python中类的实例对象可以直接通过‘.'操作访问类的实例属性(也可以是类属性) 但是不建议 还是封装一下比较好 封装的办法是
如果类中的变量和函数,其名称以双下划线“__”开头,则该变量(函数)为私有变量(私有函数),其属性等同于 private。

关于python 类中的3中属性和方法的定义和访问参见博文:
博文传送门(https://blog.csdn.net/Wjf7496/article/details/109645865)

除此之外,还可以定义以单下划线“_”开头的类属性或者类方法(例如 _name、_display(self)),这种类属性和类方法通常被视为私有属性和私有方法,虽然它们也能通过类对象正常访问,但这是一种约定俗称的用法,初学者一定要遵守。
注意,Python 类中还有以双下划线开头和结尾的类方法(例如类的构造函数_init_(self)),这些都是 Python 内部定义的,用于 Python 内部调用。我们自己定义类属性或者类方法时,不要使用这种格式。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值