首先,面向对象是什么?
面向对象是按照客观事物的自然规律进行分析。比如一些具体的事物,学生个体的班级,学号,等属性,还有吃饭、睡觉、走路等属性。还有一个很形象的比喻:
如果我们把类比作一个建筑图纸(一张房子的蓝图),那么对象就是根据这个图纸建的实实在在的房子。而shelf则是这个房子对应的门牌,这些房子可能住着不同的人,他们需要这些门牌找到对应的家。类中的属性和对象就像给这个房子设置的各种设施,一套房子建好,当然就拥有这些设施,当然你也可以根据自己的需求要不要这些设施,或者改装一下。
这里有几个概念先熟悉一下:
- 类(Class): 用来描述具有相同的属性和方法的对象的集合。它定义了该集合中每个对象所共有的属性和方法。对象是类的实例。(比如一个函数是一种方法,那么它便是多个函数组合起来的一个整体)
- 类变量:类变量在整个实例化的对象中是公用的。类变量定义在类中且在函数体之外。类变量通常不作为实例变量使用。
- 数据成员:类变量或者实例变量, 用于处理类及其实例对象的相关的数据。
- 方法重写:如果从父类继承的方法不能满足子类的需求,可以对其进行改写,这个过程叫方法的覆盖(override),也称为方法的重写。
- 局部变量:定义在方法中(函数)的变量,只作用于当前实例的类。
- 实例变量:在类的声明中,属性是用变量来表示的。这种变量就称为实例变量,是在类声明的内部但是在类的其他成员方法之外声明的。
- 继承:即一个派生类(derived class)继承基类(base class)的字段和方法(子类继承父类)。继承也允许把一个派生类的对象作为一个基类对象对待。例如,有这样一个设计:一个Dog类型的对象派生自Animal类,这是模拟"是一个(is-a)"关系(例图,Dog是一个Animal)。
- 实例化:创建一个类的实例,类的具体对象。
- 方法:类中定义的函数。
- 对象:通过类定义的数据结构实例。对象包括两个数据成员(类变量和实例变量)和方法。
还有一张有趣的图助于我们的理解:
那面向对象有哪些特性呢?
-
封装性
-
继承性
-
多态性
1、创建类
格式:
class 类名[(父类)]: #创建类必须使用class语句来创建
'类的帮助信息'
类体
例如:
class Test:
def prt(self):
print('哈哈哈哈。。。')
其中,类的帮助信息可以通过ClassName.__doc__查看
2、类变量
含义:类变量是所有实例(或对象)共有的变量。位于类中但在函数体之外。
例如:
class Account:
interest_rate = 0.0668 ###创建并初始化类变量。它可以允许下面的函数或者其他使用
def __init__(self,owner,amount):
self.owner = owner
self.amount = amount
3、类方法
含义:表示方法绑定到类
定义:定义类方法有两个关键:
-
方法的第一个参数cls是type类型
-
方法使用装饰器@classmethod声明该方法是类方法
调用方式:
- 可直接调用
- 类名.方法名
- 对象名.方法名
例如:
class Apple(object):
@classmethod #声明该方法是类方法
def get_class_apple(cls, n): #注意使用cls作为第一个参数
print ("apple: %s,%s" % (cls,n))
注意:调用类变量,这里注意类方法可以访问类变量和其他类方法,但不能访问其他实例方法和实例变量
4、实例
1、实例变量:
含义:某个实例(或对象)个体所特有的变量,在成员方法中定义。
①构造方法:——>使用 __init__方法,该方法也属于魔法方法。特别注意的是,在定义时它的第一个参数应该是 self。
例如上面一段代码所示:owner 和 amount 为实例变量,在参数self以后。
2、实例方法:(普通方法)
含义:实例方法是在类中定义的函数。定义实例方法时它的第一个参数也是 self 。
例如:
class Animal(object):
def __init__(self,age,sex=1,weight=0.0):
self.age = age #定义年龄实例变量
self.sex = sex #定义性别实例变量
self.weight = weight #定义体重实例变量
def eat(self): #实例方法
self.weight +=0.5
print('eat...')
def run(self): #实例方法
self.weight -=0.01
print('run...')
5、静态方法:
含义:方法既不与实例绑定,也不与类绑定,只想把类作为它的命名空间的一种方法。
调用方式:类名.静态方法名
注意:静态方法只能使用该静态方法所在类的静态数据成员和静态方法。这是因为使用静态方法时,该静态方法所在类可能还没有对象,即使有对象,由于用类名.静态方法名方式调用静态方法,静态方法没有this指针来存放对象的地址,无法判定应访问哪个对象的数据成员。
例如:
class Account:
interest_rate = 0.0668 #类变量利率
def __init__(self,owner,amount):
self.owner = owner #定义实例变量账户名
self.amount = amount #定义实例变量账户金额
#类方法
@classmethod
def interest_by(cls,amt):
return cls.interest_rate * amt #调用类变量,这里注意类方法可以访问类变量和其他类方法,但不能访问其他实例方法和实例变量
#静态方法
@staticmethod
def interest_with(amt):
return Account.interest_rate * amt
这里介绍了三种方法:分别是类方法,实例方法,静态方法。这三种方法应该怎么区分呢?
https://blog.csdn.net/klyz1314/article/details/16845679
https://blog.csdn.net/lanyang123456/article/details/71001866
https://blog.csdn.net/lihao21/article/details/79762681
6、对象:
(1)、创建实例对象:创建对象很简单,就是在类的后面加上一对小括号,表示调用类的构造方法。
例:
class ClassName:
age = 13
def __init__(self,name,school):
self.name = name
self.school = school
a = ClassName('names','某某小学') #创建实例对象
print('学校名字为:{0}'.format(a.school)) #访问类的属性(对象.实例)
print('年龄为:{0}'.format(ClassName.age))
(2)我们有可以添加、删除、修改类的属性。如下:
a.school = '哈哈小学' #####修改实例变量
a.age = 16 #####修改类变量
print('学校名字为:{0}'.format(a.school))
print('年龄为:{0}'.format(a.age))
a.num = 20190305 #添加一个属性
a.num = 20200998 #修改一个属性
del a.num #删除一个属性
还有如下函数方式来访问属性:
- getattr(obj, name[, default]) : 访问对象的属性。
- hasattr(obj,name) : 检查是否存在一个属性。
- setattr(obj,name,value) : 设置一个属性。如果属性不存在,会创建一个新属性。
- delattr(obj, name) : 删除属性。
hasattr(emp1, 'age') # 如果存在 'age' 属性返回 True。
getattr(emp1, 'age') # 返回 'age' 属性的值
setattr(emp1, 'age', 8) # 添加属性 'age' 值为 8
delattr(emp1, 'age') # 删除属性 'age'
7、Python的内置类属性
- __dict__ : 类的属性(包含一个字典,由类的数据属性组成)
- __doc__ :类的文档字符串
- __name__: 类名
- __module__: 类定义所在的模块(类的全名是'__main__.className',如果类位于一个导入模块mymod中,那么className.__module__ 等于 mymod)
- __bases__ : 类的所有父类构成元素(包含了一个由所有父类组成的元组)
二、封装性
封装性是面向对象的三大特性之一,Python语言没有于封装性相关的关键字,它通过特定的名称实现对变量和方法的封装。
1、私有变量:
默认情况下Python的变量是公有的,可以在类的外部访问它们。如果想让他们成为私有变量,可以在变量前加上双下划线 “__”。
例如:
class just:
__secret = 0.6 #定义私有变量
public = 0.5
def count(self):
self.__secret += 1 #调用私有变量,私有变量在类内部访问没有问题。看下
self.public += 1
print (self.__secret)
counter = just()
counter.count()
print (counter.public)
print (counter.__secret) #报错,在我的编译器里不会出现任何结果。私有变量在外部访问会出现错误
2、私有方法:
私有方法与私有变量的封装是类似的,只要在方法前加上双下划线 “__” 就是私有方法了。
例如:
class Animal(object):
def __init__(self,age,sex=1,weight=0.0):
self.age = age #定义年龄实例变量
self.sex = sex #定义性别实例变量
self.__weight = weight #定义体重实例变量
def eat(self):
self.weight +=0.5
print('eat...')
def __run(self): #定义私有方法
self.weight -=0.01
print('run...')
3、定义属性:
封装通常是对成员变量进行的封装。从严格意义上的面向对象设计中,一个类是不应该有公有的成员变量的,这些实例成员变量应该被设计为私有的,然后通过公有的 setter 和 getter 访问器访问。
????????
三、继承性
1、概念
含义:字面意思,就是有代与代之间的关系。类的继承性是面向对象的基本特性,多态性的前提是继承性。
看这样一个场景::
有一位程序员小刘,他需要在编程中描述和处理个人信息,于是定义了类Person,如下:
class Person:
def __init__(self,name,age):
self.name = name
self.age = age
def info(self):
template = 'Person[name = {0},age = {11}]'
s = template.format(self.name,self.age)
return s
一周后,小刘又遇到新的需求,需要描述和处理学生信息,于是他又定义了一个类:
class Student:
def __init__(self, name, age,school):
self.name = name
self.age = age
self.school = school #这是新的变量
def info(self):
template = 'Person[name = {0},age = {11}]'
s = template.format(self.name, self.age)
return s
在以上这一段代码中,虽然可以这样实现功能,但重复做了事情,这是不应该的。
这样的情况我们可以用继承:
class Student(Person): #声明Student类是继承自Person类,其中小括号中的是父类(也称基类),如果没有指明父类(即一对空的小括号或者省略小括号),则默认父类是object(即根类)
def __init__(self,name,age,school): #定义构造方法,子类中定义构造方法是首先要调用父类的构造方法,初始化父类实例变量
super().__init__(name,age) #调用父类的构造方法,super()函数是返回父类引用,通过它可以调用父类中的实例变量和方法
self.school = school #新的变量
当我们使用继承时,应该注意:子类只能继承父类中的公有成员变量和方法,不能继承私有成员变量和方法。
2、重写方法
含义:子类方法名与父类方法名相同,而且参数列表也相同,只是方法体不同,那么子类重写了父类的方法。面向对象的编程带来的主要好处之一是代码的重用
如下:
class diee:
def myMethons(self):
print('调用父类方法')
class son(diee):
def myMethons(self): #此方法名跟父类的方法名相同
print('重写父类方法')
c = son()
print(c.myMethons())
——>输出‘重写了父类的方法’
3、多继承
(1)、概念:一个子类有多个父类。比如:客轮是轮船也是交通工具,也就是说客轮的父类可以是轮船,也可以是交通工具。
(2)、为什么Python支持多继承?
当轮船跟交通工具都有同一个属性——>跑的时候,客轮应该怎么继承呢?
——>Python有自己的解决方案:即当子类实例调用一个方法时,先从子类中查找,如果没有找到则查找父类,父类有查找顺序,这个顺序时按照子类声明的父类列表从左到右查找,如果没有找到在找父类的父类,依次查找下去。
class ParentClass1:
def run(self):
print('父类1运行。。。')
class ParentClass2:
def run(self):
print('父类2运行。。。')
class SubClass3(ParentClass1,ParentClass2):
def run(self):
print('SubClass3运行。。。')
在python中继承中的一些特点:
- 1、如果在子类中需要父类的构造方法就需要显示的调用父类的构造方法,或者不重写父类的构造方法。
- 2、在调用基类的方法时,需要加上基类的类名前缀,且需要带上 self 参数变量。区别在于类中调用普通函数时并不需要带上 self 参数
- 3、Python 总是首先查找对应类型的方法,如果它不能在派生类中找到对应的方法,它才开始到基类中逐个查找。(先在本类中查找调用的方法,找不到才去基类中找)
四、多态性
1、概念:
2、适用范围:多态性对于动态语言Python而言意义不大,多态性又是在于运行期动态特性。
3、前提条件:两个:第一:继承——多态的发生一定是子类和父类之间;第二:重写——子类重写了父类的方法。
class Figure:
def draw(self):
print('绘制Figure...')
class Ellipse(Figure):
def draw(self):
print('绘制Ellipse..')
class Triangle(Figure):
def draw(self):
print('绘制Triangle..')
f1 = Figure() #不符合多态发生的条件
f1.draw()
f2 = Ellipse() #符合多态的条件,会发生多态
f2.draw()
f3 = Triangle() #符合多态的条件,会发生多态
f3.draw()
##多态发生时,Python解释器根据引用指向的实例调用它的方法
五、鸭子类型
六、枚举类
概念:枚举时用来管理一组相关的优先个数常量的集合,使用枚举可以提高程序的可读性,时代码更清晰且更易于维护。在Python提供枚举类型,它本质上是一种类。
1、定义枚举类
格式:
class 枚举类名 (enum.Enum):
枚举常量列表
枚举类继承自enum.Enum类,枚举中会定义多个常量成员。
例如:
import enum #
class WeekDays (enum.Enum): #定义WeekDays枚举类
#以下是初始化每一个成员值,这些成员值必须要初始化,这些常量成员值可以是任意类型,多个成员值也可以相同
MONDAY = 1
TUESDAY = 2
WEDNESDAY = 3
THURSDAY = 4
FRIDAY = 5
day = WeekDays.FRIDAY
print(day)
注意:枚举类实例化与类不同,枚举类不能调用构造方法。枚举实例value属性是返回枚举值,name属性返回枚举名。
2、限制枚举类
作用:为了使用方便
例:为了使枚举类常量成员只能使用整数类型,可以使用enum.IntEnum作为枚举父类;为了防止常量成员值重复,可以加上@enum.unique装饰器。例:
import enum
@enum.unique
class WeekDays (enum.IntEnum):
#枚举常量列表
MONDAY = 1
TUESDAY = 2
WEDNESDAY = 3
THURSDAY = 4
FRIDAY = 5
#FRIDAY = 4 #会报错
day = WeekDays.FRIDAY
print(day)
print(day.value)
print(day.name)
3、使用枚举类
定义枚举类的目的是提高程序的可读性,特别是在比较时,枚举类分厂使用。
如:
import enum
@enum.unique
class WeekDays (enum.IntEnum):
#枚举常量列表
MONDAY = 1
TUESDAY = 2
WEDNESDAY = 3
THURSDAY = 4
FRIDAY = 5
day = WeekDays.FRIDAY
if day == WeekDays.MONDAY:
print('工作日...')
elif day == WeekDays.FRIDAY:
print('学习...')
由上可知,使用枚举成员好于使用1和5这种无意义的数值。
以上纯属个人搜索的资料和一些个人见解,如有错误,欢迎指出,如有不同见解,欢迎一同探讨。
wechat:lwd0107