目录
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、主程序步骤
-
查看帮助信息
-
查看历史最高分
-
创建游戏对象,开始游戏
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
📝结尾
看到这里了还不给博主扣个:- ⛳️ 点赞☀️收藏 ⭐️ 关注!- 💛 💙 💜 ❤️ 💚💓 💗 💕 💞 💘 💖- 拜托拜托这个真的很重要!- 你们的点赞就是博主更新最大的动力!- 有问题可以评论或者私信呢秒回哦。