浅谈 Python 中类的继承

天命之路

2020-04-01 12:51:44

Personal

前言: 继承是面向对象语言(比如 C++、python)中非常强大的功能,能大幅减少代码量,接下来,我们来了解一下 $Python$ 中类的继承。(不是 Py 党的可以先去看一下:[Python快速入门](https://www.runoob.com/python/python-tutorial.html),特别详细)~~(原谅一下这个小学生式开头)~~ ### 前置知识——类 就算你不是个 Py 党,你应该也知道类是个什么。简单概括就是,他是我们定义的,用来定义对象的新类型(有点绕),与此同时,用这个类定义变量的过程叫类的**实例化**。接下来,为了方便讲述,我们把用这个类定义出来的变量叫这个类的实例 首先看如何定义一个类: ```python class A: ``` 之后在下面就可以写对这个类的描述了(注意缩进) 接下来看如何定义一个类的属性。 因为类本质上是一个新类型,所以我们在类中的定义如果想作用到它的实例中去,我们定义的每个属性就必须针对那个实例(这或许是废话),但在类中,我们并不知道接下来要用这个类定义的实例叫什么,怎么针对呢? 其实,这就要到函数里去操作...... #### 论 $Python$ 类中的函数 与平常定义没啥区别,只不过当这个参数针对实例时,第一个参数所代表的是实例 ,这样你的函数才能对实例起作用。比如说: (我们为了叙述方便,暂且固定第一个参数名为 self) ```python class A: def vall(self,a): self.val=a #假设前文定义了个实例d #则 vall 可以这么用: d.vall(1) #把实例放在句点前 #不知道句点用法的可以去看开头的链接 # 其实也还可以这么用 A.vall(d,1) #或许这样更好理解 ``` 然后, $Python$ 类规定了, \_\_init\_\_() 为构造函数(两旁各有两个下划线),可以往里传参,如果该函数针对实例,第一个参数必为 self (在类外不用给它传参,类外从函数模板中第二个参数开始传参),像这样: ```python class A: def __init__(self,a,c): self.a=a self.b="name" self.c=c #ta是这样用的 d=A(1,2) #d为一个类的实例,1和2为两个参数,分别被赋给 self.a 和 self.c ``` 之后总结一下再类外怎么用类里的函数: 构造函数: 实例 = 类名(构造函数的各个参数) 普通的,针对对象的变量或函数: 实例.函数+(参数......)/变量名 注:这里的参数不包括指代实例的名字 或 类名.函数(参数......) 注:这里的参数包括实例本身。 (为什么针对实例的变量不能这样写呢?因为变量只有通过句点才能让编译器明白它针对实例) 不针对对象的变量或函数: 类名.函数+(参数......)/变量名 这里举一个类的例子: ```python class A: number=0 #定义不针对实例的变量 def __init__(self,name,age): #定义构造函数 self.name=name self.age=age A.number+=1 #在类中也要把 'A.' 带上,否则number 会被解释器作为局部变量(这是 python 古老的特性) def bark(self): #类中方法 print(self.name,self.age) dog=A("Tom",11) #构造函数在一开始自动被使用,参数在括号里传 A.bark(dog) #调用方法 print(A.number) #不针对实例的变量的调用 #输出 Tom 11 1 ``` ---- ### 接下来讲继承部分 继承,就是在一个类中沿用另一个类中的对象(变量、函数......等等等等,在类里的都是)。被继承的叫“父类”“基类”“超类”,继承的叫“子类”“派生类”。一个类可以有多个父类,父类和子类间必须为从属关系。 先来看一下继承的语法,非常简单: ```python class 子类名(父类名): ``` 就像刚刚所说的那样,子类沿用了父类的所有对象,如果子类中没有定义构造函数,将自动应用基类中的构造函数。同样,基类中已经实现的**非私有**(这个概念等会再说)的方法也可以被继承。 For example, ```python class A: #定义父类 def __init__(self,name): #父类构造函数 self.name=name self.num=1 class B(A): def numm(self): #子类独有的方法 print(self.num,self.name) C=B("name") #沿用父类的构造函数 C.numm() #输出 1 name ``` 子类中也可以引用父类中的变量: ```python class A: def __init__(self,name): self.name=name self.age=11 class B(A): def printf(self): print(self.name,self.age) #沿用父类中的 name 和 age c=B("ttt") #沿用父类构造函数 print(c.name) #沿用父类中的 name #输出 ttt ``` 上面讲的是子类没有构造函数的情况,那如果有构造函数呢? 如果直接定义 \_\_init\_\_() ,那就只是个简单的重写了,就没法通过父类的构造函数来初始化实例了。 所以,打构造函数时,要**先继承,再构造**,这样才能获取父类的变量和继承其构造方法。 举个例子: ```python class A: def __init__(self,name,age): #父类构造函数 self.name=name self.age=age def tell(self): print(self.name,self.age) class B(A): def __init__(self,name,age,val): A.__init__(self,name,age) #这一步为继承 self.val=val #这是子类独有的构造部分 ``` 从上面可以看到,继承父类构造函数的语法是: 父类.\_\_init\_\_(self,...) 当然,方法不止这一种,想知道其他方法的可以去搜**super**关键字。 那为什么这里运用上了类似普通类中函数的调用语法呢?因为这里的实例是已经在函数模板指定了名字(即第一个参数的名字),所以可以用上类似普通类中函数调用的语法,而不用担心未定义的问题(用这种语法也可以区别父类和子类的构造函数,让解释器不迷茫)。 子类对父类方法的重写: 在子类中,如果我们想重写父类中的方法,让其在子类中有其他的意义,那该怎么办呢? 直接在子类中写就好。 ```python class A: def __init__(self,name): #父类构造函数 self.name=name self.age=11 def tell(self): #等着被重写的方法 print("Hello!My name is %s."%self.name) class B(A): def __init__(self,name,age): #构造函数也被重写了 self.name=name self.age=age def tell(self): #重写 tell print("Hello!My name is %s and I am %d years old."%(self.name,self.age)) t1=A("ttt") t2=B("kkk",17) #两个实例 t1.tell() t2.tell() #输出 Hello!My name is ttt. Hello!My name is kkk and I am 17 years old. ``` 这里重写了构造函数和 tell 方法(当父类的构造函数对子类而言需求不大时可以不继承),所以要重写时直接写就好。 接下来给大家放一个类继承的例子: ```python class Person: #基类 def __init__(self,name,age,money): #基类构造函数 self.name=name self.age=age self.money=money def tell(self): #基类方法 print("I am %s,and I am %d year old."%(self.name,self.age)) def give_money(self,to,money): #同 tell ,基类方法 self.money-=money to.money+=money class teacher(Person): def __init__(self,name,age,money,stu=[]): Person.__init__(self,name,age,money) #继承基类的构造函数 self.students=stu #独有构造部分 def tell(self): #重构 tell 方法 print("I am teacher %s,and I am %d year old."%(self.name,self.age)) print("I have %d students."%len(self.students)) print("They are",end=' ') for i in self.students: #迭代学生 print(i.name+',',end='') print() print("And now I have ¥%d"%(self.money)) #沿用基类中的 money def give(self,money): #子类中独有的函数 t=money//len(self.students) self.money-=t*len(self.students) for i in self.students: i.money+=t class student(Person): def __init__(self,name,age,money,tt=None): Person.__init__(self,name,age,money) #继承构造函数 self.teacher=tt #子类独有构造部分 def get_teacher(self,teacherr): #子类对基类的拓展(即子类独有的方法) self.teacher=teacherr teacherr.students.append(self) def give_xuefei(self,to,money): self.give_money(to,money) #这里无需再次编写,直接引用父类中的方法 p1=teacher("Wu",34,20000) p2=student("Z",12,13000) p3=student("H",13,15000) #三个实例 p2.get_teacher(p1) p3.get_teacher(p1) #两个学生拜师 p2.give_xuefei(p1,5000) #p2 给学费 p1.tell() 打印 p1 的情况 #输出 I am teacher Wu,and I am 34 year old. I have 2 students. They are Z,H, And now I have ¥25000 ``` 类继承例子二:[AC自动机](https://www.luogu.com.cn/paste/nbv3d61k) 之后来说一下之前留下来的疑问:私有方法 在类中,方法(即函数)分为三种: 1、特殊方法:被 $Python$ 指定,拥有特殊作用的方法,拥有固定的作用,比如 \_\_init\_\_ 为构造函数。重载运算符也靠此类方法(在例子二中有体现)。 2、私有方法:这些方法只能在类中被访问,在类外不能被访问,也不能被继承,在子类中用父类的私有方法会报错,定义方法为: def \_\_函数名(各种参数) (左侧为两个下划线),调用的时候也要有这两个下划线 (根据他人的提醒和自己的试验,私有方法之所以私有,是因为他的名字在访问时**被突然改变了**,而这种改变是有规律的,找到规律即可访问私有方法。这进一步地说明,python中没有什么是绝对私有的) 3、普通的公开方法,可以在类外调用,可以被继承。 类中变量的分类与此相似。(私有变量同私有方法一样,可以通过[可变类型的引用](https://blog.csdn.net/doris2016/article/details/82462841)改变其值,这里不多说) #### 完结 本人才疏学浅,请大家批判性阅读,有补充或疑问请在下方评论。(文中代码经过本人不负责任的测试,正确,输出也像文中说的那样) --- 又附:update 记录: ``` 2020.5.6 修改了“指代实例”的表述方法 2020.5.7 加了类继承例子二 2020.6.18 修改了“指代实例”的表述方法 2020.8.4 修改了私有方法的相关表述 2020.12.15 修改了私有方法的相关表述 ```