封装之如何隐藏属性

在python中用双下划线开头的方式设置属性将属性隐藏起来(设置成私有的),以双下划线开头且双下划线结尾的是python的内置方法。

# 其实这仅仅这是一种变形操作
# 类中所有双下划线开头的名称如__x都会自动变形成:_类名__x的形式:

class A:
    __N=0 # 类的数据属性就应该是共享的,但是语法上是可以把类的数据属性设置成私有的,如__N,会变形为_A__N
    def __init__(self):
        self.__X=10  # 变形为self._A__X
    def __foo(self):  # 变形为_A__foo
        print('from A')
    def bar(self):
        self.__foo()  # 只有在类内部才可以通过__foo的形式访问到.

# A._A__N是可以访问到的,即这种操作并不是严格意义上的限制外部访问,仅仅只是一种语法意义上的变形

这种变形的特点:

  1. 在类外部无法直接 A.__N
  2. 在类内部是可以直接使用:A.__N
  3. 子类无法覆盖父类 __ 开头的属性
  4. 变形只在定义类里面发生,在类外面定义是不会发生变形的
# 带 __ 的属性名,父类与子类中属性名字不同,所以不存在覆盖一说
class Foo:
    def __func(self):  # _Foo__func
        print('from foo')

class Bar(Foo):
    def __func(self):  # _Bar__func
        print('from bar')

如果子类里面有和父类的同名属性,想使用父类里的属性,不想被覆盖,把父类里的同名属性前加上 __ , 在另一个属性内进行调用。

class A:
    def __foo(self):  # _A__foo
        print('A.foo')
    
    def bar(self):
        print('A.bar')
        self.__foo()  # self._A__foo
        
class B(A):
    def foo(self):  # _B_foo
        print('B.foo')

b = B()
b.bar()

封装数据属性:明确的区分内外 

class People:
    def __init__(self, name, age):
        self.__name = name
        self.__age = age

    def tell_info(self):
        print('Name:<%s>  Age:<%s>' %(self.__name, self.__age))

    def set_info(self, name, age):
        if not isinstance(name, str):  # isinstance(m,n) m是n的实例
            print('名字必须是字符串类型')
            return
        if not isinstance(age, int):
            print('年龄必须是数字类型')
            return
        self.__name = name
        self.__age = age
        
p = People('egon', 18)
p.tell_info()

封装方法属性:隔离复杂度

class ATM:
    def __card(self):
        print('插卡')
    def __auth(self):
        print('用户认证')
    def __input(self):
        print('输入取款金额')
    def __print_bill(self):
        print('打印账单')
    def __take_money(self):
        print('取款')

    def with_draw(self):
        self.__card()
        self.__auth()
        self.__input()
        self.__print_bill()
        self.__take_money()

a = ATM()
a.with_draw()

封装与可扩展性

class Room:
    def __init__(self, name, owner, weight, length):
        self.name = name
        self.owner = owner

        self.__weight = weight
        self.__length = length

    def tell_area(self):
        return self.__weight * self.__length

r = Room('卫生间', 'alex', 10, 10)
print(r.tell_area())
# 对于用户来说,只需记住tell_area()方法来获取所需信息即可,对于开发者,可自行改变输出结果的计算方法

什么是特性property

property是一种特殊的属性,访问它时会执行一段功能(函数)然后返回值

property是把需要通过计算才能得到数据的属性,封装起来,以数据的形式直接调用

例一:BMI指数(bmi是计算而来的,但很明显它听起来像是一个属性而非方法,如果我们将其做成一个属性,更便于理解)

成人的BMI数值:

过轻:低于18.5

正常:18.5-23.9

过重:24-27

肥胖:28-32

非常肥胖, 高于32

体质指数(BMI)=体重(kg)÷身高^2(m)

EX:70kg÷(1.75×1.75)=22.86

class People:
    def __init__(self, name, weight, height):
        self.name = name
        self.weight = weight
        self. height = height
    
    @property
    def bmi(self):
        return self.weight / (self.height ** 2)

p = People('egon', 75, 1.81)
# p.bmi = p.weight / (p.height ** 2)
# print(p.bmi())
print(p.bmi)  # 有了property,使用者可以像调用数据一样调用属性,所以这个方法必须有返回值

补充:

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

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

    @name.setter  # 已经被 property 装饰过的函数可以被重新调用使用
    def name(self, val):
        if not isinstance(val, str):
            print('名字必须是字符串类型')
            return
        self.__name = val

    @name.deleter
    def name(self):
        print('不允许删除')

p = People('egon')
p.name  # 这是个访问行为,触发property装饰的
p.name = 'Egon'  # 这是个修改行为,触发name.setter装饰的
del p.name  # 这是个删除行为,触发name.deleter装饰的