老男孩教育专注IT教育10余年,只培养IT技术精英

全国免费咨询电话(渠道合作):400-609-2893

Python面向对象之封装!老男孩Python学习内容

老男孩IT教育

常见问题

2021年7月19日 17:58

封装是指隐藏对象的属性和实现细节,仅对外提供公共访问方式;我们程序设计追求“高内聚,低耦合”。

  封装是指隐藏对象的属性和实现细节,仅对外提供公共访问方式。

  我们程序设计追求“高内聚,低耦合”

  高内聚:类的内部数据操作细节自己完成,不允许外部干涉

  低耦合:仅对外暴露少量的方法用于使用。

  隐藏对象内部的复杂性,只对外公开简单的接口。便于外界调用,从而提高系统的可扩展性、可维护性。通俗的说,把该隐藏的隐藏起来,该暴露的暴露岀来。这就是封装性的设计思想。

  私有属性

  在class内部,可以有属性和方法,而外部代码可以通过直接调用实例变量的方法来操作数据,这样,就隐藏了内部的复杂逻辑。但是,从前面Student类的定义来看,外部代码还是可以自由地修改一个实例的name、score属性:

class Student(object):

    def __init__(self, name, score):
        self.name = name
        self.score = score

alvin=Student("alvin",66)
yuan=Student("yuan",88)

alvin.score=100
print(alvin.score)

  如果要让内部属性不被外部访问,可以把属性的名称前加上两个下划线__,在Python中,实例的变量名如果以__开头,就变成了一个私有变量(private),只有内部可以访问,外部不能访问,所以,我们把Student类改一改:

class Student(object):

    def __init__(self, name, score):
        self.name = name
        self.__score = score


alvin = Student("alvin",66)
yuan = Student("yuan",88)

print(alvin.__score)

  改完后,对于外部代码来说,没什么变动,但是已经无法从外部访问实例变量.__name和实例变量.__score。

  这样就确保了外部代码不能随意修改对象内部的状态,这样通过访问限制的保护,代码更加健壮。

  但是如果外部代码要获取name和score怎么办?可以给Student类增加get_score这样的方法:

class Student(object):

    def __init__(self, name, score):
        self.name = name
        self.__score = score

    def get_score(self):
        return self.__score

alvin=Student("alvin",66)
yuan=Student("yuan",88)

print(alvin.get_score())

  如果又要允许外部代码修改age怎么办?可以再给Student类增加set_score方法:

class Student(object):

    def __init__(self, name, score):
        self.name = name
        self.__score = score

    def get_score(self):
        return self.__score

    def set_score(self,score):
        self.__score = score

alvin=Student("alvin",12)
print(alvin.get_score())
alvin.set_score(100)
print(alvin.get_score())

  你也许会问,原先那种直接通过alvin.score = 100也可以修改啊,为什么要定义一个方法大费周折?因为在方法中,可以设置值时做其他操作,比如记录操作日志,对参数做检查,避免传入无效的参数等等:

class Student(object):
    ...
    def set_score(self,score):
        if isinstance(score,int) and 0 <= score <= 100:
            self.__score = score
        else:
            raise ValueError('error!')

  注意

  1、这种机制也并没有真正意义上限制我们从外部直接访问属性,知道了类名和属性名就可以拼出名字:

  _类名__属性,然后就可以访问了,如a._A__N

  2、变形的过程只在类的内部生效,在定义后的赋值操作,不会变形

class Student(object):

    def __init__(self, name, score):
        self.name = name
        self.__score = score

    def get_score(self):
        return self.__score

yuan=Student("yuan",66)
print(yuan.__dict__)
yuan.__age=18
print(yuan.__dict__)

  4、单下划线、双下划线、头尾双下划线说明:

  __foo__: 定义的是特殊方法,一般是系统定义名字 ,类似 __init__() 之类的。

  _foo: 以单下划线开头的表示的是 protected 类型的变量,即保护类型只能允许其本身与子类进行访问。(约定成俗,不限语法)

  __foo: 双下划线的表示的是私有类型(private)的变量, 只能是允许这个类本身进行访问了。

class Person(object):
    def __init__(self, name, score):
        self.name = name
        self.__score = score

class Student(Person):

    def get_score(self):
        return self.__score

    def set_score(self,score):
        self.__score=score

yuan=Student("yuan",66)
print(yuan.__dict__)
print(yuan.get_score())

  私有方法

  在继承中,父类如果不想让子类覆盖自己的方法,可以将方法定义为私有的:

class Base:
    def foo(self):
        print("foo from Base")

    def test(self):
        self.foo()

class Son(Base):
    def foo(self):
        print("foo from Son")

s=Son()
s.test()


class Base:
    def __foo(self):
        print("foo from Base")

    def test(self):
        self.__foo()

class Son(Base):
    def __foo(self):
        print("foo from Son")

s=Son()
s.test()

  property属性装饰器

  使用接口函数获取修改数据 和 使用点方法设置数据相比, 点方法使用更方便,我们有什么方法达到 既能使用点方法,同时又能让点方法直接调用到我们的接口了,答案就是property属性装饰器:

class Student(object):

    def __init__(self,name,score,sex):
        self.__name = name
        self.__score = score
        self.__sex = sex

    @property
    def name(self):
        return self.__name

    @name.setter
    def name(self,name):
        if len(name) > 1 :
            self.__name = name
        else:
            print("name的长度必须要大于1个长度")

    @property
    def score(self):
        return self.__score

    # @score.setter
    # def score(self, score):
    #     if score > 0 and score < 100:
    #         self.__score = score
    #     else:
    #         print("输入错误!")


yuan = Student('yuan',18,'male')

yuan.name = '苑昊'  #  调用了score(self, score)函数设置数据

print(yuan.name)   #  调用了score(self)函数获取数据

yuan.score = 199
print(yuan.score)

  注意,使用 @property 装饰器时,接口名不必与属性名相同。

  python提供了更加人性化的操作,可以通过限制方式完成只读、只写、读写、删除等各种操作

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

    def __get_name(self):
        return self.__name

    def __set_name(self, name):
        self.__name = name

    def __del_name(self):
        del self.__name
    # property()中定义了读取、赋值、删除的操作
    # name = property(__get_name, __set_name, __del_name)
    name = property(__get_name, __set_name)

yuan = Person("yuan")

print(yuan.name)   # 合法:调用__get_name
yuan.name = "苑昊"  # 合法:调用__set_name
print(yuan.name)

# property中没有添加__del_name函数,所以不能删除指定的属性
del p.name  # 错误:AttributeError: can't delete Attribute

  @property广泛应用在类的定义中,可以让调用者写出简短的代码,同时保证对参数进行必要的检查,这样,程序运行时就减少了出错的可能性。

  老男孩Python全栈开发课程内容主要包括:Python开发基础、Python高级编程与数据库开发、前端开发、WEB框架开发、爬虫开发、全栈项目实战、数据分析、人工智能、自动化运维与开发以及高并发语言GO开发等,独家前沿课程内容,使学员找工作更有优势!

  推荐阅读:

  Python面向对象之继承!老男孩Python学什么?

  学习Python后到底能干什么?老男孩Python

  Python 循环语句有哪些?如何使用?

本文经授权发布,不代表老男孩教育立场。如若转载请联系原作者。