python基础08-面向对象2:继承、属性、方法

面向对象2:继承、属性、方法

一、私有权限

1. 面向对象的特性

  1. 面向对象的的三大特性:封装、继承、多态
  2. 面向对象的封装特性:
    • 将属性和方法放到一起封装成一个整体,然后通过实例化对象来处理
    • 对类的属性和方法增加访问权限控制

2. 私有属性

  • 如果在属性名前面加了2个下划线’__’,则表明该属性是私有属性,否则为公有属性
  • 私有属性只能在类的内部访问
"""
私有属性:
    1. __(2个下划线)开头的属性,就是私有属性
    2. 只能在本类的内部访问,在类的外面无法直接访问
"""
class Dog(object):
    # 添加属性
    def __init__(self):
        self.__baby_count = 0   # 私有属性,以__(2个下划线)开头的属性
        self.age = 1    # 公有属性

    def print_info(self):
        print(self.__baby_count)


# 类的外部
# 创建对象
dog1 = Dog()
# print(dog1.__baby_count) # err, 私有属性,在类的外面无法直接访问
print(dog1.age)

dog1.print_info()

3. 私有方法

  • 私有方法和私有属性类似,在方法名前面加了2个下划线’__’,则表明该方法是私有方法
  • 私有方法只能在类内部使用
"""
私有方法:
    1. __(2个下划线)开头的方法,就是私有方法
    2. 只能在本类的内部访问,在类的外面无法直接访问
    3. 在类的内部调用实例方法的语法格式:self.方法名()
"""


class Dog(object):
    def __init__(self):
        self.__baby_count = 0 # 私有属性,以__(2个下划线)开头的属性
        self.age = 1

    def print_info(self):
        print(self.__baby_count)
        self.__leave()

    # 定义一个私有方法
    def __leave(self):
        print('休产假了')


dog1 = Dog()
dog1.print_info()
# AttributeError: 'Dog' object has no attribute '__leave'
# dog1.__leave() # err, 外部不能访问私有方法

二、继承的作用

1. 继承的作用

  • 继承:子类直接具备父类的能力(属性和方法)
  • 作用:解决代码重用问题,提高开发效率

2. 继承的语法格式

继承的语法格式:

class 子类名(父类名):
    pass

示例代码:

# 定义一个父类
class Father(object):
    # 添加一个属性, money
    def __init__(self):
        self.money = 9999999

    def print_info(self):
        print(self.money)


# 定义一个子类,继承与Father
class Son(Father):
    pass


# 子类创建对象
s = Son()
print(s.money)  # 子类私用继承过来的属性
s.print_info()  # 子类使用继承过来的方法

注意:

  • 子类对象调用方法有一个就近原则
    • 如果本类能找到方法,直接调用本类的方法
    • 如果本类找不到,则调用父类继承过来的方法

三、单继承和多层继承

1. 单继承

  • 单继承:子类只继承一个父类
# 定义一个父类, Animal
class Animal(object):
    def eat(self):
        print('吃东西')


# 定义一个子类,只有一个父类
class Dog(Animal):
    pass


# 创建一个子类对象
dog1 = Dog()
dog1.eat()

2. 多层继承

  • 多层继承:继承关系为多层传递,如生活中的爷爷、父亲、儿子
# 定义一个爷爷类, Animal
class Animal(object):
    def eat(self):
        print('吃东西')


# 定义一个父亲类
class Dog(Animal):
    def drink(self):
        print('喝东西')


# 定义一个儿子类
class SuperDog(Dog):
    pass


# 创建对象
sd = SuperDog()
sd.eat()
sd.drink()

四、重写父类方法

1. 子类重写父类同名方法

  • 父类的方法不能满足子类的需要,可以对父类的方法重写,重写父类方法的目的是为了给他扩展功能
  • 在子类中定义了一个和父类同名的方法(参数也一样),即为对父类的方法重写
  • 子类调用同名方法,默认只会调用子类的

示例代码:

# 定义一个父类, Animal
class Animal(object):
    # 添加一个type属性
    def __init__(self):
        print('Animal类中的__init__')
        self.type = '动物'

    # 设计一个方法,打印属性
    def print_type(self):
        print('Animal类中的print_type = ', self.type)


# 定义一个子类,继承与Animal
class Dog(Animal):
    # __init__和父类的同名,重写父类同名方法
    def __init__(self):
        print('Dog类中的__init__')
        self.type = '可爱的小狗'

    # print_type和父类的同名,重写父类同名方法
    def print_type(self):
        print('Dog类中的print_type = ', self.type)


# 定义一个子类对象
dog1 = Dog()  # 调用子类的__init__
dog1.print_type()  # 调用子类的print_type()

运行结果:

Dog类中的__init__
Dog类中的print_type =  可爱的小狗

2. 子类调用父类同名方法

  • 子类调用父类同名方法:
    1. 父类名.同名方法(self, 形参1, ……)
    2. super(子类名, self).同名方法(形参1, ……)
    3. super().同名方法(形参1, ……):是方法 2 的简写,推荐的写法

示例代码:

# 定义一个父类, Animal
class Animal(object):
    # 添加一个type属性
    def __init__(self):
        print('Animal类中的__init__')
        self.type = '动物'

    # 设计一个方法,打印属性
    def print_type(self):
        print('Animal类中的print_type = ', self.type)


# 定义一个子类,继承与Animal
class Dog(Animal):
    # __init__和父类的同名,重写父类同名方法
    def __init__(self):
        print('Dog类中的__init__')
        self.type = '可爱的小狗'

    # print_type和父类的同名,重写父类同名方法
    def print_type(self):
        print('Dog类中的print_type = ', self.type)
        print('='*20)

        # 调用父类同名函数
        # 方法1: 父类名.同名方法(self, 形参1, ……)
        Animal.__init__(self)
        Animal.print_type(self)
        print('=' * 20)

        # 方法2:super(子类名, self).同名方法(形参1, ……)
        super(Dog, self).__init__()
        super(Dog, self).print_type()
        print('=' * 20)

        # 方法3:super().同名方法(形参1, ……) # 是 4.2 方法的简写
        # 推荐使用的方法
        super().__init__()
        super().print_type()


# 定义一个子类对象
dog1 = Dog()  # 调用子类的__init__
dog1.print_type()  # 调用子类的print_type()

运行结果:

Dog类中的__init__
Dog类中的print_type =  可爱的小狗
====================
Animal类中的__init__
Animal类中的print_type =  动物
====================
Animal类中的__init__
Animal类中的print_type =  动物
====================
Animal类中的__init__
Animal类中的print_type =  动物

五、多继承

1. 多继承

  • 所谓多继承,即子类有多个父类,并且具有它们的特征。

  • 多继承的语法格式:

    class 子类名(父类1, 父类2, ……):
        pass

示例代码:

# 定义2个类,它们没有继承关系,是平级的
class SmallDog(object):
    def eat(self):
        print('吃小东西')


# 再定义一个类
class BigDog(object):
    def drink(self):
        print('大口喝水')


# 定义一个子类,多继承于上面2个父类
class SuperDog(SmallDog, BigDog):
    pass


# 定义子类对象,调用方法
sd = SuperDog()
sd.eat()
sd.drink()

运行结果:

吃小东西
大口喝水

2. 类的继承顺序

  • 查看类的继承顺序:类名.__mro__

示例代码:

# 定义2个类,它们没有继承关系,是平级的
class SmallDog(object):
    def eat(self):
        print('吃小东西')


# 再定义一个类
class BigDog(object):
    def drink(self):
        print('大口喝水')


# 定义一个子类,多继承于上面2个父类
class SuperDog(SmallDog, BigDog):
    pass


# 查看类的继承顺序
print(SuperDog.__mro__)

运行结果:

(<class '__main__.SuperDog'>, <class '__main__.SmallDog'>, <class '__main__.BigDog'>, <class 'object'>)

3. 调用父类同名方法

3.1 默认调用情况

  • 如果继承过来的2个父类的方法同名,默认调用先继承父类的同名方法
# 定义2个类,它们没有继承关系,是平级的
class SmallDog(object):
    def eat(self):
        print('吃小东西')


# 再定义一个类
class BigDog(object):
    def eat(self):
        print('啃大骨头')


# 定义一个子类,多继承于上面2个父类
class SuperDog(SmallDog, BigDog):
    pass


# 定义子类对象,调用方法
sd = SuperDog()
sd.eat()  # 默认先调用先继承的父类,即 SmallDog

运行结果:

吃小东西

3.2 子类调用父类同名方法

  • 子类调用父类同名方法:
    1. 父类名.同名方法(self, 形参1, ……):调用指定的父类
    2. super(类名, self).同名方法(形参1, ……):调用继承顺序中类名的下一个类的同名方法
    3. super().同名方法(形参1, ……):调用先继承父类的同名方法

示例代码:

# 定义2个类,它们没有继承关系,是平级的
class SmallDog(object):
    def eat(self):
        print('吃小东西')


# 再定义一个类
class BigDog(object):
    def eat(self):
        print('啃大骨头')


# 定义一个子类,多继承于上面2个父类
class SuperDog(SmallDog, BigDog):
    def eat(self):
        print('吃蟠桃')
        print('='*20)

        # 子类调用父类同名方法:
        # 1. 父类名.同名方法(self, 形参1, ……)
        SmallDog.eat(self)  # 调用SmallDog的eat()
        print('=' * 20)

        # 2. super(类名, self).同名方法(形参1, ……):调用继承顺序中类名的下一个类的同名方法
        # 继承顺序中,SmallDog的下一个类是BigDog,所以,调用BigDog的eat()
        super(SmallDog, self).eat()
        print('=' * 20)

        # 3. super().同名方法(形参1, ……) :调用先继承父类的同名方法
        super().eat()


# 定义子类对象,调用方法
sd = SuperDog()
sd.eat()

运行结果:

吃蟠桃
====================
吃小东西
====================
啃大骨头
====================
吃小东西

六、私有和继承

1. 私有和继承

  • 父类中的私有方法、属性不能直接继承使用
  • 可以通过调用继承的父类的共有方法,间接的访问父类的私有方法、属性
# 定义一个父类, Animal
class Animal(object):
    # 添加一个type属性
    def __init__(self):
        self.__type = '动物'  # 私有

    def __leave(self):  # 私有
        print('休产假3个月')

    # 通过公有方法,间接访问私有元素
    def use_private(self):
        print(self.__type)
        self.__leave()


# 定义一个子类
class Dog(Animal):
    def test(self):
        # print(self.__type) # err,私有不能直接继承使用
        # self.__leave() # err,私有不能直接继承使用
        pass


# 创建子类对象
dog1 = Dog()
dog1.use_private()

七、多态

1. 多态

  • 多态:多种形态,调用同一个函数,不同表现
  • 因为Python是动态语言,站在用户的角度,本身就是多态,不存在非多态的情况
  • 实现多态的步骤:
    1. 实现继承关系
    2. 子类重写父类方法
    3. 通过对象调用该方法

示例代码:

"""
1. 多态:多种形态,调用同一个函数,不同表现

2. 实现多态的步骤:
  1. 实现继承关系
  2. 子类重写父类方法
  3. 通过对象调用该方法

"""


# 定义一个父类, Animal
class Animal(object):
    def eat(self):
        print('吃东西')


# 定义一个子类Dog,继承于Animal
class Dog(Animal):
    def eat(self):
        """重写父类方法"""
        print('啃骨头')


# 定义一个子类Cat,继承于Animal
class Cat(Animal):
    def eat(self):
        """重写父类方法"""
        print('吃小鱼')


# 定义一个函数,用于测试多态
def func(temp):
    temp.eat()


# 创建子类对象
d = Dog()
c = Cat()

# 调用同一个函数,不同表现
# 传递d参数,调用Dog的eat()
# 传递c参数,调用Cat的eat()
func(d)  # 啃骨头
func(c)  # 吃小鱼

八、实例属性、类属性

1. 实例属性和类属性

1.1 专业名词说明

  • 在Python中 “万物皆对象”
  • 通过类创建的对象 又称为 实例对象对象属性 又称为 实例属性
  • 类本身也是一个对象,执行class语句时会被创建,称为 类对象,为了和实例对象区分开来,我们习惯叫类

1.2 实例属性

  • 通过在__init__方法里面给实例对象添加的属性

  • 在类的外面,直接通过实例对象添加的属性

  • 实例属性必须通过实例对象才能访问

    # 定义类
    class 类名(object):
        def __init__(self):
            self.实例属性变量1 = 数值1
            self.实例属性变量2 = 数值3
    
    # 创建实例对象
    实例对象名 = 类名()
    
    # 添加属性
    实例对象名.实例属性变量3 = 数值3

1.3 类属性

  • 类属性就是 类对象 所拥有的属性,它被 该类的所有实例对象 所共有

  • 定义在类里面,类方法外面的变量就是类属性

  • 类属性可以使用 类名实例对象 访问,推荐使用类名访问

    # 定义类
    class 类名(object):
        类属性变量 = 数值1
    
        def __init__(self):
            pass

1.4 示例代码

class Dog(object):
    # 类属性
    count = 0

    def __init__(self):
        # 实例属性
        self.name = '大黄狗'


# 类属性同构类名访问,格式:类名.类属性名字
print(Dog.count)

# 创建实例对象
dog1 = Dog()
print(dog1.count)  # 实例对象访问类属性
print(dog1.name)  # 实例属性

运行结果:

0
0
大黄狗

2. 类属性和实例属性的区别

  • 类属性就是 类对象 所拥有的属性,它被 该类的所有实例对象 所共有
  • 实例属性 要求 每个对象 为其 单独开辟一份内存空间 ,只属于某个实例对象的

示例代码:

"""
1. 定义一个类属性count,用于记录实例对象初始化的次数
2. __init__添加实例属性name,每初始化1次,类属性count加1
"""

class Dog(object):
    # 类属性
    count = 0

    def __init__(self, _name):
        # 实例属性
        self.name = _name

        # 每初始化一次,类属性数量加1
        Dog.count += 1


# 打印类属性的值
print(Dog.count)

# 创建1个对象
d1 = Dog('旺财')
# 打印:实例属性,类属性
print(d1.name, Dog.count)

d2 = Dog('旺钱')
print(d2.name, Dog.count)

d3 = Dog('旺仔')
print(d3.name, Dog.count)

# 通过实例对象,访问类属性
print(d1.count, d2.count, d3.count)

运行结果:

0
旺财 1
旺钱 2
旺仔 3
3 3 3

3. 注意点

3.1 修改类属性

  • 类属性只能通过类对象修改,不能通过实例对象修改
# 类属性修改,只能通过类名修改,不能通过对象名修改

class Dog(object):
    # 类属性
    count = 0

# 通过类名修改
Dog.count = 1
print(Dog.count)

print('='*30)
# 对象名.变量 = 数据 默认操作给实例对象添加实例属性,已经不能操作类属性
# 如果类属性名字和实例属性名字相同,实例对象名只能操作实例属性
d1 = Dog()
d1.count = 250

print(Dog.count, d1.count)

运行结果:

1
==============================
1 250

3.2 类属性和实例属性同名

  • 如果类属性和实例属性同名,实例对象名只能操作实例属性
  • 结论:操作类属性建议使用类名,避免不必要的麻烦
class Dog(object):
    # 类属性
    count = 666

    def __init__(self):
        self.count = 250  # 实例属性

# 创建对象
# 如果类属性和实例属性同名,实例对象名只能操作实例属性
d1 = Dog()
print(Dog.count, d1.count)

运行结果:

666 250

3.3 私有类属性

  • 类属性也可以设置为 私有,前边添加两个下划线__

    class Dog(object):
        # 类属性
        __count = 0
    
    print(Dog.__count)  # 类的外面,不能直接访问私有类属性,err

九、类方法、静态方法

1. 类方法

  • 类对象所拥有的方法,主要为了在没有创建实例对象前提下,处理类属性
  • 需要用装饰器@classmethod来标识其为类方法
  • 对于类方法,第一个参数必须是类对象(代表类),一般以cls作为第一个参数,这个参数不用人为传参,解释器会自动处理
"""
类方法:为了方便处理类属性
    1. 用装饰器 @classmethod 来标识其为类方法
    2. 一般以 cls 作为第一个参数,代表当前这个类,这个参数不用人为传参,解释器会自动处理
    3. 类方法调用:
        3.1 类名.类方法()    推荐用法
        3.2 实例对象名.类方法()
"""


class Dog(object):
    # 类属性
    count = 0

    # 定义类方法
    @classmethod
    def print_num(cls):  # 参数cls代表当前的类
        # print('count = ', Dog.count)
        print('count = ', cls.count)


# 调用类方法
Dog.print_num()

2. 静态方法

  • 需要通过装饰器@staticmethod来进行修饰,静态方法默认情况下, 既不传递类对象也不传递实例对象(形参没有self/cls)
  • 当方法中 既不需要使用实例对象也不需要使用类对象时,定义静态方法
  • 取消不需要的参数传递,有利于 减少不必要的内存占用和性能消耗
  • 静态方法 也能够通过 实例对象类对象(类名) 去访问。
"""
静态方法:
    1. 需要通过装饰器@staticmethod来进行修饰默认情况下
    2. 既不传递类对象也不传递实例对象(形参没有self/cls)
    3. 静态方法调用:
        3.1 类名.静态方法()    推荐用法
        3.2 实例对象名.静态方法()
"""


class Dog(object):
    # 定义静态方法
    @staticmethod
    def normal_func():
        print('一个和实例属性、类属性没有关系的普通方法')


# 调用静态方法
Dog.normal_func()