文章部分转自:https://github.com/jackh001/python100Day/blob/master
自己的话:读书不觉已春深,一寸光阴一寸金
一台大G正在向我家车库驶来
眼泪你别问 . joker这个男人你别恨
面向对象编程基础与进阶
什么是面向对象的编程:
把一组数据结构和处理它们的方法组成对象,把相同行为的对象归纳为类,通过类的封装隐藏内部细节,通过继承实现类的特化和泛化,通过多态实现基于对象类型的动态分派。
一、类和对象
类是对象的蓝图和模板,而对象是类的实例。
1.定义类
**在Python中可以使用class关键字定义类,**然后在类中通过之前学习过的函数来定义方法,这样就可以将对象的动态特征描述出来,代码如下所示:
# 在Python2中这个object一定要写,但是在Python3中可写可不写(**默认就继承了object**).
#
class Student(object):
# __init__是一个特殊方法用于在创建对象时进行初始化操作
# 通过这个方法我们可以为学生对象绑定name和age两个属性
def __init__(self, name, age):
self.name = name
self.age = age
def study(self, course_name):
print('%s正在学习%s.' % (self.name, course_name))
def watch_av(self):
if self.age < 18:
print('%s只能观看《熊出没》.' % self.name)
else:
print('%s正在观看岛国爱情动作片.' % self.name
# 实例
student = Student()
# PEP 8要求标识符的名字用全小写多个单词用下划线连接
# 但是很多程序员和公司更倾向于使用驼峰命名法(驼峰标识)
#例如,下面是分别用骆驼式命名法和下划线法命名的同一个函数:
#printEmployeePaychecks();
#print_employee_paychecks();
#第一个函数名使用了骆驼式命名法——函数名中的每一个逻辑断点都有一个大写字母来标记;第二个函数名使用了下划线法----函数名中的每一个逻辑断点都有一个下划线来标记。
写在类中的函数,我们通常称之为(对象的)方法,这些方法就是对象可以接收的消息
class Student(object):
"""
第二步初始化,初始化自己.
当你的类有一些公用变量的时候,你就可以初始化
初始化中经常放入公有的变量.
"""
def __init__(self,name):
"""
在类中,一切的变量和函数都要印记(self)
"""
self.name = name
def def1(self,num):
self.num = num
print(self.num)
print(self.name)
def def2(self):
print(self.num)
print(self.name)
if __name__ == "__main__":
# 实例,类名带括号“Student()”,直接运行初始化函数
student = Student('Jasmine')
student.def1(100)
student.def2()
结果:
100
Jasmine
100
Jasmine
(*)课堂截图:
2.创建和使用对象
当我们定义好一个类之后,可以通过下面的方式来创建对象并给对象发消息:
def main():
# 创建学生对象并指定姓名和年龄
stu1 = Student('jasmine', 18)
# 给对象发study消息
stu1.study('大数据一班')
# 给对象发watch_av消息
stu1.watch_av()
stu2 = Student('joker', 25)
stu2.study('也不行')
stu2.watch_av()
if __name__ == '__main__':
main()
3.访问可见性问题
在很多面向对象编程语言中,我们通常会将对象的属性设置为私有的(private)或受保护的(protected),简单的说就是不允许外界访问,而对象的方法通常都是公开的(public),因为公开的方法就是对象能够接受的消息。**在Python中,属性和方法的访问权限只有两种,也就是公开的和私有的,如果希望属性是私有的,在给属性命名时可以用两个下划线作为开头。**下面的代码可以验证这一点:
# 私有变量,变量名前面加"__"
# 如果非要使用私有变量,那么可以使用dir(class())去查看它真正的名字.
# 私有变量/函数,在类内部可以直接调用.
# 如果你想体现一个变量/函数特别重要你可以使用"_"
class Test:
def __init__(self, foo):
self.__foo = foo
def __bar(self):
print(self.__foo)
print('__bar')
def main():
test = Test('hello')
# AttributeError: 'Test' object has no attribute '__bar'
test.__bar()
# AttributeError: 'Test' object has no attribute '__foo'
print(test.__foo)
if __name__ == "__main__":
main()
但是,Python并没有从语法上严格保证私有属性或方法的私密性,它只是给私有的属性和方法换了一个名字来“妨碍”对它们的访问,事实上如果你知道更换名字的规则仍然可以访问到它们,下面的代码就可以验证这一点:
class Test:
def __init__(self, foo):
self.__foo = foo
def __bar(self):
print(self.__foo)
print('__bar')
def main():
test = Test('hello')
test._Test__bar()
print(test._Test__foo)
if __name__ == "__main__":
main()
在实际开发中,我们并不建议将属性设置为私有的,因为这会导致子类无法访问
4.@property装饰器
之前我们讨论过Python中属性和方法访问权限的问题,虽然我们不建议将属性设置为私有的,但是如果直接将属性暴露给外界也是有问题的,比如我们没有办法检查赋给属性的值是否有效。我们之前的建议是将属性命名以单下划线开头,通过这种方式来暗示属性是受保护的,不建议外界直接访问,那么如果想访问属性可以通过属性的getter(访问器)和setter(修改器)方法进行对应的操作。如果要做到这点,就可以考虑使用@property包装器来包装getter和setter方法,使得对属性的访问既安全又方便,代码如下所示:
class Person(object):
def __init__(self, name, age):
self._name = name
self._age = age
# 访问器 - getter方法
@property
def name(self):
return self._name
# 访问器 - getter方法
@property
def age(self):
return self._age
# 修改器 - setter方法
@age.setter
def age(self, age):
self._age = age
def play(self):
if self._age <= 16:
print('%s正在欢乐海沙滩晒太阳.' % self._name)
else:
print('%s正在出海.' % self._name)
def main():
person = Person('Jasmine', 18)
person.play()
person.age = 22
person.play()
# person.name = '啥玩意' # AttributeError: can't set attribute
if __name__ == '__main__':
main()
(*)课堂截图:
访问器(装饰器):把函数变成了一个属性
5.__slots__魔法
我们讲到这里,不知道大家是否已经意识到,Python是一门动态语言。通常,动态语言允许我们在程序运行时给对象绑定新的属性或方法,当然也可以对已经绑定的属性和方法进行解绑定。但是如果我们需要限定自定义类型的对象只能绑定某些属性,可以通过在类中定义__slots__变量来进行限定。需要注意的是__slots__的限定只对当前类的对象生效,对子类并不起任何作用。
class Person(object):
# 限定Person对象只能绑定_name, _age和_gender属性
__slots__ = ('_name', '_age', '_gender')
def __init__(self, name, age):
self._name = name
self._age = age
@property
def name(self):
return self._name
@property
def age(self):
return self._age
@age.setter
def age(self, age):
self._age = age
def play(self):
if self._age <= 16:
print('%s正在玩水.' % self._name)
else:
print('%s正在玩蛇.' % self._name)
def main():
person = Person('joker', 39)
person.play()
person._gender = '男'
# AttributeError: 'Person' object has no attribute '_is_gay'
# person._is_gay = True
课堂截图(动态绑定):
结果为:100000
课堂截图:
下面要print,上面就要有return返回值,否则会打印None。
上图中的这两个 joker.a,第一个走的是修改器,第二个走的是装饰器。
6.静态方法和类方法
之前,我们在类中定义的方法都是对象方法,也就是说这些方法都是发送给对象的消息。实际上,我们写在类中的方法并不需要都是对象方法。属于类而并不属于对象的,我们可以使用静态方法来解决这类问题:
静态方法的使用方法
例如:我们定义一个“三角形”类,通过传入三条边长来构造三角形,并提供计算周长和面积的方法,但是传入的三条边长未必能构造出三角形对象,因此我们可以先写一个方法来验证三条边长是否可以构成三角形,这个方法很显然就不是对象方法,因为在调用这个方法时三角形对象尚未创建出来(因为都不知道三条边能不能构成三角形),所以这个方法是属于三角形类而并不属于三角形对象的。我们可以使用静态方法来解决这类问题,代码如下所示:
from math import sqrt
class Triangle(object):
def __init__(self, a, b, c):
self._a = a
self._b = b
self._c = c
@staticmethod
def is_valid(a, b, c):
return a + b > c and b + c > a and a + c > b
def perimeter(self):
return self._a + self._b + self._c
def area(self):
half = self.perimeter() / 2
return sqrt(half * (half - self._a) *
(half - self._b) * (half - self._c))
def main():
a, b, c = 3, 4, 5
# 静态方法和类方法都是通过给类发消息来调用的
if Triangle.is_valid(a, b, c):
t = Triangle(a, b, c)
print(t.perimeter())
# 也可以通过给类发消息来调用对象方法但是要传入接收消息的对象作为参数
# print(Triangle.perimeter(t))
print(t.area())
# print(Triangle.area(t))
else:
print('无法构成三角形.')
if __name__ == '__main__':
main()
7.类之间的关系
简单的说,类和类之间的关系有三种:is-a、has-a和use-a关系。
- is-a关系也叫继承或泛化,比如学生和人的关系、手机和电子产品的关系都属于继承关系。
- has-a关系通常称之为关联,比如部门和员工的关系,汽车和引擎的关系都属于关联关系;关联关系如果是整体和部分的关联,那么我们称之为聚合关系;如果整体进一步负责了部分的生命周期(整体和部分是不可分割的,同时同在也同时消亡),那么这种就是最强的关联关系,我们称之为合成关系。
- use-a关系通常称之为依赖,比如司机有一个驾驶的行为(方法),其中(的参数)使用到了汽车,那么司机和汽车的关系就是依赖关系。
二、继承和多态
刚才我们提到了,可以在已有类的基础上创建新类,这其中的一种做法就是让一个类从另一个类那里将属性和方法直接继承下来,从而减少重复代码的编写。提供继承信息的我们称之为父类,也叫超类或基类;得到继承信息的我们称之为子类,也叫派生类或衍生类。子类除了继承父类提供的属性和方法,还可以定义自己特有的属性和方法,所以子类比父类拥有的更多的能力,下面我们先看一个继承的例子:
class Person(object):
"""人"""
def __init__(self, name, age):
self._name = name
self._age = age
@property
def name(self):
return self._name
@property
def age(self):
return self._age
@age.setter
def age(self, age):
self._age = age
def play(self):
print('%s正在愉快的玩耍.' % self._name)
def watch_av(self):
if self._age >= 18:
print('%s正在观看爱情动作片.' % self._name)
else:
print('%s只能观看《熊出没》.' % self._name)
class Student(Person):
"""学生"""
def __init__(self, name, age, grade):
super().__init__(name, age)
self._grade = grade
@property
def grade(self):
return self._grade
@grade.setter
def grade(self, grade):
self._grade = grade
def study(self, course):
print('%s的%s正在学习%s.' % (self._grade, self._name, course))
class Teacher(Person):
"""老师"""
def __init__(self, name, age, title):
super().__init__(name, age)
self._title = title
@property
def title(self):
return self._title
@title.setter
def title(self, title):
self._title = title
def teach(self, course):
print('%s%s正在讲%s.' % (self._name, self._title, course))
def main():
stu = Student('jasmine', 18, '高三')
stu.study('计算机')
stu.watch_av()
t = Teacher('joker', 39, '老程序猿')
t.teach('水学')
t.watch_av()
if __name__ == '__main__':
main()
课堂截图(继承):
注意:1.括号里写目标。
2.在子类初始化父类。