文章目录
面向对象编程是将数据和操作数据相关的方法封装到对象中。
面向对象&面向过程
面向过程:关注程序的逻辑流程,是一个执行者思维,适合编写小规模程序,更适合一个人的工作,关键是找动词
面向对象:适合分工协作,每个人造不同的零件最后组装,适合从宏观上把握,从整体上分析整个系统,但是具体到实现部分的微观操作,仍然需要面向过程的思路去处理,关键是找名词。
对象的进化
随着编程面临的问题越来越复杂,编程语言本身也在进化,从主要处理简单数据开始,随着数据变多进化"数组";数据类型变复杂,进化成了"结构体";处理数据的方式和逻辑变复杂,进化出了“对象”。
对象:就是将不同类型的数据,方法(即函数)放在一起,就是对象。
类的定义
如果把对象比作一个饼干,那么类就是制造这个饼干的模具,所以对象就有同样的属性。
一个典型的类的结构
# 定义类的时候,名字首字母大写
class Student:
# 定义属性,名字固定,均为__init__
# self 必须位于第一个参数,而且必须有
def __init__(self, name, score):
self.name = name
self.score = score
# self 必须位于第一个参数
def say_score(self):
print("{0}的分数是:{1}".format(self.name, self.score))
# 类名+括号代表相当于调用的是属性,就是第一个__init__
s1 = Student('Di', 18)
s1.say_score()
运行结果
构造函数__init__()
之前提过一个对象包括id,type 和value ,那么value 细分为属性(attribute)和方法(method)。
__init__方法要点
- 名称固定,必须是__init__
- 第一个参数固定,必须是:self。self指的是刚刚创建好的实例对象
- 构造的目的就是为了初始化对象的实例属性,如下代码就是初始化实例。
def __init__(self, name, score):
self.name = name
self.score = score
可以理解为这一步是为了定义参数:把形参加到括号里面,在下面定义参数。
通过类名(参数列表)来调用构造函数
调用后,将创建好的对象返回给相应的变量。
比如 s1 = Studen(‘Di’,18)包括了下列两步:
- new() 方法:用于创建对象,但我们一般无需重新定义该方法.
- int()方法:初始化创建好的对象。初始化是指给对象里面的属性赋值。
实例属性
刚开始定义的是 属性,后来的的say_score定义的是方法。
# 定义类的时候,名字首字母大写
class Student:
# 定义属性,名字固定,均为__init__
# self 必须位于第一个参数,而且必须有
def __init__(self, name, score):
self.name = name
self.score = score
# self 必须位于第一个参数
def say_score(self):
print("{0}的分数是:{1}".format(self.name, self.score))
# 类名+括号代表相当于调用的是属性,就是第一个__init__
s1 = Student('Di', 18)
print(s1.name)
# 调用方法属性
s1.say_score()
# 增加属性:年龄和工资
s1.age = 32
s1.salary = 3000
print(s1.salary)
# 再增加一个对象
s2 = Student('Di1', 30)
print(s2.name)
# 删除对象,里面的属性也会随之消失
del s1
s1.name
运行结果
Di
Traceback (most recent call last):
Di的分数是:18
3000
File "D:/PycharmProjects/MyTest/Day_7/learn_class.py", line 37, in <module>
Di1
s1.name
NameError: name 's1' is not defined
实例方法
实例方法是从属于实例对象的方法,实例方法的定义格式如下:
def 方法名(self[形参列表]):
函数体
方法的调用格式如下:
对象.方法名()
要点
- 定义实例方法时,第一个参数必须为self。和前面一样,self指当前的实例对象。
- 调用实例方法时,不需要也不能给self传参,但是可以增加其他的参数。
还是上面的例子:调用函数的两种写法
s1.say_score()
Student.say_score(s1)
运行结果
Di的分数是:18
Di的分数是:18
函数和方法的区别
- 都是用来完成一个功能的语句块,本质一样
- 方法调用时,通过对象来调用,方法从属于特定实例对象,普通函数没有这个特点。
- 直观上看,方法定义时需要传递self,函数不需要
其他
- dir()可以获得所有对象属性
print(dir(s1))
结果
['__class__', '__delattr__', '__dict__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', '__gt__', '__hash__', '__init__', '__init_subclass__', '__le__', '__lt__', '__module__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__', '__weakref__', 'name', 'say_score', 'score']
- obj.__dict__对象的属性字典
print(s1.__dict__)
运行结果
{'name': 'Di', 'score': 18}
- pass 空:表示还没想好怎么写,那就暂时不写
class Man:
pass
- isinstance(对象,类型) 判断“对象”是不是“指定类型”
# 判断s1是不是Student的实例对象,如果是的话就是True,如果有调用的话那就可以是。
print(isinstance(s1,Student))
总结
- 类是一个对象,这个对象也有自己的固有属性,但是在值那里面还分为了属性和方法
- 定义类的话,要先学会定义属性,然后定义方法,学会如何调用(赋属性,用方法)
- 其他是用来判断类的属性等。
类对象
class Student1:
pass
print(id(Student1))
print(type(Student1))
stu = Student1
stu()
print(id(stu))
运行结果
2377311793064
# 查找这个类的type 指的是这个type的类型是一个模具,是一个饼干类型的
<class 'type'>
# 把Student 赋值给Stu,那么两者的id值是一样的
2377311793064
类属性和类方法
类属性
区别实例方法,实例对象,实例属性,类属性
# 定义类的时候,名字首字母大写
class Student:
company = 'SXT' # 类属性
count = 0 # 类属性
def __init__(self, name, score):
self.name = name # 实例属性
self.score = score
Student.count += 1
# self 必须位于第一个参数
def say_score(self): # 实例方法
print('我的公司是:', Student.company)
print("{0}的分数是:{1}".format(self.name, self.score))
s1 = Student('Di', 18) # s1是实例对象,自动调用__init__()方法
s2 = Student('Di1', 90)
s3 = Student('Di2', 70)
# 调用方法属性,实例对象是s1
s1.say_score()
# 判断这个类中共有几个Student对象
print('一共创建了{0}个实例对象'.format(Student.count))
# 因为每创建一次,就调用一次__init__,所以Student.count就加一,所以可以计算出一共调用了多少次
运行结果
我的公司是: SXT
Di的分数是:18
一共创建了3个对象
图例详解【重要】
尚学堂就是类属性
如果要再调用一次实例对象,那么就生成一个s2。
类方法
类方法是从属于"类对象"的方法,类方法通过装饰器@classmethod来定义。
class Student:
company = 'SXT'
@classmethod
def printCompany(cls):
print(cls.company)
Student.printCompany()
运行过程分析
思考: 为什么类里面要有类属性,类方法,实例属性,实例方法?
回答: 就像一个班,一个班有整体信息,也有个人信息,我想观察一个班的整体状况,就需要定义类属性,然后利用类方法计算出相应的参考值,但是一个班里还有许多同学,我需要知道每个同学的状况,就需要知道实例属性和实例方法。
总结
实例属性:定义(init),调用: 实例对象
类对象:定义,调用
静态方法
Python 中允许定义与“类对象”无关的方法,称为“静态方法”
“静态方法”和在模块中定义普通函数没有区别,只不过"静态方法"放到了“类的名字空间里面”,需要通过“类调用"。
静态方法访问实例属性和实例方法会导致错误,但是可以访问类对象和类属性
跟类方法分别不是那么清晰。
class Student:
company = 'SXT'
@staticmethod
def add(a, b):
print('{0}+{1}={2}'.format(a, b, a + b))
return a + b
Student.add(20, 30)
运行结果
20+30=50
_del_方法(析构函数)和垃圾回收机制
_del_方法成为"析构方法",用于实现对象被销毁时所需的操作。比如:释放对象占用的资源,例如:打开的文件资源,网络连接等。
Python 实现自动的垃圾回收,当对象没有被引用时(引用计数为0)由垃圾回收期调用_del_方法。
例如这个图,这个对象被abc引用了三次,那么当abc 都不引用的时候,这个对象就被回收了。
# 析构方法测试
class Person:
def __del__(self):
print("销毁对象{0}".format(self))
p1 = Person()
p2 = Person()
运行结果
销毁对象<__main__.Person object at 0x0000021AB0E4B4E0>
销毁对象<__main__.Person object at 0x0000021AB0F194A8>
_call_方法和可调用对象
定义了_call_方法的对象,称为‘可调用对象’,即该对象可以像函数一样被调用。
obj()实际上调用的是_call_方法
# 测试可调用方法
class SalaryAccount:
'''工资计算器'''
def __call__(self, salary):
print('算工资啦')
yearSalary = salary * 12
daySalary = salary // 22.5
hourSalary = daySalary // 8
return dict(yearSalary=yearSalary, daySalary=daySalary, hourSalary=hourSalary)
s = SalaryAccount()
print(s(30000))
运行结果
算工资啦
{'yearSalary': 360000, 'daySalary': 1333.0, 'hourSalary': 166.0}
方法没有重载
在其它语言中,保证参数的类型,数量和方法名一样,那么该方法一致,但是Python中不声明变量类型,就不可以。所以不要使用重名的函数!!
方法的动态性
Python 是动态语言,可以为类添加新方法,也可以修改类的方法。
class Person:
def work(self):
print('努力工作')
def watch_video(s):
print("{0}在玩游戏".format(s))
def work2(s):
print('好好工作')
# 为类添加新方法
Person.watch = watch_video
p = Person()
p.work()
p.watch()
# 修改类的方法
Person.work = work2
p.work()
运行结果
努力工作
<__main__.Person object at 0x00000230F50FF1D0>在玩游戏
好好工作
私有属性和私有方法
- 通常设定:两个下划线开头的属性是私有的(private),其他为公开的(public)
- 类内部可以访问私有属性(方法)
- 类外部不能直接访问私有属性(方法)
- 类外部可以通过“_类名__私有属性(方法)”进行访问
【注】方法本质上也是属性
class Employee:
def __init__(self, name, age):
self.name = name
self.__age = age # 私有属性
def __work(self): # 私有方法
print("好好工作")
# 自己调用自己的属性不用_Employee
print('我的年龄是{0}'.format(self.__age))
e = Employee('Di', 22)
print(e.name)
# 因为age前面添加了两个下划线,所以age变成私有属性,外部不能访问
print(e._Employee__age)
print(dir(e))
e._Employee__work()
```python
###############运行结果####################
Di
22
['_Employee__age', '_Employee__work', '__class__', '__delattr__', '__dict__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', '__gt__', '__hash__', '__init__', '__init_subclass__', '__le__', '__lt__', '__module__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__', '__weakref__', 'name']
好好工作
我的年龄是22
结果说明
可以看到:
- 添加两个下划线就变成了私有属性,对外部私有。
- e的属性第一个就是那样的,可以通过看dir来判断怎么访问属性。
property 装饰器
@property 可以将一个方法的调用方式变成“属性调用”。
# 测试@property的性能:加了property之后,salary原本是一个函数,可是现在可以被当成一个属性进行访问了,但是不能赋值
class Employee:
@property
def salary(self):
print('好好学习')
return 10000
s1 = Employee()
print(s1.salary)
s1.salary = 2000
运行结果
Traceback (most recent call last):
好好学习
File "D:/PycharmProjects/MyTest/Day_7/learn_property.py", line 20, in <module>
s1.salary = 2000
AttributeError: can't set attribute
10000
一个小题目:将属性改为私有属性,这样用户就不能从外部访问j进行修改,通过get和set来对属性进行获取和修改。
方法1:利用 get 和 set 进行访问
class Employee:
def __init__(self, name, salary1):
self.__name = name
self.__salary1 = salary1
def get_salary(self):
return self.__salary1
def set_salary(self, salary):
if 100 < salary < 50000:
self.__salary1 = salary
else:
print("录入错误,请重新输入")
s1 = Employee('Di', 3000)
# 因为 name 和 salary 私有化了,所以可以通过get来访问
print(s1.get_salary())
s1.set_salary(-20000)
s1.set_salary(30200)
print(s1.get_salary())
######################运行结果#####################
3000
录入错误,请重新输入
30200
方法2:利用property
class Employee:
def __init__(self, name, salary):
self.__name = name
self.__salary = salary
@property
def salary(self):
return self.__salary
@salary.setter
def salary(self, salary):
if 100 < salary < 50000:
self.__salary = salary
else:
print('录入错误,请重新输入')
s1 = Employee('Di', -3000)
print(s1.salary)
s1.salary = 200
print(s1.salary)
####################运行结果####################
C:\ProgramData\Anaconda3\python.exe D:/PycharmProjects/MyTest/Day_7/learn_property.py
-3000
200
面向对象三大特征介绍
-
封装(隐藏)
隐藏对象的属性和实现细节,只对外提供必要的方法,相当于将细节封装起来,只对外暴露相关调用方法。 -
继承
继承可以让子类具有父类的特性,提高代码的重用性。
是一种增量进化,在父类设计不变的情况下,可以增加新的功能,改进已有的算法。 -
多态
多态是指同一个方法调用由于对象不同产生不同的行为,生活中这样的例子比比皆是,同样是休息,张三的方式是睡觉,李四的休息是玩游戏。
继承
Tips: 如何继承父类的名称和方法?方法可以直接用,名称作显式声明。
- “代码复用”的重要手段
- 父类:父类和基类
- 子类:子类或者派生类
Python 支持多重继承,一个子类可以继承多个父类。继承的语法格式如下:
- 语法格式如下:
class 子类类名(父类1[,父类2,…]):
类体
如果类定义里没有指定父类,则默认父类是object类
class Person:
def __init__(self, name, age):
self.name = name
self.age = age
# self.__age = age
def say_age(self):
print('学习')
class Student(Person):
def __init__(self, name, age, score):
Person.__init__(self, name, age) # 必须显式的调用父类初始化方法,不然解释器不会去调用
self.score = score
print(Student.mro())
# 调查父类:Student→Person→Object
a = Student("Di", 10, 90)
# 继承了父类的方法
a.say_age()
# 继承了父类的属性
print(a.age)
# print(a._Person__age)
#################运行结果###################
[<class '__main__.Student'>, <class '__main__.Person'>, <class 'object'>]
学习
10
类成员的继承和重写
- 成员继承:子类继承了父类除构造方法之外的所有成员
- 方法重写:子类可以重新定义父类中的方法,这样就覆盖父类的方法,也称为“重写”
# 测试方法的重写
class Person:
def __init__(self, name, age):
self.name = name
self.age = age
def say_age(self):
print('我的年龄是{0}'.format(self.age))
def say_introduce(self):
print('我的名字叫{0},我今年{1}'.format(self.name, self.age))
class Student(Person):
def __init__(self, name, age, score):
Person.__init__(self, name, age) # 必须显式的调用父类初始化方法,不然解释器不会去调用
self.score = score
'''重写了父类的方法'''
def say_introduce(self):
print('报告老师,我的名字是{0}'.format(self.name))
a = Student("Di", 18, 90)
a.say_introduce()
####################运行结果#################
报告老师,我的名字是Di
查看类的继承层次和结构
通过mor()或者_mro_可以输出这个类 的继承层次结构。
通过dir()查看对象属性
重写_str_()方法
# 测试重写object的_str_()
class Person:
def __init__(self, name):
self.name = name
def __str__(self):
return '报告老师,我的名字是{0}'.format(self.name)
p = Person('Di')
print(p)
########################运行结果###########################
报告老师,我的名字是Di
多重继承
Python 支持多重继承,一个子类可以有多个"直接父类"。这样,就具备了“多个父类”的特点。尽量避免使用。
mro()
Python支持多继承,如果父类有相同名字的方法,在没有指定父类名字的时候,解释器将从左到右按照顺序搜索。
class A:
def aa(self):
print('aa')
def say(self):
print('say aa')
class B:
def bb(self):
print('bb')
def say(self):
print('bb')
# 如果B和A都有相同的方法,那么python按照从左至右的方法进行选择,下面这个是B,那么就先调用B,如果A放在左边,那么先调用A
class C(B, A):
def cc(self):
print('cc')
c = C()
c.say()
c.cc()
c.bb()
#########################运行结果#########################
bb
cc
bb
super()获得父类定义
class A:
def say(self):
print('A')
class B(A):
def say(self):
# 找到父类里面的定义
super().say()
#同样写法:A.say(self)
print('B')
B().say()
#########################运行结果#####################################
A
B
多态
多态指的是同一个方法调用由于对象不同产生不同的行为。
class Chinese(Man):
def eat(self):
print('中国人用筷子吃饭')
class English(Man):
def eat(self):
print('英国人用叉子吃饭')
class Indian(Man):
def eat(self):
print('印度人用手吃饭')
def manEat(m):
if isinstance(m, Man):
m.eat() # 多态,一个方法调用,根据对象不同调用不同的方法。
else:
print('不能吃饭')
# 子类也是父类的对象
print(isinstance(Chinese(), Man))
manEat(Chinese())
######################运行结果########################
True
中国人用筷子吃饭
特殊方法和运算符重载
# 运算符的重载
class Person:
def __init__(self, name, age):
self.name = name
self.__age = age
def __add__(self, other):
if isinstance(other, Person):
return "{0}--{1}".format(self.name, other.name)
else:
return "不是同类对象,不能相加"
p1 = Person('Di', 19)
p2 = Person('Panda', 25)
print(p1 + p2)
###################运行结果############################
Di--Panda
特殊属性
class A:
def aa(self):
print('aa')
def say(self):
print('say aa')
class B:
def bb(self):
print('bb')
def say(self):
print('bb')
class C(B, A):
def cc(self):
print('cc')
# A的一个层级父类
print(A.__base__)
# C的多个父类(多继承)
print(C.__bases__)
# C的多层级结构
print(C.__mro__)
# 子类列表
print(object.__init_subclass__)
##############################运行结果#########################
<class 'object'>
(<class '__main__.B'>, <class '__main__.A'>)
(<class '__main__.C'>, <class '__main__.B'>, <class '__main__.A'>, <class 'object'>)
<built-in method __init_subclass__ of type object at 0x00000000526DC580>
对象的浅拷贝和深拷贝
- 变量的赋值操作:只是形成两个变量,实际还是指向同一个对象
- 浅拷贝:Python一般都是浅拷贝。拷贝时,对象包含的子对象内容不拷贝。因此,源对象和拷贝对象会引用同一个子对象。
- 深拷贝:使用copy的deepcopy模块,递归拷贝对象中包含的子对象。源对象和拷贝对象所有的子对象也不同。
# 测试对象的浅拷贝和深拷贝
import copy
class MobilePhone:
def __init__(self, cpu, screen):
self.cpu = cpu
self.screen = screen
class CPU:
def calculate(self):
print("算一个数字出来")
print("CPU对象", self)
class Screen:
def show(self):
print("显示一个好看的画面")
print("screen:", self)
c1 = Screen()
c2 = CPU()
m1 = MobilePhone(c2, c1)
m2 = copy.copy(m1)
print(m1, m1.cpu, m1.screen)
# m2只会复制第一个对象,下面的不会复制
print(m2, m2.cpu, m2.screen)
m3 = copy.deepcopy(m1)
print(m1, m1.cpu, m1.screen)
# m3深拷贝,会全部复制,所以看到的都不一样
print(m3, m3.cpu, m3.screen)
#############################运行结果##################################
<__main__.MobilePhone object at 0x0000021AB1DFC160> <__main__.CPU object at 0x0000021AB1DFC0F0> <__main__.Screen object at 0x0000021AB066F208>
<__main__.MobilePhone object at 0x0000021AB1DFC240> <__main__.CPU object at 0x0000021AB1DFC0F0> <__main__.Screen object at 0x0000021AB066F208>
<__main__.MobilePhone object at 0x0000021AB1DFC160> <__main__.CPU object at 0x0000021AB1DFC0F0> <__main__.Screen object at 0x0000021AB066F208>
<__main__.MobilePhone object at 0x0000021AB1DFC2B0> <__main__.CPU object at 0x0000021AB1DFC320> <__main__.Screen object at 0x0000021AB1DFC390>
组合
什么时候用"父类"?“ is -a ”的关系。我们可以使用继承,从而实现子类拥有父类的方法和属性。“is-a ”关系指的是类似这样的关系:狗是动物,dog is animal。狗类就应该继承动物。
什么时候用组合?也能实现一个类拥有另一个类的方法和属性。“has-a”关系指的是,手机拥有CPU。
class A1:
def say_a1(self):
print('a1,a1,a1')
class B1(A1):
pass
b1 = B1()
b1.say_a1()
class A2:
def say_a2(self):
print('a2,a2,a2')
class B2:
def __init__(self, a):
self.a = a
a2 = A2()
b2 = B2(a2)
b2.a.say_a2()
##################################运行结果##################################
a1,a1,a1
a2,a2,a2
class MobilePhone:
def __init__(self, cpu, screen):
self.cpu = cpu
self.screen = screen
class CPU:
def calculate(self):
print("算一个数字出来")
print("CPU对象", self)
class Screen:
def show(self):
print("显示一个好看的画面")
print("screen:", self)
a = MobilePhone(CPU(), Screen())
a.cpu.calculate()
设计模式_工厂模式的实现
设计模式是面对对象语言特有的内容,是我们在面临某一类问题时候固定的做法,设计模式有很多种,比较流行的是“GOF23”种设计模式。
工厂模式
class CarFactory:
def create(self, brand):
if brand == '奔驰'
return Benz()
elif brand == '宝马'
return BMW()
elif brand == '比亚迪'
return BYD()
class Benz:
pass
class BMW:
pass
class BYD:
pass
a = CarFactory()
b1 = a.create("奔驰")
b2 = a.create("宝马")
单例模式
单例模式的核心作用是确保一个类只有一个实例,并且提供一个访问该实例全局访问点
单例模式只生成一个实例对象,减少了对系统资源的开销。
# 测试单例模式
class MySingleton:
__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 == True:
print('init.......')
self.name = name
MySingleton.__init_flag = False
a = MySingleton('aa')
b = MySingleton('bb')
print(a)
print(b)
############################运行结果###################################
init.......
<__main__.MySingleton object at 0x000001542A4EF240>
<__main__.MySingleton object at 0x000001542A4EF240>