参考+借鉴+模仿+学习
- Python面向对象编程从零开始(1)——从没对象到有对象
- Python面向对象编程从零开始(2)—— 与对象相互了解
- 【python学习笔记】Python面向对象的理解(封装,继承,多态)
- 分享3 个Python冷知识
文章目录
object oriented programming (OOP)面向对象编程
- 类:对具有相同数据和方法的一组对象的描述或定义。
- 对象:对象是一个类的实例。
- 实例(instance):一个对象的实例化实现。
- 标识(identity):每个对象的实例都需要一个可以唯一标识这个实例的标记。
- 实例属性(instance attribute):一个对象就是一组属性的集合。
- 实例方法(instance method):所有存取或者更新对象某个实例一条或者多条属性的函数的集合。
- 类属性(classattribute):属于一个类中所有对象的属性,不会只在某个实例上发生变化
- 类方法(classmethod):那些无须特定的对性实例就能够工作的从属于类的函数。
1、类、对象、方法
# 定义一个类
class Car:
def drive(self):
print('我正在开车')
def turnover(self):
print('翻车了')
# 创建一个对象
xx = Car()
# 调用类方法
xx.drive()
xx.turnover()
结果为
我正在开车
翻车了
2、类的属性
可以在类中定义属性,也可以在对象中添加属性
class Car:
def drive(selt):
print('我正在开车')
def turnover(self):
print('翻车了')
#创建一个对象
xiao_jie_jie = Car()
xiao_jie_jie.drive()#调用xiao_jie_jie指向的对象的方法
xiao_jie_jie.turnover()
#添加属性,属性就是变量
xiao_jie_jie.name = '小红'
xiao_jie_jie.age = 18
print ("%s的年龄是%d"%(xiao_jie_jie.name,xiao_jie_jie.age))
结果为
我正在开车
翻车了
小红的年龄是18
3、封装、继承、多态
3.1 封装(Encapsulation)
封装:就是隐藏对象的属性和实现细节,仅对外提供公共访问方式(对外部隐藏对象的工作细节)。
list1 = [1,3,2,7,5]
list1.sort()
list1
output
[1, 2, 3, 5, 7]
list1是列表list的实例对象,我们调用了sort方法()
3.2、继承(Inheritance)
子类自动共享父类之间数据和方法的机制
如果一个类 A 继承自另一个类 B,就把这个 A 称为 B 的子类,把 B 称为 A 的父类、基类或超类。继承可以使得子类具有父类的各种属性和方法,而不需要再次编写相同的代码(偷懒)。
在子类继承父类的同时,可以重新定义某些属性,并重写某些方法,即覆盖父类的原有属性和方法,使其获得与父类不同的功能。另外,为子类追加新的属性和方法也是常见的做法。
class list2222 (list):
pass
list2 = list2222()
list2.append(1)
list2.append(3)
list2.append(2)
list2.append(4)
print(list2)
list2.sort()
print(list2)
output
[1, 3, 2, 4]
[1, 2, 3, 4]
list2222继承list类,实例对象list2,也可以调用list的append()、sort()方法
1)如果子类和父类的方法同名,子类会覆盖父类
# 如果子类和父类的方法同名,子类会覆盖父类
class Parent:
def hello(self):
print("****")
class Child(Parent):
def hello(self):
print("####")
p = Parent()
p.hello()
c = Child()
c.hello()
Output
****
####
2)多层继承
# 多层继承 class A(B,C,D……)
class Base1:
def fool1(self):
print("i'm a fool")
class Base2:
def fool2(self):
print("i'm a fool,too")
class A(Base1,Base2):
pass
a = A()
a.fool1()
a.fool2()
Output
i'm a fool
i'm a fool,too
多重继承使用不当会导致重复调用(也叫 钻石继承、菱形继承)的问题,关于__init__的说明请参考本博客的附录
class A():
def __init__(self):
print("进入A…")
print("离开A…")
class B(A):
def __init__(self):
print("进入B…")
A.__init__(self)
print("离开B…")
class C(A):
def __init__(self):
print("进入C…")
A.__init__(self)
print("离开C…")
class D(B, C):
def __init__(self):
print("进入D…")
B.__init__(self)
C.__init__(self)
print("离开D…")
实例化D的时候
d = D()
output
进入D…
进入B…
进入A…
离开A…
离开B…
进入C…
进入A…
离开A…
离开C…
离开D…
进入了两次A。
这有什么危害?我举个例子,假设 A 的初始化方法里有一个计数器,那这样 D 一实例化,A 的计数器就跑两次(如果遭遇多个钻石结构重叠还要更多),很明显是不符合程序设计的初衷的(程序应该可控,而不能受到继承关系影响)
为了让大家都明白,这里只是举例最简单的钻石继承问题,在实际编程中,如果不注意多重继承的使用,会导致比这个复杂N倍的现象,调试起来不是一般的痛苦……所以一定要尽量避免使用多重继承。
如何解决这种问题呢,super()
大发神威
class A():
def __init__(self):
print("进入A…")
print("离开A…")
class B(A):
def __init__(self):
print("进入B…")
super().__init__()
print("离开B…")
class C(A):
def __init__(self):
print("进入C…")
super().__init__()
print("离开C…")
class D(B, C):
def __init__(self):
print("进入D…")
super().__init__()
print("离开D…")
实例化
d = D()
output
进入D…
进入B…
进入C…
进入A…
离开A…
离开C…
离开B…
离开D…
3)继承__init__
class Fish1:
def __init__(self):
self.length = 10
self.weight = 5
def eat(self):
self.weight+=1
print("当前的weight:",self.weight)
def growth(self):
self.length+=1
print("当前的length:",self.length)
class Fish2(Fish1):
pass
class Fish3(Fish1):
def __init__(self):
self.hungry = True
#Fish1.__init__(self)
#super().__init__()
def eat(self):
if self.hungry:
print("开吃")
else:
print("吃饱了")
测试
f2 = Fish2()
f2.eat()
f2.growth()
f3 = Fish3()
f3.eat()
f3.growth()
output
当前的weight: 6
当前的length: 11
开吃
---------------------------------------------------------------------------
AttributeError Traceback (most recent call last)
<ipython-input-6-8ce1c7ea1927> in <module>()
5 f3 = Fish3()
6 f3.eat()
----> 7 f3.growth()
<ipython-input-5-2ff9b0c109ac> in growth(self)
7 print("当前的weight:",self.weight)
8 def growth(self):
----> 9 self.length+=1
10 print("当前的length:",self.length)
11
AttributeError: 'Fish3' object has no attribute 'length'
报错了,没有 length
的属性,没有继承父类的__init__
,有如下两种解决方法
-
调用未绑定的父类方法
在子类的__init__
函数下添加Fish1.__init__(self)
,不推荐这种方法,因为如果改了继承类还需要修改父类Fish1
的名字 -
使用super函数
在子类的__init__
函数下添加super().__init__()
,推荐这种方法,会自己找继承的父类
修改后的output
当前的weight: 6
当前的length: 11
开吃
当前的length: 11
parent
使用super()或父类的名称调用父类的初始化
super
class Parent:
def __init__(self, city, address):
self.city = city
self.address = address
class Child(Parent):
def __init__(self, city, address, university):
super().__init__(city, address) # 调用父类的初始化方法
self.university = university
child = Child('A', 'B', 'C')
print(child.city, child.address, child.university) # A B C
父类的名字
class Parent:
def __init__(self, city, address):
self.city = city
self.address = address
class Child(Parent):
def __init__(self, city, address, university):
Parent.__init__(self, city, address) # 调用父类的初始化方法,区别于 super,这里需要有 self
self.university = university
child = Child('A', 'B', 'C')
print(child.city, child.address, child.university) # A B C
3.3、多态(Polymorphism)
可以对不同类的对象调用相同的方法,产生不同的结果
class A:
def fun(self):# 记得要加self
print('i love you')
class B:
def fun(self):# 记得要加self
print('i love x')
a = A()
b = B()
a.fun()
b.fun()
output
i love you
i love x
3.4、重载(Overloading)
重载是指在同一个类中允许有多个方法具有相同的名字,但参数列表不同(参数的数量或类型不同)。重载用于提供不同的方法来处理不同的输入参数。
特点:
- 编译时多态:方法的选择在编译时决定。
- 方法签名:重载的方法必须具有不同的参数列表。
- 灵活性:允许使用相同的名字来实现不同的功能。
__eq__() for ==
__sub__() for -
__mul__() for *
__truediv__() for /
__ne__() for !=
__ge__() for >=
__gt__() for >
eg
class Journey:
def __init__(self, location, destination, duration):
self.location = location
self.destination = destination
self.duration = duration
def __eq__(self, other):
return ((self.location == other.location) and
(self.destination == other.destination) and
(self.duration == other.duration))
first = Journey('Location A', 'Destination A', '30min')
second = Journey('Location B', 'Destination B', '30min')
print(first == second)
总结
- 封装:隐藏内部实现细节,通过公共接口访问数据。
- 继承:允许子类继承父类的属性和方法,实现代码重用。
- 多态:允许将子类对象视为父类对象来使用,实现动态方法调用。
- 重载:在同一个类中允许方法名相同但参数列表不同的方法。
4、公有和私有
class Person:
name = '小明'
person = Person()
person.name
output
'小明'
python属性和方法都是公有的,有 name mangling
机制(在变量名或者函数名之前加上“__”两个下划线,那么这个变量或者函数就会变为私有的了),如下
如果想要在外部访问,那么只需要在名称前面加上 ‘_类名’ 变成 ‘_类名__名称’。
class Engineer:
def __init__(self, name):
self.name = name
self.__starting_salary = 10
dain = Engineer('Dain')
print(dain._Engineer__starting_salary) # 10
print(dain.Engineer__starting_salary) # 报错
output
Traceback (most recent call last):
File "/usercode/file.py", line 9, in <module>
print(dain.Engineer__starting_salary) # 报错
AttributeError: 'Engineer' object has no attribute 'Engineer__starting_salary'
再看看下面的例子
class Person:
__name = '小明'
试试
person = Person()
person.name
output
AttributeError: 'Person' object has no attribute 'name'
再试试
person = Person()
person.__name
output
AttributeError: 'Person' object has no attribute '__name'
都报错了,只能内部访问,外部不行,可以通过定义函数的方式访问
class Person:
__name = '小明'
def get_name(self):
return self.__name
person = Person()
person.get_name()
output
'小明'
但是,name mangling
其实是伪私有,我们可以通过_类名__变量名
访问,当然我们并不提倡这种抬杠较真粗暴不文明的访问形式……
class Person:
__name = '小明'
person = Person()
person._Person__name
output
'小明'
Note:python的私有机制是伪私有
5、组合
把类的实例化,放在一个新的类里面,横向之间关系的类用 组合
,纵向的用继承
Python 继承机制很有用,但容易把代码复杂化以及依赖隐含继承。因此,经常的时候,我们可以使用组合来代替。在Python里组合其实很简单,直接在类定义中把需要的类放进去实例化就可以了。
class Turtle:
def __init__(self,x):
self.num = x
class Fish:
def __init__(self,x):
self.num = x
# 组合,把类的实例化,放在一个新的类里面
class Pool:
def __init__(self,x,y):
self.turtle = Turtle(x)
self.fish = Fish(y)
def print_num(self):
print("水池里面有乌龟%d 个,有鱼%d个"%(self.turtle.num,self.fish.num))
pool = Pool(1,2)
pool.print_num()
output
水池里面有乌龟1 个,有鱼2个
下面看一个稍微复杂的例子,在附录魔法方法 __str__
中 小姐姐煮面
例子的基础上改进,这里是买房,构建两个类,一个是房子,一个是家具,都有面积属性,随着家具的增加,房子空余面积变小
class Home:
def __init__(self, area, info, address):
self.area = area # 房子的面积
self.info = info # 房子的户型
self.address = address # 房子的地址
self.left_area = area # 房子空余面积
self.furnitures = [] # 房子中的家具
def __str__(self):
return "房子的面积:{},户型:{},地址:{},现有家具:{},剩余面积:{}".format(self.area, self.info, self.address, self.furnitures,
self.left_area)
def add_furnitures(self, item): # 添加家具
self.left_area -= item.area # 房屋空余面积相应减少
self.furnitures.append(item.furniture) # 新增房子中的家具
class Furniture:
def __init__(self, furniture, area):
self.furniture = furniture # 家具的名字
self.area = area # 家具的面积
def __str__(self):
return "家具{}的面积为{}m2".format(self.furniture, self.area)
home = Home(120, '三室一厅', '上海市 黄浦区 人民大道 666号')
print(home)
bed = Furniture('双人豪华大床', 6)
print(bed)
home.add_furnitures(bed)
print(home)
aircondition = Furniture('立式空调', 1)
home.add_furnitures(aircondition)
print(home)
output
房子的面积:120,户型:三室一厅,地址:上海市 黄浦区 人民大道 666号,现有家具:[],剩余面积:120
家具双人豪华大床的面积为6m2
房子的面积:120,户型:三室一厅,地址:上海市 黄浦区 人民大道 666号,现有家具:['双人豪华大床'],剩余面积:114
房子的面积:120,户型:三室一厅,地址:上海市 黄浦区 人民大道 666号,现有家具:['双人豪华大床', '立式空调'],剩余面积:113
6、属性和方法名相同,方法会被覆盖
class ABC:
def x (self):
print("hello")
abc = ABC()
abc.x()
abc.x = 1
print(abc.x)
abc.x()
output
hello
1
---------------------------------------------------------------------------
TypeError Traceback (most recent call last)
<ipython-input-21-3e5e59179b1f> in <module>()
6 abc.x = 1
7 print(abc.x)
----> 8 abc.x()
TypeError: 'int' object is not callable
7、绑定
Python 严格要求方法需要有实例才能被调用,这种限制其实就是Python所谓的绑定概念
class BB:
def printBB():
print("hello")
BB.printBB()
bb=BB()
bb.printBB()
output
hello
---------------------------------------------------------------------------
TypeError Traceback (most recent call last)
<ipython-input-22-dbb760d5e8f1> in <module>()
4 BB.printBB()
5 bb=BB()
----> 6 bb.printBB()
TypeError: printBB() takes 0 positional arguments but 1 was given