一、封装

封装是隐藏对象的属性和实现细节,仅对外公开接口,控制在程序中属性的读取和修改的访问级别。

封装就是将抽象得到的数据和行为(或功能)相结合,形成一个有机的整体,也就是将数据与操作数据的源代码进行有机的结合,形成“类”,其中数据和函数都是类的成员。

1、简单理解封装

顾名思义,封装属性就是把已有的属性封装到一个类里面去:

class Person():

    def __init__(self, name, age, sex):
        self.name = name
        self.age = age
        self.sex = sex

jack = Person('jack', 18, '男')
#将jack、 18、 男 封装到jack对象(self)的name、age、sex中
#name、age、sex又封装在了Person类中
print(jack.__dict__)
#{'name': 'jack', 'age': 18, 'sex': '男'}

分析:self是一个形式参数,创建什么对象,它就代表那个对象

2、调用封装的属性

通过对象直接调用:

class Person():

    def __init__(self, name, age, sex):
        self.name = name
        self.age = age
        self.sex = sex

jack = Person('jack', 18, '男')
print(jack.name, jack.sex, jack.age)
#jack 男 18

分析:在这里,我们可以用对象名随意调用到自身的属性,并进行属性修改,从安全的角度来看,这样是很不安全的,所以需要将属性隐藏起来,也就是私有化。

私有化属性的方法:类中定义私有的,只有类内部使用,外部无法访问(比如_(杠) __(杠杠) )

class Person():

    def __init__(self, name, age, sex):
        self.__name = name
        self.__age = age
        self.__sex = sex

jack = Person('jack', 18, '男')
print(jack._Person__name)#jack
print(jack.name)
#Error:'Person' object has no attribute 'name'

分析:
1、通过使用__(杠杠)的方法使得类Person属性name、age、sex成功私有化,子类无法直接调用,但是通过jack._Person__name的方式可以调用到私有化的属性,并且能对其修改,说明python在设置私有属性的时候,只是把属性的名字换成了其他的名字。

2、类中以_或者__的属性,都是私有属性,禁止外部调用。虽然可以通过特殊的手段获取到,并且赋值,但是这么做不觉的很蛋疼么,本来就是设置私有属性,还非要去强制修改。

私有化属性设置好了,不可能是存在那里谁都不让使用的,要不然设置私有化属性就失去了本身的意义,我们只是不想让私有化属性直接被随意的修改,而不是拒绝访问,所以还需要给私有化属性提供查找和修改的接口,我们只需要通过对接口的控制,就能有效的控制私有化属性的数据安全,比如对接口进行设置,就不会出现age被赋值为负数。

class Person(object):

    def __init__(self, name, age, sex):
        self.__name = name
        self.__age = age
        self.__sex = sex

    def get_age(self):
        return self.__age

    def set_age(self, age):
        if age > 150 or age < 0:
            print('年龄必须大于0,小于150')
        else:
           self.__age = age

jack = Person('jack', 18, '男')

#访问age属性
print(jack.get_age())#18
#修改age属性
jack.set_age(100)
print(jack.get_age())#100
#非法修改age属性
jack.set_age(-20)#年龄必须大于0,小于150
print(jack.get_age())#100

分析:这样就完美了,我们既可以访问到实例化对象内部的属性,也可以在数据安全的情况下(禁止非法数据修改),修改对象的属性

3、python自带的调用私有化数据的方法

前面,我们用set和get的方式来调用或修改对象本身的私有化属性,达到了数据安全的目的,其实python中提供了一种直接用obj.属性名的方式调用类的私有化属性,也能保证数据安全。

class Person(object):

    def __init__(self, name, age, sex):
        self.__name = name
        self.__age = age
        self.__sex = sex

    @property
    def age(self):
        return self.__age

    @age.setter
    def age(self, age):
        if age > 150 or age < 0:
            print('年龄必须大于0,小于150')
        else:
           self.__age = age

jack = Person('jack', 18, '男')

#访问age属性
print(jack.age)#18
#修改age属性
jack.age = 100
print(jack.age)#100
#非法修改age属性
jack.age = -20#年龄必须大于0,小于150
print(jack.age)#100

分析:
1、使用 @property 装饰器时,接口名不必与属性名相同。

2、凡是赋值语句,就会触发set方法。获取属性值,会触发get方法。

3、我们可以使用@property装饰器来创建只读属性,@property装饰器会将方法转换为相同名称的只读属性,可以与所定义的属性配合使用,这样可以防止属性被修改。

二、多态

接口的多种不同的实现方式即为多态。

多态最核心的思想就是,父类的引用可以指向子类的对象,或者接口类型的引用可以指向实现该接口的类的实例。

多态是一种运行期的行为,不是编译期行为!在编译期间它只知道是一个引用,只有到了执行期,引用才知道指向的是谁。这就是所谓的“软绑定”。

多态是一项让程序员“将改变的事物和未改变的事物分离开来”重要技术。
1、多态性

多态性是指指具有不同功能的函数可以使用相同的函数名,这样就可以用一个函数名调用不同内容的函数。

在面向对象方法中一般是这样表述多态性:向不同的对象发送同一条消息,不同的对象在接收时会产生不同的行为。

不同的行为就是指不同的实现,即执行不同的函数。

class Animals(object):

    def talk(self):
        pass

class Person(Animals):

    def talk(self):
        print('高级语言')

class Cat(Animals):

    def talk(self):
        print('喵喵喵')

class Dog(Animals):

    def talk(self):
        print('汪汪汪')


per = Person()
cat = Cat()
dog = Dog()

# 定义一个统一的接口来访问
def fun(obj):
    obj.talk()

fun(per)#高级语言
fun(cat)#喵喵喵
fun(dog)#汪汪汪

分析:
1、per对象、cat对象、dog对象是通过Animals类实现的三种不同形态,这就是多态的体现。

2、per、cat、dog对象都是通过fun(obj)的同一种方式调用,实现了不同的效果,这就是多态性的体现,所以多态性可以说是一个接口,多种实现

3、多态性的优点:
3.1、增加了程序的灵活性:以不变应万变,不论对象千变万化,使用者都是同一种形式去调用,如fun(obj)
3.2、增加了程序额可扩展性:通过继承Animal类派生新的类(Person类、Cat类、Dog类),使用者无需更改自己的代码,还是用fun(obj)去调用
2、鸭子类型

调用不同的子类将会产生不同的行为,而无须明确知道这个子类实际上是什么,这是多态的重要应用场景。而在python中,因为鸭子类型(duck typing)使得其多态不是那么酷,原因是python是强类型的动态脚本语言,不使用显示数据类型声明,且确定一个变量的类型是在第一次给它赋值的时候。

鸭子类型是动态类型的一种风格。在这种风格中,一个对象有效的语义,不是由继承自特定的类或实现特定的接口,而是由"当前方法和属性的集合"决定。这个概念的名字来源于由James Whitcomb Riley提出的鸭子测试,“鸭子测试”可以这样表述:“当看到一只鸟走起来像鸭子、游泳起来像鸭子、叫起来也像鸭子,那么这只鸟就可以被称为鸭子。”

在鸭子类型中,关注的不是对象的类型本身,而是它是如何使用的。例如,在不使用鸭子类型的语言中,我们可以编写一个函数,它接受一个类型为"鸭子"的对象,并调用它的"走"和"叫"方法。在使用鸭子类型的语言中,这样的一个函数可以接受一个任意类型的对象,并调用它的"走"和"叫"方法。如果这些需要被调用的方法不存在,那么将引发一个运行时错误。任何拥有这样的正确的"走"和"叫"方法的对象都可被函数接受的这种行为引出了以上表述,这种决定类型的方式因此得名。

鸭子类型通常得益于不测试方法和函数中参数的类型,而是依赖文档、清晰的代码和测试来确保正确使用。

class Duck(object):

    def walk(self):
        print('I walk like a duck')

    def swim(self):
        print('I swim like a duck')

class Person():

    def walk(self):
        print('this one walk like a duck')

    def swim(self):
        print('this man swim like a duck')


def fun(obj):
    obj.walk()
    obj.swim()

fun(Duck())
# I walk like a duck
# I swim like a duck
fun(Person())
#this one walk like a duck
#this man swim like a duck

分析:可以看出Pseron类拥有和Duck类一样的方法,当程序调用Duck类,并利用了其中的walk和swim方法时,我们传入Person类也一样可以运行,程序并不会检查类型是不是Duck,只要他拥有 walk()和swim()方法,就能被正确地调用。

再举例,如果一个对象实现了__getitem__方法,那python的解释器就会把它当做一个collection,就可以在这个对象上使用切片,获取子项等方法;

如果一个对象实现了__iter__和next方法,python就会认为它是一个iterator,就可以在这个对象上通过循环来获取各个子项。

class Foo:
    def __iter__(self):
        pass

    def __next__(self):
        pass

from collections import Iterable
from collections import Iterator

print(isinstance(Foo(), Iterable)) # True
print(isinstance(Foo(), Iterator)) # True

在这里插入图片描述
希望本文对你有所帮助~~如果对软件测试、接口测试、自动化测试、面试经验交流感兴趣可以加入我们。642830685,免费领取最新软件测试大厂面试资料和Python自动化、接口、框架搭建学习资料!技术大牛解惑答疑,同行一起交流。

Logo

开放原子开发者工作坊旨在鼓励更多人参与开源活动,与志同道合的开发者们相互交流开发经验、分享开发心得、获取前沿技术趋势。工作坊有多种形式的开发者活动,如meetup、训练营等,主打技术交流,干货满满,真诚地邀请各位开发者共同参与!

更多推荐