面向对象之封装
一、对象的特有名称空间
__init__方法会在实例化对象时被调用
1、会为实例化的对象形成空的名称空间
2、就是一个方法,可以被传参,在类名(实参)这种方式下调用并传参 __init__(self 形参)
3、第一个self就是要产生的当前对象 重点:在方法内部,形参拿到了实参值,利用self.属性名 = 形参 = 实参值, 对对象的名称空间添加属性
class Student: # def __init__(self, name, sex): # print('2>>>', self) # self.name = name # self.sex = sex # def fn(): # print('fn run ') def set_stu(stu, name, sex): stu.name = name stu.sex = sex # print(Student.__dict__) # Student.fn() # fn run stu1 = Student() print(stu1.__dict__) # {} Student.set_stu(stu1, 'Bob', 'male') print(stu1.__dict__) # {'name': 'Bob', 'sex': 'male'} print(stu1.name) # Bob print(stu1.sex) # male stu2 = Student() Student.set_stu(stu2, 'Tom', 'female') print(stu2.__dict__) # {'name': 'Tom', 'sex': 'female'} print(stu2.name, stu2.sex) # Tom female
二、对象方法:
对象调用类的方法(类中方法的第一个默认参数:对象方法),建议使用对象调用
class Student: def __init__(self, name): self.name = name def study(self): print(self.name + 'study') stu = Student('Bob') stu.study() # Bobstudy stu2 = Student('Tom') stu2.study() # Tomstudy # 类中方法的第一个默认参数:对象方法 # 总结:对象调用类的方法 class Student: pass stu = Student() def fn(a, b): print('fn run') stu.fn = fn stu.fn(10, 20) # fn run class A: def test(self): print(self) pass a = A() a.test() # <__main__.A object at 0x0000000002767550> A.test(a) # <__main__.A object at 0x0000000002767550> A.__dict__['test'](a) # <__main__.A object at 0x0000000002767550>
三、类方法:
可以被类与对象调用的方法,第一个参数一定是类,类方法不建议拿对象来调用
class Tool: # 类自己的方法 def add(cls, n1, n2): cls.fn() return n1 + n2 def fn(): pass # a如果被外界对象tool调用,那么内部调用b,b其实也是被外界对象tool调用的 def a(self): self.b() def b(self): pass res = Tool.add(Tool, 10, 20) # 类调用,外界传入两个参数,内部接收到两个 print(res) # 30 # 问题:类的名字,对象都可以使用,但是出现了类与对象使用时,参数个数不一致 # tool = Tool() # print(tool.add(10, 20)) # 对象调用,外界传入两个参数,内部接收到三个,第一个是对象本身 class Tool: def add(cls, n1, n2): return n1 + n2 print(Tool.add(Tool, 10, 20)) # 30 # 类调用 tool = Tool() print(tool.add(100, 200)) # 300 # 对象调用 class Tool: # 类方法:可以被类与对象调用的方法,第一个参数一定是类 # 类方法不建议拿对象来调用 @classmethod def add(cls, n1, n2): print(id(cls)) # 5421352 cls.test() return n1 + n2 @classmethod def test(cls): pass print(Tool.add(10, 20)) # 30 tool = Tool() print(tool.add(100, 200)) # 300 print(id(Tool), id(tool)) # 5421352 41383584 # 对象调用所属类的类方法,默认第一个参数传入的是 对象.__class__ 就是所属类 print(tool.__class__) # <class '__main__.Tool'>
四、属性与方法的总结
class OldBoy: # 属于类的属性 name = '老男孩' # 属于对象的属性 def __init__(self, name): self.name = name # 属于类的方法 # 需求:获取机构的名字 @classmethod def get_class_name(cls): return cls.name # 属于对象的方法 # 需求:获取校区的名字 def get_school_nmae(cls): return cls.name # 创建校区 shanghai = OldBoy('上海校区') shenzhen = OldBoy('深圳校区') # 类方法的使用:建议使用类调用 print(OldBoy.get_class_name()) # 老男孩 # 对象方法的使用:建议使用对象调用 print(shanghai.get_school_nmae()) # 上海校区 print(shenzhen.get_school_nmae()) # 深圳校区
五、封装:(封装的终极奥义:明确地区分内外,对外是隐藏的,对内是开放的)
1、定义:把一堆数据属性和方法属性整合到对象中
封装(从字面意思理解)就是隐藏,隐藏指的是在类内部将一个属性藏起来
让类外部的使用者无法直接用到。在py中就是用__开头将一个属性藏起来.
补充说明:封装绝对不是单纯意义的隐藏
需知定义属性的目的就是为了让使用者去用,使用者要想使用类内部隐藏的属性
需要类的设计者在类内部开一个接口(定义一个方法),在该方法内访问隐藏的属性
使用者以后就通过该方法来“间接地”访问内部隐藏的属性
作为类的设计者可以在接口之上附加任意逻辑从而严格控制类的使用者对属性的操作
优点:外界不能直接访问,让内部的属性与方法具有安全保障
2、如何封装(封装语法)
封装的原理:把用__开头的名字更名为 _类名__变量名,所以直接通过 变量名 | __变量名就访问不到
对象属性的封装,对外提供接口
如何把属性隐藏起来,就在属性前面加上__开头(注意不要加__结尾)
①其实这种隐藏只是一种语法上的变形,对外不对内
class Foo: __x = 1111 # _Foo__x=1111 def __init__(self, y): self.__y = y # self._Foo__y=y def __f1(self): # _Foo__f1 print('Foo.f1') def get_y(self): print(self.__y) # print(self._Foo__y) obj = Foo(2222) # print(obj.x) # 报错 # print(obj.__x) # 报错 print(obj._Foo__x) # 1111 print(obj._Foo__y) # 2222 obj._Foo__f1() # Foo.f1 obj.get_y() # 2222
②这种语法意义上变形,只在类定义阶段发生一次,类定义之后,新增的__开头的属性都没有变形的效果
Foo.__aa=1 print(Foo.__dict__) obj.__bb=2 print(obj.__dict__) # {'_Foo__y': 2222, '__bb': 2}
③如果父类不想让子类覆盖自己的方法,可以在方法名前加__开头
class Foo: def __f1(self): # _Foo__f1 print('Foo.f1') def f2(self): print('Foo.f2') self.__f1() # obj._Foo__f1() class Bar(Foo): def __f1(self): # _Bar__f1 print("Bar.f1") obj = Bar() obj.f2() # 结果为: # Foo.f2 # Foo.f1
3、为什么要用封装(封装的目的)
①封装数据属性的目的:把数据属性封装起来,然后需要开辟接口给类外部的使用者使用,
好处是:我们可以在接口之上添加控制逻辑,从而严格空间访问者对属性的操作
class People: def __init__(self, name, age): self.__name = name self.__age = age def tell_info(self): # u=input('user>>: ').strip() # p=input('pwd>>: ').strip() # if u == 'egon' and p == '123': print(self.__name, self.__age) def set_info(self, name, age): if type(name) is not str: raise TypeError('用户名必须为str类型') if type(age) is not int: raise TypeError('年龄必须为int类型') self.__name = name self.__age = age p = People('egon', 18) p.tell_info() # egon 18 p.set_info('EGON', 19) p.tell_info() # EGON 19
②封装函数属性的目的:为了隔离复杂度
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() obj = ATM() obj.withdraw() # 结果为: # 插卡 # 用户认证 # 输入取款金额 # 打印账单 # 取款
4、封装特性:@property
①property是一种特殊的属性,访问它时会执行一段功能(函数)然后有返回值
②为什么要用:将一个类的函数定义成特性以后,对象再去使用的时候obj.name, 根本无法察觉自己的name是执行了一个函数然后计算出来的,
这种特性的使用方式遵循了统一访问的原则
③用法:
例题: 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 * self.height) egon = People('egon', 75, 1.80) print(egon.bmi) # 加上@property后,egon.bmi后不加括号,相当于调用了bmi() # 没有加上@property,要想运行,egon.bmi() # 首先需要明确.bmi是算出来的,不是一个固定死的值,也就说我们必须编写一个功能,每次调用该功能都会立即计算一个值 egon = People('egon', 75, 1.80) # 但很明显人的bmi值听起来更像一个名词而非动词 print(egon.bmi()) # 会报错。egon.bmi后面加括号相当于是调用了bmi函数,但是bmi函数上被property装饰了之后 # ,bmi函数被伪装成一个数据属性了。 # 于是我们需要为bmi这个函数添加装饰器@property,将其伪装成一个数据属性 egon.weight = 70 print(egon.bmi) # 调用egon.bmi本质就是触发函数bmi的执行,从而拿到其返回值
六、对象的属性方法封装与接口
1、对象的属性封装
①对象的属性值一般都来源于外界,外界是有权力再次访问的
②封装的目的不是让外界无法访问,而且不让其直接访问,可以在完成安全处理后再访问
③如何做到外界还是通过变量名来对属性进行取值赋值
-- __money被封装,外界还是可以通过 对象.money 取值赋值
2、对象的方法封装一般的实现需求都是,这些方法只在内部使用
# 对象的属性与方法封装集中与类的属性与方法封装原理一样 class AAA: def __init__(self, money): self.__money = money self.__id = 1000 @property def id(self): return self.__id @id.setter def id(self, id): self.__id = id # 对象的属性封装 # 1.对象的属性值一般都来源于外界,外界是有权力再次访问的 # 2.封装的目的不是让外界无法访问,而且不让其直接访问,可以在完成安全处理后再访问 # 3.如何做到外界还是通过变量名来对属性进行取值赋值 # -- __money被封装,外界还是可以通过 对象.money 取值赋值 # 取值 @property # 在外界可以 对象.money 进行取值 def money(self): # print('走方法拿到的money') return self.__money # 赋值 @money.setter # 在外界可以 对象.money = 新值 进行赋值 def money(self, money): self.__money = money # 删除 @money.deleter def money(self): # print('逗你玩') del self.__money def get_money(self, flag): if flag == '自家人': return self.__money return 0 def set_money(self, money): self.__money += money # 对象的方法封装一般的实现需求都是,这些方法只在内部使用 def __test(self): print('test run') a = AAA(88888) print(a.get_money('自家人')) # 88888 a.set_money(100000) print(a.get_money('自家人')) # 188888 print(a.money) # 188888 a.money = 999999 print(a.money) # 999999
七、绑定方法和非绑定方法
1、对象的绑定方法:有被任何装饰器装饰的方法
在类内部定义的函数,默认就是给对象来用,而且是绑定给对象用的,称为对象的绑定方法
绑定对象的方法特殊之处:应该由对象来调用,对象来调用,会自动将对象当作第一个参数传入self
2、类的绑定方法:用classmethod装饰器装饰的方法。
classmehtod是给类用的,即绑定到类,类在使用时会将类本身当做参数传给类方法的第一个参数
(即便是对象来调用也会将类当作第一个参数传入), python为我们内置了函数classmethod来把类中的函数定义成类方法
class People: country = 'china' def __init__(self, name): self.name = name def run(self): print('%s running...' % self.name) @classmethod def eat(cls, name): cls.name = name print(cls.name +' eating') p1 = People('l_egon') People.eat('egon') # egon eating
3、非绑定方法:用staticmethod装饰器装饰的方法
特性:既不跟类绑定,也不跟对象绑定,这意味着谁都能用,谁来用都是一个普通函数,也就是说没有自动传值的特性了
class People: country = 'china' def __init__(self, name): self.name = name def run(self): print('%s running...' % self.name) @staticmethod def eat(): # 被装饰之后eat()就是一个普通的函数, print('eat') p = People('tank') p.eat() # eat People.eat() # eat