python 面向对象三大特性之封装与多态

python 面向对象三大特性之封装与多态

在上一篇中,我们将继承这一重要的面向对象的特性进行了介绍。

这一篇是对剩下的封装和多态进行讲解。

面向对象之封装

  1. 封装:就是将数据和功能'封装'起来。
  2. 隐藏:将数据和功能隐藏起来不让用户直接调用,而是开发一些接口间接调用从而可以在接口内添加额外的操作。
  3. 伪装:将类里面的方法伪装成类里面的数据。

这第一点不必展开了,就是class关键字做的事,在python面向对象编程思想及语法基础中就已经介绍了。

隐藏

类在定义阶段类体中:

如果属性的名字前面加了两个下划线,那么它们的名字在后续的调用阶段就无法直接通过原本的名字调用到:

class A:
    __name = 'leethon'
    
print(A.__name)  # AttributeError: type object 'A' has no attribute '__name'

如上述代码中报出属性错误,明明定义了这个属性,却无法通过这个名字调用。

检查一下类A的属性:

print(A.__dict__)  #
"""
{'__module__': '__main__', '_A__name': 'leethon', '__dict__': <attribute '__dict__' of 'A' objects>, '__weakref__': <attribute '__weakref__' of 'A' objects>, '__doc__': None}
"""

主要看这个'_A__name': 'leethon'与我们之前在类体中定义的属性十分相像,但是属性名变了个形式:

_类名__属性名,所以我们无法通过原本的__name来找到这个属性。

而如果我们在类体中使用__name来查找这个属性。

class A:
    __name = 'leethon'

    @classmethod
    def get(cls):
        print(cls.__name)

A.get()
"""
leethon   # 访问到了这个数据
"""

也就是说,定义这个类的人可以在类体中使用这些数据。

除了类,对象也可以设置隐藏属性,但是只能在类体中设置,对象在类体中设置独有方式的方法就只有__init__方法:

class A:
    def __init__(self, name, age, hobby):
	    self.__name = name
        self.__age = age
        self.__hobby = hobby
        

以上就是隐藏数据与功能不让用户使用的一些方法。

但是数据和功能本身就是拿来用的,不能通过这个方式用,我们在设计这个类的人就要提供另外的方式去让用户可以以我们限定的方式去使用这些数据:

让用户只能读,不能改
# 常规属性:
class A:
    def __init__(self, name):
        self.name = name
        
a = A('leethon')
print(a.name)  # leethon
a.name = 'lee'
print(a.name)  # lee
"""leethon直接被改动,变短了,这怎么行呢,我这个对象的名字写了你就不要改了,看看就好"""

# 只读属性:
class B:
    def __init__(self, name):
        self.__name = name

    def get_name(self):
        print(self.__name)

a = B('leethon')
a.get_name()  # leethon
a.__name = 'lee'
a.get_name()  # leethon
""" leethon这次保持了原本的长度,因为外面使用的__name只是添加了个新属性,没有对原本的属性造成修改,不过用户还是可以通过get_name的函数查看对象的属性值 """

隐藏总结

  • 在类体中以__属性名的方式定义属性,在类体中可以通过同样的方式调用
  • 在类体外以__属性名的方式定义属性,在任何地方都可以通过同样的方式调用(没有隐藏)
  • 在类体中以隐藏方式定义的属性,理论上可以在类体外访问到,但不建议。

伪装

我们刚才的例子中,是用get_name()提供了一个接口来访问__name,但是我们原本是通过a.name就能访问的,现在却只能用一种函数的方式来取,会给调用者一些困惑。

所以在python中我们可以通过装饰器(内置的)来将函数的功能属性,伪装为数据属性。

class C:
    def __init__(self, name):
        self.__name = name

    @property  # 属性伪装装饰器
    def name(self):
        return self.__name

a = C('leethon')
print(a.name)  # leethon
a.name = 'lee'  # AttributeError: can't set attribute
print(a.name)

我们通过原本的a.name的调用方式就可以访问到name了,但是不能通过原本的方式对name属性进行更改。

除了把函数伪装成只可以查的数据属性,也可以伪装为可以改、可以删的数据属性:

class D:
    def __init__(self, name):
        self.__name = name

    @property  # 属性伪装装饰器
    def name(self):
        return self.__name

    @name.setter  # 让属性可更改
    def name(self, value):
        self.__name = value

    @name.deleter  # 让属性可删除
    def name(self):
        del self.__name


a = D('leethon')
print(a.name)  # leethon
a.name = 'lee'  
print(a.name)  # lee
del a.name 
print(a.name)  # 提示没有这个属性的报错

在使用setter和deleter之前都需要加装property才可以加装这两个属性,即先能查后才能改或者删。

伪装总结

  • 我们隐藏属性是为了限制用户对这个属性的增删改查的权限,开放接口函数让用户只能按照我们限定给他们的方式来使用这些属性。
  • 通过property限制用户只能读取某个属性
  • 加装property后可以通过在加装函数名.setter函数名.deleter的方式让数据可改和可删

面向对象之多态

鸭子模型

面向对象中多态是指一种事物可以有多种形态,但是针对相同的功能应该定义相同的方法。
这样无论我们拿到的是哪个具体的事物,都可以通过相同的方法调用功能。

比如,数据类型中的字符串、列表、字典,我们都可以使用数据.__len__的方式得到它们的元素个数。

s1 = 'hello world'
l1 = [11, 22, 33, 44]
d = {'name': 'jason', 'pwd': 123}
print(s1.__len__())  # 11
print(l1.__len__())  # 4
print(d.__len__())  # 2

“鸭子测试”可以这样表述:一只鸟走起来像鸭子、游泳起来像鸭子、叫起来也像鸭子,那么这只鸟可以被称为鸭子。在鸭子类型中,关注点在于对象的行为,而不是关注对象所属的类型。

上述引用于一段对鸭子模型的表述。

在Linux系统中,一切皆文件,实际上以面向对象的表述来说,就是指所有的对象都是可以读写的。

那么我们可以定义一个父类,里面有读写功能,而所有类都继承这个类,也都应该有自己的读写功能。

class File:
    def read(self): pass
    def write(self): pass

class Memory(File):
    def read(self): pass
    def write(self): pass

class Disk(File):
    def read(self): pass
    def write(self): pass

python语言推崇鸭子模型,所以只要像就可以,但是你如果不像,也不会报错。

抽象类

如果想要通过父类对子类需要定义的函数做限制,也可以通过内置模块abc中的元类装饰器来限制。

通过以下这种方式定义的类是抽象类,抽象类不能实例化产生对象。

import abc

class File(metaclass=abc.ABCMeta):
    @abc.abstractmethod  # 限制继承这个类的子类必须有read这个函数属性,否则报错
    def read(self): pass
    @abc.abstractmethod
    def write(self): pass

class A(File):  # 以上述类为父类
    pass
obj = A()
# 运行报错,TypeError: Can't instantiate abstract class A with abstract methods read, write
posted @ 2022-11-07 16:58  leethon  阅读(63)  评论(0编辑  收藏  举报