day26 封装、多态、常用内置函数、反射【hasattr、getattr、setattr、delattr】、动态导入模块

时间:2021-08-18 21:57:47

今日内容

一、封装
什么是封装
对外不对内,不让类外部直接使用
对外部隐藏内部的属性以及实现的细节,隐藏并不是完全不给外部使用,给外部提供使用的接口


python中属性权限分两种:公开的【没有任何限制,外界可以操作数据】-- 默认
私有的【只有当前类本身能够访问】
为什么要学习封装
1.提高安全性【属性】,什么样的属性需要封装?
当这个对象存在一个机密必的属性,例如人的身份证、银行卡密码等

2.隔离复杂度【方法】,什么样的方法需要封装?
一个为内部提供支持的方法,不应该让外界直接访问的,如:在ATM项目中取款,只提供给外界一个入口,
类内部自调登入功能,成功后自调显示余额、输入取款金额、保存记录等。
还有如电脑开机键,就做了一系列的封装


如何封装
通过在类定义阶段对需要封装的属性/方法变形:__开头的属性/方法名会在类定义阶段变形添加_类名 如:_person__id_card
变形后实在要访问,可以用字典的方式访问:obj.__dic__['_person__id_card']
封装后还是可以给外界使用的

属性封装 --
1)类内定义一个接口函数,再把结果return回给外界调用者,这样return前可以添加一些条件限制,
2)修改属性:定义一个方法,将新值赋值给__开头的属性,达到修改属性的效果
问题:方问属性,变成调用函数,给调用者造成困扰?



class Student:
def __init__(self, name, age, gender, id_card):
self.name = name
self.age = age
self.gender = gender
self.__id_card = id_card

def get_id_card(self, pwd):
if pwd == '123':
return self.__id_card
raise Exception('密码错误')

def set_id_card(self, new_id):
if isinstance(new_id, str) and len(new_id) == 18:
self.__id_card = new_id
else:
raise Exception('身份证号码,必须是之符串,且长度必须为18!')

stu = Student('fanny', 18, 'female', 123456)
print(stu.get_id_card('123'))

stu.set_id_card('012345678945632101')
print(stu.get_id_card('123'))

解决方法: 用@property将一个方法伪装成一个属性,
用@id_card【名字等于@property下函数名】.setter来修改属性
用@

class Student:
def __init__(self, name, age, gender, id_card):
self.name = name
self.age = age
self.gender = gender
self.__id_card = id_card


@property #getter 用于访问私有属性的值,也可以设置普通属性【初始化不能定义,如BMI计算】
def id_card(self):
pwd =input('pwd:').strip()
if pwd == '123':
return self.__id_card
print('密码错误,不能访问')

@id_card.setter #用来设置私有属性的值,也可以设置普通属性
def id_card(self, new_id):
if isinstance(new_id, str) and len(new_id) == 18:
self.__id_card = new_id
else:
print('身份证号码,必须是之符串,且长度必须为18!')

@id_card.deleter #用来删除私有属性的值,也可以删除普通属性
def id_card(self):
print('can not delete salray!')
del self.__dict__['_Student__id_card']



stu = Student('fanny', 18, 'female', 123456)
# stu.id_card

stu.id_card = '123569874123654780'
# print(stu.id_card)

# print(stu.__dict__) #{'name': 'fanny', 'age': 18, 'gender': 'female', '_Student__id_card': '123569874123654780'}

del stu.id_card

stu.id_card # 'Student' object has no attribute '_Student__id_card'

Property的另一种使用场景【设置普通属性】:计算属性BMI

class Persoon:
def __init__(self, name, height, weight):
self.name = name
self.height = height
self.weight = weight
# self.BMI = weight / (height ** 2)

@property
def BMI(self):
return self.weight / (self.height ** 2)

@BMI.setter
def BMI(self, nwe_BMI):
print('BMI 不支持自定义...')

p1 = Persoon('egon', 1.7, 80)
print(p1.BMI)
p1.BMI =10

二、多态:
多太指的是同一种事物的多种形态
多个不同类型对象,可以响应同一方法,并且产生不同结果, 【统一】以不变应万变,提高了灵活性、扩展性

多态的好处: 可以在不用考虑对象具体类型前提下而直接使用对象,不需要关心各个类型对象的具体实现细节

在多态背景下,父类不是让子类继续它的功能,而是建立了一种规范

python 崇尚鸭子类型 指的是子类并不需要从属于哪个类,只要多个类之间约定好都定义同名的函数
统一后就用一个接口中,使用方便

例:一个用来管理动物的方法,只要你传入是一个有同名方法的对象,就可以使用
class Cat:
def dark(self):
print('喵喵')

def run(self):
print('轻轻地跑')

def sleep(self):
print('趴着跑')

class Pig:
def dark(self):
print('哼哼')

def run(self):
print('四条腿跑')

def sleep(self):
print('侧躺着睡')

class Manger_anmal:
def dark(self, obj):
obj.dark()

def run(self, obj):
obj.run()

def sleep(self, obj):
obj.sleep()

cat = Cat()
pig1 = Pig()

obj = Manger_anmal()
obj.dark(cat)
obj.dark(pig1)

三、常用的内置函数: __str__ __del__

__str__ # 在对象被打印时自动触发,可以用来定义对象被打印时的输出信息
# 注意:必须返回一个字符串类型的值


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

def __str__(self):
print('run....')
return '<name: %s age:%s>' %(self.name, self.age )

obj1 = People('egon', 18)
print(obj1) #print(obj1.__str__())

__del__ : 该函数不是用来删除对象的,【在对象删除前做一些清理操作】是用来关闭清理回收对象以外其他相关资源,比如打开文件后,通知操作系统关闭文件爱你
触发时机:
1.程序运行结束 解释器退出时,自动删除所有数据 【例1】
2.在对象被删除时【del obj】触发该方法,【例2】
-----------------------------------
例1:
class Foo:
def __del__(self):
print('run ...')

obj = Foo()
print('===========')

输出:
===========
run ...

--------------------------------------------
例2:
class Foo:
def __del__(self):
print('run ...')

obj = Foo()
del obj
print('===========')

输出:
run ...
===========

四、反射 通过字符串来操作属性

hasatter(obj,'dir') 判断obj是否有某个属性 返回一个bool值 【obj是任何对象】
#print('dir' in MY_CMD.__dict__)


getattr(cmd, 'dir',None) 返回一个属性,可能是数据属性【变量名】,也可能是一个方法属性【函数对象】
返回一个dir绑定方法,加()可运行 #<bound method MY_CMD.dir of <__main__.MY_CMD object at 0x000001B73AAE9470>>

setattr(cmd, 'x', 222 ) 新增或修改某个属性,有'x'就修改该属性的值,没有就新增一个属性及值
print(cmd.x)

setattr(cmd, 'y', 666 )
print(cmd.y)


delattr(cmd, 'name') 删除某个属性,只能删除该对象自己私有的属性,如果该属性是类的,就不能删,可以将cmd变成
delattr(cmd, 'x') #AttributeError: x
delattr(MY_CMD, 'x')

import os
class MY_CMD:
x = 111
def __init__(self, name):
self.name = name


def dir(self):
os.system("dir")

def ipconfig(self):
os.system("ipconfig")

cmd = MY_CMD('fanny')

print(hasattr(MY_CMD, 'dir')) #print('dir' in MY_CMD.__dict__)
print('dir' in MY_CMD.__dict__)

print(hasattr(cmd, 'name'))
print('name' in cmd.__dict__)
print('name' in MY_CMD.__dict__) #$False


getattr(cmd, 'dir',None)()
print(getattr(cmd, 'name', None))
setattr(cmd, 'x', 222 )
print(cmd.x)

setattr(cmd, 'y', 666 )
print(cmd.y)

delattr(cmd, 'name')
delattr(cmd, 'x') #AttributeError: x
delattr(MY_CMD, 'x')


五、动态导入模块
直接写import为静态导入,建立在提前已经知道有这个模块
动态导入 指的是 在需要的任何时候,通过指定字符串类型的包名称来导入需要的模块
该方式常用在框架中,因为框架设计者不可能提前预知后续需要的模块和类

import importlib
mk = importlib.import_module(m_name)