下面我们将构造两个类,分别是Person类和Student类。
class Person: def __init__(self, name, job, age): self.name = name self.job = job self.age = age
class语句定义了名为Person的类,然后在构造函数(__init__
)中给self赋值。构造函数在实例化的时候会自动调用并自动将实例传入第一个参数self中,然后通过给self赋值,实例就拥有了name,job,age属性。
对于熟悉C++的人而言self.name=name
是非常熟悉的,在C++里这里就是this->name=name
。赋值运算符左边的是实例的属性,而右边的是构造函数的形式参数。
现在,我们修改一下Person类的构造函数,给job和age添加上默认值,显得更加合理。
class Person: def __init__(self, name, job=None, age=1): self.name = name self.job = job self.age = age
现在,我们实例化Person的时候,可以之传入name即可。接下来实例化两个对象。
class Person: def __init__(self, name, job=None, age=1): self.name = name self.job = job self.age = age if __name__ == "__main__": person1 = Person('Zhangsan', '法外狂徒', age=30) person2 = Person('Lisi') print(person1.name, person1.job) print(person2.name, person2.job)
执行这段代码的输出如下所示:
Zhangsan 法外狂徒 Lisi None
上面的例子现在还是非常简单的,但是却展示了一些核心内容,person1和person2的属性是独立的,它们是两个不同的命名空间。我们可以有多个Person类的实例化,就像一段代码中可以有很多个列表一样。类是对象工厂
随着时间推移,人的年龄会增加,现在给Person类加上一个addage方法用来增加年龄。
class Person: def __init__(self, name, job=None, age=1): self.name = name self.job = job self.age = age def addage(self, age=1): # 默认age=1 self.age += age if __name__ == "__main__": person1 = Person('Zhangsan', '法外狂徒', age=30) person2 = Person('Lisi') print(person1.name, person1.job) print(person2.name, person2.job) person2.addage() # 增加年龄 print(person2.age)
实际上,我们完全可以在类外直接操作age属性来完成年龄的增加。例如:
person2.age += 1 print(person2.age)
类方法和在类外修改相比,提供了更好的可维护性。而且这样的方法将会是所有实例都拥有的方法。这就是“封装”带来的好处。
现在,为了更方便的显示打印,我们需要重载运算符来实现这点。先来看看我们现在直接打印person1对象的结果:
print(person1)
打印结果如下:
<__main__.Person object at 0x7f2f209fff40>
可以看到,打印了类名和在内存中的地址。不太直观,因此我们重载__repr__
或者__str__
来提供更好的显示效果。这里选择重载__repr__
。如下所示:
class Person: def __init__(self, name, job=None, age=1): self.name = name self.job = job self.age = age def addage(self, age=1): self.age += age def __repr__(self): # 重载__repr__ return F"{self.name},{self.job},{self.age}"
现在,我们来执行打印person1的代码,输出如下所示:
Zhangsan,法外狂徒,30
关于__repr__
,这里不做介绍,这里的重点是说明运算符重载的用处。
下面,我们来继承父类,实现定制化的行为,例如,我们需要一个学生类,那么学生的职业就是学生。我们的Student类实现如下所示:
class Student(Person): def __init__(self, name, age=3): Person.__init__(self, name, job='student', age=age)
Student继承Person类,然后覆盖了Person类的构造函数。覆盖的方式很巧妙,将job='student’传入给了父类Person的构造函数。
前面我们说过,我们很少使用X.__XXX__
的方式去调用双下划线方法,但是这里我们使用了Person.__init__
直接调用了父类的构造函数。需要注意当我们使用类.方法
这种方式的时候,需要手动传入self参数。因为使用实例.方法
调用的时候,python会自动将实例传入self参数。
现在来生成一个Student对象,然后打印一下看看输出。
student1 = Student('xiaoming', age=8) print(student1)
输出结果如下所示:
xiaoming,student,8
通常而言,作为学生是会有一个成绩好坏的评价指标。我们现在给学生类加上评价方法以及评价指标属性。现在的Student类如下所示:
class Student(Person): def __init__(self, name, age=3, grade='E'): self.grade = grade # 成绩属性 Person.__init__(self, name, job='student', age=age) def setGrade(self, grade): # 设置成绩 self.grade = grade def __repr__(self): # 覆盖父类的__repr__ return Person.__repr__(self) + "," + self.grade
我们给学生扩展了一个属性grade用来表示学生的成绩情况,默认值为E,同时新增方法setGrade来设置学生的成绩。覆盖父类的__repr__
方法来实现子类的__repr__
。
student1 = Student('xiaoming', age=8) student1.setGrade('B') print(student1)
输出结果如下:
xiaoming,student,8,B
组合类就是把对象嵌套在一起,来形成组合对象。我们来看一下新的类Manager
class Manager: def __init__(self, name, age, grade='E'): self.stu = Student(name, age, grade) def __getattr__(self, attr): return getattr(self.stu, attr) def __repr__(self): return str(self.stu) stu1 = Manager('xiaoming', age=8) stu1.setGrade('B') # 通过__getattr__,我们能够使用setGrade方法。 print(stu1)
Manager类的属性stu是Student类的实例,输出和上面的student1是一模一样的。值得注意的是Manager是代理模式的一个典型代表。委托是一种基于组合的结构,它管理一个被包装在内部的对象。
需要介绍一下__getattr__
,我们使用stu1.setGrade的时候,Manager类并没有setGrade方法。但是,我们调用成功了,这就得益于当访问object不存在的属性时会调用__getattr__
方法。该方法在Manager的实现中是使用getattr() 函数返回Student对象的属性值。
既然__getattr__
可以获取Student实例的属性,那么为什么还需要实现__repr__
方法? 这是因为python2.2引入了新式类,我们在Python3中只有所谓的“新式类”,新式类中是无法通过通用属性管理器找到它们的隐式属性。因为必须得实现__repr__
方法才能打印stu对象。
到这里也差不该结束这个例子了,这个例子差不多说明了设计OOP的一些思路。虽然它不够健全,但是它确实说明了一些问题。