python3面向对象编程案例,python面向对象编程例题

大家好,本文将围绕python面向对象编程的三大特性展开说明,python面向对象编程综合运用是一个很多人都想弄明白的事情,想搞清楚python面向对象编程项目列举需要先了解以下几个事情。

面向对象的实际例子

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

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

创建类并编写构造函数

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对象。

特殊的类属性

  • instance.__class__提供了创建实例的类的链接,例如:

    class C:
    ...
    
    obj = C()
    print(obj.__class__)
    

    直接在终端下执行这段代码,结果如下:

      <class '__main__.C'>
    

    它告诉你obj实例是继承自__main__.C,也就是当前文件(__main__代表当前文件)的C类。如果你是在别的地方导入类C所在的模块文件,那么将会显示“模块文件名.C”,例如,我们直接在交互式命令行下导入模块。

      >>> import 类5
      <class '类5.C'>
    
  • 类有一个__name__属性,是类名的字符串。例如:

    print(C.__name__)
    

    这将会直接打印出C

  • 类有__base____bases__属性,__base__会列出其直接基类(如果该类有多个父类,那么将会显示第一个父类的名称),而__bases__则会列出显式继承的多个父类。例如:

    class C:
    ...
    
    class C0:
        ...
    
    class C1(C0, C):
        ...
    
    obj = C()
    print(C.__base__)
    print(C.__bases__)
    
    obj1 = C1()
    print(C1.__base__)
    print(C1.__bases__)
    

    输出结果如下所示:

      <class 'object'>
      (<class 'object'>,)
      <class '__main__.C0'>
      (<class '__main__.C0'>, <class '__main__.C'>)
    

    可以看到,在python3中都是“新式类”,即所有的类都有一个父类object,因此,C虽然没有显式继承自某个类,但是会默认继承自object,因此打印出来的父类是object;而C1显式继承自C0和C,此时则只打印显式继承的父类。

  • object.__dict__给类对象和实例对象提供了一个字典,将所有命名空间对象中的属性都存储为键值对。例如:

    class Attr:
        a = 1
        b = 2
        def __init__(self, c, d):
            self.c = c
            self.d = d
        
        def func():
            print(666)
        
    obj = Attr(3, 4)
    print(obj.__dict__)
    print(Attr.__dict__)
    

    执行结果如下所示:

      {'c': 3, 'd': 4}
      {'__module__': '__main__', 'a': 1, 'b': 2, '__init__': <function Attr.__init__ at 0x7fb53df61040>, 'func': <function Attr.func at 0x7fb53df610d0>, '__dict__': <attribute '__dict__' of 'Attr' objects>, '__weakref__': <attribute '__weakref__' of 'Attr' objects>, '__doc__': None}
    

    正如我们预期的一样,对于实例对象而言,__dict__存储实例对象命名空间的属性;而对于类对象而言,__dict__存储类对象的命名空间的属性。

工具方法的命名习惯

如果我们有某个方法是不像用作其它用途的,那么通常的习惯是添加一个单下划线的前缀,表示这个工具方法只是用作某种特定的用途,并非通用的方法。这样做的好处是,如果你的类会被别的人继承,这样就能避免一部分的冲突。例如:类C有一个getname来获取特定的名称,但是继承类C的类C1也实现了一个getname方法,这将会覆盖父类的方法,从而导致错误。如果使用上面的命名方式,会极大的避免这样的错误出现。

总结

到这里也差不该结束这个例子了,这个例子差不多说明了设计OOP的一些思路。虽然它不够健全,但是它确实说明了一些问题。

  • 18
    点赞
  • 14
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值