面向对象简介
python完全采用了面向对象的思想,是真正面向对象的变成语言,完全支持面向对象的基本功能,例如:继承、多态、封装等
python中,一切皆为对象。前面学习的数据类型、函数等都是对象
面向过程和面向对象思想
面向过程和面向对象的区别
面向过程和面向对象都是对软件分析、设计和开发的一种思想,它知道着人们以不同的方式去分析、设计和开发软件。C语言是一种典型的面向过程语言,java是一种典型的面向对象语言。
面向对象可以帮助我们从宏观上把握、从整体上分析整个系统。但是,具体到实现部分的围观操作(就是一个个方法),仍然需要面向过程的思路去处理。我们千万不要把面向过程和面向对象队里起来。他们是相辅相成的。面相对象离不开面向过程!
面向对象和面向过程总结
- 都是解决问题的思维方式,都是代码组织的方式
- 面向过程是一种"执行者思维",解决简单问题可以使用面向过程
- 面向对象时一种"设计者思维",解决复杂、需要协作的问题可以使用面向对象
类的定义
类可以看做是一个模版或者图纸,系统根据类的定义来造出对象。
类:我们叫做class。对象:我们叫做object,instance(实例)
python中,"一切皆为对象"。类也称为"类对象",类的实例也称为"实例对象"。
定义类的语法格式如下:
class Student(object):
def __init__(self, name, score, *jineng):
self.name = name
self.score = score
self.skill = jineng
def say_score(self):
print("{}的分数为{}".format(self.name, self.score))
def attack(self, skill):
print("{}使用了{}技能".format(self.name, skill))
def main(self):
"""
整个对象的主函数
:return:
"""
oder = input("请输入你要使用的技能1.九阴白骨爪,2.六脉神剑")
if oder == 1:
self.attack(self.skill[0])
else:
self.attack(self.skill[1])
if __name__ == '__main__':
student1 = Student("张三", 19, '九阴白骨爪', '六脉神剑')
# print(student1.skill[0])
student1.main()
python对象完整内存结构
类是抽象的,也称为"对象的模版"。
__init__构造方法和__new__方法
初始化对象,我们需要定义构造函数__init__()方法。构造方法用于执行"实例对象的初始化工作",即对象创建后,初始化当前对象的相关属性,无返回值。
class CarFactory(object):
__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):
if CarFactory.__init_flag:
print("init.....")
CarFactory.__init_flag = False
def createCar(self, brand):
if brand == "奔驰":
return Benz()
elif brand == "宝马":
return BMW()
elif brand == "比亚迪":
return BYD()
else:
return "位置品牌,无法创建"
在 Python 中,cls
和 self
的作用是不同的。
-
self
: 代表当前对象的实例。通常在实例方法中使用,以访问实例的属性和方法。 -
cls
: 代表当前类,通常在类方法或特殊方法(如__new__
)中使用,以访问类的属性和方法。
在你提供的代码中,cls
用于 __new__
方法,表示当前类 MySingleton
,而不是类的实例。这是用来创建类的实例而不是访问特定实例的属性。
所以,虽然两者的角色有相似之处(都是引用),但 cls
和 self
是在不同的上下文中使用的。
从GPT-4o获取更智能的答案
实例属性
实例属性是从属于实例对象的属性,也称为"实例变量"。他的使用有如下几个要点:
- 实例属性一般在__init__()方法中通过如下代码定义:
self.实例属性名=初始值 - 在本类的其他实例方法中,也是通过self进行访问
self.实例属性名 - 创建实例对象后,通过实例对象访问:
obj01=类名()
obj01.实例属性名=值
实例方法
函数和方法的区别
- 都是用来完成一个功能的语句块,本质一样。
- 方法调用时,通过对象来调用。方法从属于特定实例对象,普通函数没有这个特点
- 直观上看,方法定义时需要传递self,函数不需要
实例对象的方法调用本质
class Student(object):
def __init__(self, name, score, *jineng):
self.name = name
self.score = score
self.skill = jineng
def say_score(self):
print("{}的分数为{}".format(self.name, self.score))
def attack(self, skill):
print("{}使用了{}技能".format(self.name, skill))
def main(self):
"""
整个对象的主函数
:return:
"""
oder = input("请输入你要使用的技能1.九阴白骨爪,2.六脉神剑")
if oder == 1:
self.attack(self.skill[0])
else:
self.attack(self.skill[1])
if __name__ == '__main__':
student1 = Student("张三", 19, '九阴白骨爪', '六脉神剑')
# print(student1.skill[0])
Student.attack(student1, '九阴白骨爪')
student1.main()
其他操作
- dir(obj)可以获得对象的所有属性、方法
- obj.__dict__对象的属性字典
- pass空语句
- isintance(对象,类型)判断对象是不是指定类型
类属性
类属性是从属于"类对象"的属性,也称为"类对象"。由于,类属性从属于类对象,可以被所有实例对象共享
类属性的定义方式:
class 类名:
类变量名=初始值
在类中或者类的外面,我们可以通过:类名.类变量名来读写
内存分析实例对象和类对象创建过程
class Student(object):
company = "重庆交通大学" # 类属性
count = 0 # 类属性
def __init__(self, name, score):
self.name = name # 实例属性
self.score = score
Student.count = Student.count + 1
def say_score(self): # 实例方法
print("我的公司是:", Student.company)
print(self.name, '的分数为:', self.score)
if __name__ == '__main__':
s1 = Student('王总', 80) # s1是实例对象,自动调用__init__()方法
s2 = Student('张三', 70)
s1.say_score()
print('一共创建了{}个Student对象'.format(Student.count))
类方法
类方法是从属于"类对象"的方法。类方法通过装饰器@classmethod来定义,格式如下:
@classmethod
def 类方法(cls [,形参列表]):
方法体
注意事项
- @classmethod必须位于方法上面一行
- 第一个cls必须有;cls指的就是"类对象"本身
- 调用类方法格式:类名.类方法名(参数列表)。参数列表中,不需要也不能给cls传值
- 类方法中访问实例属性和实例方法会导致错误
- 子类继承父类方法时,传入clsshi子类对象,而非父类对象
对于类方法不可用实例对象中的数据。!!!
静态方法
python中允许定义与"类对象"无关的方法,称为"静态方法"
"静态方法"和在模块中定义的普通函数没有区别,只不过"静态方法"放到了"类的名字空间里面",需要通过"类调用"。
在 Python 中,staticmethod 函数是一种装饰器,用于将函数转换为静态方法。静态方法与实例方法和类方法不同,它们不需要类实例作为第一个参数,也不需要类作为第一个参数,因此可以在不需要访问类或实例属性的情况下调用。
静态方法通过装饰器@staticmethod来定义,格式如下:
@staticmethod
def 静态方法名([形参列表]):
方法体
注意事项:
- @staticmethod必须位于方法上面一行
- 调用静态方法格式:类名.静态方法名(参数列表)
- 静态方法中访问实例属性和实例方法会导致错误
静态对象也不能使用实例对象的数据!!!
class Student(object):
company = "SXT" # 类属性
def __init__(self, name):
self.name = name
@classmethod
def printCompany(cls):
print("这个学生的company属性为:", cls.company)
@staticmethod
def add(a, b):
print("{}+{}={}".format(a, b, a + b))
return a + b
if __name__ == '__main__':
sum = Student.add(10, 20)
print(sum)
student = Student('李阳')
student.add(10, 20)
del()
del()称为"析构方法",用于实现对象被销毁时所需的操作。比如:释放对象占用的资源,例如:打开的文件资源,网络连接等。
python实现自动的垃圾回收,当对象没有被引用时(引用计数为0),有垃圾回收器调用__del__()
我们也可以通过del语句删除对象,从而保证调用__del__()。
系统会自动提供__del__方法,一般不需要自定义析构方法。
call方法和可调用对象
-
python中,凡是可以将()直接应用到自身并执行,都称为可调用对象。
-
可调用对象包括自定义的函数、Python内置函数。
-
定义了__call__()的对象,称为"可调用对象",即该对象可以像函数一样被调用
-
该方法使得实例对象可以调用普通函数那样,以"对象名()的形式使用"
class Car(object):
def init(self, age, money):
self.money = money
self.age = agedef __call__(self, age, money): print("call方法") print("车龄{0}\n金额{1}".format(age, money)) def say_money(self): print("常规方法") print("车龄{0}\n金额{1}".format(self.age, self.money))
if name == 'main':
car = Car(3, 200000)
car.say_money()
print()
car(3, 20000)#使用了call函数,可以使实例对象能够像函数名一样调用
方法没有重载
如果我们在类体中定义了多个重名的方法,只有最后一个方法有效
建议:不要使用重名的方法!python中方法没有重载
-
在其他一些语言(比如:java)中,可以定义多个重名的方法,只要保证方法签名唯一即可。方法签名包含3个部分:方法名、参数数量、参数类型。
-
python中,方法的参数没有声明类型(调用时确定参数的类型),参数的数量也可以由可变参数控制。因此,python中没有方法的重载
python中没有方法的重载。定义多个同名的方法,只有最后一个有效
class Person(object):
name = '李阳'def say_hi(self): print("hello") def say_hi(self): print("{},hello".format(self.name))
if name == 'main':
person = Person()
person.say_hi()
方法的动态性
python是动态语言,我们可以动态的为类添加新的方法,或者动态的修改类的已有方法
# python中没有方法的重载。定义多个同名的方法,只有最后一个有效
class Person(object):
name = '李阳'
def say_hi(self):
print("hello")
def say_hi(self):
print("{},hello".format(self.name))
if __name__ == '__main__':
person = Person()
person.say_hi()
为什么这里的对象外部函数,需要传入参数self,但是在对象调用函数的时候为什么不需要传入参数。因为这里person.say_h1()实际上是默认传入了person一个实例对象。
私有属性和私有方法(实现封装)
python对于类的成员没有严格的访问控制限制,这于其他面向对象语言有区别。关于私有属性和私有方法,有如下要点:
-
通常我们约定,两个下划线开头的属性是私有的(private)。其他为公共的(public)。
-
类内部可以访问私有属性(方法)
-
类外部不能直接访问私有属性(方法)
-
类外部可以通过_类名_私有属性(方法)名访问私有属性(方法)
class Employee(object):
__company = "重庆" # 解释器运行时,把__company转成了_Employee__companydef __init__(self, name, age): self.name = name self.__age = age self.age = age def say_company(self): print("我的公司是:", Employee.__company) print("我的年龄是:", self.__age)
print(dir(Employee))
print(Employee._Employee__company)
a = Employee("王总", 18)
a.say_company()
print("这是一个公共属性", a.age)
print("这是一个私有属性", a._Employee__age)
私有方法
class Employee(object):
__company = "重庆" # 解释器运行时,把__company转成了_Employee__company
def __init__(self, name, age):
self.name = name
self.__age = age
self.age = age
def say_company(self):
print("我的公司是:", Employee.__company)
print("我的年龄是:", self.__age)
def __work(self):
print("我的工作就是玩!")
# print(dir(Employee))
print(Employee._Employee__company)
a = Employee("王总", 18)
a.say_company()
a._Employee__work()
print("这是一个公共属性", a.age)
print("这是一个私有属性", a._Employee__age)
@property装饰器
@property是一个内置的装饰器,允许你通过方法来定义一个属性,使得你能够以访问属性的方式来调用方法。它通常用于控制属性的访问、修改和删除操作,同时隐藏一些复杂的实现细节。
@property主要用于帮助我们处理属性的读操作、写操作。对于某一个属性,我们可以直接通过:
emp1.salary=3000
如上的操作读操作、写操作。但是,这种做法不安全。比如,我需要限制薪水必须为1-10000的数字。这时候,我们就需要通过使用装饰器@property来处理
主要作用
-
简化属性访问:通过
@property
装饰器可以将方法转化为属性,使得用户在访问时无需直接调用方法,而是像访问普通属性一样使用。 -
封装逻辑:可以在属性的访问和修改过程中加入额外的逻辑,比如验证数据的有效性或进行其他计算。
-
提供只读属性:通过
@property
可以定义只读属性,避免直接修改某些敏感属性。 -
控制属性的读取、修改和删除:可以使用
@property
配合@<property_name>.setter
和@<property_name>.deleter
来定义属性的设置器和删除器,控制属性的赋值和删除操作。class Employee(object):
def init(self, name, salary):
self.name = name
self.__salary = salary@property def salary(self): print("薪资是:", self.__salary) return self.__salary @salary.setter def salary(self, salary): if 0 < salary < 1000000: self.__salary = salary else: print("薪资录入错误!只能在0-100000之间!") @salary.deleter def salary(self): print("Deleting salary...") del self.__salary
if name == 'main':
emp1 = Employee('老高', 30000)
print(emp1.salary)
emp1.salary = 50000
print(emp1.salary)
del emp1.salary
print(emp1.salary)
属性和方法命名总结
- _xxx:保护成员,不能用from module import *导入,只有类对象和子对象能访问这些成员。
- xxx:系统定义的特殊成员
- __xxx:类中的私有成员,只有类对象自己能访问,子类对象也不能访问。(但,在类外部可以通过对象名._类名__xxx这种特殊方法访问。python不存在严格意义的私有成员)
None对象的特殊性
None不是False,None不是0,None不是字符串。None和任何其他的数据类型比较永远返回False
a = None
if a is None and a == None:
print("a是None") # 会执行
if a==False or a==0:
print("None不等于False")
if语句判断时,空列表、空字典、空元组、0等一系列表空和无的对象会被转换为Fasle
面向对象三大特征介绍
python是面向对象的语言,支持面向对象编程的三大特性:继承、封装(隐藏)、多态
封装(隐藏)
隐藏对象的属性和实现细节,只对外提供必要的方法。相当于将"细节封装起来",只对外暴露"相关调用方法"。
通过前面学习的"私有属性、私有方法"的方式、实现"封装"。python追求简洁语法,没有严格的语法级别的"访问控制符",更多的是依靠程序员自觉实现
继承
继承可以让子类具有父类的特性,提高了代码的重用性
从设计上是一种增量进化,原有父类设计不变的情况下,可以增加新的功能,或者改进已有的算法。
多态
多态指同一个方法调用由于对象不同会产生不同的行为。生活中这样的例子比比皆是;同样是休息方法,人不同休息方法不同。张三休息是睡觉,李四休息是玩游戏,程序员休息是"敲几行代码"。
继承
继承是面向对象编程的三大特征之一。继承让我们更加容易实现类的扩展,实现代码的重用,不用在重新发明轮子
如果一个新类继承自一个设计好的类,就直接具备了已有类的特征,就大大降低了工作难度。已有的类,我们陈伟"父类或者基类",新的类,我们称为"子类或者派生类"
语法格式
Python支持多重继承,一个子类可以继承多个父类。继承的语法格式如下:
class 子类类名(父类1[,父类2,...])
类体
如果在类定义中没有指定父类,则默认父类是object类。也就是说,object是所有类的父类,里面定义了一些所有类共有的默认实现,比如:new()
关于构造函数:
-
子类不重写__init__,实例化子类时,会自动调用父类定义的__init__。
-
子类重写了__init__时,实例化子类,就不会调用父类已经定义的__init__。
-
如果重写了__init__时,要继承父类的构造方法,可以使用super关键字,也可以使用如下格式调用:
class Student:
passclass Car:#父类是object
passclass Benz(Car):
passclass Person(object):
def init(self, name, age):
print("创建Person")
self.name = name
self.age = agedef say_age(self): print("{0}的年龄为{1}岁".format(self.name, self.age))
class Student(Person):
def init(self, name, age, score):
self.name = name
self.age = age
self.score = scores1 = Student("老王", 18, 90)
s1.say_age()class Person(object):
def init(self, name, age):
print("创建Person")
self.name = name
self.age = agedef say_age(self): print("{0}的年龄为{1}岁".format(self.name, self.age))
class Student(Person):
def init(self, name, age, score):
#调用父类构造方法,可以使用如下两种方式:
Person.init(self, name, age)
#super(Student, self).init(name, age)
print("创建Student类")
self.score = scores1 = Student("老王", 18, 90)
s1.say_age()
类成员的继承和重写
-
成员继承:子类继承了父类除构造方法之外的所有成员,私有属性和私有方法也可以被继承
-
方法重写:子类可以重新定义父类中的方法,这样就会覆盖父类的方法,也称为"重写"
class Person(object):
def init(self, name, age):
print("创建Person")
self.name = name
self.__age = agedef say_age(self): print("{0}的年龄为{1}岁".format(self.name, self.__age))
class Student(Person):
def init(self, name, age, score):
# Person.init(self, name, age)
super(Student, self).init(name, age)
print("创建Student类")
self.score = scores1 = Student("老王", 18, 90)
s1.say_age()class Person(object):
def init(self, name, age):
self.name = name
self.age = agedef say_age(self): print(self.name, "的年龄是:", self.age) def say_name(self): print("我是", self.name)
class Student(Person):
def init(self, name, age, score):
Person.init(self, name, age)
self.score = scoredef say_score(self): print(self.name, "的分数是:", self.score) def say_name(self): print("报告老师,我是", self.name)#父类方法的重写
s1 = Student("老高", 15, 85)
s1.say_score()
s1.say_name()
s1.say_age()
查看类的继承层次结构
通过类的方法mro()或者类的属性__mro__可以输出这个类的继承层次结构
class A: pass
class B(A): pass
class C(B): pass
print(C.mro())
object根类
object类是所有类的父类,因此所有的类都有object类的属性和方法。我们显然有必要深入研究一下object类的结构。对于我们继续深入学习python很有好处
dir()查看对象属性
为了深入学习对象,先学习内置函数dir(),他可以让我们方便的看到指定对象所有的属性
class Person(object):
def __init__(self, name, age):
self.name = name
self.age = age
def say_age(self):
print(self.name, "的年龄是:", self.age)
obj = object()
print(dir(obj))
s2 = Person("老高", 18)
print(dir(s2))
print(s2.say_age)
print(type(s2.say_age))
重写__str__()方法
class Person(object):
def __init__(self, name, age):
self.name = name
self.__age = age
def __str__(self):
"""
将对象转化成一个字符串描述,一般用于print方法
:return:
"""
print("重写__str__方法")
return "名字是:{0},年龄是{1}".format(self.name, self.__age)
p = Person("老高", 18)
print(p)
s = str(p)
多重继承
python支持多重继承,一个子类可以有多个"直接父类"。这样,就具备了"多个父类"的特点。但是由于这样会被"类的整体层次"搞的异常复杂,尽量避免使用
class A(object):
def aa(self):
print("这是A的方法")
class B(object):
def bb(self):
print("这是B的方法")
class C(B, A):
def cc(self):
print("这是C的方法")
if __name__ == '__main__':
c = C()
c.cc()
c.bb()
c.aa()
MRO方法解析顺序
Python支持多继承、如果父类中有相同名字的方法,在子类中没有指定父类名时,解释器将"从左向右"按顺序搜索。
MRO(Method Resolution Order):方法解析顺序。我们可以通过mro()方法获得"类的层次结构",方法解析顺序也是按照这个"类的层次结构"寻找的。
class A(object):
def aa(self):
print("aa")
def say(self):
print("say AAA!")
class B(object):
def bb(self):
print("bb")
def say(self):
print("say BBB!")
class C(B, A):
def cc(self):
print("cc")
c = C()
print(C.mro()) # 打印类的层次结构
c.say() # 解释器寻找方法是"从左到右"的方式寻找,此时会执行B类中的say()
super()获得父类的定义
class A(object):
def __init__(self):
print("A的构造方法")
def say(self):
print("A:", self)
print("say AAA")
class B(A):
def __init__(self):
super(B, self).__init__()#调用父类的构造方法
print("B的构造方法")
def say(self):
#A.say(self) 调用父类的say方法
super(B, self).say()#通过super()调用父类的方法
print("say BBB")
b=B()
b.say()
多态
多态是指同一个方法调用由于对象不同可能会产生不同的行为
比如:现实生活中,同一个方法,具体实现会完全不同。比如:同样是调用人"吃饭"的方法,中国人用筷子吃饭,英国人用刀叉吃饭,印度人用手吃饭
关于多态要注意一下2点:
-
多态是方法的多态,属性没有多态。
-
多态的存在有2个必要条件:继承、方法重写
class Animal(object):
def shout(self):
print("动物叫了一声")class Dog(Animal):
def shout(self):
print("小狗,汪汪")class Cat(Animal):
def shout(self):
print("小猫,喵喵喵")def animalShout(animal):
animal.shout() # 会产生多态,传入的对象不同,则调用的方法也不一样animalShout(Dog())
animalShout(Cat())
对象的浅拷贝和深拷贝
浅拷贝
python拷贝一般都是浅拷贝。拷贝时,对象包含的子对象内容不拷贝。因此,源对象和拷贝对象会引用同一个子对象
import copy
class MobilePhone(object):
def __init__(self, cpu):
self.cpu = cpu
class CPU(object):
pass
c = CPU()
m = MobilePhone(c)
print("浅拷贝")
m2 = copy.copy(m) # 浅拷贝,m2是m对象的一个浅拷贝对象
print("m:", id(m))
print("m2:", id(m2))
print("m中的cpu:", id(m.cpu))
print("m2中的cpu:", id(m2.cpu))
深拷贝
使用copy模块的deepcopy函数,递归拷贝对象中包含的子对象。源对象金额拷贝对象所有的子对象也不同。
import copy
class MobilePhone(object):
def __init__(self, cpu):
self.cpu = cpu
class CPU(object):
pass
c = CPU()
m = MobilePhone(c)
print("深拷贝...")
m3 = copy.deepcopy(m) # 深拷贝
print("m", id(m))
print("m3", id(m3))
print("m中的cpu", id(m.cpu))
print("m3中的cpu", id(m3.cpu))
组合
除了继承,"组合"也能实现代码的复用!"组合"核心是"将父类对象作为子类的属性"。
class CPU(object):
def calculate(self):
print("正在计算,算个12345!")
class Screen(object):
def show(self):
print("显示一个好看的画面,亮瞎你的钛合金眼")
class MobilePhone(object):
def __init__(self, cpu, screen):
self.cpu = cpu
self.screen = screen
c = CPU()
s = Screen()
m = MobilePhone(c, s)
m.cpu.calculate()
m.screen.show()
设计模型
工厂模式实现
class Benz(object):
pass
class BMW(object):
pass
class BYD(object):
pass
class CarFactory(object):
def createCar(self, brand):
if brand == "奔驰":
return Benz()
elif brand == "宝马":
return BMW()
elif brand == "比亚迪":
return BYD()
else:
return "位置品牌,无法创建"
factory = CarFactory()
c1 = factory.createCar("奔驰")
c2 = factory.createCar("宝马")
print(c1)
print(c2)
单例模式实现
单例模式的核心作用就是确保一个雷只有一个实例,并且提供一个访问该实例的全局访问点。
单例模式只生成一个实例对象,减少了对系统资源的开销。当一个对象的产生需要比较多的资源,如读取配置文件、产生其他依赖对象时,可以产生一个"单例对象",然后永久主流内存中,从而极大地降低开销
!!!单例模式有多重实现的方式,我们这里推荐重写__new__()的方法。
class MySingleton(object):
__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("初始化第一个对象...")
self.name = name
MySingleton.__init_flag = False
a = MySingleton("aa")
print(a.name)
b = MySingleton("bb")
print(b.name)
工厂和单例模式结合
class CarFactory(object):
__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):
if CarFactory.__init_flag:
print("init.....")
CarFactory.__init_flag = False
def createCar(self, brand):
if brand == "奔驰":
return Benz()
elif brand == "宝马":
return BMW()
elif brand == "比亚迪":
return BYD()
else:
return "位置品牌,无法创建"
factory = CarFactory()
c1 = factory.createCar("奔驰")
c2 = factory.createCar("宝马")
print(c1)
print(c2)
factory2 = CarFactory()
print(factory)
print(factory2)
单例模式和工厂的结合,factory工厂只能创建一个实例对象,不能多创建,但是生产车的实力对象,可以多生产。