面向对象进阶
decorotor - 装饰器/包装器
def record(fn):
def wrapper(*args,**kwargs):
print('执行%s' % fn.__name__)
print(args)
print(kwargs)
# 此行代码在执行被装饰的函数
# 在这行代码前后我们可以附加其他代码
# 这些代码可以让我们在执行函数时做一些额外的工作
val = fn(*args,**kwargs)
print('%s执行完成' % fn.__name__)
print('返回了%d' % val)
# 返回被装饰的执行结果
return val
return wrapper
# 通过装饰器f函数 让f函数在执行时做一些额外的工作
@record
def f(n):
if n==0 or n==1:
return 1
return n * f(n-1)
if __name__ == '__main__':
print(f(5))
@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('王大锤', 12)
person.play()
person.age = 22
person.play()
# person.name = '白元芳' # AttributeError: can't set attribute
if __name__ == '__main__':
main()
slots魔法
Python允许我们在程序运行时给对象绑定新的属性或方法,当然也可以对已经绑定的属性和方法进行解绑定。但是如果我们需要限定自定义类型的对象只能绑定某些属性,可以通过在类中定义slots变量来进行限定。需要注意的是slots的限定只对当前类的对象生效,对子类并不起任何作用。
# 限定Person对象只能绑定_name, _age和_gender属性
__slots__ = ('_name', '_age', '_gender')
可变参数 - 元组
def foo(*args)
关键字参数 - 字典 - 根据参数名来决定如何执行
def foo(**kwargs)
命名关键字参数 -前面参数名可写可不写,后面必须写
如果前面有可变参数,后面就不用写*
def foo(a, b, c, *, name, age)
高阶函数 - f(f(x))
通过向函数中传入函数 可以写出更通用的代码
calc函数中的第二个参数是另一个函数 它代表了一个二元运算
这样calc函数就不需要跟某一种特定的二元运算耦合在一起
所以calc函数变得通用性更强 可以由传入的第二个参数来决定到底做什么
def calc(my_list, op):
total = my_list[0]
for index in range(1, len(my_list)):
total = op(total, my_list[index])
return total
def bar():
def foo(a, b, c, *, name, age):
print(a + b + c)
print(name, ':', age)
return foo
def say_hello(**kwargs):
print(kwargs)
for key in kwargs:
print(key, '--->', kwargs[key])
if 'name' in kwargs:
print('你好, %s!' % kwargs['name'])
elif 'age' in kwargs:
age = kwargs['age']
if age <= 16:
print('你还是个小屁孩')
else:
print('你是一个成年人')
else:
print('请提供个人信息!')
def add(x, y):
return x + y
def mul(x, y):
return x * y
def main():
mylist = [1, 3, 5, 7, 9]
print(calc(mylist, add))
print(calc(mylist, mul))
x = bar()
x(1, 2, 3, name='Hao', age=38)
if __name__ == '__main__':
main()
继承 - 从已经有的类创建新类的过程
提供继承信息的称为父类(超类/基类)
得到继承信息的称为子类(派生类/衍生类)
通过继承我们可以将子类中的重复代码抽取到父类中
子类通过继承并复用这些代码来减少重复代码的编写
将来如果要维护子类的公共代码只需要在父类中进行操作即可
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 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('王大锤', 15, '初三')
stu.study('数学')
stu.watch_av()
t = Teacher('骆昊', 38, '叫兽')
t.teach('Python程序设计')
t.watch_av()
if __name__ == '__main__':
main()
类之间的三种关系:
-
has-a - 关联
比如部门和员工的关系,汽车和引擎的关系
关联关系如果是整体和部分的关联,那么我们称之为聚合关系
-
use-a - 依赖
比如司机有一个驾驶的行为(方法),其中(的参数)使用到了汽车,那么司机和汽车的关系就是依赖关系
-
is-a - 继承或泛化
比如学生和人的关系、手机和电子产品的关系
静态方法和类方法
之前,我们在类中定义的方法都是对象方法,也就是说这些方法都是发送给对象的消息。实际上,我们写在类中的方法并不需要都是对象方法,例如我们定义一个“三角形”类,通过传入三条边长来构造三角形,并提供计算周长和面积的方法,但是传入的三条边长未必能构造出三角形对象,因此我们可以先写一个方法来验证三条边长是否可以构成三角形,这个方法很显然就不是对象方法,因为在调用这个方法时三角形对象尚未创建出来(因为都不知道三条边能不能构成三角形),所以这个方法是属于三角形类而并不属于三角形对象的。我们可以使用静态方法来解决这类问题,代码如下所示:
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()
和静态方法比较类似,Python还可以在类中定义类方法,类方法的第一个参数约定名为cls,它代表的是当前类相关的信息的对象(类本身也是一个对象,有的地方也称之为类的元数据对象),通过这个参数我们可以获取和类相关的信息并且可以创建出类的对象,代码如下所示
抽象类
Python没有从语言层面支持抽象类的概念
我们可以通过abc模块来制造抽象类的效果
在定义类的时候通过指定metaclass=ABCMeta可以将类声明为抽象类
抽象类是不能创建对象的 抽象类存在的意义是专门拿给其他类继承
abc模块中还有一个包装器abstractmethod
通过这个包装器可以将方法包装为抽象方法 必须要求子类进行重写
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这个消息 但是不同的员工表现出了不同的行为
# 因为三个子类都重写了get_salary方法 所以这个方法会表现出多态行为
print('%s本月工资为: ¥%s元' %
(emp.name, emp.get_salary()))
if __name__ == '__main__':
main()
优质代码,高级编程两大原则:
高内聚 低耦合 (high cohesion low coupling)