1. __slots__变量
- 由于Python是一门动态语言,通常,动态语言允许我们在程序运行时:
- 给对象绑定新的属性或方法
- 也可对已经绑定的属性和方法进行解除绑定
- 如果我们需要限定自定义类型的对象只能绑定某些属性,可以通过在类中定义__slots__变量来进行限定。
- 注意:__slots__的限定只对当前类的对象生效,对子类并不起任何作用。
class Student(object):
# 用tuple定义允许绑定的属性名称
# 限定Student对象只能绑定 name和age属性
__slots__ = ('name', 'age')
class GraduateStudent(Student):
pass
s = Student() # 创建新的实例
s.name = 'Jackson' # 绑定属性'name'
s.age = 20 # 绑定属性'age'
# s.score = 100
# ERROR: AttributeError: 'Student' object has no attribute 'score'
# slots的限定只对当前类的对象生效,对子类并不起任何作用:
g = GraduateStudent()
g.score = 100
print('g.score =', g.score) # g.score = 100
2. 静态方法和类方法
- 之前,我们在类中定义的方法都是对象方法,也就是说这些方法都是发送给对象的消息。
- 实际上,我们写在类中的方法并不需要都是对象方法,例如:
- 我们定义一个“三角形”类,通过传入三条边长来构造三角形,并提供计算周长和面积的方法
- 但是传入的三条边长未必能构造出三角形对象,因此我们可以先写一个方法来验证三条边长是否可以构成三角形,这个方法很显然就不是对象方法,因为在调用这个方法时三角形对象尚未创建出来(因为不知道三条边能不能构成三角形)
- 所以这个方法是属于三角形类,而并不属于三角形对象的
- 我们可以使用静态方法来解决这类问题:
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()
# 12
# 6.0
- 和静态方法比较类似,Python还可以在类中定义类方法:
- 类方法的第一个参数约定名为cls
- 它代表的是当前类相关的信息的对象(类本身也是一个对象,有的地方也称之为类的元数据对象)
- 通过这个参数我们可以获取和类相关的信息并且可以创建出类的对象
from time import time, localtime, sleep
class Clock(object):
"""数字时钟"""
def __init__(self, hour=0, minute=0, second=0):
self._hour = hour
self._minute = minute
self._second = second
# 定义类方法:
@classmethod
def now(cls):
ctime = localtime(time())
return cls(ctime.tm_hour, ctime.tm_min, ctime.tm_sec)
def run(self):
"""走字"""
self._second += 1
if self._second == 60:
self._second = 0
self._minute += 1
if self._minute == 60:
self._minute = 0
self._hour += 1
if self._hour == 24:
self._hour = 0
def show(self):
"""显示时间"""
return '%02d:%02d:%02d' % \
(self._hour, self._minute, self._second)
def main():
# 通过类方法创建对象并获取系统时间
clock = Clock.now()
while True:
print(clock.show())
sleep(1)
clock.run()
if __name__ == '__main__':
main()
3. 类之间的关系
- 简单的说,类和类之间的关系有三种:is-a、has-a和use-a关系:
- s-a关系(继承或泛化),比如学生和人的关系、手机和电子产品的关系都属于继承关系。
- has-a关系(关联),比如部门和员工的关系,汽车和引擎的关系都属于关联关系。
- 聚合关系:关联关系如果是整体和部分的关联;
- 合成关系:如果整体进一步负责了部分的生命周期(整体和部分是不可分割的,同时同在也同时消亡),那么这种也就是最强的关联关系。
- use-a关系(依赖),比如司机有一个驾驶的行为(方法),其中(的参数)使用到了汽车,那么司机和汽车的关系就是依赖关系。
- 我们可以使用UML(统一建模语言)来进行面向对象建模,其中一项重要的工作就是把类和类之间的关系用标准化的图形符号描述出来。
4. 继承和多态
- 刚才我们提到了,可以在已有类的基础上创建新类,这其中的一种做法就是让一个类从另一个类那里将属性和方法直接继承下来,从而减少重复代码的编写。
- 提供继承信息的称之为父类,也叫超类或基类;
- 得到继承信息的称之为子类,也叫派生类或衍生类。
- 子类除了继承父类提供的属性和方法,还可以定义自己特有的属性和方法,所以子类比父类拥有的更多的能力
- 里氏替换原则:在实际开发中,我们经常会用子类对象去替换掉一个父类对象,这是面向对象编程中一个常见的行为
- 里氏替换原则的内容可以描述为:“派生类(子类)对象可以在程序中代替其基类(超类)(父类)对象。”
- 下面先看一个继承的例子:
# 父类:
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正在学习Python.' % self._name)
def watch_mv(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('李元芳', 15, '高三')
stu.study('数学')
stu.watch_mv()
t = Teacher('狄仁杰', 25, '大Boss')
t.teach('如何成为Python大牛')
t.watch_mv()
if __name__ == '__main__':
main()
# 高三的李元芳正在学习数学.
# 李元芳那就看看《喜羊羊与灰太狼》吧.
# 狄仁杰大Boss正在讲如何成为Python大牛.
# 狄仁杰可以打王者荣耀了.
- 子类在继承了父类的方法后,可以对父类已有的方法给出新的实现版本,这个动作称之为方法重写(override)。
- 通过方法重写我们可以让父类的同一个行为在子类中拥有不同的实现版本,当我们调用这个经过子类重写的方法时,不同的子类对象会表现出不同的行为,这个就是多态(poly-morphism)。
from abc import ABCMeta, abstractmethod
class Pet(object, metaclass=ABCMeta):
"""宠物"""
def __init__(self, nickname):
self._nickname = nickname
# 抽象方法
@abstractmethod
def make_voice(self):
"""发出声音"""
pass
class Dog(Pet):
"""狗"""
def make_voice(self):
print('%s: 汪汪汪...' % self._nickname)
class Cat(Pet):
"""猫"""
def make_voice(self):
print('%s: 喵...喵...' % self._nickname)
def main():
pets = [Dog('旺财'), Cat('凯蒂'), Dog('阿黄')]
for pet in pets:
pet.make_voice()
if __name__ == '__main__':
main()
# 旺财: 汪汪汪...
# 凯蒂: 喵...喵...
# 阿黄: 汪汪汪...
- 上面的代码中,我们将Pet类处理成了一个抽象类:
- 所谓抽象类就是不能够创建对象的类,这种类的存在就是专门为了让其他类去继承它
- Python从语法层面并没有像Java或C#那样提供对抽象类的支持,但是我们可以通过abc模块的ABCMeta元类和abstractmethod包装器来达到抽象类的效果
- 如果一个类中存在抽象方法那么这个类就不能够实例化(创建对象)
- 上面的代码中,Dog和Cat两个子类分别对Pet类中的make_voice抽象方法进行了重写并给出了不同的实现版本,
- 当我们在main函数中调用该方法时,这个方法就表现出了多态行为(同样的方法做了不同的事情)
综合案例1(工资结算系统):
"""
某公司有三种类型的员工 分别是部门经理、程序员和销售员
需要设计一个工资结算系统,根据提供的员工信息来计算月薪
1.部门经理的月薪是每月固定15000元
2.程序员的月薪按本月工作时间计算,每小时150元
3.销售员的月薪是1200元的底薪加上销售额5%的提成
"""
from abc import ABCMeta, abstractmethod
class Employee(object, metaclass=ABCMeta):
"""员工"""
def __init__(self, name):
"""
初始化方法
:param name: 姓名
"""
self._name = name
@property
def name(self):
return self._name
@abstractmethod
def get_salary(self):
"""
获得月薪
:return: 月薪
"""
pass
class Manager(Employee):
"""部门经理"""
def get_salary(self):
return 15000.0
class Programmer(Employee):
"""程序员"""
def __init__(self, name, working_hour=0):
super().__init__(name)
self._working_hour = working_hour
@property
def working_hour(self):
return self._working_hour
@working_hour.setter
def working_hour(self, working_hour):
self._working_hour = working_hour if working_hour > 0 else 0
def get_salary(self):
return 150.0 * self._working_hour
class Salesman(Employee):
"""销售员"""
def __init__(self, name, sales=0):
super().__init__(name)
self._sales = sales
@property
def sales(self):
return self._sales
@sales.setter
def sales(self, sales):
self._sales = sales if sales > 0 else 0
def get_salary(self):
return 1200.0 + self._sales * 0.05
def main():
emps = [
Manager('刘备'), Programmer('诸葛亮'),
Manager('曹操'), Salesman('荀彧'),
Salesman('吕布'), Programmer('张辽'),
Programmer('赵云')
]
for emp in emps:
if isinstance(emp, Programmer):
emp.working_hour = int(input('请输入%s本月工作时间: ' % emp.name))
elif isinstance(emp, Salesman):
emp.sales = float(input('请输入%s本月销售额: ' % emp.name))
# 同样是接收get_salary这个消息但是不同的员工表现出了不同的行为(多态)
print('%s本月工资为: ¥%s元' %
(emp.name, emp.get_salary()))
if __name__ == '__main__':
main()