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机制采用双下划线开头的方式将属性隐藏起来(设置成私有的),这仅仅只是一种变形操作,类中所有双下滑线开头的属性都会在类定义阶段、检测语法时自动变成“_类名__属性名”的形式

  1. 在类外部无法直接访问双下滑线开头的属性,但知道了类名和属性名就可以拼出名字:_类名__属性,然后就可以访问了
  2. 在类内部是可以直接访问双下滑线开头的属性的
  3. 变形操作只在类定义阶段发生一次,在类定义之后的赋值操作,不会变形。
>>> 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...
posted @ 2022-07-28 21:13  梦想有双休  阅读(42)  评论(0编辑  收藏  举报