一、什么是封装
从封装本身的意思去理解,封装就好像是拿一个袋子,将数据和功能一起装进去,然后将袋子封上口子。照这种逻辑看,封装=‘隐藏’,但是封装不仅仅是隐藏。
在封装的基础上,我们可以将装到对象或者类中的属性给隐藏起来。
二、如何隐藏属性
(一)隐藏属性需要注意的四个事项
1.在定义类或者初始化对象时,在属性前加双下划线,就会将该属性隐藏起来;但该隐藏其实始只是一种变形“ _类名__属性名 ”,并没有真的隐藏起来。
2.该变形操作是在 类定义阶段扫描语法时发生的变形,类定义之后添加的__开头的属性不会发生变形;
3.该隐藏是对外不对内;
4.在继承中,父类如果不想让子类覆盖自己的方法,可以将方法定义为私有。
(二)实际操作示例:
-
# 例1: class Student: __school = 'oldboy' def __init__(self, x, y, z): self.__name = x self.age = y self.gender = z def __choose(self): print('%s 正在选课' % self.__name) stu_obj1 = Student('冯疯子', 18, 'male') stu_obj1.__x = 111 print(stu_obj1.__dict__) # {'_Student__name': '冯疯子', 'age': 18, 'gender': 'male', '__x': 111} ######### 如下,类定义之后添加的__开头的属性不会发生变形 print(stu_obj1.__x) # 111 ######### 如下,name的隐藏其实只是一种变形“_类名__属性名”,并没有真的隐藏起来 print(stu_obj1.__dict__) # {'_Student__name': '冯疯子', 'age': 18, 'gender': 'male', '__x': 111} ######### 如下,知晓调用格式的情况下,还是可以调用隐藏属性 print(stu_obj1._Student__name) # 冯疯子 ######### 如下,school的隐藏其实只是一种变形“_类名__属性名”,并没有真的隐藏起来 print(Student.__dict__) # {'__module__': '__main__', '_Student__school': 'oldboy', '__init__': <function Student.__init__ at 0x000002A51FAF71F0>, '_Student__choose': <function Student.__choose at 0x000002A51FAF7160>, '__dict__': <attribute '__dict__' of 'Student' objects>, '__weakref__': <attribute '__weakref__' of 'Student' objects>, '__doc__': None} print(Student._Stdent__school) # AttributeError: type object 'Student' has no attribute '_Stdent__school' ######### 如下,知晓调用格式的情况下,还是可以调用隐藏属性 print(stu_obj1._Student__school) # oldboy
三、隐藏数据/功能属性的意义
封装并不是单纯意义的隐藏。
(一)封装数据的意义
在类内开放接口,让外界使用者通过接口来操作属性值,我们可以在接之上附加任意的逻辑来严格控制外界使用者对属性的操作。
-
class Student: __school = 'oldboy' def __init__(self, x, y, z): self.__name = x self.__age = y self.gender = z def __choose(self): print('%s 正在选课' % self.name) def set_age(self, x): if type(x) is not int: print('年龄必须是整型!') return self.__age = x def get_age(self): print(self.__age) def del_age(self): del self.__age stu_obj1 = Student('张三', 18, 'male') stu_obj1.set_age('qwert') # 年龄必须是整型! stu_obj1.set_age(19) stu_obj1.get_age() # 19 print(stu_obj1.__dict__) # {'_Student__name': '张三', '_Student__age': 19, 'gender': 'male'}
(二)封装功能的意义
隔离复杂度。(如:将实现一个功能的前置步骤隐藏起来)
-
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 withdraw(self): self.__card() self.__auth() self.__input() self.__print_bill() self.__take_money() a=ATM() a.withdraw()
四、特性property
(一)什么是特性property
property是一个内置函数,是一种特殊的属性,访问它时会执行一段功能(函数)然后返回值。
property可以作为装饰器使用,可以隐藏函数调用时必须加括号的这一特性,让函数调用伪装的看起来像一个调用一个数据。
把函数属性伪装成数据属性。
# 例一:BMI指数
class People:
def __init__(self, name, weight, height):
self.name = name
self.height = height
self.weight = weight
@property
def bmi(self):
return self.weight / (self.height ** 2)
p1 = People('egon', 88, 1.58)
print(p1.bmi) # 35.250761095978206
(二)为何要用property
将一个类的函数定义成特性以后,对象再去使用obj.name的时候,无法察觉到自己的name是执行了一个函数然后计算出来的,这种特性的使用方式遵循了统一的访问原则。
面向对象封装的三种方式:**【public】不封装,对外公开;【protected】这种封装方式对外不公开,但对朋友(friend)或者子类(形象的说法是“儿子”)公开;【private】**这种封装对谁都不公开。
python并没有在语法上把他们三个内置到自己的class机制中,在C++里一般会将所有的数据都设置为私有的,然后提供set和get方法(接口)去设置和获取,在python中通过property方法可以实现。
(三)使用property的两种方法:
1.原始的property使用方法
直接调用property函数/装饰器的功能(查,改,删),名字赋值给我们想要操作的属性。
-
class Student: __school = 'oldboy' def __init__(self, x, y, z): self.__name = x self.__age = y self.gender = z def get_name(self): print('访问控制') return self.__name def set_name(self, x): print('赋值控制') self.__name = x def del_name(self): print('删除控制') del self.__name def get_age(self): return self.__age def set_age(self, x): if type(x) is not int: print('必须是整型') return self.__age = x def del_age(self): print('不允许删除') age = property(get_age, set_age, del_age) name = property(get_name, set_name, del_name) stu_obj1 = Student('张三', 18, 'female') # 访问控制 print(stu_obj1.name) # 张三 stu_obj1.name = '矮根' # 赋值控制 del stu_obj1.name # 删除控制
2.将property当作装饰器使用(推荐使用)
访问函数属性上直接当作装饰器用,赋值以及删除使用"属性名.setter/deleter"。
-
class Student: __school = 'oldboy' def __init__(self, x, y, z): self.__name = x self.__age = y self.gender = "male" @property def name(self): print('访问控制') return self.__name @name.setter def name(self, x): print('赋值控制') self.__name = x @name.deleter def name(self): print('删除控制') del self.__name stu_obj = Student('张三', 18, 'male') stu_obj.name # 访问控制
四、绑定和非绑定方法
(一)什么是绑定方法和非绑定方法
类中定义的函数,有绑定与非绑定方法。
1.绑定方法:谁来调用就会将谁当作第一个参数传入
(1)绑定给对象的方法:类中定义的函数默认就是绑定给对象的方法,应该是由对象调用,会把对象当作第一个参数传入。(若对象调用此方法,会报错,因为没有参数自动传入,需要手动传参)
(2)绑定给类的方法:在类中的函数加上一个装饰器@classmethod,该函数就绑定给类了,应该是由类来调用,会把类当作第一个参数传入。(对象也可以调用,但还是传入的类的参数)
2.非绑定方法:既不与类绑定也不与对象绑定,就是一个普通的函数,谁对可以来调用。没有自动传参的效果,需要在函数上添加装饰器@staticmethod。
(二)使用案例
例题1:
-
class People: def __init__(self, name, age): self.name = name self.age = age def tell_info(self): print('<%s:%s>' % (self.name, self.age)) @classmethod def f1(cls): print(cls) @staticmethod def f2(x, y, z): print(x, y, z) p1 = People('egon', 88) p1.tell_info() # <egon:88> ###############————默认就是绑定给对象的方法,应该是由对象调用 print(p1.tell_info) # <bound method People.tell_info of <__main__.People object at 0x0000023E9CC6E0D0>> ###############————在类中的函数加上一个装饰器@classmethod,该函数就绑定给类了,应该是由类来调用,会把类当作第一个参数传入 print(People.f1) # <bound method People.f1 of <class '__main__.People'>> ###############————在类中的函数加上一个装饰器@classmethod,该函数就绑定给类了,应该是由类来调用,会把类当作第一个参数传入 People.f1() # <class '__main__.People'> ###############————使用类调用函数,需必须传参一个对象 People.tell_info(p1) # <egon:88> ###############————既不与类绑定也不与对象绑定,就是一个普通的函数,谁对可以来调用 print(p1.f2) # <function People.f2 at 0x000001C86F767280> ###############————既不与类绑定也不与对象绑定,就是一个普通的函数,谁对可以来调用 print(People.f2) # <function People.f2 at 0x000001FFD44F7280> ###############————没有自动传参的效果 p1.f2(1, 2, 3) # 1 2 3 People.f2(1, 2, 3) # 1 2 3
例题2:
-
# # settings # IP = "127.0.0.1" # PORT = 3306 import uuid import settings class MySQL: def __init__(self, ip, port): self.mid = self.__create_id() self.ip = ip self.port = port def tell_info(self): print("%s:<%s:%s>" % (self.mid, self.ip, self.port)) @staticmethod def __create_id(): return uuid.uuid4() @classmethod def from_conf(cls): return cls(settings.IP, settings.PORT) obj = MySQL('10.10.11.11', 3306) obj.tell_info() # 91411d09-3ec4-4bc5-91fa-643731b1e605:<10.10.11.11:3306> obj1 = MySQL.from_conf() obj1.tell_info() # 265fb714-2445-4c95-b429-158bee68b258:<127.0.0.1:3306>