Python 笔记 — 面向对象进阶

目录

Python 面向对象的三大特性:继承,封装,多态。

封装:根据职责将属性和方法封装到一个抽象的类中。

继承:实现代码的重用,相同的代码不需要重复的编写。

多态:不同的对象调用相同的方法,产生不同的执行结果,增加代码的灵活度。

一、封装

把很多数据封装到一个对象中,把固定功能的代码封装到一个代码块,函数、对象、打包成模块。这都属于封装的思想。在面向对象思想中,把一些看似无关紧要的内容组合到一起统一进行存储和使用,这就是封装

在类中定义的方法其实就是把数据和数据的操作封装起来了,在创建了对象之后,只需要给对象发送一个消息(调用方法)就可以执行方法中的代码,也就是说只需要知道方法的名字和传入的参数(方法的外部视图),而不需要知道方法内部的实现细节(方法的内部视图)。

封装的意义:

将属性和方法放到一起做为一个整体,然后通过实例化对象来处理。

隐藏内部实现细节,只需要和对象及其属性和方法交互就可以了。

对类的属性和方法增加访问权限控制。

1、类中封装数据

对于面向对象的封装来说,其实就是使用构造方法将内容封装到对象中,然后通过对象直接或者 self 间接获取被封装的内容。

# 1、封装数据
class Person:
    def __init__(self, name, age):
        self.name = name
        self.age = age
        
    def add(self):
        print('姓名:%s, 年龄是: %s' % (self.name, self.age))
        
# 将姓名和年龄分别封装到对象 p1、p2 中 self 的 name 和 age 属性中
# 每个对象中都有 name 和 age 属性
p1 = Person('Alice', 20)
p2 = Person('Bob', 18)

# 2、调用数据,分为两种:通过对象直接调用、通过 self 间接调用
# (1)通过对象直接调用被封装的内容
# 根据保存格式可以如此调用被封装的内容:对象.属性名
print(p1.name)  # Alice
print(p1.age)  # 20

print(p2.name)  # Bob
print(p2.age)  # 20

# (2)通过 self 间接调用被封装的内容
p1.add()  # 姓名:Alice, 年龄是: 20
p2.add()  # 姓名:Bob, 年龄是: 18

# Python 默认会将 p1 传给 self 参数,即:p1.add(p1),
# 所以,此时方法内部的 self = p1,即:self.name 是 Alice,self.age 是 20

外部能随意访问到对象的属性,并且随意修改,这样数据是不安全的,因此我们需要将属性隐藏起来。

2、类中定义私有的

私有属性:类独有的,一般情况下不会去在外面用。

在 Python 中定义私有变量只需要在变量名或函数名前加上 “__” 两个下划线,那么这个函数或变量就会为私有的了。声明该方法为私有方法,不能在类的外部调用。

私有权限:在属性名和方法名前面加上两个下划线 __

类的私有属性和私有方法,都不能通过对象直接访问,但是可以在本类内部访问。

类的私有属性和私有方法,都不会被子类继承,子类也无法访问。

私有属性和私有方法往往用来处理类的内部事情,不通过对象处理,起到安全作用。

格式

xx(没有前缀): 这种命名格式表示公开的属性或方法,可以在类的内部和外部访问。-
这是默认的命名格式,不具有特殊的含义。

_xx(单下划线前缀): 这种命名格式表示属性或方法是受保护的,意味着它们在类的内部和子类中可以访问,但不鼓励在外部直接访问。-
虽然 Python 中没有严格的访问控制,但这个约定表示了属性或方法的受保护性质。

__xx(双下划线前缀): 这种命名格式表示属性或方法是私有的,意味着它们在类的内部可以访问,但在外部无法直接访问。-
Python 使用名称重整来在属性或方法名前添加 _类名 来实现私有性,但仍然可以通过 _类名__属性名 的方式从外部访问。

__xx__(双下划线前后缀): 这种命名格式表示特殊的方法。-
例如类的构造方法 __init__、析构方法 __del__ 等。这些方法在 Python 中具有特殊的含义,用于实现类的特殊功能。

xx_(单下划线后缀): 这种命名格式表示属性或方法与 Python 关键字冲突,因此添加了一个下划线后缀来避免冲突。-
例如,class_ 是一个合法的属性名,以避免与 Python 关键字 class 冲突。

私有属性

class Person:
    name = 'Alice'
    _age = 20
    __sex = '女'  # 私有静态属性

pr = Person()
print(Person.name)  # Alice
print(Person._age)  # 20

# 私有属性,外部不能直接访问
print(Person.__sex)  # 报错:AttributeError: type object 'Person' has no attribute '__sex'


class Person:
    def __init__(self, name, age):
        self.__name = name
        self.__age = age

p1 = Person('Alice', 18)

print(p1.name)  # 报错,无该属性,AttributeError: 'Person' object has no attribute 'name'
print(p1.__name)  # 报错,无该属性,AttributeError: 'Person' object has no attribute '__name'
print(p1._Person__name)  # Alice

伪私有属性和私有方法(科普)

Python 中,并没有真正意义的私有,在给属性、方法命名时,实际是对名称做了一些特殊处理,使得外界无法访问到。

处理方式:在名称前面加上 _类名_类名__名称

类中以 _ 或者 __ 的属性,都是私有属性,禁止外部调用。虽然我们可以通过特殊的手段获取到,并且赋值,但是最好不要这么做(约定俗成)。

访问私有属性,__name

class Person:
    def __init__(self):
        self.__name = 'Alice'
    def funa(self):
        print(self.__name)

pr = Person()
pr.__name  # 错误,AttributeError: 'Person' object has no attribute '__name'
pr.funa()  # Alice

私有方法

class Person:
    def __funa(self):
        print('hello world')
    def funb(self):
        print('hello python')
        self.__funa()

pr = Person()
# 不能通过对象访问
pr.__funa()  
# 运行结果:
# 报错:AttributeError: 'Person' object has no attribute '__funa'

# 类内部可以访问
pr.funb()
# 运行结果:
# hello python
# hello world

对于这些私有成员来说,它们只能在类的内部使用,不能在类的外部以及派生类(子类)中使用。

二、继承

概念

继承可以使得子类别具有父类别的所有方法和属性,而不需要再次编写相同的代码。在令子类别继承父类别的同时,可以重新定义某些属性,并重写某些方法,即覆盖父类别的原有属性和方法,使其获得与父类别不同的功能。另外,为子类别追加新的属性和方法也是常见的做法。

在程序中,继承描述的是多个类之间的所属关系。

如果一个类 A 里面的属性和方法可以复用,则可以通过继承的方式,传递到类 B 里。 那么类 A 就是基类,也叫做父类;类 B 就是派生类,也叫做子类。

语法

class 类名(父类名):
	pass

案例

# 例一:不使用继承
class Animal:
    def eat(self):
        print("吃")
    def sleep(self):
        print("睡")

class Dog:
    def eat(self):
        print("吃")
    def sleep(self):
        print("睡")
    def bark(self):
        print("汪汪叫")

# 创建一个对象
wangcai = Dog()
wangcai.eat()
wangcai.sleep()
wangcai.bark()
# 运行结果:
# 吃
# 睡
# 汪汪叫


# 例二:
class Animal:
    def eat(self):
        print("吃---")
    def sleep(self):
        print("睡---")
        
class Dog(Animal):
    def bark(self):
        print("汪汪叫---")
        
# 创建一个对象
wangcai = Dog()
wangcai.eat()
wangcai.sleep()
wangcai.bark()

# 运行结果:
# 吃---
# 睡---
# 汪汪叫---

# Dog 类是 Animal 类的子类,Animal 类是 Dog 类的父类,Dog 类从 Animal 类继承
# Dog 类是 Animal 类的派生类,Animal 类是 Dog 类的基类,Dog 类从 Animal 类派生


# 例三:
class A:
    def __init__(self):
        self.age = 18

    def add(self):
        print(self.age + 10)

class B(A):
    pass

b = B()
print(b.age)  # 18
b.add()  # 28

1、新式类与旧式类(经典类)

在 Python 中,旧式类和新式类是在不同版本的 Python 中对类的实现方式的称呼。新式类引入了一些改进和特性,用来更好地支持面向对象编程。

1.1、旧式类(经典类)

旧式类是指在 Python 2.2 之前的版本中定义的类,它的类继承方式相对简单。旧式类没有继承自 object 类,因此缺少一些在新式类中的特性。旧式类中的方法解析顺序是按照广度优先搜索的方式进行的。

class OldStyleClass:
    pass

1.2、新式类

新式类是指在 Python 2.2 及以后版本中定义的类,它们都隐式地继承自内置的 object 类。新式类引入了更多的面向对象编程特性,如属性访问控制、方法解析顺序的改进等。新式类中的方法解析顺序是按照 C3 线性化算法计算的,这种方式更加准确和灵活。

class NewStyleClass(object):
    pass

1.3、Python 3 中的统一

在 Python 3 中,所有的类都被视为新式类,无论是否显式地继承自 object。这意味着在 Python 3 中不再需要显式地继承 object 类来定义一个新式类。

1.4、差异

(1)默认继承关系:新式类隐式地继承自 object 类,而旧式类不继承。

(2)属性访问控制:新式类引入了属性的访问控制机制,可以通过属性修饰符来控制属性的读写权限。

(3)方法解析顺序(MRO):新式类的方法解析顺序按照 C3 线性化算法计算,更加准确和灵活。而旧式类的方法解析顺序是广度优先搜索。

(4)super() 函数:新式类中的 super() 函数更加强大,可以在多层继承中更精确地调用父类的方法。

2、单继承

一个子类只继承自一个父类。

class Animal:
    Aname = '动物类'
    def __init__(self, name, sex, age):
        self.name = name
        self.sex = sex
        self.age = age

    def eat(self):
        print('%s在吃东西'% self)

class Dog(Animal):
    pass  # 子类可以继承父类所有的属性和方法,哪怕自己没有,也可以使用父类的属性和方法。

# 调用父类的属性,方法
print(Dog.Aname)  # 动物类
Dog.eat('旺财')  # 旺财在吃东西

# 实例化对象,对象执行父类的属性,方法
p1 = Dog('哈哈','男',10)  # 创建子类实例对象
p1.Sname = '222'
print(p1.Sname)  # 222
# 子类对象可以直接使用父类的方法
p1.eat()  # <__main__.Person object at 0x000001C4FDBB7490>在吃东西

class Animal:
    def __init__(self, name):
        self.name = name

    def speak(self):
        print("动物的说话方法")
        pass  

class Dog(Animal):
    def speak(self):
        print("狗的说话方法")
        return f"{self.name}说汪汪~"

class Cat(Animal):
    def speak(self):
        print("猫的说话方法")
        return f"{self.name}说喵喵~"

# 创建子类的实例
dog = Dog("旺财")
cat = Cat("猫猫")

# 调用子类的方法
print(dog.speak())
# 狗的说话方法
# 旺财说汪汪~
print(cat.speak())
# 猫的说话方法
# 猫猫说喵喵~

3、继承的传递性

C 类从 B 类继承,B 类又从 A 类继承,那么 C 类就具有 B 类和 A 类的所有属性和方法。 子类拥有父类以及父类的父类中封装的所有属性和方法。

class Animal:
    def __init__(self, name):
        self.name = name
    def eat(self):
        print("吃---")
    def sleep(self):
        print("睡---")

class Dog(Animal):
    def bark(self):
        print("%s在汪汪叫"%self.name)
        
class Black(Dog):
    def fly(self):
        print("%s说:我会飞"%self.name)

# 创建一个小黑的对象
black = Black('小黑')
black.fly()  # 小黑说:我会飞
black.bark()  # 小黑在汪汪叫
black.eat()  # 吃---

4、重写

应用场景

子类拥有父类的所有方法和属性,子类继承自父类,可以直接享受父类中已经封装好的方法,一般不需要再次开发 。但是,当父类的方法实现不能满足子类需求时,可以对方法进行重写

覆盖父类的方法

如果在开发中,父类的方法实现和子类的方法实现,完全不同,就可以使用覆盖的方式,在子类中重新编写父类的方法实现。

具体的实现方式:

就相当于在子类中定义了一个和父类同名的方法并且实现。重写之后,在运行时,只会调用子类中重写的方法,而不再会调用父类封装的方法。

class Animal:
    def speak(self):
        return "动物发出叫声"

class Dog(Animal):
    def speak(self):
        return "狗发出汪汪叫声"

# 创建子类的实例
dog = Dog()

# 调用子类的方法,重写父类的方法
print(dog.speak())  # 狗发出汪汪叫声

对父类方法进行扩展

如果在开发中,子类的方法实现中包含父类的方法实现,父类原本封装的方法实现是子类方法的一部分,就可以使用扩展的方式。

具体的实现方式:

  • 在子类中重写父类的方法

  • 在需要的位置使用 super().父类方法 来调用父类方法的执行

  • 代码其它的位置针对子类的需求,编写子类特有的代码实现

    class Animal:
    def speak(self):
    return “动物发出叫声”

    class Dog(Animal):
    # 在子类中重写父类的方法
    def speak(self):
    return “狗发出汪汪叫声”

    class DogWithTricks(Dog):
    def speak(self):
    # 在需要的位置使用 super().父类方法 来调用父类方法的执行
    original_sound = super().speak() # 调用父类的方法
    return f"{original_sound},然后摇尾巴"

    # 代码其它的位置针对子类的需求,编写子类特有的代码实现
    def do_trick(self):
        return "狗展示花式表演"
    

    创建子类实例

    dog = DogWithTricks()

    调用子类的方法,包括重写父类方法和子类特有方法

    print(dog.speak()) # 狗发出汪汪叫声,然后摇尾巴
    print(dog.do_trick()) # 狗展示花式表演

super

在 Python 中 super 是一个特殊的类,super() 就是使用 super 类创建出来的对象。 最常使用的场景就是在重写父类方法时,调用在父类中封装的方法实现。

注意:

在开发时,父类名和 super() 两种方式不要混用,如果使用当前子类名调用方法,会形成递归调用,出现死循环。

class Animal(object):
    def bark(self):
        print("汪汪叫")

class Dog(Animal):
    def fly(self):
        print("我会飞")
        
    def bark(self):
        print("来了来了")
        # 父类名.方法(self)
        Animal.bark(self)
        # 注意:如果使用子类调用方法,会出现递归调用 - 死循环!
        # Dog.bark(self)
        
black = Dog()
black.fly()
black.bark()
# 运行结果:
# 我会飞
# 来了来了
# 汪汪叫

使用 super(). 调用原本在父类中封装的方法

class Animal(object):
    def __init__(self, name):
        self.name = name
    def sleep(self):
        print("睡---")
    
class Dog(Animal):
    def eat(self):
        print('%s在吃东西' % self.name)
        
    def sleep(self):
        super().sleep()
        print("%s在汪汪叫" % self.name)

do = Dog('black')
do.eat()
do.sleep()

# 运行结果:
# black在吃东西
# 睡---
# black在汪汪叫

5、继承父类构造方法,并进行改写

class A:
    def __init__(self, name):
        self.name = name
        print('父类中的名字是:', self.name)
        
    def test(self):
        print('父类中的%s哈哈哈' % self.name)

class A2(A):
    # 继承父类构造方法,并进行改写
    def __init__(self, name):
        # 调用父类方法的形式一
        # super().__init__(name)

        # 调用父类方法的形式二
        A.__init__(self, name)

        # 然后再执行子类自己的一部分操作
        self.name = name
        print('子类中的名字:', self.name)

    def test(self):
        print('子类中的%s哈哈哈' % self.name)

a2 = A2('ls')
a2.test()

# 运行结果:
# 父类中的名字是: ls
# 子类中的名字: ls
# 子类中的ls哈哈哈


class A:
    def __init__(self, name):
        self.name = name
        print('父类中的名字是:', self.name)

    def test(self):
        print('父类中的%s哈哈哈' % self.name)

class A2(A):
    # 继承父类构造方法,并进行改写
    def __init__(self, name, age):
        # 调用父类方法的形式一
        # super().__init__(name)
        
        # 调用父类方法的形式二
        A.__init__(self, name)
        
        # 然后再执行子类自己的一部分操作
        self.name = name
        self.age = age
        print('子类中的名字:%s, 年龄是%s'% (self.name,self.age))

    def test(self):
        print('子类中的%s哈哈哈' % self.name)

a2 = A2('ls', 12)
a2.test()

# 运行结果:
# 父类中的名字是: ls
# 子类中的名字:ls, 年龄是12
# 子类中的ls哈哈哈

6、父类私有属性和方法

私有属性、方法是对象的隐私,不对外公开,外界以及子类都不能直接访问。

私有属性、方法通常用于做一些内部的事情。

(1)子类对象不能在自己的方法内部,直接访问父类的私有属性或私有方法。

class A:
    def __init__(self):
        self.num1 = 100
        self.__num2 = 200

    def __test(self):
        print("私有方法 %d %d" % (self.num1, self.__num2))

class B(A):
    def demo(self):
        print('哈哈哈')
        # 1. 在子类的对象方法中,不能访问父类的私有属性
        # print("访问父类的私有属性 %d" % self.__num2)

        # 2. 在子类的对象方法中,不能调用父类的私有方法
        # self.__test()

# 创建一个子类对象
b = B()
b.demo()

# 在外界不能直接访问对象的私有属性/调用私有方法
# print(b.__num2)
# b.__test()

# 运行结果:
# 哈哈哈

(2)子类对象可以通过父类的公有方法间接访问到私有属性或私有方法。

class A:
    def __init__(self):
        self.num1 = 100
        self.__num2 = 200

    def __test(self):
        print("私有方法 %d %d" % (self.num1, self.__num2))

    def test(self):
        print("父类的公有方法 %d" % self.__num2)
        self.__test()

class B(A):
    def demo(self):
        print(123)
        # 1. 访问父类的公有属性
        # print("子类方法 %d" % self.num1)

        # 2. 调用父类的公有方法
        # self.test()

b = B()
# b.demo()

# 在外界访问父类的公有属性/调用公有方法
print(b.num1)
b.test()

# 运行结果:
# 100
# 父类的公有方法 200
# 私有方法 100 200

7、多继承

(1)子类可以拥有多个父类,并且具有所有父类的属性和方法。

class A:
    def test(self):
        print("test 方法")

class B:
    def demo(self):
        print("demo 方法")

class C(A, B):
    # 多继承可以让子类对象,同时具有多个父类的属性和方法
    pass

# 创建子类对象
c = C()
c.test()
c.demo()

# 运行结果:
# test 方法
# demo 方法

(2)如果不同的父类中存在同名的方法,子类对象在调用方法时,谁先继承就用谁。

提示:开发时,应该尽量避免这种容易产生混淆的情况!如果父类之间存在同名的属性或者方法,应该尽量避免使用多继承。

class A:
    def play(self):
        print('这是儿子')

class B:
    def play(self):
        print('这是女儿')

class C(B, A):  # 谁先继承就用谁
    pass

c = C()
c.play()

# 确定C类对象调用方法的顺序
print(C.__mro__)

# 运行结果:
# 这是女儿
# (<class '__main__.C'>, <class '__main__.B'>, <class '__main__.A'>, <class 'object'>)

Python 中针对类提供了一个内置属性 __mro__ 可以查看方法搜索顺序。

MRO(method resolution order),主要用于在多继承时判断方法、属性的调用路径,判断子类调用的属性来自于哪个父类。

Python2.3 之前,MRO 是基于深度优先算法的,自2.3开始使用 C3 算法,定义类时需要继承 object,这样的类称为新式类。

C3 算法最早被提出是用于 Lisp 的,应用在 Python 中是为了解决原来基于深度优先搜索算法不满足本地优先级,和单调性的问题。

本地优先级: 指声明时父类的顺序,比如 C(A,B),如果访问 C 类对象属性时,应该根据声明顺序,优先查找 A 类,然后再查找 B 类。

单调性: 如果在 C 的解析顺序中,A 排在 B 的前面,那么在 C 的所有子类里,也必须满足这个顺序。

# 输出结果:
(<class '__main__.C'>, <class '__main__.A'>, <class '__main__.B'>, <class 'object'>)

在搜索方法时,是按照 __mro__ 的输出结果,从左至右的顺序查找的。

如果在当前类中找到方法,就直接执行,不再搜索。

如果没有找到,就查找下一个类中是否有对应的方法,如果找到,就直接执行,不再搜索。

如果找到最后一个类,还没有找到方法,程序报错。

多继承可以继承多个父类,也继承了所有父类的属性和方法。

注意:如果多个父类中有同名的属性和方法,则默认使用第一个父类的属性和方法(根据类的魔法属性 mro 的顺序来查找),多个父类中,不重名的属性和方法,不会有任何影响。 如果子类和父类的方法名和属性名相同,则默认使用子类的。

# 子类重写父类的同名方法和属性
class A:
    def play(self):
        print('这是儿子')

class B:
    def play(self):
        print('这是女儿')

class C(B, A):  # 谁先继承就用谁
    def play(self):
        A.play(self)  # 这是儿子
        # super().play()  # 这是女儿
        print('就是这样')

c = C()
c.play()  # 就是这样

子类继承了多个父类,如果父类类名修改了,那么子类也要涉及多次修改。而且需要重复写多次调用,显得代码臃肿。 使用 super() 可以逐一调用所有的父类方法,并且只执行一次。调用顺序遵循 mro 类属性的顺序。

注意:如果继承了多个父类,且父类都有同名方法,则默认只执行第一个父类的(同名方法只执行一次,目前 super() 不支持执行多个父类的同名方法)。

案例

# 一代:
class Robot(object):
    def __init__(self, year, name):
        self.year = year
        self.name = name

    def walk(self):
        print('%s只能平地行走,遇到障碍会摔倒' % self.name)

    def produce(self):
        print('{}年生产的机器人,名字是{}'.format(self.year, self.name))

rot = Robot(2000, '罗马一代')
rot.walk()  # 罗马一代只能平地行走,遇到障碍会摔倒
rot.produce()  # 2000年生产的机器人,名字是罗马一代

# 二代:
class Robot2(Robot):
    def walk(self):
        print('%s可以避开障碍物' % self.name)

rot = Robot2(2002, '罗马二代')
rot.walk()  # 罗马二代可以避开障碍物
rot.produce()  # 2002年生产的机器人,名字是罗马二代

# 三代:
class Robot(object):
    def __init__(self, year, name):
        self.year = year
        self.name = name

    def walk(self):
        print('%s只能平地行走,遇到障碍会摔倒' % self.name)

    def produce(self):
        print('{}年生产的机器人,名字是{}'.format(self.year, self.name))

class Robot2(object):
    def walk(self):
        print('%s可以避开障碍物' % self.name)

class Robot3(Robot, Robot2):  # 三代同时继承一代和二代,调用就近选择
    def run(self):
        print('%s可以跑步' % self.name)

rot = Robot3(2004, '罗马三代')  # 创建第三代实例

rot.walk()  # 罗马三代只能平地行走,遇到障碍会摔倒
rot.run()  # 罗马三代可以跑步
rot.produce()  # 2004年生产的机器人,名字是罗马三代

三、多态

定义时的类型和运行时的类型不一样,此时就成为多态

多态的概念是应用于 Java 和 C 这一类强类型语言中,而 Python 崇尚“鸭子类型”。

鸭子类型: 虽然我想要一只"鸭子",但是你给了我一只鸟。 可是只要这只鸟走路像鸭子,叫起来像鸭子,游泳也像鸭子,我就认为这是鸭子。 它不关注对象的类型,而是关注对象具有的行为(方法)。

Python 是强类型的动态脚本语言:

  • 强类型:不允许不同类型相加。例如:整形+字符串会报类型错误。
  • 动态:不使用显示数据类型声明,且确定一个变量的类型是在第一次给它赋值的时候。
  • 脚本语言:一般是解释性语言,运行代码只需要一个解释器,不需要编辑。

Python 的多态,就是弱化类型,重点在于对象参数是否有指定的属性和方法,如果有就认定合适,而不关心对象的类型是否正确。

多态

同一种事物的多种形态(一个抽象类有多个子类,因而多态的概念依赖于继承)。

多态性

一种调用方式,不同的执行效果(多态性) 。

多态介绍

不同的子类对象,调用相同的父类方法,产生不同的执行结果。

多态可以增加代码的灵活度。

以继承和重写父类方法为前提。

是调用方法的技巧,不会影响到类的内部设计。

举例:

支付方式可以有几种,微信支付,支付宝支付,苹果支付等,这几个不同的支付都统一于支付,像这样几个类都统一于某一个类或者某一个方法,或者说一个类有不同的形态的情况就属于多态。

虽然几种支付方式都归一于支付类,执行的方法 一样,但是每一个支付方式都有自己的特性,实现的形态也不一样,即为多态性。

简单来说: 同一个操作作用于不同的对象,可以有不同的解释,产生不同的执行结果。

同样的 + 号可以用不同的对象相加,体现了多态的功能

print(1+1)  # 2
print('hello'+'python')  # hellopython

len() 传不同的参数,也体现多态

print('world', len('world'))  # world 5
print('[1,2,3]', len([1, 2, 3]))  # [1,2,3] 3

对象所属的类之间没有继承关系

class WeChatPay(object):
    def pay(self, money):
        print('已经使用微信支付了%s元' % money)

class Alipay(object):
    def pay(self, money):
        print('已经使用支付宝支付了%s元' % money)

# py 可以是 WeChatPay 实例,也可以是 Alipay 实例
py = WeChatPay()
py.pay(100)  # 已经使用微信支付了100元

对象所属的类之间有继承关系(应用更广)

class Animal:
    def run(self):
        print('动物在走')

class People(Animal):
    def run(self):
        print('人正在走')

class Pig(Animal):
    def run(self):
        print('猪在走')

class Dog(Animal):
    def run(self):
        print('狗在走')

peo1 = People()
pig1 = Pig()
d1 = Dog()

peo1.run()  # 人正在走
pig1.run()  # 猪在走
d1.run()  # 狗在走

多态性:

是指具有不同功能的函数可以使用相同的函数名,这样就可以用一个函数名调用不同内容的函数。

向不同的对象发送同一条消息,不同的对象在接收时会产生不同的行为(即方法)。

所谓消息,就是调用函数,不同的行为就是指不同的实现,即执行不同的函数。

class A:
    def show(self):
        print('A.show')

class S1(A):
    def show(self):
        print('S1.show')

class S2(A):
    def show(self):
        print('S2.show')

# 多态性依赖于:继承
# 多态性:定义统一的接口,一个接口,多种实现
def func(obj):  # obj 这个参数没有类型限制,可以传入不同类型的值
    obj.show()  # 调用的逻辑都一样,执行的结果却不一样

s1 = S1()
func(s1)  # 在 func 函数中传入 S1 类的对象 s1,执行 S1 的 show 方法,结果:S1.show
s2 = S2()
func(s2)  # 在 func 函数中传入 S2 类的对象 s2,执行 S2 的 show 方法,结果:S2.show

通俗点理解:

定义 obj 这个变量是说的类型是 A 的类型,但是在真正调用 func 函数时给其传递的不一定是 A 类的实例对象, 有可能是其子类的实例对象。

多态性的好处

  • 增加了程序的灵活性

以不变应万变,不论对象千变万化,使用者都是同一种形式去调用,如 func(Animal)

  • 增加了程序额可扩展性

通过继承 Animal 类创建了一个新的类,使用者无需更改自己的代码,还是用 func(Animal) 去调用

class Dog(object):
    def __init__(self, name):
        self.name = name

    def play(self):
        print('%s在玩耍' % self.name)

class Black(Dog):
    def play(self):
        print('%s飞到天上去玩耍' % self.name)

class Person(object):
    def __init__(self, pname):
        self.pname = pname

    def plays(self, dog):
        print('%s 和%s 在快乐的玩耍' % (self.pname, dog.name))
        # 在方法内部,直接让 狗对象 调用 plays 方法
        dog.play()

# 创建狗对象
xiaohei = Dog('小黑')
# xiaohei2 = Black('小小黑')
# xiaohei.play()
# xiaohei2.play()

# 总结:
# Person 类中只需要让 狗对象 调用 play 方法,而不关心具体是什么狗

# 创建一个张三对象
zs = Person('张三')

# 在程序执行时,传入不同的 狗对象 实参,就会产生不同的执行效果

# 小明调用和狗玩的方法
zs.plays(xiaohei)

# 运行结果:
# 张三 和小黑 在快乐的玩耍
# 小黑在玩耍

多态更容易编写出出通用的代码,做出通用的编程,以适应需求的不断变化!

四、类的常用装饰器

1、@classmethod 装饰器

用于定义类方法,类方法可以通过类名调用,而不需要实例化对象。类方法的第一个参数通常是 cls,表示类本身。类方法可以在不创建对象的情况下执行操作。

class Person:
    age = 20

    # 类方法
    @classmethod
    def human(cls):
        return cls.age

p = Person()
print(p.human())  # 可以用过实例对象引用
print(Person.human())  # 可以通过类对象引用

# 运行结果:
# 20
# 20


# 对类属性进行修改
class Person:
    age = 20

    @classmethod
    def get_age(cls):
        print(cls.age)

    @classmethod
    def set_age(cls, age):
        cls.age = age

p = Person()
p.get_age()  # 20
Person.get_age()  # 20

p.set_age(18)
p.get_age()  # 18
Person.get_age()  # 18

2、@staticmethod 装饰器

用于定义静态方法,静态方法与类和实例无关,不会自动传递任何参数。它们与类紧密相关,但不访问类或实例属性。静态方法可以通过类名调用。

class Dog(object):
    @staticmethod
    def run():
        # 不需要访问实例属性也不需要访问类属性的方法
        print('狗狗在跑')

dog = Dog()
dog.run()  # 狗狗在跑


class A:
    age = 10
    @staticmethod
    def get_age():
        return A.age

a = A()
print(a.get_age())  # 10
print(A.get_age())  # 10

3、@property 装饰器

用于将一个方法转换为只读属性,使得我们可以通过访问属性的方式来调用方法,而不需要使用括号。这对于获取对象的某些属性值非常有用,可以使代码更加简洁和直观。

class Circle:
    def __init__(self, radius):
        self._radius = radius

    @property
    def radius(self):
        return self._radius

    @property
    def area(self):
        return 3.14 * self._radius ** 2

# 创建 Circle 实例
circle = Circle(5)

# 通过属性访问方法,而不是方法调用
print(circle.radius)  # 5
print(circle.area)  # 78.5
  • @property 装饰器用于定义一个属性的 getter 方法,允许以属性的方式访问方法的返回值。

  • @xxx.setter 装饰器用于定义属性的 setter 方法,允许以属性的方式设置属性的值。

  • @xxx.deleter 装饰器用于定义属性的 deleter 方法,允许以属性的方式删除属性。

    class MyClass:
    def init(self, value):
    self._value = value

    @property
    def value(self):
        return self._value
    
    @value.setter
    def value(self, new_value):
        if new_value > 0:
            self._value = new_value
        else:
            raise ValueError("Value must be greater than 0")
    
    @value.deleter
    def value(self):
        del self._value
    

    创建 MyClass 实例

    obj = MyClass(42)

    使用属性访问 getter 方法

    print(obj.value) # 42

    使用属性访问 setter 方法

    obj.value = 50
    print(obj.value) # 50

    使用属性访问 deleter 方法

    del obj.value

    print(obj.value) # 报错,因为属性已被删除,AttributeError: ‘MyClass’ object has no attribute ‘_value’. Did you mean: ‘value’?

五、方法综合案例

设计一个 Game 类,实现记录玩家游戏得分情况。

1、属性:

定义一个类属性 top 记录游戏的历史最高分

定义一个实例属性 player 记录当前游戏的玩家姓名

2、方法:

静态方法 help 显示游戏帮助信息

类方法 show_top 显示历史最高分

实例方法 start 开始当前玩家的游戏

3、主程序步骤

  1. 查看帮助信息

  2. 查看历史最高分

  3. 创建游戏对象,开始游戏

    class Game(object):
    top = 0

    @staticmethod
    def help():
        print('让僵尸走进房间')
    
    @classmethod
    def show_top(cls):
        print('游戏最高分是%d' % cls.top)
    
    def __init__(self, player):
        self.player = player
    
    def start(self):
        print('%s开始游戏了' % self.player)
        Game.top = 99
    

    1.查看游戏帮助

    Game.help()

    2.查看优秀最高分

    Game.show_top()

    3.创建对象,开始优秀

    obj = Game(‘张三’)
    obj.start()

    4.游戏结束,查看游戏得分

    Game.show_top()

    运行结果:

    让僵尸走进房间

    游戏最高分是0

    张三开始游戏了

    游戏最高分是99

📝结尾

看到这里了还不给博主扣个:- ⛳️ 点赞☀️收藏 ⭐️ 关注!- 💛 💙 💜 ❤️ 💚💓 💗 💕 💞 💘 💖- 拜托拜托这个真的很重要!- 你们的点赞就是博主更新最大的动力!- 有问题可以评论或者私信呢秒回哦。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值