Python面向对象之封装、多态、反射
派生方法的实战演练(重要)
出现问题
json序列化python数据类型是有限制的,无法正常序列划会报错
import datetime
import json
d = {
't1': datetime.datetime.today(), # datetime.datetime.today() 无法序列化
't2': datetime.date.today() # ...
}
res = json.dumps(d)
print(res)
"""
上述代码会报错 无法正常序列化
raise TypeError(f'Object of type {o.__class__.__name__} '
TypeError: Object of type datetime is not JSON serializable
json序列化python数据类型是有限制的 不是所有的类型都可以
+-------------------+---------------+
| Python | JSON |
+===================+===============+
| dict | object |
+-------------------+---------------+
| list, tuple | array |
+-------------------+---------------+
| str | string |
+-------------------+---------------+
| int, float | number |
+-------------------+---------------+
| True | true |
+-------------------+---------------+
| False | false |
+-------------------+---------------+
| None | null |
+-------------------+---------------+
ps:即将要被序列化的数据 里里外外都必须是上述类型才可以
"""
解决方式1
手动将不符合数据类型要求的数据转成符合要求的
# 解决方式1:手动将不符合数据类型要求的数据转成符合要求的
d = {
't1': str(datetime.datetime.today()), # 将datetime.datetime.today()转换成字符串
't2': str(datetime.date.today()) # ...
}
res = json.dumps(d)
print(res) # {"t1": "2022-07-28 15:16:02.998236", "t2": "2022-07-28"}
解决方式2
利用派生方法,对JSONEncoder源码的default方法做修改(派生)
# 解决方式2:利用派生方法
d = {
't1': datetime.datetime.today(),
't2': datetime.date.today()
}
"""
class JSONEncoder:
pass
dumps(obj,cls=None):
if cls == None:
cls = JSONEncoder
return cls(...) # JSONEncoder()
查看JSONEncoder源码发现序列化报错是有default方法触发的
raise TypeError(f'Object of type {o.__class__.__name__} '
f'is not JSON serializable')
我们如果想要避免报错 那么肯定需要对default方法做修改(派生)
"""
class MyJsonEncode(json.JSONEncoder):
def default(self, o):
'''o就是json即将要序列化的数据'''
if isinstance(o, datetime.datetime):
return o.strftime('%Y-%m-%d %H:%M:%S')
elif isinstance(o, datetime.date):
return o.strftime('%Y-%m-%d')
# 如果是可以序列化的类型 那么不做任何处理 直接让它序列化即可
return super().default(o)
res = json.dumps(d, cls=MyJsonEncode)
print(res)
json.dumps(d, cls=MyJsonEncode) # {"t1": "2022-07-28 15:44:40", "t2": "2022-07-28"}
封装
即将数据或者功能隐藏起来(包起来 装起来)
隐藏属性
Python的Class机制采用双下划线开头的方式将属性隐藏起来(设置成私有的),这仅仅只是一种变形操作,类中所有双下滑线开头的属性都会在类定义阶段、检测语法时自动变成“_类名__属性名”的形式
- 在类外部无法直接访问双下滑线开头的属性,但知道了类名和属性名就可以拼出名字:_类名__属性,然后就可以访问了
- 在类内部是可以直接访问双下滑线开头的属性的
- 变形操作只在类定义阶段发生一次,在类定义之后的赋值操作,不会变形。
>>> class Person:
... d_type = '人类'
... __school = '清华大学' # 隐藏
... __name = 'kwan'
... __age = 18
... def get_info(self):
... return f"""
... ++++++++++++++++++++++++++
... 姓名:{self.__name}
... 年龄:{self.__age}
... 性别:{self.__school}
... ++++++++++++++++++++++++++
... """
... def eat(self):
... print('11111')
...
>>> obj = Person()
>>> print(obj.get_info())
++++++++++++++++++++++++++
姓名:kwan
年龄:18
性别:清华大学
++++++++++++++++++++++++++
>>> print(Person.__dict__)
{'__module__': '__main__', 'd_type': '人类', '_Person__school': '清华大学', '_Person__name': 'kwan', '_Person__age': 18, 'get_info': <function Person.get_info at 0x0000019B34224550>, 'eat': <function Person.eat at 0x0000019B342245E0>, '__dict__': <attribute '__dict__' of 'Person' objects>, '__weakref__': <attribute '__weakref__' of 'Person' objects>, '__doc__': None}
>>>
>>> print(obj._Person__school)
清华大学
>>>
>>> Person.d_type = 123
>>> print(Person.d_type)
123
>>>
>>> print(obj.__school) # 无法访问
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
AttributeError: 'Person' object has no attribute '__school'
>>>
>>> print(Person.__school) # 无妨访问
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
AttributeError: type object 'Person' has no attribute '__school'
>>>
>>> Person.__xxx = 'aaaa'
>>> print(obj.__xxx)
aaaa
开放接口
隐藏的目的不是让用户无法使用,而是给这些隐藏的数据开设特定的接口,让用户使用接口才可以去使用,我们在接口中添加一些额外的操作
>>> class Teacher:
... def __init__(self,name,age): #将名字和年纪都隐藏起来
... self.__name=name
... self.__age=age
... def tell_info(self): #对外提供访问老师信息的接口
... print('姓名:%s,年龄:%s' %(self.__name,self.__age))
... def set_info(self,name,age): #对外提供设置老师信息的接口,并附加类型检查的逻辑
... if not isinstance(name,str):
... raise TypeError('姓名必须是字符串类型')
... if not isinstance(age,int):
... raise TypeError('年龄必须是整型')
... self.__name=name
... self.__age=age
...
>>>
>>> t=Teacher('kwan',18)
>>> t.set_info(‘kwan','19') # 年龄不为整型,抛出异常
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "<stdin>", line 11, in set_info
TypeError: 年龄必须是整型
>>> t.set_info('kwan',19) # 名字为字符串类型,年龄为整形,可以正常设置
>>> t.tell_info() # 查看老师的信息
姓名:kwan,年龄:19
property伪装属性
可以简单的理解为,将方法伪装成数据
obj.name # 数据只需要点名字
obj.func() # 方法至少还要加括号
# 伪装之后可以将func方法伪装成数据 obj.func
例
BMI指数是用来衡量一个人的体重与身高对健康影响的一个指标,计算公式为
体质指数(BMI)=体重(kg)÷身高^2(m)
EX:70kg÷(1.75×1.75)=22.86
身高或体重是不断变化的,因而每次想查看BMI值都需要通过计算才能得到,但很明显BMI听起来更像是一个特征而非功能,为此Python专门提供了一个装饰器property,可以将类中的函数“伪装成”对象的数据属性,对象在访问该特殊属性时会触发功能的执行,然后将返回值作为本次访问的结果
>>> class Person:
... def __init__(self, name, weight, height):
... self.name = name
... self.weight = weight
... self.height = height
... @property
... def BIM(self):
... return self.weight / (self.height ** 2)
...
>>> p1 = Person('jason', 78, 1.83)
>>> p1.BIM
23.291229956104985
使用property有效地保证了属性访问的一致性。另外property还提供设置和删除属性的功能
>>> class Foo:
... def __init__(self,val):
... self.__NAME=val #将属性隐藏起来
... @property
... def name(self):
... return self.__NAME
... @name.setter
... def name(self,value):
... if not isinstance(value,str): #在设定值之前进行类型检查
... raise TypeError('%s must be str' %value)
... self.__NAME=value #通过类型检查后,将值value存放到真实的位置self.__NAME
... @name.deleter
... def name(self):
... raise PermissionError('Can not delete')
...
>>> obj = Foo('kwan')
>>> obj.name
kwan
>>> obj.name='kwan' #触发name.setter装饰器对应的函数name(f,’kwan')
>>> obj.name=123 #触发name.setter对应的的函数name(f,123),抛出异常TypeError
>>> del obj.name #触发name.deleter对应的函数name(f),抛出异常PermissionError
多态
多态指的是一类事物有多种形态,比如动物有多种形态:猫、狗、猪
>>> class Animal(object):
... def spark(self):
... pass
...
>>>
>>> class Cat(Animal):
... def spark(self):
... print('喵喵喵')
...
>>>
>>> class Dog(Animal):
... def spark(self):
... print('汪汪汪')
...
>>>
>>> class Pig(Animal):
... def spark(self):
... print('哼哼哼')
...
#实例化得到三个对象
>>> cat = Cat()
>>> dog = Dog()
>>> pig = Pig()
"""
一种事物有多种形态 但是相同的功能应该有相同的名字
这样的话 以后我无论拿到哪个具体的动物 都不需要管到底是谁 直接调用相同的功能即可
无论你是鸡 鸭 猫 狗 猪 只要你想叫 你就调固定的叫的功能
"""
>>> cat.spark()
喵喵喵
>>> dog.spark()
汪汪汪
>>> pig.spark()
python也提供了一种强制性的操作(了解即可),应该是自觉遵守
import abc
# 指定metaclass属性将类设置为抽象类,抽象类本身只是用来约束子类的,不能被实例化
class Animal(metaclass=abc.ABCMeta):
@abc.abstractmethod # 该装饰器限制子类必须定义有一个名为talk的方法
def talk(self): # 抽象方法中无需实现具体的功能
pass
class Person(Animal): # 但凡继承Animal的子类都必须遵循Animal规定的标准
def talk(self):
pass
def run(self):
pass
obj = Person()
反射
反射即通过字符串来操作对象的数据或方法
方法
方法 | 描述 |
---|---|
hasattr() | 判断对象是否含有某个字符串对应的属性 |
getattr() | 获取对象字符串对应的属性 |
setattr() | 根据字符串给对象设置属性 |
delattr() | 根据字符串给对象删除属性 |
class Teacher:
def __init__(self,full_name):
self.full_name = full_name
t = Teacher('jason Lin')
# hasattr(object,'name')
hasattr(t,'full_name') # 按字符串'full_name'判断有无属性t.full_name
# getattr(object, 'name', default=None)
getattr(t,'full_name',None) # 等同于t.full_name,不存在该属性则返回默认值None
# setattr(x, 'y', v)
setattr(t,'age',18) # 等同于t.age=18
# delattr(x, 'y')
delattr(t,'age') # 等同于del t.age
例
需求
判断用户提供的名字在不在对象可以使用的范围内
class Student:
school = '清华大学'
def choice_course(self):
print('选课')
stu = Student()
方式1
利用异常处理(过于繁琐)
try:
if stu.school:
print(f"True{stu.school}")
except Exception:
print("没有属性")
"""
变量名school 与字符串school 区别大不大
stu.school
stu.'school'
两者虽然只差了引号 但是本质是完全不一样的
"""
方式2
获取用户输入的名字 然后判断该名字对象有没有
while True:
target_name = input('请输入您想要核查的名字>>>:').strip()
'''上面的异常更加不好实现 需要用反射'''
# print(hasattr(stu, target_name))
# print(getattr(stu, target_name))
if hasattr(stu, target_name):
# print(getattr(stu, target_name))
res = getattr(stu, target_name)
if callable(res):
print('拿到的名字是一个函数', res())
else:
print('拿到的名字是一个数据', res)
else:
print('不好意思 您想要查找的名字 对象没有')
print(stu.__dict__)
stu.name = 'jason'
stu.age = 18
print(stu.__dict__)
setattr(stu, 'gender', 'male')
setattr(stu, 'hobby', 'read')
print(stu.__dict__)
del stu.name
print(stu.__dict__)
delattr(stu, 'age')
print(stu.__dict__)
"""
以后只要在需求中看到了关键字
....对象....字符串
那么肯定需要使用反射
"""
实例
>>> class FtpServer:
... def serve_forever(self):
... while True:
... inp = input('input your cmd>>: ').strip()
... cmd, file = inp.split()
... if hasattr(self, cmd): # 根据用户输入的cmd,判断对象self有无对应的方法属性
... func = getattr(self, cmd) # 根据字符串cmd,获取对象self对应的方法属性
... func(file)
... def get(self, file):
... print('Downloading %s...' % file)
... def put(self, file):
... print('Uploading %s...' % file)
...
>>>
>>> obj = FtpServer()
>>> obj.serve_forever()
input your cmd>>: get 1.txt
Downloading 1.txt...
input your cmd>>: put 1.txt
Uploading 1.txt...