Python面向对象的实际例子(一)

面向对象的实际例子

下面我们将构造两个类,分别是Person类和Student类。

  • Person类将创建一个处理和人相关的信息的类
  • Student类将定制化Person,修改了所继承的行为。
    下面我们来一步步构造上面这两个类。

创建类并编写构造函数

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的一些思路。虽然它不够健全,但是它确实说明了一些问题。

上一篇:Python - 魔法方法


下一篇:单例模式总结