Python 面向对象
目录
面向过程 V.S. 面向对象
面向过程(procedure oriented):关注程序的逻辑流程,按照步骤进行
面向对象(object oriented):关注软件中对象之间的关系
简单数据 -> 数组 -> 结构体 -> 对象
将不同类型的数据、方法放到一起,就是对象
面向对象可以帮助从宏观上把握、分解整个系统,具体到实现部分的方法,仍然需要面向过程的思路去处理
类的定义
类定义数据类型的属性(数据)和方法(行为),也就是说,类将行为和状态打包在一起
从一个类创建对象时,每个对象会共享这个类的行为(类中定义的方法),但会有自己的属性值(不共享状态)
类名一般首字母大写,多个单词采用驼峰原则
class Student
# def__init__(self):
# self.name = "Jason"
# self.score = 60
def __init__(self, name, score):
self.name = name
self.score = score
def print_score(self):
print("{0}'s score is {1}".format(self.name, self.score))
Student1 = Student("Jason", 80) # 调用构造方法
Student1.print_score()
object 根类
通过类的方法mro()或者类的属性__mro__可以输出这个类的继承层次结构
class A:pass
class B(A):pass
class C(B):pass
print(C.mro())
[<class '__main__.C'>, <class '__main__.B'>, <class '__main__.A'>, <class 'object'>]
object 类是所有类的父类,因此所有的类都有object 类的属性和方法
dir()查看对象属性
方法,实际上也是属性。只不过这个属性的类型是“method”而已
例子:
age <class ‘int’>
name <class ‘str’>
say_age <class ‘method’> #方法
构造函数
类是抽象的,也称之为“对象的模板”
通过类这个模板,创建类的实例对象,然后使用类定义的功能。
一个Python 对象包含如下部分:
- id
- type
- value
- 属性(attribute)
- 方法(method)
创建对象,我们需要定义构造函数__init__()方法,用于执行“实例对象的初始化工作”,即对象创建后,初始化当前对象的相关属性,无返回值
第一个参数固定,必须为:self(可以修改),self指的就是刚建好的实例对象
通过类名调用构造函数,创建新的对象(方法共用)
Python 中的self 相当于C++中的self 指针,JAVA 和C#中的this关键字
实例属性 & 实例方法
self.实例属性名 = 初始值
实例方法也可看作属性
def 方法名(self [, 形参列表])
函数体
调用:对象.方法名([实参列表])
调用实例方法时,不需要也不能给self 传参,self 由解释器自动传参
其他操作:
- dir(obj)可以获得对象的所有属性、方法
- obj.__dict__ 对象的属性字典
- pass 空语句
- isinstance(对象,类型) 判断“对象”是不是“指定类型”
类对象、类属性、类方法、静态方法
类对象
当解释器执行class语句时,就会创建一个类对象(<class ‘type’>)
类属性(类变量)
class 类名:
类变量名= 初始值
或通过:“类名.类变量名”来读写
class Student:
age = 18 # 类属性
def __init__(self, name, score):
self.name = name # 实例属性
self.score = score # 实例属性
def print_score(self): # 实例方法
print("{0}'s score is {1}".format(self.name, self.score))
Student1 = Student("Jason", 80) # 调用构造方法
Student1.print_score()
类方法
类方法通过装饰器@classmethod 来定义,格式如下:
@classmethod
def 类方法名(cls [,形参列表]) :
函数体
要点如下:
- @classmethod 必须位于方法上面一行
- 第一个cls 必须有;cls 指的就是“类对象”本身;
- 调用类方法格式:“类名.类方法名(参数列表)”。参数列表中,不需要也不能给cls 传值
- 类方法中访问实例属性和实例方法会导致错误
- 子类继承父类方法时,传入cls 是子类对象,而非父类对象
静态方法
“静态方法”和在模块中定义普通函数没有区别,只不过“静态方法”放到了“类的名字空
间里面”,需要通过“类调用”
不需要传入cls,可用于定义与类别属性无关的方法
@staticmethod
def 静态方法名([形参列表]) :
函数体
要点如下:
- @staticmethod 必须位于方法上面一行
- 调用静态方法格式:“类名.静态方法名(参数列表)”。
- 静态方法中访问实例属性和实例方法会导致错误
class Student:
age = 18 # 类属性
def __init__(self, name, score):
self.name = name # 实例属性
self.score = score # 实例属性
@classmethod
def print_age(cls):
print(cls.age)
@staticmethod
def add(a,b):
print(a+b)
def print_score(self): # 实例方法
print("{0}'s score is {1}".format(self.name, self.score))
Student1 = Student("Jason", 80) # 调用构造方法
Student1.print_score()
Student.print_age()
Student.add(10, 20)
方法
特殊方法
Python 的运算符实际上是通过调用对象的特殊方法实现的,比如:
a = 20
b = 30
c = a+b
d = a.__add__(b)
print("c=",c)
print("d=",d)
运算结果:c= 50,d= 50
常见的特殊方法统计如下:
__del__方法(析构函数)和垃圾回收机制
__del__方法用于实现对象被销毁时所需的操作
Python的自动垃圾回收机制:当对象没有被引用时(引用计数为0),由垃圾回收器调用__del__方法
也可以通过del语句调用__del__方法
class Student:
def __del__(self):
print("销毁对象{0}".format(self))
p1 = Student()
p2 = Student()
del p2
print("程序结束")
销毁对象<__main__.Student object at 0x000001B460741160>
程序结束
销毁对象<__main__.Student object at 0x000001B4605B8460>
Process finished with exit code 0
__call__方法和可调用对象
定义了__call__方法的对象称为可调用对象,即该对象可以像函数一样被调用
class Computation:
def __call__(self, a, b):
print("computation result")
return a + b
test = Computation()
print(test.__call__(10, 20))
print(test(10, 20))
computation result
30
computation result
30
Process finished with exit code 0
Python中的方法没有重载
在其他语言中,可以定义多个重名的方法,只要保证方法签名唯一即可;方法签名包含3
个部分:方法名、参数数量、参数类型
Python 中,方法的的参数没有声明类型(调用时确定参数的类型),参数的数量也可以由
可变参数控制,因此,Python 中是没有方法的重载的
如果在类体中定义了多个重名的方法,只有最后一个方法有效 -> 建议:不要使用重名的方法
方法的动态性
Python 是动态语言,可以动态的为类添加新的方法,或者动态的修改类的已有的方法
#测试方法的动态性
class Person:
def work(self):
print("努力上班!")
def play_game(self):
print("{0}玩游戏".format(self))
def work2(s):
print("好好工作,努力上班!")
Person.play = play_game
Person.work = work2
p = Person()
p.play()
p.work()
特殊属性
属性和方法命名总结
- _xxx:保护成员,不能用“from module import * ”导入,只有类对象和子类对象能访问这些成员
- __xxx__:系统定义的特殊成员
- __xxx: 类中的私有成员,只有类对象自己能访问,子类对象也不能访问。(但,在类外部可以通过“对象名. _类名__xxx”这种特殊方式访问,Python 不存在严格意义的私有成员)
方法和属性都遵循上面的规则
类编码风格
- 类名首字母大写,多个单词之间采用驼峰原则
- 实例名、模块名采用小写,多个单词之间采用下划线隔开
- 每个类,应紧跟“文档字符串”,说明这个类的作用
- 可以用空行组织代码,在类中,使用一个空行隔开方法;模块中,使用两个空行隔开多个类
面向对象的三大特征
- 封装(隐藏)
通过私有属性和方法的方式实现封装 - 继承
让子类具有父类的特性,提高了代码的重用性 - 多态
多态是指一个方法的调用,由于对象不同,会产生不同的行为
封装(私有属性和私有方法)
- 通常,两个下划线开头的属性是私有的(private),其他为公共的(public);
- 类内部可以访问私有属性(方法);
- 类外部不能直接访问私有属性(方法);
- 类外部可以通过“_类名__私有属性(方法)名”访问私有属性(方法);
方法本质上也是属性
# test private feature
class Student:
def __init__(self, name, age):
self.name = name
self.__age = age
def __show(self):
print("The student's name is {0}.".format(self.name))
print("The student's age is {0}.".format(self.__age)) # 内部可以调用
Student1 = Student("Jason", 18)
print(Student1.name) # 可直接从外部访问
print(Student1._Student__age) # 不可直接从外部访问
Student1._Student__show() # 私有方法
@property 装饰器
@property可以将一个方法的调用方式变成“属性调用”,增加读写操作(getter,setter)
对于某一个属性,我们可以直接通过:emp1.salary = 30000
但是,这种做法不安全,需要增加限制,要通过getter、setter 方法来处理
# get & set 方法
class Student:
def __init__(self, name, age):
self.name = name
self.__age = age
def get_age(self):
return self.__age
def set_age(self, age):
if 0 < age <= 18:
self.__age = age
else:
print("Age out of range!")
@property
def __show(self):
print("The student's name is {0}.".format(self.name))
print("The student's age is {0}.".format(self.__age)) # 内部可以调用
Student1 = Student("Jason", 18)
print(Student1.name) # 可直接从外部访问
print(Student1._Student__age) # 不可直接从外部访问
Student1.get_age()
Student1.set_age(22)
Student1.set_age(15)
# Student1._Student__show() # 私有方法
Student1._Student__show # 装饰器调用
# getter setter of property
class Student:
def __init__(self, name, age):
self.name = name
self.__age = age
@property # 属性调用
def age(self):
return self.__age
@age.setter
def age(self, age):
if 0 < age <= 18:
self.__age = age
else:
print("Age out of range!")
Student1 = Student("Jason", 18)
Student1.age = 20
继承
如果一个新类继承自一个设计好的类,就直接具备了已有类的特征,就大大降低了工作难度
Python 支持多重继承,一个子类可以继承多个父类。继承的语法格式如下:
class 子类类名(父类1[,父类2,…]):
类体
如果在类定义中没有指定父类,则默认父类是object 类,也就是说,object 是所有类的父类,里面定义了一些所有类共有的默认实现,比如:__new__()
定义子类时,必须在其构造函数中调用父类的构造函数,调用格式如下:
父类名.__init__(self, 参数列表)
# object -> person -> student
class Person:
def __init__(self, name, age, gender):
self.name = name
self.age = age
self.__gender = gender
def get_age(self):
print("Age is {0}.".format(self.age))
class Student(Person):
def __init__(self, name, age, gender, grade):
Person.__init__(self, name, age, gender) # 不然解释器不会调用
self.grade = grade
Student1 = Student("Jason", 18, "boy", 80)
Student1.get_age()
print(Student1._Person__gender) # 父类私有属性的调用
类成员的继承和重写
- 成员继承:子类继承了父类除构造方法之外的所有成员
- 方法重写:子类可以重新定义父类中的方法,这样就会覆盖父类的方法,也称为“重写”
重写__str__( )方法
object 有一个__str__()方法,用于返回一个对于“对象的描述”,对应于内置函数str(),经常用于print()方法,
class Person:
def __init__(self,name,age):
self.name = name
self.__age = age
def __str__(self):
'''将对象转化成一个字符串,一般用于print 方法'''
return "名字是:{0},年龄是{1}".format(self.name,self.__age)
p = Person("Jason",18)
print(p)
多重继承
Python支持多重继承,一个子类可以有多个直接父类
尽量避免使用造成复杂的类的整体层次
在子类没有指定父类名时,解释器将
“从左向右”按顺序搜索 -> MRO (Method Resolution Order):方法解析顺序,获得“类的层次结构”
super( )获得父类的定义
class A:
def say(self):
print("A: ",self)
print("say AAA")
class B(A):
def say(self):
#A.say(self) 调用父类的say 方法
super().say() #通过super()调用父类的方法
print("say BBB")
组合
“is-a”关系,我们可以使用“继承”,从而实现子类拥有的父类的方法和属性,例如:MobilePhone is an electrical device
“has-a”关系,我们可以使用“组合”,也能实现一个类拥有另一个类的方法和属性,例如:MobilePhone has a CPU
#组合测试
class MobilePhone:
def __init__(self,cpu,screen):
self.cpu = cpu
self.screen = screen
class CPU:
def calculate(self):
print("计算")
class Screen:
def show(self):
print("显示画面")
c = CPU()
s = Screen()
m = MobilePhone(c,s)
m.cpu.calculate() #通过组合,我们也能调用cpu 对象的方法,相当于手机对象间接拥有了“cpu 的方法”
m.screen.show()
多态
同一个方法调用由于对象不同可能会产生不同的行为
- 多态是方法的多态,属性没有多态
- 多态的存在有2 个必要条件:继承、方法重写
class Animal:
def shout(self):
print("动物叫了一声")
class Dog(Animal):
def shout(self):
print("小狗,汪汪汪")
class Cat(Animal):
def shout(self):
print("小猫,喵喵喵")
def animalShout(a):
if isinstance(a,Animal):
a.shout() #传入的对象不同,shout 方法对应的实际行为也不同
animalShout(Dog())
animalShout(Cat())
isinstance() 函数来判断一个对象是否是一个已知的类型,类似 type()
isinstance() 与 type() 区别:
- type() 不会认为子类是一种父类类型,不考虑继承关系
- isinstance() 会认为子类是一种父类类型,考虑继承关系
如果要判断两个类型是否相同推荐使用 isinstance()
语法
isinstance(object, classinfo)
设计模式
设计模式是面向对象语言特有的内容,是我们在面临某一类问题时候固定的做法
工厂模式
工厂模式实现了创建者和调用者的分离,使用专门的工厂类将选择实现类、创建对象进行统一的管理和控制
class CarFactory:
def createCar(self,brand):
if brand == "Benz":
return Benz()
elif brand == "BMW":
return BMW()
else:
return "未知品牌"
class Benz:
pass
class BMW:
pass
class BYD:
pass
factory = CarFactory() # 首先创建统一管理的工厂类
c1 = factory.createCar("Benz") # 在工厂类下创建品牌
单例模式
单例模式(Singleton Pattern)的核心作用是确保一个类只有一个实例,并且提供一
个访问该实例的全局访问点
单例模式只生成一个实例对象,减少了对系统资源的开销。当一个对象的产生需要比较
多的资源,如读取配置文件、产生其他依赖对象时,可以产生一个“单例对象”,然后永久
驻留内存中,从而极大的降低开销
单例模式有多种实现的方式,推荐重写__new__()的方法
__obj = None
__init_flag = True
def __new__(cls, *args, **kwargs):
if cls.__obj == None: # 只在实例不存在的时候才创建
cls.__obj = object.__new__(cls)
return cls.__obj
def __init__(self,name):
if MySingleton.__init_flag: # 只初始化一次 否则每次调用都会重新初始化
print("init....")
self.name = name
MySingleton.__init_flag = False