1 类和对象
对象是具有某些特性和功能的具体事物的抽象。每个对象都具有描述其特征的属性及附属于它的行为。如:一个人有姓名、性别、身高、体重等特征描述,也有走路、说话、学习、开车等行为。
每个对象都有一个类,类是创建对象实例的模板,是对对象的抽象和概括,它包含对所创建对象的属性描述和行为特征的定义。如:我们在马路上看到的汽车都是一个个的汽车对象,它们都属于汽车类。
面向对象的其它重要概念:
- 封装
- 继承
- 多态
- 对基类方法的覆盖或重写。
2 定义和使用类
2.1 类定义
创建类的语法为:
class 类名:
属性(成员变量)
属性
...
成员函数(成员方法)
...
例如,定义一个Person类:
class Person:
num = 1 #成员变量
def SayHello(self): #成员函数
print("Hello!")
2.2 对象定义
对象是类的实例。如果人类是一个类的话,那么某个具体的人就是一个对象。只有定义了具体的对象,才可以通过“对象名.成员”的方式来访问其中的数据成员或成员函数。
创建对象的语法为:
对象名 = 类名()
例如,下面的代码定义了一个类Person的对象p:
p = Person()
p.SayHello() #访问成员函数SayHello()
3 属性和方法
属性:
公有属性 (属于类,每个类一份)
普通属性 (属于对象,每个对象一份)
私有属性 (属于对象,跟普通属性相似,只是不能通过对象直接访问)
方法:(按作用)
构造方法
析构函数
方法:(按类型)
普通方法
私有方法(方法前面加两个下划线)
静态方法
类方法
属性方法
3.1 构造函数__init__
- 方法__init__()又被称为构造器(constructor)或构造函数;
- 用__init__()这个特殊的方法(函数)可以方便地自己对类的属性进行定义;
- 定义完init()方法后,创建的每个实例都有自己的属性,也方便直接调用类中的函数
- 成员函数需要被手动调用,而构造函数在创建对象的过程中是自动被调用的。
class Box:
def __init__(self, width, height, depth): #这里的init函数就相当于跟类同名的函数,即指代类Box本身实例化了一个具体的Box
self.width = width
self.height = height
self.depth = depth
def getVolume(self):
return self.width * self.height * self.depth
b = Box(10, 20, 30) #构造函数在创建对象的过程中是自动被调用的,
print(b.getVolume())
3.2 析构函数
析构函数__del__(self): 在程序结束时自动执行,如果程序没有结束,那么析构函数函数里面的代码不会执行。
执行时间:
- 如果该函数在class里面,那么类里面的方法运行完毕,就会自动执行析构函数函数的代码;
- 手动删除:del+变量名 删除之后不能再次使用。
例如:
class Box:
def __init__(self, width, height, depth): #这里的init函数就相当于跟类同名的函数,即指代类Box本身实例化了一个具体的Box
self.width = width
self.height = height
self.depth = depth
def __del__(self):
print("Box's object is not existed!")
def getVolume(self):
return self.width * self.height * self.depth
b = Box(10, 20, 30) # 构造函数在创建对象的过程中是自动被调用的,
print(b.getVolume())
print(b)
del b #删除b对象变量
3.3 类属性和实例属性
- 类属性是在类中定义的属性,它是和这个类所绑定的,这个类中的所有对象都可以访问。访问时可以通过类名来访问,也可以通过实例名来访问。
- 实例属性是与类的实例相关联的数据值,是这个实例私有的,只有这个对象自己可以访问。当一个实例被释放后,它的属性同时也被清除了。
例1:
# 定义了类之后,Python就会为类分配一块内存空间,里面放它的相关属性和方法。
# 这里在类中定一个了一个类属性,相当于在Person类的内存空间中有了值为10的age属性。
class Person():
age = 10
# 给类名加上函数调用符号,就相当于创建了一个对象。
# 现在我们分别创建了一个叫沈腾的人和一个叫马丽的人。
# 创建后,Python也会为对象分别分配内存空间
st = Person()
ml = Person()
# 使用对象访问类属性,它会先去对象的内存空间里面找age属性,如果没有,就向上找对象所属类的内存空间。
# 而在这里它并不是直接取了对象的内存空间的age属性,因为此时对象的内存空间里面还没有age属性。
# 这里实际上是对象访问了类的内存空间,从类内存空间中取出来age属性的值,并打印出来
# 所以这里沈腾对象属性值和马丽对象属性值都是类属性值10
print("st对象:%s" % st.age)
print("ml对象:%s" % ml.age)
# 要记住一点,只要给对象有了赋值操作,那么就相当于给对象的内存空间中动态创建了一个属性,所以这里此时沈腾对象的内存空间中有了一个age属性了,是属于这个对象的属性,也就是我们所说的实例属性。
# 那么此时st.age会先在对象内存空间中找age属性,找到了,就不会再去类内存空间中找,所以此时st.age访问的是对象内存空间的age属性。
# 所以此时沈腾对象属性值是沈腾对象内存空间age属性的值,也就是12。而非类属性值10。
st.age = 12
print("st对象:%s" % st.age)
# 因为马丽对象还没有进行赋值操作,所以它还没有在它自己的内存空间中动态创建age属性。所以它这里访问的依旧是类内存空间中的age属性值。
# 而上述语句改变的是沈腾内存空间中age的值,所以不会影响到马丽对象属性值。
# 这里打印马丽对象属性值也就是类的属性值10。
print("ml对象:%s" % ml.age)
# 因为上面的赋值语句改变的是对象的内存空间,所以类属性的值其实并没有改变,依旧是原来的值10。
print("类属性值:%s" % Person.age)
根据运行结果分析如下:
- 在没有st.age没有执行之前的打印值都是类属性的值10,而st.age=12执行后,st.age值就变成了12;
- ml.age和Person.age都没有变,依旧是类属性的10。
例2:可以在类的构造方法__init__中对对象的属性进行初始化,这里也是相当于对对象的属性进行了赋值操作,所以也是在对象的内存空间中动态的创建了实例属性。因为self参数就相当于对象自己,self.age在对象创建后就相当于对象.age,如st.age,ml.age,和上面例子原理是一样的。
class Person():
age = 10
def __init__(self, age):
self.age = age
# 因为在构造方法中就已经给self.age属性赋值了。
# 所以创建对象时,沈腾对象内存空间中和马丽对象内存空间中就都有了age属性,这是属于对象的属性。
# 所以以后在使用st.age和ml.age时,访问到的都是对象内存空间中的age值了。
st = Person(18)
ml = Person(9)
# 这里就访问的是对象内存空间中的age属性值了
# 所以分别打印18和9
print("st对象:%s" % st.age)
print("ml对象:%s" % ml.age)
# 这里给沈腾对象的age属性重新赋值,改变的也仅是沈腾对象内存空间中的age属性值
# 不会改变马丽对象内存空间age属性值和类内存空间age属性值。
st.age = 12
print("st对象:%s" % st.age)
print("ml对象:%s" % ml.age)
print("类属性值:%s" % Person.age)
总结如下:
- 类属性在类创建时就存在于类的内存空间中。
- 如果类的构造函数中没有初始化对象属性,那么对象在创建时内存空间是没有这个属性的。
- 实例属性是通过赋值语句来动态创建的。如果没有动态创建,通过对象访问和类同名的属性时,会现在对象内存空间中查找是否有该属性,没有就去类内存空间中查找。
- 如果已经动态创建了实例属性,那么Python使用对象访问和类同名的属性时,是一定先访问对象内存空间中的实例属性的。
3.4 私有成员与公有成员
类的成员,是类的数据成员和类的方法的统称。为了控制对类的成员的访问权限,类的成员分:公有、保护 、私有三种类型。
- 公有成员:任何时候都可以访问的成员即为公有成员,在定义类的公有成员时没有什么特殊的要求;
- 保护成员:名称以一条下划线“_”开头的成员为保护成员。例如:_x、_fun()。保护成员一般都是可以访问的,只是不能用“from module import * ”语句把其它模块定义的保护成员导入到当前模块;
- 私有成员:名称以两条下划线“__”开头的成员为私有成员。例如:__x、__fun()。私有成员一般不能在类的定义语句外访问。不过,通过“类名._类名私有成员名”(类名和私有成员名之间不要有没有任何的分隔符)或“对象名._类名私有成员名”这样的特殊方式仍然可以访问。
例1:
class Mycls01:
x = 1 # 公有成员
_y = 2 # 保护成员
__z = 3 # 私有成员
# 通过类名访问:
print(Mycls01.x) # 公有成员能访问:1
print(Mycls01._y) # 保护成员也能访问:2
print(Mycls01._Mycls01__z) # 私有成员通过“类名._类名私有成员名”的方式可以访问:3
print(Mycls01.__z) # 私有成员不能直接用类名访问:AttributeError: type object 'Mycls01' has no attribute '__z'
# 通过对象名访问:
obj01 = Mycls01()
print(obj01.x) # 公有成员能访问:1
print(obj01._y) # 保护成员也能访问:2
print(obj01._Mycls01__z) # 私有成员通过“对象名._类名私有成员名”的方式可以访问:3
print(obj01.__z) # 私有成员不能直接用对象名访问:AttributeError: 'Mycls01' object has no attribute '__z'
例2:
class Mycls02:
def __init__(self):
self.a = 7 # 公有成员
self._b = 8 # 保护成员
self.__c = 9 # 私有成员
obj02 = Mycls02()
print(obj02.a) # 公有成员能访问:7
print(obj02._b) # 保护成员也能访问:8
print(obj02._Mycls02__c) # 私有成员通过“对象名._类名私有成员名”的方式可以访问:9
print(obj02.__c) # 私有成员不能直接用对象名访问:AttributeError: 'Mycls02' object has no attribute '__c'
3.5 静态方法
静态方法的定义如下:
- 通过装饰器@staticmethod定义静态方法;
- @staticmethod必须写在方法上;
- 在静态方法中访问实例属性和实例方法会导致错误;
- 调用格式:“类名.静态方法名(参数列表)”。
例1::
class Person:
# 类属性
school = "SWPU"
tuition = 100000
count = 0
# 实例属性
def __init__(self,name,age):
self.name = name
self.age = age
Person.count = Person.count+1
# 静态实例
@staticmethod
def addNum(a,b):
print("{0}+{1}={2}".format(a,b,a+b))
return a+b
# 实例方法
def get_score(self):
print("姓名:{0};年龄:{1}".format(self.name,self.age))
stu1 = Person("Zhang", 22)
stu1.get_score()
Person.addNum(1,2)
4 继承
继承用于类的创建上,新创建的叫子类,而被继承的叫做父类。子类可以使用父类属性,继承是描述类与类之间的关系。
为什么要用继承呢?因为继承可以减少代码的冗余以及提高代码的重用性。我们在工作中,用到继承的地方很多。
Python里继承总共有单继承、多继承和多层继承。
4.1 单继承
单继承指的是子类只继承一个父类。
例1:
class A():
def __init__(self):
self.a = 'a'
def test_a(self):
print("aaaa")
class B(A):
def __init__(self):
self.b = 'b'
def test_b(self):
self.test_a() #调用A类的方法test_a
print("bbbb")
obj = B()
obj.test_b()
4.2 多继承
多继承指的是子类继承了多个父类。
class A():
def __init__(self):
self.a = 'a'
def test_a(self):
print("aaaa")
class B():
def __init__(self):
self.b = 'b'
def test_b(self):
print("bbbb")
class C(A, B):
def __init__(self):
self.c = 'c'
def test_c(self):
self.test_a()
self.test_b()
例子中,C类就分别继承了A类和B类的方法。 多层继承就是指子类继承的父类也有继承别的类,这里就不举例了。
4.3 子类重写父类方法
在某些场景下,子类继承了父类的属性和方法,但子类有同名的方法,这时候就需要重写子类的方法了。
class A():
def __init__(self):
self.a = 'a'
def test(self):
print("aaaa")
class B(A):
def __init__(self):
self.b = 'b'
super().__init__()
def test(self):
print("bbbb")
例子中B类继承A类的属性和test()方法,但B也有test方法,那么只需要在B类中重新定义个test()方法即可。
4.4 继承中的注意事项
- 子类如果重写了__init__方法,子类就不会自动继承父类__init__中的属性。如果要继承父类的属性,需要用到super方法,我们在B类的__init__方法中加上:
super(子类,self).__init__(参数0,参数1...)
父类名称.__init__(self,参数0,参数1...)
若继承父类的所有属性就直接用:
super().__init__()