面向对象程序设计:
- 面向对象程序设计(Object Oriented Programming,OOP)主要针对大型软件设计而提出,使得软件设计更加灵活,能够很好地支持代码复用和设计复用,并且使得代码具有更好的可读性和可扩展性。
- 面向对象程序设计的一条基本原则是计算机程序由多个能够起到子程序作用的单元或对象组合而成
面向对象程序设计的一个关键性观念是将数据以及对数据的操作封装在一起,组成一个相互依存、不可分割的整体,即对象。对于相同类型的对象进行分类、抽象后,得出共同的特征而形成了类,面向对象程序设计的关键就是如何合理地定义和组织这些类以及类之间的关系。
创建类时用变量形式表示的对象属性称为数据成员/成员属性,用函数形式表示的对象行为称为成员方法,成员属性和成员方法统称为类的成员。
基础知识
类定义语法:
# Python中使用class关键字来定义类,类名的首字母一般要大写
class Human:
def info(self):
print("I like playing basketball")
# 实例化:
human = Human()
human.info() # I like playing basketball
# 使用内置方法isinstance()来测试一个对象是否为某个类的实例。
isinstance(human, Human) # True
I like playing basketball
True
self参数
类的所有实例方法都必须至少有一个名为“self”的参数,并且必须是方法的第一个形参(如果有多个形参的话),self参数代表将来要创建的对象本身。参数的意义即绑定该对象的函数。
-
在类的实例方法中访问实例属性时需要以self为前缀。
-
在外部通过对象调用对象方法时并不需要传递这个参数
-
如果在外部通过类调用对象方法则需要显式为self参数传值。
类成员与实例成员
-
实例的数据成员一般是指在构造函数__init__()中定义的,定义和使用时必须以self作为前缀;属于类的数据成员是在类中所有方法之外定义的。
-
在主程序中(或类的外部),实例属性属于实例(对象),只能通过对象名访问;而类属性属于类,可以通过类名或对象名都可以访问。
-
在实例方法中可以调用该实例的其他方法,也可以访问类属性以及实例属性
-
在Python中,可以动态地为自定义类和对象增加或删除成员
class Car:
length = 3 #定义类属性
def __init__(self, color):
self.color = color #定义实例属性
car = Car("Red") #实例化对象
print(car.color, car.length) #查看实例属性和类属性的值
car.length = 110000 #修改类属性
Car.name = "宝马" #动态增加类属性
car.color = "Yellow" #修改实例属性
print(car.color, car.length, car.name)
Red 3
Yellow 110000 宝马
Python类型的动态性使得我们可以动态为自定义类及其对象增加新的属性和行为,俗称混入(mixin)机制
import types
def setSpeed(self, speed):
self.speed = speed
car.setSpeed = types.MethodType(setSpeed, car) #动态增加成员方法
car.setSpeed(50) #调用成员方法
print(car.speed) #setSpeed这个方法只有car1有
def printSpeed(self):
print(self.speed)
car.printSpeed = types.MethodType(printSpeed, car)
car.printSpeed() # 打印50
del car.printSpeed # 删除
car.printSpeed() # 'Car' object has no attribute 'printSpeed'
50
50
---------------------------------------------------------------------------
AttributeError Traceback (most recent call last)
/tmp/ipykernel_51597/3800583545.py in <module>
14 car.printSpeed() # 打印50
15 del car.printSpeed # 删除
---> 16 car.printSpeed()
AttributeError: 'Car' object has no attribute 'printSpeed'
函数和方法是有区别的。方法一般指与特定实例绑定的函数,通过对象调用方法时,对象本身将被作为第一个参数隐式传递过去,普通函数并不具备这个特点。
class Demo:
pass
t = Demo()
def test(self, val): #普通函数
self.value = val
test(t, 3) #必须为self参数传值
print(t.value)
t.test = types.MethodType(test, t)#绑定的方法
t.test(93)
print(t.value) #不需要为self参数传值
3
93
私有成员与公有成员
Python并没有对私有成员提供严格的访问保护机制。
-
在定义类的成员时,如果成员名以两个下划线“__”或更多下划线开头而不以两个或更多下划线结束则表示是私有成员。
-
私有成员在类的外部不能直接访问,需要通过调用对象的公开成员方法来访问,也可以通过Python支持的特殊方式来访问。
公开成员既可以在类的内部进行访问,也可以在外部程序中使用
>>> class A:
def __init__(self, value1=0, value2=0):
self.value1 = value1
self.__value2 = value2
def setValue(self, value1, value2):
self.value1 = value1
self.__value2 = value2
def show(self):
print(self.value1)
print(self.__value2)
>>> a = A()
>>> a.value1
0
>>> a._A__value2 #在外部访问对象的私有数据成员
0
在Python中,以下划线开头的变量名和方法名有特殊的含义,尤其是在类的定义中。
- _xxx:(单下划线)受保护成员,不能用’from module import *'导入;
- __xxx__:系统定义的特殊成员;
- __xxx:私有成员,只有类对象自己能访问,子类对象不能直接访问到这个成员,但在对象外部可以通过“对象名._类名__xxx”这样的特殊方式来访问。
注意:Python中不存在严格意义上的私有成员。
方法
在类中定义的方法可以粗略分为四大类:公有方法、私有方法、静态方法和类方法。
-
私有方法的名字以两个下划线“__”开始,每个对象都有自己的公有方法和私有方法,在这两类方法中可以访问属于类和对象的成员;
-
公有方法通过对象名直接调用,私有方法不能通过对象名直接调用,只能在定义时属于对象的方法中通过self调用或在外部通过Python支持的特殊方式来调用。
-
静态方法和类方法都可以通过类名和对象名调用,但不能直接访问属于对象的成员,只能访问属于类的成员。可以没有参数(是的,可以没有self,cls)。
-
如果通过类名来调用属于对象的公有方法,需要显式为该方法的self参数传递一个对象名,用来明确指定访问哪个对象的数据成员。静态方法和类方法都可以通过类名和对象名调用,但不能直接访问属于对象的成员,只能访问属于类的成员。
class Root:
__total = 0
def __init__(self, v): #构造方法
self.__value = v
Root.__total += 1
def show(self): #普通实例方法
print('self.__value:', self.__value)
print('Root.__total:', Root.__total)
@classmethod #修饰器,声明类方法
def classShowTotal(cls): #类方法
print(cls.__total)
@staticmethod #修饰器,声明静态方法
def staticShowTotal(): #静态方法
print(Root.__total)
r = Root(3)
r.classShowTotal() #通过对象来调用类方法
r.staticShowTotal() #通过对象来调用静态方法
r.show()
rr = Root(5)
Root.classShowTotal() #通过类名调用类方法
Root.staticShowTotal() #通过类名调用静态方法
1
1
self.__value: 3
Root.__total: 1
2
2
常用特殊方法:![请添加图片描述](https://img-blog.csdnimg.cn/direct/31c310d5a25b42faa5fae639c903e5d3.png)
属性
只读属性
>>> class Test:
def __init__(self, value):
self.__value = value
@property
def value(self): #只读,无法修改和删除
return self.__value
>>> t = Test(3)
>>> t.value #这里t.value不是成员变量也不是方法而是property属性
3
>>> t.value = 5 #只读属性不允许修改值
AttributeError: can't set attribute
>>> t.v=5 #动态增加新成员
>>> t.v
5
>>> del t.v #动态删除成员
>>> del t.value #试图删除对象属性,失败
AttributeError: can't delete attribute
>>> t.value
3
可读、可写属性
>>> class Test:
def __init__(self, value):
self.__value = value
def __get(self):
return self.__value
def __set(self, v):
self.__value = v
value = property(__get, __set)
def show(self):
print(self.__value)
>>> t = Test(3)
>>> t.value #允许读取属性值
3
>>> t.value = 5 #允许修改属性值
>>> t.value
5
>>> t.show() #属性对应的私有变量也得到了相应的修改
5
>>> del t.value #试图删除属性,失败
AttributeError: can't delete attribute
可读、可修改、可删除的属性。
>>> class Test:
def __init__(self, value):
self.__value = value
def __get(self):
return self.__value
def __set(self, v):
self.__value = v
def __del(self):
del self.__value
value = property(__get, __set, __del)
def show(self):
print(self.__value)
>>> t = Test(3)
>>> t.show()
3
>>> t.value
3
>>> t.value = 5
>>> t.show()
5
>>> t.value
5
>>> del t.value #删除属性
>>> t.value #对应的私有数据成员已删除
AttributeError: 'Test' object has no attribute '_Test__value'
>>> t.show()
AttributeError: 'Test' object has no attribute '_Test__value'
>>> t.value = 1 #为对象动态增加属性和对应的私有数据成员
>>> t.show()
1
>>> t.value
1
在Python中,使用 @property 注解的属性和普通成员变量有几个区别:
-
访问方式:
普通成员变量可以直接访问,而 @property 注解的属性通常是通过方法来访问的,尽管可以像访问普通属性一样进行访问,但实际上是在调用方法。 -
属性计算:
@property 注解的属性通常用于计算属性值,它们提供了一种更灵活的方式来控制属性的行为。通过属性方法,可以在每次访问属性时进行一些额外的计算或逻辑处理。 -
属性赋值:
对于普通成员变量,可以直接对其进行赋值。而 @property 注解的属性通常只定义了一个 getter 方法,如果需要设置属性值,需要额外定义 setter 方法。
class MyClass:
def __init__(self):
self._x = 0 # 普通成员变量
@property
def x(self):
"""Getter 方法"""
print("Getting value of x")
return self._x
@x.setter
def x(self, value):
"""Setter 方法"""
print("Setting value of x")
self._x = value
# 创建对象
obj = MyClass()
# 访问普通成员变量
print(obj._x) # 直接访问
print("--------")
# 访问 @property 注解的属性
print(obj.x) # 通过属性方法访问
print("--------")
# 设置 @property 注解的属性
obj.x = 42 # 通过 setter 方法设置属性值
print("--------")
print(obj.x) # 通过属性方法访问新值
print("--------")
0
--------
Getting value of x
0
--------
Setting value of x
--------
Getting value of x
42
--------
继承
继承是用来实现代码复用和设计复用的机制,是面向对象程序设计的重要特性之一。设计一个新类时,如果可以继承一个已有的设计良好的类然后进行二次开发,无疑会大幅度减少开发工作量。
在继承关系中,已有的、设计好的类称为父类或基类,新设计的类称为子类或派生类。派生类可以继承父类的公有成员,但是不能继承其私有成员。
Python支持多继承,如果父类中有相同的方法名,而在子类中使用时没有指定父类名,则Python解释器将从左向右按顺序进行搜索。
class A:
def __init__(self): #构造方法可能会被派生类继承,如果子类没有init
self.__private()
self.public()
def __private(self): #私有方法在派生类中不能直接访问\
print('__private() method in A')
def public(self): #公开方法在派生类中可以直接访问,也可以被覆盖
print('public() method in A')
class B(A):
def __private(self): #没有继承A, 这不存在覆盖基类的私有方法
print('__private() method in B')
def public(self): #覆盖了继承自A类的公开方法public
print('public() method in B')
b = B()
class C(A):
def __init__(self): #显式定义构造函数
self.__private() #这里调用的是类C的私有方法
self.public()
def __private(self):
print('__private() method in C')
def public(self):
print('public() method in C')
c = C()
__private() method in A
public() method in B
__private() method in C
public() method in C
多态
所谓多态(polymorphism),是指基类的同一个方法在不同派生类对象中具有不同的表现和行为。派生类继承了基类行为和属性之后,还会增加某些特定的行为和属性,同时还可能会对继承来的某些行为进行一定的改变,这都是多态的表现形式。
>>> class Animal(object): #定义基类
def show(self):
print('I am an animal.')
>>> class Cat(Animal): #派生类,覆盖了基类的show()方法
def show(self):
print('I am a cat.')
>>> class Dog(Animal): #派生类
def show(self):
print('I am a dog.')
>>> class Tiger(Animal): #派生类
def show(self):
print('I am a tiger.')
>>> class Test(Animal): #派生类,没有覆盖基类的show()方法
pass
>>> x = [item() for item in (Animal, Cat, Dog, Tiger, Test)]
>>> for item in x: #遍历基类和派生类对象并调用show()方法
item.show()
I am an animal.
I am a cat.
I am a dog.
I am a tiger.
I am an animal.