Python进阶篇04-面向对象编程
面向对象编程
面向对象编程和面向过程编程的区别:
类和实例
类:抽象的、用于创建实例的基础模板,类里面可以定义这个类所拥有的基础的属性。
实例:根据类而创建的具体的对象,实例拥有类的基础属性和方法(类中的函数),比如 人类 就是一个类,而一个个具体的人,就是根据人类创建出来的具体的对象。
我们使用class
关键字来定义类,类名使用驼峰命名法:
class People:
def __init__(self, name, age):
self.name = name
self.age = age
def hello(self):
print('你好 {}'.format(self.name))
def how_old(self):
print('我{}岁了'.format(self.age))
创建类的实例化对象:
mike = People('mike', 15) # 根据__init__方法的参数来传参
mike.hello()
__init__()
类的初始化方法,定义该类的实例化对象必须绑定的基础属性,此方法第一个参数必须是self,指创建的实例本身,但在进行实例化时不需要传递此参数,python会自动传递,创建实例时就会将该类的基础属性绑定给实例对象,为通过self变量给实例化对象绑定属性;init方法中无参数时,创建实例不需要传参,有参数时,则创建实例时需要按照参数进行传递。- Python中创建类时默认继承了
object
类,不需要多余写,若要继承其他类,则需要class People(ObjectName):
- 面向对象编程中有个“数据封装”,在类中我们有基础属性,而直接在类中定义访问类中数据的函数,这样根据类实例化对象之后,可以直接通过调用实例的方法(即类中定义的函数)去访问实例对象中的数据,外部不需要知道类及其实例中的细节,这就称为封装(就像一个集装箱,其中有两个窗口,一个可以往外送东西,一个可以往里放东西,我们只需要在窗口输入特定的参数,就可以实现往集装箱放东西、取东西,而不需要关心其中是如何放、取)。
- “封装的目的在于保护类内部数据结构的完整性, 因为使用类的用户无法直接看到类中的数据结构,只能使用类允许公开的数据,很好地避免了外部对内部数据的影响,提高了程序的可维护性。用户只能通过暴露出来的方法访问数据时,你可以在这些方法中加入一些控制逻辑,以实现一些特殊的控制”。
访问限制
前面讲到,将类进行封装,可以避免外部随意操作内部的数据,那么如何使外部无法随意操作类及类的实例对象的内部数据呢?
可以看到,之前的写法是可以在外部随意操作内部数据的,这样很容易产生错误,因为我们无法控制外部的操作。
class People:
def __init__(self, name, age):
self.name = name
self.age = age
def hello(self):
print('你好 {}'.format(self.name))
def how_old(self):
print('我{}岁了'.format(self.age))
mike = People('mike', 15)
print(mike.name)
mike.name = 'pika'
print(mike.name)
----------
mike
pika
- 在Python中,我们可以在属性前面加上两个下划线
__
,这样就可以让内部属性无法被外部访问,使之变成了一个私有变量;
class People:
def __init__(self, name, age):
self.__name = name
self.__age = age
def get_name(self):
return self.__name
def set_name(self, n):
self.__name = n
return True
mike = People('mike', 15)
print(mike.__name)
----------
Traceback (most recent call last):
File ".........pythonProject/main.py", line 33, in <module>
print(mike.__name)
AttributeError: 'People' object has no attribute '__name'
- 但这种并非真正的私有变量,在Python内部,是将这样的变量修改为了
_ClassName__variable
,因此直接通过原来的变量名无法进行访问,这是一种双方默认的私有,即双方自觉遵守规定的私有;
print(mike._People__name) # 极为不建议,错误的做法
- 在Python中,还有一种
__variable__
的变量,这种是Python的特殊变量,可以直接进行访问,如__name__
,__file__
等; - 还有一种以单下划线开头的变量名
_variable
,这种的含义是,我将其标记为了私有变量,虽然可以进行访问,但请你不要访问它; - 将变量设置为私有变量后,为了外部可以 设置/获取 私有变量的值,就可以给类添加 get方法 与 set方法;
print(mike.get_name())
mike.set_name('kk')
print(mike.get_name())
----------
mike
kk
- 外部不可以再操作私有变量,但注意 虽然在表面看来可以对私有变量赋值,实际上那只是给对象新增了一个属性;
mike.name = 'pika' # 此时的name已经是一个新的属性,相当于给mike对象新增了一个name属性
print(mike.name)
mike.__name = 'test' # 相当于给mike对象新增了一个__name属性
print(mike.__name)
print(mike.get_name()) # 原来的__name属性,还是原来的值
----------
pika
test
mike
继承和多态
继承
- 我们定义一个类的时候,可以从现有的类中继承,被继承的类即为父类(或者基类、超类),新定义的类即为子类;
- 当新类继承某个类后,即拥有了父类的全部功能,继承是实现代码复用的重要方式,可以减少很多重复性代码、使代码更便于维护;
- 继承某个类后,我们还可以在子类上覆盖重写父类的功能 或者 扩展新的特色功能;
- Python中,所有的类都默认继承
Object
类; - 继承分为 单继承、多重继承;
单继承
单继承即普通的继承,一个类继承另一个类。
class People:
def __init__(self, name, age):
self.__name = name
self.__age = age
def get_name(self):
return '我的名字是{}'.format(self.__name)
def set_name(self, n):
self.__name = n
return True
def say(self):
print('我是一个人类')
class Adult(People):
def say(self):
print('我是一个成年人')
class Child(People):
def say(self):
print('我是一个小孩子')
sky = Adult('sky', 17)
print(sky.get_name())
sky.say()
Adult和Child都继承了People类,拥有People类的所有功能,也可以自己定义新的特色功能
多重继承
在上述例子中,再扩展一下,生活中一般分为工作和读书,那么假如我们有一些类:读书的成年人、读书的孩子、工作的成年人、工作的孩子...,此时我们就可以使用多重继承,可以使一个类同时拥有多种特质(功能)
class Work:
@staticmethod
def work():
print('我在打工,我好累')
class Study:
@staticmethod
def study():
print('我要好好学习')
class ChildLabour(Child, Work):
pass
class StudyAdult(Adult, Study):
pass
maria = ChildLabour('maria', 13)
maria.say()
maria.work()
----------
我是一个小孩子
我在打工,我好累
ChildLabour类同时继承了Child和Work,即为工作的孩子-童工类,同时拥有了Child和Work的特质(功能);通过多重继承,我们可以自由对基类进行组合,以创造出合适的新类
多态
- 多态的概念依赖于继承,因为继承,子类拥有了父类的方法,而子类不仅可以重写父类的方法,还可以自己添加自己特有的方法,最终同一个类型的对象,执行相同的方法时,会出现多种不同的结果,即同一类事物,有多重形态;
- 当我们调用一个类的实例对象中的方法时,会先从此类本身的定义中寻找此方法,如果没有,则再找父类是否有这个方法,按照此顺序依次向上寻找;
- 因此,当子类与父类拥有同样的方法时,调用子类的实例化对象的这个方法,那么调用的是子类的方法,这种子类与父类拥有相同方法我们称之为重写,即重新编写父类的方法,使其更“特色”;
- 在上述例子中,Adult和Child都继承了People类,都重写了People类的say()方法,分别调用Adult.say()、Child.say(),却会出现不同的结果,这就是因为继承而出现的多态;
- 多态是 发生在父类与子类之间,并且因为子类重写了父类的方法,而产生了调用同一种方法出现不同结果的现象;
如何运用多态呢?如果仅仅是通过继承和重写,在子类中定义相同的方法而使其出现不同的结果,和在子类中定义不同的方法也没什么区别;而假如我们通过其他外部函数,针对某一类对象,统一进行调用并对数据进行处理,这样我们只需要知道传入此类型的对象 或者 拥有此种需要的方法的对象 即可,如果还需要新增新的类去使用这个外部函数来进行处理的话,再定义一个新的子类就可以了,不需要去动这个外部函数的内部功能,这样提高了代码的灵活性、增加了程序的可扩展性。
class People:
def __init__(self, name, age):
self.__name = name
self.__age = age
def say(self):
print('我是一个人类')
class Adult(People):
def say(self):
print('我是一个成年人')
class Child(People):
def say(self):
print('我是一个小孩子')
def say_what(people):
print('我是一个{}'.format(type(people)))
people.say()
p = People('kk', 12)
a = Adult('mike', 19)
c = Child('lisa', 10)
say_what(p)
say_what(a)
say_what(c)
----------
我是一个<class '__main__.People'>
我是一个人类
我是一个<class '__main__.Adult'>
我是一个成年人
我是一个<class '__main__.Child'>
我是一个小孩子
我们可以通过一个方法来处理同一类型的对象,再加上多态,从而得到不同的结果,如果我们需要增加其他处理逻辑,直接修改say_what()函数即可;如果我们需要增加新的对象进行处理,再增加新的子类或者拥有say()方法的对象即可。
实例属性和类属性
实例属性:可以通过实例变量直接给实例化对象添加属性,也可以通过定义类时的self变量给实例化对象绑定属性;
类属性:直接在类中绑定在类上的属性,属于类本身,可以通过 classname.name 来进行访问,不过该类的实例化对象也可以访问;
class People:
count = 0 # 绑定在类的属性,可以通过 People.count访问
def __init__(self, name):
self.name = name # 通过 self 变量给实例化对象绑定属性
People.count += 1
mike = People('mike')
mike.age = 17 # 通过变量,直接给实例化对象绑定属性,类无此属性
print(mike.age)
print('给mike直接绑定count属性前,打印count属性值:{}'.format(mike.count)) # mike对象本身没有这个属性,会向上查找类的count属性
mike.count = 666
print('给mike直接绑定count属性之后,打印count属性值:{}'.format(mike.count))
print('给mike直接绑定count属性之后,打印People类的count属性值:{}'.format(People.count))
# 给mike对象绑定后,mike对象的有count属性,则会直接打印出mike对象的属性
# 但是People的类属性 count 并未改变,只是通过mike对象无法再访问到这个类属性
----------
给mike直接绑定count属性前,打印count属性值:1
给mike直接绑定count属性之后,打印count属性值:666
给mike直接绑定count属性之后,打印People类的count属性值:1
“注意在实际生活中,不要对实例属性和类属性使用相同的名字,这样实例属性会覆盖掉类属性,而删除掉实例属性后,再使用相同名称访问到的是类属性,这样可能会出现意外的错误。“