一、⾯向对象 和 ⾯向过程
对于⼤型程序,其逻辑和功能都很复杂,如果 按照业务逻辑 去想,我们往往⽆从下⼿。
⾯向对象编程 (OOP,Object Oriented Programming)可以理解为 将具有相互关系的数据/操作封装成对象,以对象的⻆度去处理问题,让对象来完成相应处理
按照业务逻辑 编写五⼦棋程序,过程应该是这样的:
1.先显示棋盘() 2.执⿊棋⼿下⼦() 3.接着刷新界⾯() 4.然后判断⿊⼦是否胜出() 5.执⽩棋⼿下⼦() 6.接着刷新界⾯() 7.然后判断⽩⼦是否胜出() 8.重复2-7
按照业务逻辑 从上到下 设计程序 的⽅式,叫做 ⾯向过程编程(Procedure Oriented Programming,POP,⾯向过程程序设计)
⾯向过程编程 最易被初学者接受,其往往⽤⼀⻓段代码来实现指定功能,⼀步接⼀步,环环相扣
对于⼤型项⽬,如果使⽤⾯向过程编程,则很容易导致 代码结构过于紧密、耦合度⾼,易出现代码冗余,并且 不利于团队开发
以对象的视⻆ 编写五⼦棋程序,过程是这样的:
1. 根据 数据和操作 抽象出对象: 棋⼿ 棋盘 计算系统 2. 各对象实现⾃⼰的功能: 棋⼿.下⼦ 棋盘.刷新界⾯ 计算系统.判断胜负 .. 3. 使⽤对象执⾏操作 来完成业务逻辑: 棋盘.刷新界⾯ 棋⼿.下⼦ 计算系统.判断胜负 ...
⾯向对象编程 的优点
将数据和业务抽象为 对象,有利于程序整体结构的分析和设计,使设计思路更加清晰
业务以对象为单位,对象各⾃完成⼯作,减少了代码耦合度,有助于业务升级 和 代码重构
⽅便⼯作划分,有利于提⾼团队开发的效率。
二、类和对象
对象的组成
对象中包含两个组成部分:
属性: ⽤于记录与对象相关的数据,⽐如姓名,年龄,身⾼,肤⾊等
⽅法: ⽤于实现与对象相关的操作,⽐如吃饭,睡觉,⻜⾏,歌唱等
类
描述共同⾏为的集合,称为 类 (class)
很多事物存在 相同的操作/⾏为,⽐如⼈都进⾏吃饭、睡觉,狗都会跑会叫等等
类是总结事物特征的 抽象概念,⽽对象是 具体存在的某个实物
类和对象的关系
在编程中,类就是创建对象的模板 或者说 制造⼿册,⽤来定义对象公共的⾏为
类总结了对象的共同特征,有利于复⽤代码创建拥有相同特征的对象
每个对象必须有⼀个对应的类
三、定义类和定义方法
定义类
定义⼀个类,格式如下:
class 类名: ⽅法列表
类名 的命名规则按照"⼤驼峰"
定义方法
类是定义对象的共同⾏为的,也就是说 在类中定义对象的⽅法
定义⽅法:
class 类名: def ⽅法名(self): ...
⽅法的格式和函数类似,也可以设置参数和返回值,但是 需要设置第⼀个参数为 self。
注意定义⽅法需要在类的缩进中
demo:定义⼀个Car类
# 定义类 class Cat: # ⽅法 def eat(self): print("猫在吃⻥....") def drink(self): print("猫在喝可乐...")
四、创建对象
python中,可以根据已经定义的类去创建出对象
创建对象的格式为:
引⽤对象的变量名 = 类名()
创建对象demo:
# 根据类,创建⼀个对象
tom = Cat()
调用方法:
引⽤对象的变量名.⽅法名()
注意:虽然定义⽅法时设置第⼀个参数 self ,但是 调⽤⽅法时不要传递对应 self 的参数。
# 创建了⼀个对象 tom = Cat() tom.eat() # 调⽤对象的eat⽅法 tom.drink()
定义/使⽤属性
定义/设置属性 格式:
引⽤对象的变量名.属性名 = 数据
属性和变量类似,⾸次赋值时会定义属性:
# 创建对象 tom = Cat() # ⾸次赋值属性,会定义属性 tom.age = 3 # 定义属性后,再赋值属性,会修改属性保存的数据 tom.age = 1 # 获取属性值并打印 print(tom.age)
第一次给对象的属性赋值就是定义一个属性,再次赋值只是修改属性记录的值。
五、self关键字
关键字 self 主要⽤于对象⽅法中,表示调⽤该⽅法的对象。
某个对象调⽤其⽅法时,python解释器会把这个对象作为第⼀个参数传递给⽅法,所以开发者只需要在定义⽅法时 “预留” 第⼀个参数为 self即可
class Dog: def eat(self): print("%s在吃东西" % self.name) dog1 = Dog() dog1.name = "旺财" dog1.eat() dog2= Dog() dog2.name = "来福" dog2.eat()
结果:
旺财在吃东西
来福在吃东西
六、__init__()⽅法定义属性
在Python中,__xx__() 双下划线的方法一般叫魔法方法(运算符重载方法),都具有特殊含义,并且在特定的情况下自动调用。
__init__() ⽅法叫做 对象的初始化⽅法,主要用于对象的初始化操作,如定义属性。在 创建⼀个对象后默认会被调⽤,不需要⼿动调⽤
class Dog: def __init__(self): self.type = "小型动物" def eat(self): print("%s在吃东西" % self.name) dog1 = Dog() # 给对象开辟内存空间 print(dog1.type)
结果:小型动物
__init__() ⾃定义参数
__init__(self) 除了默认参数 self ,还可以设置任意个数的⾃定义参数,例如 __init__(self,x,y,z)
init⽅法 设置的⾃定义参数必须和创建对象时传递的参数保持⼀致,例如 tom = Cat(x,y,z)
开发者可以 设置⾃定义参数,为对象的默认属性提供 不同的初始值
开发中,⼀般会在 __init__() 中定义对象的属性。
开发者可以 实现这个⽅法,并在该⽅法中定义属性并设置初始值
想要自定义属性的初始值,可以设置init的自定义参数。
init自定义参数对应的实参在创建对象的圆括号里边传递。
class Dog: def __init__(self,name): # 给自定义方法添加相应的形参,叫自定义构造方法 self.type = "小型动物" self.name = name # 自定义对象属性的初始值 def eat(self): print("%s在吃东西" % self.name) dog1 = Dog("旺财") print(dog1.name) dog2 = Dog("来福") print(dog2.name)
七、__str__()⽅法
打印dog1对象时,输出的是dog1的地址
class Dog: def __init__(self,name): # 给自定义方法添加相应的形参,叫自定义构造方法 self.type = "小型动物" self.name = name # 自定义对象属性的初始值 def eat(self): print("%s在吃东西" % self.name) dog1 = Dog("旺财") print(dog1.name) print(dog1) dog2 = Dog("来福") print(dog2.name) print(dog2)
结果
旺财 <__main__.Dog object at 0x000002A89F753FD0> 来福 <__main__.Dog object at 0x000002A89F753F10>
如果我们想输出某些特定的内容,则需要使用 __str__方法
class Dog: def __init__(self,name): # 给自定义方法添加相应的形参,叫自定义构造方法 self.type = "小型动物" self.name = name # 自定义对象属性的初始值 def eat(self): print("%s在吃东西" % self.name) def __str__(self): return "我是%s"%(self.name) dog1 = Dog("旺财") print(dog1.name) print(dog1) dog2 = Dog("来福") print(dog2.name) print(dog2)
结果
旺财
我是旺财
来福
我是来福
print输出对象时,会自动调用 __str__,return的内容会被输出。
如果没有定义__str__方法,直接print打印对象,会看到创建出来的对象在内存中的地址,只要对象定义了 __str__(self) ⽅法,就会 打印该⽅法return的信息描述。
八、私有属性
定义set方法用来赋值,定义get方法用来取值。
class Dog: def __init__(self): # 给自定义方法添加相应的形参,叫自定义构造方法 self.age = None def setAge(self,age): if age > 0: self.age = age def getAge(self): return self.age dog1 = Dog() dog1.setAge(-10) # set方法赋值 print(dog1.getAge()) # get方法取值 dog1.setAge(10) print(dog1.getAge()) dog1.age = -9 print(dog1.getAge())
以上代码虽然避免了脏数据,但是外部仍然可以访问age属性。此时我们需要将age属性私有化
class Dog: def __init__(self): # 给自定义方法添加相应的形参,叫自定义构造方法 self.__age = None def setAge(self,age): if age > 0: self.__age = age def getAge(self): return self.__age dog1 = Dog() dog1.setAge(-10) print(dog1.getAge()) dog1.setAge(10) print(dog1.getAge()) dog1.age = -9 print(dog1.getAge())
结果:
None 10 10 #外部设置值未成功
私有属性只能在类的内部访问,格式:
属性名前加双下划线,如__age
属性私有化之前,需要提供get和set方法来让外部通过调用方法的形式间接的访问私有属性。定义一对get和set方法,在set方法中对数据进行安全判断后再使用。
为了避免属性被设置为 脏数据,更好的保护属性安全,⼀般的处理⽅式为
1)、添加⼀个set⽅法,在⽅法内部先判断数据的有效性,再赋值属性
2)、将属性定义为 私有属性,避免对象在外部直接操作属性
如果在属性名前⾯加了2个下划线'__',则表明该属性是私有属性,否则为公有属性
私有属性只能在类的内部访问
九、私有方法
私有方法和私有属性类似,也是只能在类的内部使用,在方法名字前面加双下划线。
class Dog: def __init__(self): # 给自定义方法添加相应的形参,叫自定义构造方法 self.__age = None def setAge(self,age): if age > 0: self.__age = age else: self.__info("age") def getAge(self): return self.__age def __info(self,property_name): print("%s属性赋值不成功" % property_name) dog1 = Dog() dog1.setAge(-10) print(dog1.getAge()) dog1.setAge(10) print(dog1.getAge())
私有⽅法和私有属性的 设计⽬的主要有两个:
1)、保护数据或操作的安全性
2)、向使⽤者隐藏核⼼开发细节
十、__del__() ⽅法
作用:设置对象被删除前的“临终遗言”,也可以检查当前某个对象是否被删除了。当对象被删除时会自动调用。
创建对象后,python解释器默认调⽤ __init__() ⽅法;
class Dog: def __init__(self,name): self.name = name def __del__(self): print("%s 要被删除了"% self.name) dog1 = Dog("旺财") del dog1 print("*****************")
结果:
旺财 要被删除了
*****************
当有1个变量保存了某个对象的引⽤时,此对象的引⽤计数就会加1,即此时dog1计数会加1.
当使⽤del删除变量dog1时,只有当变量指向的对象的所有引⽤都被删除后,由于没有变量指向该对象,也就是引⽤计数为0时,才会真删除该对象(释放内存空间),这时才会触发 __del__() ⽅法
class Dog: def __init__(self,name): self.name = name def __del__(self): print("%s 要被删除了"% self.name) dog1 = Dog("旺财") a = dog1 del dog1 print("*****************")
虽然删除了dog1变量,但是仍然有a变量指向对象,所以先执行打印操作,等代码执行完,a变量才会被回收,所以对象才会被释放,从而触发__del__方法。
class Dog: def __init__(self,name): self.name = name def __del__(self): print("%s 要被删除了"% self.name) def main(): dog1 = Dog("旺财") main() print("*****************")
由于函数内部定义的变量是局部变量,函数执行完成就被销毁了。
在开发中,如果项目比较复杂时,可以用此方法来验证对象是否能正常销毁。
十一、继承
⾯向对象三⼤特性: 封装 继承 多态
继承:某个类直接具备另⼀个类的能⼒(属性和⽅法)
格式:
class ⼦类名(⽗类名):
作用:减少代码冗余,提升代码可读性。
class Animal: def eat(self): print("-----吃-----") def drink(self): print("-----喝-----") class Dog(Animal): pass dog1 = Dog() dog1.eat() dog1.drink()
结果:
-----吃----- -----喝-----
子类添加新功能
class Animal: def eat(self): print("-----吃-----") def drink(self): print("-----喝-----") class Dog(Animal): def bark(self): print("-----汪汪叫------") dog1 = Dog() dog1.eat() dog1.drink() dog1.bark()
多层继承
class Animal: def eat(self): print("-----吃-----") def drink(self): print("-----喝-----") class Dog(Animal): def bark(self): print("-----汪汪叫------") class XTQ(Dog): """定义了⼀个哮天⽝ 类""" pass dog1 = XTQ() dog1.eat() dog1.drink() dog1.bark()
结果:
-----吃----- -----喝----- -----汪汪叫------
注意:
1)、⽗类中的 私有⽅法、私有属性,不会被⼦类继承
2)、可以通过调⽤继承的⽗类的共有⽅法,间接的访问⽗类的私有⽅法、属性
class Animal: def __init__(self): self.__num2 = 2 def __run(self): print("-----跑-----") def eat(self): print("-----吃-----") def drink(self): print("-----喝-----") class Dog(Animal): def bark(self): print("-----汪汪叫------") self.__run()
print(self.__num2) dog1 = Dog() dog1.bark()
结果:
Traceback (most recent call last): File "F:\work\pythonProject\SecondDemo\ClassAndObject.py", line 99, in <module> dog1.bark() File "F:\work\pythonProject\SecondDemo\ClassAndObject.py", line 96, in bark self.__run() AttributeError: 'Dog' object has no attribute '_Dog__run' -----汪汪叫------
私有方法也是一样的.
class Animal: def __init__(self): self.__num2 = 2 def __run(self): print("-----跑-----") def eat(self): print("-----吃-----") def drink(self): print("-----喝-----") def test(self): print(self.__num2) self.__run() class Dog(Animal): def bark(self): print("-----汪汪叫------") # self.__run() # print(self.__num2) dog1 = Dog() dog1.test()
结果:
2
-----跑-----
十二、重写⽗类⽅法
当⼦类实现⼀个和⽗类同名的⽅法时,叫做 重写⽗类⽅法
⼦类重写了⽗类⽅法,⼦类再调⽤该⽅法将不会执⾏⽗类的处理。
使用场景:当父类的某个方法不能满足我们的需求,重写父类方法,做自己需要的功能。
class Animal: def eat(self): print("-----吃-----") def drink(self): print("-----喝-----") class Dog(Animal): def bark(self): print("-----汪汪叫------") class XTQ(Dog): def bark(self): print("----嗷嗷叫-----") dog1 = XTQ() dog1.bark()
结果:
----嗷嗷叫-----
调⽤被重写的⽗类⽅法
⼦类重写了⽗类⽅法,仍然想执⾏⽗类中的⽅法,两种方式:
方式一:父类名.方法名(当前对象),注意点:需要手动传递对象
class Animal: def eat(self): print("-----吃-----") def drink(self): print("-----喝-----") class Dog(Animal): def bark(self): print("-----汪汪叫------") class XTQ(Dog): def bark(self): Dog.bark(self) dog1 = XTQ() dog1.bark()
方式二:super(类名,对象).方法名()
class Animal: def eat(self): print("-----吃-----") def drink(self): print("-----喝-----") class Dog(Animal): def bark(self): print("-----汪汪叫------") class XTQ(Dog): def bark(self): super(XTQ,self).bark() dog1 = XTQ() dog1.bark()
方式二的简写:super().方法名()
class Animal: def eat(self): print("-----吃-----") def drink(self): print("-----喝-----") class Dog(Animal): def bark(self): print("-----汪汪叫------") class XTQ(Dog): def bark(self): super().bark() dog1 = XTQ() dog1.bark()
十三、多继承
类名.__mro__查看类的继承链
多继承中,多个父类有同名方法时,想要调用指定父类的方法,先重写此方法,再指定 父类名.方法(self)
class A: def printA(self): print('----A----') # 定义⼀个⽗类 class B: def printB(self): print('----B----') # 定义⼀个⼦类,继承⾃A、B class C(A,B): def printC(self): print('----C----') obj_C = C() obj_C.printA() obj_C.printB()
结果:
----A---- ----B----
如果在上⾯的多继承例⼦中,如果⽗类A和⽗类B中,有⼀个同名的⽅法,那么通过⼦类去调⽤的时候,调⽤哪个?
class A: def print(self): print('----A----') # 定义⼀个⽗类 class B: def print(self): print('----B----') # 定义⼀个⼦类,继承⾃A、B class C(A,B): def print(self): # A.print(self) B.print(self) obj_C = C() print(C.__mro__) obj_C.print()
结果:
(<class '__main__.C'>, <class '__main__.A'>, <class '__main__.B'>, <class 'object'>) ----B----
十四、多态
多态的概念是应⽤于Java和C#这⼀类强类型语⾔中,⽽Python崇尚“鸭⼦类型”。
所谓多态:定义时的类型和运⾏时的类型不⼀样,此时就成为多态。即需要使用父类对象的地方,也可以使用子类对象。
Python伪代码实现Java或C#的多态
Python “鸭⼦类型”:可以不是鸭子,长得像就行。并不强制数据的类型,只要数据完成了需要的处理,就可以执行,Python动态语言独有的特性。几乎可以理解为没有多态。
鸭⼦类型语⾔中,函数/⽅法可以接受⼀个任意类型的对象作为参数/返回值,只要该对象实现了代码后续⽤到的属性和⽅法就不会报错
鸭子类型是多态的升级版,
class F1(object): def show(self): print('F1.show') class S1(F1): def show(self): print('S1.show') class S2(F1): def show(self): print('S2.show') def Func(obj): obj.show() s1_obj = S1() Func(s1_obj) s2_obj = S2() Func(s2_obj)
结果
S1.show
S2.show
十五、实例属性、类属性
属性记录的数据不同时,定义实例属性。属性记录的数据始终相同时,定义类属性(节省内存)
1、类属性定义格式:在类的内部,方法的外部,直接向定义变量一样。
2、类属性也可以私有,私有后也只能在类的内部使用,前面加双下划线。
3、修改类属性时,只能通过类对象来修改。
4、实例对象不能修改类属性,而是创建了实例对象自己的属性
5、类对象和实例对象都可以访问类属性
注意:尽量不要类属性和实例属性同名,如果同名时,实例对象会访问自己实例属性,类对象访问类属性。如果有同名对象属性,实例对象会优先访问对象属性
实例属性:
类属性:
类属性就是 类对象 所拥有的属性,它被 该类的所有实例对象 所共有。
类属性可以使⽤ 类对象 或 实例对象 访问
class Dog: type = "狗" # 类属性 dog1 = Dog() dog1.name = "旺财" dog2 = Dog() dog2.name = "来福" # 类属性 取值 print(Dog.type) # 结果:狗 print(dog1.type) # 结果:狗 print(dog2.type) # 结果:狗
使⽤场景:
类的实例 记录的某项数据 始终保持⼀致时,则定义类属性
实例属性 要求 每个对象 为其 单独开辟⼀份内存空间 来记录数据,⽽ 类属性 为全类所共有 ,仅占⽤⼀份内存,更加节省内存空间。
十六、类⽅法、静态⽅法
类⽅法:
类对象所拥有的⽅法
需要⽤装饰器 @classmethod 来标识其为类⽅法,对于类⽅法,第⼀个参数必须是类对象,⼀般以 cls 作为第⼀个参数。
class Dog(object): __type = "狗" # 类⽅法,⽤classmethod来进⾏修饰 @classmethod def get_type(cls): return cls.__type print(Dog.get_type())
使⽤场景:
当⽅法中 需要使⽤类对象 (如访问私有类属性等)时,定义类⽅法
类⽅法⼀般和类属性配合使⽤
静态⽅法
需要通过装饰器 @staticmethod 来进⾏修饰,静态⽅法既不需要传递类对象也不需要传递实例对象(形参没有self/cls)。
静态⽅法 也能够通过 实例对象 和 类对象 去访问。
class Dog(object): type = "狗" def __init__(self): name = None # 静态⽅法 @staticmethod def introduce(): # 静态⽅法不会⾃动传递实例对象和类对象 print("⽝科哺乳动物,属于⻝⾁⽬..") dog1 = Dog() Dog.introduce() # 可以⽤ 实例对象 来调⽤ 静态⽅法 dog1.introduce() # 可以⽤ 类对象 来调⽤ 静态⽅法
结果:
⽝科哺乳动物,属于⻝⾁⽬..
⽝科哺乳动物,属于⻝⾁⽬..
使⽤场景:
2、取消不需要的参数传递,有利于 减少不必要的内存占⽤和性能消耗
注意点:
类中定义了同名的对象⽅法、类⽅法、静态⽅法时,调⽤⽅法会优先执⾏最后定义的⽅法
class Dog: def demo_method(self): print("对象⽅法") @classmethod def demo_method(cls): print("类⽅法") @staticmethod def demo_method(): # 被最后定义,调⽤时优先执⾏ print("静态⽅法") dog1 = Dog() Dog.demo_method() # 结果: 静态⽅法 dog1.demo_method() # 结果: 静态⽅法
结果
静态⽅法
静态⽅法