Python 面向对象编程(下)

464 阅读7分钟

在前面的内容中,我们知道了如何去创建类以及实例化一个对象,并且了解了如何在一个类中间定义一个方法(函数),在接下来的这一小节里,我们将学习:

  1. 封装

  2. 类之间的关系

封装

封装是一种将抽象性函数接口的实现细节部分包装、隐藏起来的方法。同时,它也是一种防止外界调用端,去访问对象内部实现细节的手段,这个手段是由编程语言本身来提供的。适当的封装,可以将对象使用接口的程序实现部分隐藏起来,不让用户看到,同时确保用户无法任意更改对象内部的重要数据。它可以让代码更容易理解与维护,也加强了代码的安全性。

接下来,我们将使用Python中的装饰器来实现常见的代码封装

@property装饰器

当我们想要获取类中的属性值时,不建议直接访问类的属性,如果想要获取修改属性时,可以通过属性的gettersetter来进行对应的操作,这样一来不仅访问十分方便,代码也变得更加安全

class Student:
    def __init__(self,name,age):
        self._name = name 
        self._age = age
    
    # 获取属性
    @property
    def name(self):
        return self._name

    # 获取属性, 之后调用可以直接使用 .age
    @property
    def age(self):
        return self._age

    # 设置 age, age之后加 .setter
    @age.setter 
    def age(self,age):
        self._age = age 
    
    def prompt(self):
        if self._age < 18:
            print("{} can not drink wine".format(self._name))
        else:
            print("{} are allowed to drink".format(self._name))


if __name__ == '__main__':
    Cyberist = Student("Cyberist", 20)
    Cyberist.prompt()

    Cyberist.age = 10  # 因为之前使用过了setter,所以可以直接这样使用
    Cyberist.name = "Who"  # AttributeError: can't set attribute

在这里,我们使用单下划线_来表示私有属性,因为在这里我们使用了装饰器,安全已经得到了相应的保证。

__slots__

Python是一门动态的语言,我们可以在程序的运行中给我们定义的对象赋予新的属性已经新的方法,但是如果我们要限定我们的对象只能绑定一定的属性,我们可以在类中定义__slot__变量来进行限定,但是值得我们注意的是,__slot__的限定只对当前类的对象生效,对子类并不起作用


class Student:
    __slots__ = ('_name', '_age') # 限定属性的范围

    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 prompt(self):
        if self._age < 18:
            print("{} can not drink wine".format(self._name))
        else:
            print("{} are allowed to drink".format(self._name))


if __name__ == '__main__':
    Cyberist = Student("Cyberist", 20)
    Cyberist.prompt() 
    Cyberist._is_work_hard = True  # AttributeError: 'Student' object has no attribute '_is_work_hard'


@staticmethod

在类中定义方法,我们可以选择调用对象的本身,使用self(其实可以使用任意词,Python中约定俗称为self),但是实际上我们可能使用的函数并不需要调用对象的某些属性或者方法。比如说,在Student类中定义一个方法可以计算输入数字的和,此时不需要调用类中的任何属性,这样我们可以使用静态方法来解决这些问题

class Student:
    __slots__ = ('_name', '_age') # 限定属性的范围

    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
    
    @staticmethod  # 静态方法说明
    def add(num1, num2):
        print(num1+num2)


if __name__ == '__main__':
    Cyberist = Student("Cyberist", 20)
    Cyberist.add(1,2)

类之间的关系

在类之间存在着三种关系:is-ahas-a以及use-a

  • is-a:为继承关系,或者我们可以将其称为泛化,比如动物和猫之间的关系,电子产品和电脑之间的关系

  • has-a: 为关联关系,比如说电脑和其CPU之间的关系,关联关系如果是整体和部分的关系,那么我们称之为聚合关系;如果整体进一步负责了部分的生命周期,那么这种关系为最强的关联关系,我们称之为合成关系

  • use-a: 为依赖关系,比如说狙击手和狙击枪之间的关系,狙击手进行设置就需要使用狙击枪,在这里狙击手狙击的动作可以认为是类的方法,而狙击枪则是其参数。

利用类之间的这种关系,我们可以在已有类的基础之上来完成某些操作,进行某些行为,亦可以在已有类的基础之上创建一个新的类,从而减少代码的复用,同时也有利于代码的管理和维护。

继承

在上面我们提到了,我们可以利用已有类创建一个新的类,我们可以使用继承来实现。在继承中,被继承的类我们称为基类或者父类,继承之后的类我们称为子类或者派生类。在继承的过程中,子类继承了父类的所有属性和方法,同时还可以定义自己的方法。在Python中我们使用下面的格式进行继承

class Child(Father):  # 使用括号继承
    def __init__(self):
        pass

我们只需要将父类作为参数传给子类便可继承父类,接下来我们举一个详细的例子来进行说明。

class People:
    """人,基类"""
    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
    
    
    def __str__(self):
        return "I am {}, aged {}".format(self._name,self._age)
    
    
class Student(People):
    """学生,子类"""
    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("{}{}学习了{}".format(self._grade,self._name, course))


class Teacher(People):
    """老师,子类"""
    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("{}{}正在讲解{}".format(self._title, self._name,course))

if __name__ == '__main__':
    student= Student("Cyberist", 20, '大二')
    teacher = Teacher("Edgar", 30, '教授')
    student.study('基本电路理论')
    print("学生名字为: ", student.name)
    teacher.teach("理论力学")
    print("老师的名字为: ",teacher.name)


在这个例子中,People是基类,Student和Teacher是子类,继承于People,子类中含有父类的所有方法,我们可以随时的调用父类的方法,如上面的代码中调用了父类中的属性方法,可以获取到对象的名称。

在子类中使用了一个特殊的函数super(),super是什么,其实super就相当于继承的父类,我们可使用父类进行替代,在这里我们可以使用下面的代码进行替换

People.__init__(name, age) 

但是super做的远不止这些,因为,当一个类进行多次继承的时候,super类可以大量简化代码量,因为super函数只需要执行一次便可以初始化所有的参数,如下:

# 不使用super()函数,继承多个父类的子类代码累赘
class A:
  def __init__(self):
    pass
class B(A):
  def __init__(self):
    A.__init__(self)

class C(A):
  def __init__(self):
    A.__init__(self)

class D(A):
  def __init__(self): 
    A.__init__(self)

class E(B, C, D):
  def __init__(self):
    B.__init__(self)
    C.__init__(self)
    D.__init__(self)
  
# 使用super()函数,继承多个父类的子类只需要调用一个init方法
class A:
  def __init__(self):
    pass

class B(A):
  def __init__(self):
    super(B, self).__init__()

class C(A):
  def __init__(self):
    super(C, self).__init__()
    
class D(A):
  def __init__(self):
    super(D, self).__init__()
    
class E(B, C, D):
  def __init__(self):
    super(E, self).__init__()

多态

如果子类中有和父类中相同名称的方法,父类的方法不起作用,只有子类中的起作用,称为重载(override) 如果使用同一个基类进行继承,当我们对继承于父类的同一个行为(方法)可以进行重载之后,不同的子类对这个基类表现出不同的行为,这就是多态(poly-morphism)


from abc import ABCMeta, abstractmethod


class Pet(object, metaclass=ABCMeta):
    """宠物"""

    def __init__(self):
        pass 

    @abstractmethod
    def bark(self):
        """发出声音"""
        pass


class Dog(Pet):
    """狗"""
    def __init__(self):
        super().__init__()

    def bark(self):
        print('汪汪汪...')


class Cat(Pet):
    """猫"""
    def __init__(self):
        super().__init__()

    def bark(self):
        print('喵...喵...')


def main():
    pets = [Dog(), Cat()]
    for pet in pets:
        pet.bark()


if __name__ == '__main__':
    main()

在上面的例子中,我们把Pet类作为一个抽象的基类,即抽象类,抽象类有一个特点就是不能够进行实例化,我们不能使用这个抽象类创建对象,这个类的存在便是为了被继承。 但是Python并不像C++等语言一样提供对抽象类的支持,但是我们可以使用abc模块中的ABCMeta源类和装饰器abstractmethod来达到抽象的效果。我们在Dog和Cat两个不同的类中重写了父类的bark方法,使得两个类具有不同的行为表现,这就是多态---同样的方法,但是不同的效果


关注公众号,学习更多Python知识

公众号