面向对象
文章目录
面向对象的简介
对象
我的另外一个blog有详细的讲什么是对象,还有可变对象和不可变对象,这里不多做赘述,详情可以点击链接查看
面向对象
提到面向对象,不得不提提面向过程,可以说面向对象是由面向过程发展而来的
面向对象和面向过程
这个也是我的blog,有讲到面向过程和面向对象
这里我再说一说
面向过程和面向对象首先都是编程的思想
面向过程关注的是解决问题的步骤,通过将问题的解决分为一个一个的步骤来进行编程,这样的编程更加符合我们人类的思维过程,但这样往往只适用于一种功能,复用性低
而面向对象关注的对象,至于该对象是如何解决问题的,解决问题的步骤则不关注,这样的方式不太容易阅读,而且不符合我们的思维过程,编写起来比较困难;但它的好处是复用性高,易于维护
类的快速入门
- 类首先是一个对象,是用来创建对象的对象,类型为type
- 类和对象的关系:类相当于一张建筑图纸,对象则相当于该图纸上一个个成型的建筑。所以我们称对象为类的实例(instance)
- 我们可以用isinstance来判断该对象是否是类的实例
# a = 123
# a = int(123)
# f = isinstance(a,int)
# print(a,f)
# int也是一个类,同理float,list,bool...都是类
像int(),list()等都是Python内置的类。通常这些内置的类不能够满足我们的需求
所以我们用关键字class来自定义类
# class MyClass:
# pass
# print(MyClass) # <class '__main__.Myclass'>
# print(id(MyClass),type(MyClass)) # 2025575315856 <class 'type'>
# 类的对象是type类型
# mc = MyClass() # mc是MyClass创建的一个实例对象
# print(mc,type(mc)) # <__main__.MyClass object at 0x000001D79F927670> <class '__main__.MyClass'>
# mc的类型是MyClass
# 根据mc和MyClass的id可以看出两个是不同的对象
我们可以向对象中添加变量,对象中的变量我们称之为属性。
语法:对象.属性名 = 属性值
# mc.name = 'Sleet'
# print(mc.name)
类的定义
对象都是现实生活中的事物的抽象
现实生活中的事物其实由两部分组成:
- 数据(属性)
- 行为(方法)
# 定义一个人的类
# class Person:
#
# name = 'Sleet' # 在类中定义的变量,所有实例都可以进行访问(公共属性)
# age = 19
# 在类中的一的函数,我们称之为方法,这些方法所有实例都可以访问
# 在类中定义的函数在调用时默认传递一个参数,所以在定义时也至少要传递一个形参
# def speak(self):
# print('加油呀~')
# p1 = Person()
# print(p1.name)
# p1.speak()
属性和方法的查找流程
当我们调用对象的属性或者方法时,解析器首先会在当前对象中查找,如果有则直接返回值,如果没有就在创建该对象的类中查找,如果有则直接返回值,如果没有就报错
类和对象中都可以保存属性,通常如果该属性是所有实例共有的就在类中定义,如果是某个实例私有的,就在对象中定义
self参数
前面说到在类中定义的函数都至少要定义一个形参,因为在调用时会默认传递一个参数
当我们打印这个参数时发现,返回的地址和调用该函数的对象本身的返回的地址是一样的。
这说明对象在调用函数时默认传递的参数其实就是对象本身
所以我们习惯把这个参数命名为self
# class Person:
#
# def speak(self):
# print('加油~',self.name)
# # print(a)
# p1 = Person()
# p1.name = '蜘蛛侠'
# p1.speak()
# print(p1)
# <__main__.Person object at 0x000001FB66578430>
# <__main__.Person object at 0x000001FB66578430>
# 说明在调用类里面的函数默认传递的那个参数就是调用者本身(即p1调用,传递的参数是p1本身)
# 所以我们习惯把这个默认传递的参数命名为self
# p2 = Person()
# p2.name = '蝙蝠侠'
# p2.speak()
类的特殊方法
- 在类中我们可以定义一些特殊的方法
- 这些方法的名称形如:__ 方法名 __,(双下划线中间一个方法名)
- 这些特殊方法不需要我们自己调用,它们会在特定的时候自动调用
1. __ init __
# class Person:
#
# print('Person代码块中的代码')
#
# def __init__(self,name):
# print('晚上好')
# # 通过self向新创建的对象来初始化属性
# self.name = name
# print('Person代码块中的代码')
# p1 = Person('钢铁侠')
输出的结果为
Person代码块中的代码
Person代码块中的代码
晚上好
根据结果可以看出
- 当定义类之后,会执行类里面的语句,所以两个print语句先打印了出来
- 当对象被创建之后,__ init __ 函数会自动调用,所以输出了“晚上好”
封装
封装的引入
首先我们定义一个类
# 定义一个车类
# 属性 name color 方法:run(), laba()
# class Car():
#
# def __init__(self,name,color):
# self.name = name
# self.color = color
#
# def run(self):
# print('%s的%s车开始跑了'%(self.color,self.name))
#
# def laba(self):
# print('dididi')
当我们创建该类的实例,传递车的名字和颜色的时候会发现,我们可以传递任意值,这样会导致如果我们输入的不是车,它也能执行车的功能。这样的方式会让我们的属性非常不安全。因此我们需要让我们的数据更加安全:
- 属性不能随意修改
- 属性不能修改成任何值
所以,我们需要对数据进行封装
封装
封装是面向对象的三大特性之一
封装是指隐藏对象中一些不希望被外部访问到的属性和方法
那如何隐藏一个属性
一、将属性名修改为非常规值
# class Dog:
#
# def __init__(self,name,age):
# self.hidden_name = name
# self.hidden_age = age
将我们常用的name修改成hidden_name,这样其实也能够随意访问和修改,只要将访问的name也改成hidden_name即可。但这能起到一定的提醒作用,也就是说告诉别人,这是一个希望隐藏的值。
二、在属性名前面加上双下划线(__属性名)
# class Person:
#
# def __init__(self,name):
# self.__name = name
这样定义的属性名,在外部就访问不到了,当然也修改不了
但其实这样在属性名加上双下划线之后,Python会自动修改这个属性名,改成 _类名__属性名 。访问“ _类名__属性名 ”同样也能访问到和修改,只不过Python不建议这样做
三、在属性名前加上单下划线(_属性名)
# class Person:
#
# def __init__(self,name):
# self._name = name
这样定义的属性名,和第一种情况一样,只能够起到提醒的作用,告诉别人这是一个封装的属性,在外部同样能够访问和修改
对封装属性的访问和修改
那么既然属性被封装起来了,如果想要访问和修改要怎么做呢
访问
需要在类中定义一个getter函数,getter只是一个名称,并非特殊函数
# def get_name(self):
# # get_name用来获取对象中的name属性
# return self.__name
修改
需要在类中定义一个setter函数来设置,setter同样只是一个名称,并非特殊函数
# def set_name(self,name):
# # set_name用来设置对象中的name属性
# self.__name = name
如果定义的类中有这两个函数,说明封装起来的属性允许被修改,如果没有这两个函数,说明封装起来的属性不希望被修改,这时候就不要去修改了
property装饰器
@property 用来创建只读属性,@property 装饰器会将方法转换成相同名称的只读属性
# class Person:
#
# def __init__(self,name):
# self._name = name
#
# @property
# def name(self):
# print('get方法执行了')
# return self._name
#
# # setter方法的装饰器 @属性名.setter
# @name.setter
# def name(self,name):
# print('setter方法执行了')
# self._name = name
#
# p = Person('葫芦娃')
# p.name = '超人'
# print(p.name()) # p.name() ---> 葫芦娃
# print(p.name) # name是一个方法
本来name是一个方法,在创建的对象进行调用时,需要在后面加上括号"()",但通过@property装饰了该函数时,直接调用函数名name就可以了。这样更符合我们的思维过程。
继承
继承也是面向对象的特性之一
- 在类后面的括号中可以指定父类
- 通过继承可以获取父类的属性和方法
- 增加了类的复用性,使类与类之间产生了关系
super()
super()可以获取当前类的父类,通过super()返回的对象在调用父类方法时不需要传递self参数
class Person:
def __init__(self,name,age,sex):
self.name = name
self.age = age
self.sex = sex
def Speak(self):
print('我会说话')
def Walk(self):
print('我能走路')
class Student(Person):
def __init__(self,name,age,sex):
# super()获取当前类的父类,初始化父类的name,age和sex属性
super().__init__(name,age,sex)
def Study(self):
print('我需要学习')
s = Student('张同学','16','男')
s.Speak()
# 子类继承了父类的两个方法
s.Walk()
s.Study()
方法的重写
如果子类中存在与父类同名的方法,通过实例对象调用这个方法时,会调用子类的方法而非父类的方法。这个特点我们称之为方法的重写(覆盖)
方法的查找流程:
- 现在当前类中查找,如果没有,则去当前类的父类中查找
- 如果当前类的父类中没有,就去当前类的父类的父类中查找,以此类推,直到查找到
- 如果到最后都没有找到,则会报错
class A:
def test(self):
print('A')
class B(A):
def test(self):
print('B')
class C(B):
pass
c = C()
c.test()
多重继承
一个类可以在括号中指定多个父类,这就叫多重继承,这个子类可以获取所有父类中的方法。当几个父类中存在同名的方法时,会按从左到右的顺序进行查找,直到没有找到报错。
class A:
def test(self):
print('A')
class B:
def test(self):
print('B')
# class C(B,A):
# pass
# class C(A,B):
# pass
# c = C()
# c.test()
多态
多态也是面向对象的特性之一,从字面上理解就是多种形态
一个对象可以有多种形态进行呈现,这就是多态
class A:
def __init__(self,name):
self._name = name
@property
def get_name(self):
return self._name
@get_name.setter
def set_name(self,name):
self._name = name
class B:
def __init__(self,name):
self._name = name
@property
def get_name(self):
return self._name
@get_name.setter
def set_name(self,name):
self._name = name
a = A('Sleet')
b = B('Jerry')
# 定义一个函数,这种形式就是一个多态,obj可以有多种形态
def speak(obj):
print(obj.get_name,'很厉害')
# 这种形式就违反了多态的原则,限制了对象
def speak2(obj):
if isinstance(obj,A):
print(obj.get_name)
speak(a)
speak(b)
面向对象的三大特性
- 封装: 确保对象中数据的安全
- 继承: 保证了对象的扩展性
- 多态: 保证了程序的灵活性
属性和方法
属性
- 类属性:在类中直接定义的属性是类属性,类属性能够通过类和实例对象进行访问,但只能通过类进行修改
- 实例属性:在实例对象中定义的属性是实例属性,实例属性只能够通过实例对象进行访问和修改
方法
- 实例方法:默认传递的参数是self,实例方法可以通过类和实例对象进行调用,但通过类进行调用时需要手动传递参数self
- 类方法:用@classmethod装饰器装饰的方法,默认传递的参数是cls,能够通过类和实例进行调用
- 静态方法:用@staticmethod装饰器装饰的方法,不需要传递参数,是一种工具方法,与当前类无关
class Person:
# 在类中定义的属性是类属性
# 类属性能够通过类和实例对象进行访问,但只能通过类进行修改
character = 'handsome'
def __init__(self,name,age,sex):
self._name = name
self._age = age
self._sex = sex
# 以self为参数传递的是实例方法
# 实例方法可以通过类和实例对象进行访问,不过通过类进行访问时需要传递self参数
def test1(self):
print('这是实例方法')
# 通过@classmethod装饰器装饰的方法是类方法
# 类方法能够通过类和实例对象进行访问
# 类方法默认传递的参数是cls
@classmethod
def test2(cls):
print('这是类方法')
# 静态方法是用@staticmethod装饰器装饰的方法
# 静态方法不需要传递默认的参数
# 静态方法只是一种工具,与当前类无关
@staticmethod
def test3():
print('这是静态方法')
c = Person('a',12,'nan')
# 实例属性,通过实例对象添加的属性是实例属性
# 实例属性只能通过实例对象进行访问和修改
c.character = 'ugly'
print(Person.character)
print(c.character)
# handsome
# ugly