【Python基础学习】第十五节 类与继承(类与继承,这一篇就足够了)

时间:2022-10-26 11:46:45

Python基础学习之类与继承

1. 面向对象 名词解释

  • 类(Class): 用来描述具有相同的属性和方法的对象的集合。类中定义了类对象所共有的属性和方法。对象是类的实例。
  • 方法:类中定义的函数,被称为:方法。
  • 属性:类中的类变量,我感觉都可以被叫做属性。
  • 类变量:类变量在整个实例化的对象中是公用的。类变量定义在类中且在函数体之外。类变量通常不作为实例变量使用。
  • 对象:对象是类的实例。对象包括两个数据成员(类变量和实例变量)和方法
  • 数据成员:类变量或者实例变量, 用于处理类 & 实例对象的数据。
  • 方法重写:父类中已经有了对应的方法,但是在继承时不满足子类的要求,此时可以对父类的方法进行改写,这个过程叫方法的覆盖(override),被称为方法的重写。
  • 局部变量:定义在类中方法内的变量,只作用于当前实例的类。
  • 实例变量:对于每个实例都独有的数据,就是实例变量;在类的声明中,属性是用变量来表示的。这种变量就称为实例变量,是在类声明的内部但是在类的其他成员方法之外声明的。
  • 继承:即一个派生类(derived class)继承基类(base class)的字段和方法。继承也允许把一个派生类的对象作为一个基类对象对待。
  • 实例化:创建一个类的实例,类的具体对象。

2. 类的详解

Python 中类的创建比较容易,先举一个例子,然后对其进行讲解;

2.1 类的举例:

#!/usr/bin/env python
# -*- coding: utf-8 -*-
# author:Zhang Kai time:2020/10/13

class food:
    '''
    水果类,用来统计水果的名字和数量;
    '''
    food_count = 0

    def __init__(self, name, qty):
        self.name = name
        self.qty = qty
        food.food_count += 1

    def print_name(self):
        print('我有%s个%s' % (self.qty, self.name))

    def print_food_count(self):
        print('我一共有%s种水果'%food.food_count)


apple = food('苹果',10)   # 创建实例对象
# 下面为实例属性的调用
print(apple.name)    # 输出:苹果

# 下面为类属性的调用
print(apple.food_count)   # 输出:1

# 以下两句为:类的方法调用
apple.print_name()    # 输出:我有10个苹果
apple.print_food_count()    # 输出:我一共有1种水果

pear = food('梨',5)   # 创建实例对象
pear.print_name()     # 输出:我有5个梨
pear.print_food_count()     # 输出:我一共有2种水果
apple.print_food_count()     # 输出:我一共有2种水果; 我们可以看到苹果对象的food_count也发生了变化;

说明:

  • food_count = 0;定义了一个类变量,这个变量在所有类的实例之间共享;在上面代码的最后一句apple.print_food_count()我们可以看到水果的种类已经变成了2;
  • def __init__(self, name, qty):;这是一个特殊的方法,被称为类的构造函数或初始化方法,在创建实例的时候,就会调用该方法;这一点非常非常的重要!
  • self: 在定义方法时,我们都首先传入了self 参数,这里的self 代表了所生成的实例本身;虽然在调用时不必传入相应的参数,但是 self 在定义类的方法时是必须传入的,这一点也非常重要!
  • 上面这个简单的例子中,几乎用到了所有类的相关知识,包括:类的定义,类的帮助文档,类的初始化,类的实例,类的属性调用,类的方法调用等;下面对其进行详细介绍;

2.2 创建方法:

使用关键字 class 声明一个类,class 后为类名,再加一个冒号(:)。类的定义语句缩进一个tab 然后书写即可:

class ClassName:
   '类的帮助信息'   # 类的说明文档
   <语句>   # 类中包含的内容,包括:类成员,方法,数据属性。

2.3 类的帮助文档

与函数一样,在类定义时,使用三个引号即可定义类的帮助文档,我们通过ClassName.__doc__ 的方法就可以调用它,例如上面例子中的:

class food:
    '''
    水果类,用来统计水果的名字和数量;
    ''' # 定义了帮助文档

print(food.__doc__)    # 输出:水果类,用来统计水果的名字和数量;

2.4 类的初始化

我们可以在类的初始化函数 classname.__init__中对类进行初始化;在创建实例的时候,都会先调用 init 函数;例如:

def __init__(self, name, qty):   # 这里传入的参数,需要再创建实例的时候传入;
	self.name = name   # 把传入的参数赋值给实例的对应属性;
	self.qty = qty   # 把传入的参数赋值给实例的对应属性;
	food.food_count += 1   # 修改类属性的值,这里为:每当新创建一个实例时,就将food_count 加1;

2.5 方法中的self

从上面的初始化函数,我们可以看到,在类中的方法(函数)定义时,必须首先传入一个 self (实际上,叫什么名字都行,只是人们都是用self, 类似于约定俗成); 从上面的例子我们可以看出,self 其实代表的就是即将生成的实例本身;

class Test:
    def prt(self):
        print(self)
        print(self.__class__)
        print(Test)


ob = Test()    
ob.prt()    

输出:

<__main__.Test object at 0x000002730B2C5BA8>
<class '__main__.Test'>
<class '__main__.Test'>

从上面的例子我们也可以看出,self 指向的是实例的地址;self.__class__ 指向Test 类;Test 类本身也指向Test 类;

2.6 类的实例创建

Python 中没有类创建的关键字,类的实例化类似函数调用方式。直接调用类,传入参数即可;例如:

apple = food('苹果',10)   # 创建实例对象
pear = food('梨',5)   # 创建实例对象

这样就创建了两个水果实例,是不是非常的简单?

2.7 属性的调用 & 修改

Python 中对于类的属性调用也非常简单,直接用.来调用即可;例如:

# 下面为实例属性的调用
print(apple.name)    # 输出:苹果

# 下面为类属性的调用
print(apple.food_count)   # 输出:1

我们还可以直接修改属性值,例如:

apple.qty=13   # 修改属性值为13
apple.print_name()   # 输出:我有13个苹果

除此之外,python还提供了一些函数,用来访问属性值:

  • getattr(obj, name[, default]) : 访问对象的属性。
  • hasattr(obj,name) : 检查是否存在一个属性。
  • setattr(obj,name,value) : 设置一个属性。如果属性不存在,会创建一个新属性。
  • delattr(obj, name) : 删除属性。
getattr(apple,'qty')   # 得到返回值 13
hasattr(apple,'qty')   # qty存在,得到返回值 True
setattr(apple,'qty',20)  # 修改数量为 20
delattr(apple,'qty')   # 删除属性值

备注:上面的这几个函数,千万注意这里的引号,对象名没有引号,属性名要加引号,切记!

2.8 类的方法创建 & 调用

Python类的方法创建:也比较容易,难的是灵活的应用;定义方法时,和定义普通函数基本一样,仅有的区别去下:

  1. 类的方法定义时,需要首先传入一个self参数;
  2. 类的方法可以调用类的属性和实例的属性;
    def print_name(self):
        print('我有%s个%s' % (self.qty, self.name))

    def print_food_count(self):
        print('我一共有%s种水果'%food.food_count)

Python 类的调用:直接用实例调用即可,例如上面例子中的代码:

pear.print_name()     # 输出:我有5个梨
pear.print_food_count()     # 输出:我一共有2种水果
apple.print_food_count()     # 输出:我一共有2种水果; 我们可以看到苹果对象的food_count也发生了变化;

2.9 类的内置属性

  • __dict__ : 返回类的属性(包含一个字典,由类的数据属性组成)
  • __doc__ :返回类的文档字符串
  • __name__: 返回类名
  • __module__: 返回类定义所在的模块
  • __bases__ : 返回类的所有父类构成元素(包含了一个由所有父类组成的元组)
print(food.__doc__)         # 输出:水果类,用来统计水果的名字和数量;
print(food.__name__)        # 输出:food
print(food.__module__)      # 输出:__main__
print(food.__bases__)       # 输出:(<class 'object'>,)

# 为了让输出好看一些,引入pprint模块;
import pprint
pprint.pprint(food.__dict__)

food.__dict__ 的输出:

mappingproxy({'__dict__': <attribute '__dict__' of 'food' objects>,
              '__doc__': '\n    水果类,用来统计水果的名字和数量;\n    ',
              '__init__': <function food.__init__ at 0x000001C0A2AA6730>,
              '__module__': '__main__',
              '__weakref__': <attribute '__weakref__' of 'food' objects>,
              'food_count': 2,
              'print_food_count': <function food.print_food_count at 0x000001C0A2AA6840>,
              'print_name': <function food.print_name at 0x000001C0A2AA67B8>})

2.10 实例的删除

一个实例的删除,会调用析构函数 __del__; 当对象不再被使用时,__del__方法运行: Python 的析构函数,不需要我们进行调用,程序自己会根据程序的运行自行调用;那么我们为什么要学析构函数呢? 因为,有时我们希望在实例被删除的时候做一些事情,比如本章所使用的例子中,我们希望水果实例被删除的时候,水果种类减1,这是我们就会用到析构函数,举例如下:

class food:
    '''
    水果类,用来统计水果的名字和数量;
    '''
    food_count = 0

    def __init__(self, name, qty):
        self.name = name
        self.qty = qty
        food.food_count += 1

    def print_name(self):
        print('我有%s个%s' % (self.qty, self.name))

    def print_food_count(self):
        print('我一共有%s种水果'%food.food_count)

    def __del__(self):    # 析构函数,应该加在类的创建中
        class_name = self.__class__.__name__
        food.food_count -= 1
        print(self.name, "没有了,我现在有%s种水果" %food.food_count)

# 创建两个实例
apple = food('苹果',10)
pear = food('梨',5)

pear.print_food_count()
# 删除一个实例
del apple
pear.print_food_count()

输出:

我一共有2种水果
苹果 没有了,我现在有1种水果
我一共有1种水果
梨 没有了,我现在有0种水果   # 这一条输出,就是python主动调用析构函数所产生的;

这里我们可以看到,在程序的最后,虽然我们没有删除 梨 这个实例,但是程序运行到最后Python会主动调取析构函数,把所有的实例释放,这就是python厉害的地方;因为别的语言,我们还需要手动调用一次析构函数;而python为我们做了这些工作;

2.11 私有属性 & 私有属性

2.10 中的类的例子,已经比较完整,但是我们发现,我们可以通过修改属性值修改水果的种类数;例如:

food.food_count = 20    # 修改类属性
pear.print_food_count()  # 输出:我一共有20种水果

这样任意修改类属性的值,有时并不是我们想要的结果;例如这里的水果种类,我们只希望在创建实例 & 删除实例的时候,对其进行修改,其他时候我们不可以修改;我们要如何实现呢? 这里就要用到 私有属性 & 私有方法;

2.11.1 私有属性

声明属性时,属性名以两个下划线开头,就定义了私有属性,私有属性不能在类的外部被使用;举例如下:

class food:
    '''
    水果类,用来统计水果的名字和数量;
    '''
    __food_count = 0

    def __init__(self, name, qty):
        self.name = name
        self.qty = qty
        food.__food_count += 1

    def print_name(self):
        print('我有%s个%s' % (self.qty, self.name))

    def print_food_count(self):
        print('我一共有%s种水果'%self.__food_count)

    def __del__(self):    # 析构函数,应该加在类的创建中
        class_name = self.__class__.__name__
        food.__food_count -= 1
        print(self.name, "没有了,我现在有%s种水果" %self.__food_count)

注意这里的几点变化:

  • 声明私有属性时,声明方法为:__food_count = 0,是两个下划线开头;
  • 私有属性的调用时,必须在类的内部!使用self.__food_count 来调用实例的私有属性,使用food.__food_count 来调用类的私有属性;这里有两个重点:1. 只能在类的内部调用;2. 调用时使用self.__food_count调用实例的私有属性 或者food.__food_count来调用类的私有属性;

访问私有属性:Python不允许实例化的类访问私有数据,但有另一种方法:可以使用 object._className__attrName( 对象名._类名__私有属性名 )访问属性,参考以下实例:

class food:
    '''
    水果类,用来统计水果的名字和数量;
    '''
    __food_count = 0

    def __init__(self, name, qty):
        self.name = name
        self.qty = qty
        food.__food_count += 1
        self.__print_name()
        self.__print_food_count()

    def __print_name(self):
        print('我有%s个%s' % (self.qty, self.name))

    def __print_food_count(self):
        print('我是%s,food类的水果数量为:%s'%(self.name,food.__food_count))

    def __del__(self):    # 析构函数,应该加在类的创建中
        class_name = self.__class__.__name__
        food.__food_count -= 1
        print(self.name, "没有了,我现在有%s种水果" %food.__food_count)
        self.__print_food_count()


apple = food('苹果',10)
print(apple._food__food_count)   # 访问了类的私有属性,输出:1

2.11.2 类属性 & 实例属性

上述私有属性定义完成后,我们已经不可以通过外部对其属性进行访问了,保护了数据的安全;但是,这时我们依然可以通过类的方法来修改该属性值,比如我们在类内部定义一个方法add_1来调用私有属性__food_count,就可以增加__food_count的数量,在此之前,我们先来明确一下 类属性 & 实例属性:

class food:
    '''
    水果类,用来统计水果的名字和数量;
    '''
    __food_count = 0

    def __init__(self, name, qty):
        self.name = name
        self.qty = qty
        food.__food_count += 1

    def print_name(self):
        print('我有%s个%s' % (self.qty, self.name))

    def print_food_count(self):
        print('我是%s,我一共有%s种水果,food类的水果数量为:%s'%(self.name,self.__food_count,food.__food_count))

    def __del__(self):    # 析构函数,应该加在类的创建中
        class_name = self.__class__.__name__
        food.__food_count -= 1
        print(self.name, "没有了,我现在有%s种水果" %food.__food_count)

    def add_1(self):
        food.__food_count +=1
        self.print_food_count()


apple = food('苹果',10)
pear = food('梨',5)
apple.add_1()    # 我是苹果,我一共有3种水果,food类的水果数量为:3  
apple.add_1()   # 我是苹果,我一共有4种水果,food类的水果数量为:4
apple.add_1()   # 我是苹果,我一共有5种水果,food类的水果数量为:5
apple.print_food_count()    # 我是苹果,我一共有5种水果,food类的水果数量为:5
pear.print_food_count()     # 我是梨,我一共有5种水果,food类的水果数量为:5
pear.add_1()                 # 我是梨,我一共有6种水果,food类的水果数量为:6

注意:在 initdel , add_1,三个方法中,对私有属性进行修改时,都使用 food.__food_count,因为类属性是所有实例共享的数据。在调用实例属性时,会优先调用实例属性的值,但是在上面的例子中,我们没有定义实例的属性值,这是python会帮我们调用类属性的值; 如果这里全部变更为实例属性 self.__food_count, 则会为每个实例创建实例的私有属性,类的私有属性不会改变,如下:

class food:
    '''
    水果类,用来统计水果的名字和数量;
    '''
    __food_count = 0

    def __init__(self, name, qty):
        self.name = name
        self.qty = qty
        self.__food_count += 1

    def print_name(self):
        print('我有%s个%s' % (self.qty, self.name))

    def print_food_count(self):
        print('我是%s,我一共有%s种水果,food类的水果数量为:%s'%(self.name,self.__food_count,food.__food_count))

    def __del__(self):    # 析构函数,应该加在类的创建中
        class_name = self.__class__.__name__
        self.__food_count -= 1
        print(self.name, "没有了,我现在有%s种水果" %food.__food_count)

    def add_1(self):
        self.__food_count +=1
        self.print_food_count()


apple = food('苹果',10)
pear = food('梨',5)
apple.add_1()    # 我是苹果,我一共有2种水果,food类的水果数量为:0
apple.add_1()   # 我是苹果,我一共有3种水果,food类的水果数量为:0
apple.add_1()   # 我是苹果,我一共有4种水果,food类的水果数量为:0
apple.print_food_count()    # 我是苹果,我一共有4种水果,food类的水果数量为:0
pear.print_food_count()     # 我是梨,我一共有1种水果,food类的水果数量为:0
pear.add_1()                 # 我是梨,我一共有2种水果,food类的水果数量为:0

从上例中我们可以看出,实例的属性self.__food_count 与 类属性 food.__food_count 是完全不一样的;在调用实例属性时,会优先调用实例属性的值self.__food_count,但是在上面的例子中,我们没有定义实例的属性值,这是python会帮我们调用类属性的值food.__food_count

上面这一部分主要说了类属性 & 实例属性;

2.11.3 私有方法

两个下划线开头,声明该方法为私有方法,不能在类的外部调用。在类的内部调用 self.__private_methods;举例如下:

class food:
    '''
    水果类,用来统计水果的名字和数量;
    '''
    __food_count = 0

    def __init__(self, name, qty):
        self.name = name
        self.qty = qty
        self.__food_count += 1

    def print_name(self):
        print('我有%s个%s' % (self.qty, self.name))

    def print_food_count(self):
        print('我是%s,我一共有%s种水果,food类的水果数量为:%s'%(self.name,self.__food_count,food.__food_count))

    def __del__(self):    # 析构函数,应该加在类的创建中
        class_name = self.__class__.__name__
        self.__food_count -= 1
        print(self.name, "没有了,我现在有%s种水果" %food.__food_count)

    def __add_1(self):    # 私有方法定义
        self.__food_count +=1
        self.print_food_count()


apple = food('苹果',10)
pear = food('梨',5)

apple.print_food_count()    # 我是苹果,我一共有4种水果,food类的水果数量为:0
pear.print_food_count()     # 我是梨,我一共有1种水果,food类的水果数量为:0

3. 类的继承

类的继承最大的好处就是代码的重用,通过继承创建的新类称为子类或派生类,被继承的类称为基类或父类。

3.1 继承的特点

1、如果在子类中需要父类的构造方法就需要显式的调用父类的构造方法,或者不重写父类的构造方法。 2、在调用基类的方法时,需要加上基类的类名前缀,且需要带上 self 参数变量。区别在于类中调用普通函数时并不需要带上 self 参数。 3. 调用子类方法时,Python 总是首先查找子类中的方法,如果找不到,它才开始到基类中逐个查找。 4. 如果在子类声明时,继承元组中列了一个以上的类,这就是多重继承。

3.2 继承实例

class Parent:  # 定义父类
    parentAttr = 100

    def __init__(self):
        print("调用父类构造函数")

    def parentMethod(self):
        print('调用父类方法')

    def setAttr(self, attr):
        Parent.parentAttr = attr

    def getAttr(self):
        print("父类属性 :", Parent.parentAttr)


class Child(Parent):  # 定义子类
    def __init__(self):
        print("调用子类构造方法")

    def childMethod(self):
        print('调用子类方法')


c = Child()  # 实例化子类
c.childMethod()  # 调用子类的方法
c.parentMethod()  # 调用父类方法
c.setAttr(200)  # 再次调用父类的方法 - 设置属性值
c.getAttr()  # 再次调用父类的方法 - 获取属性值

3.3 子类的初始化

情况一:子类需要自动调用父类的方法:子类不重写__init__()方法,实例化子类后,会自动调用父类的__init__()的方法。

情况二:子类不需要自动调用父类的方法:子类重写__init__()方法,实例化子类后,将不会自动调用父类的__init__()的方法。

情况三:子类重写__init__()方法又需要调用父类的方法:使用super关键词:super(子类,self).__init__(参数1,参数2,....)

class Child(Parent):
	def __init__(self, name):   
		super(Child, self).__init__(name)

举例:

class Father(object):
    def __init__(self, name):
        self.name = name
        print("name: %s" % (self.name))

    def getName(self):
        return 'Father ' + self.name


class Son(Father):
    def __init__(self, name,age):
        super(Son, self).__init__(name)
        print("hi")
        self.name = name
        self.age = age

    def getName(self):
        return 'Son:' + self.name + '\nage:' + self.age


if __name__ == '__main__':
    son = Son('runoob','18')
    print(son.getName())

输出:

name: runoob
hi
Son:runoob
age:18

3.4 继承判定函数

python还提供了继承判定的函数:

  • issubclass(sub,sup) 判断一个类sub是另一个类sup的子类或者子孙类,是则返回True;
  • isinstance(obj, Class) 判断 obj是Class类的实例对象或者是一个Class子类的实例对象,是则返回True。
print(issubclass(Parent, Child))    # False
print(issubclass(Child, Parent))    # True
print(isinstance(c, Child))    # True
print(isinstance(c, Parent))   # True

3.5 方法重写

调用子类方法时,Python 总是首先查找子类中的方法,如果找不到,它才开始到基类中逐个查找。

#!/usr/bin/python
# -*- coding: UTF-8 -*-
 
class Parent:        # 定义父类
   def myMethod(self):
      print '调用父类方法'
 
class Child(Parent): # 定义子类
   def myMethod(self):
      print '调用子类方法'
 
c = Child()          # 子类实例
c.myMethod()         # 子类调用重写方法