其他分享
首页 > 其他分享> > 派生,三大特性之封装、多态,property伪装、反射

派生,三大特性之封装、多态,property伪装、反射

作者:互联网

目录

一、派生实战演练

1.代码发生错误

import datetime
import json

d = {
    't1': datetime.datetime.today(),
    't2': datetime.datetime.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数据型是有限制的,不是所有的类型都可以
要被序列化的数据,里里外外都必须是下面类型才可以(利用查看源码的方式查看JSONEncoder)
json.JSONEncoder
    +-------------------+---------------+
    | Python            | JSON          |
    +===================+===============+
    | dict              | object        |
    +-------------------+---------------+
    | list, tuple       | array         |
    +-------------------+---------------+
    | str               | string        |
    +-------------------+---------------+
    | int, float        | number        |
    +-------------------+---------------+
    | True              | true          |
    +-------------------+---------------+
    | False             | false         |
    +-------------------+---------------+
    | None              | null          |
    +-------------------+---------------+

'''

2.解决方法

解决方式一:手动将不符合数据类型要求的数据转换成符合要求的
import datetime
import json

d = {
    't1': str(datetime.datetime.today()),
    't2': str(datetime.datetime.today())
}
res = json.dumps(d)
print(res)
解决方式二:利用派生方法
什么是派生:子类继承父类并且在子类中写了一个与父类相同的方法,重写了,然后利用super方法去调用父类的方法,在调用的过程中可以再添加一些功能
d = {
    't1': datetime.datetime.today(),
    't2': datetime.datetime.today()
}
res = json.dumps(d)
def dumps(obj, *, skipkeys=False, ensure_ascii=True, check_circular=True,
        allow_nan=True, cls=None, indent=None, separators=None,
        default=None, sort_keys=False, **kw):
        ...
'''
obj用来接收传过来的数据
1.cls=None是一个默认值形参
2.class JSONEncoder:  # JSONEncoder是一个类
    	pass
	dumps(obj,cls=None):
        if cls == None:
            cls = JSONEncoder  # 赋了一个新的东西
        return cls(...)   # JSONEncoder(),类名加括号--->实例化对象
3.打开源码,会发现类JSONEncoder()下面有对象实例化的__init__方法
4.主要研究JSONEncoder(),会发现它下面有一个default方法,它里面的内容是:
	raise TypeError(f'Object of type {o.__class__.__name__} '
                        f'is not JSON serializable')
  会发现跟我们上面报的错一样,raise是主动抛出异常,说明我们报错的原因就是由default触发的
5.想要它不报错的解决措施是:
	写一个类继承JSONEncoder(),然后在这个类里面也定义一个default方法,然后以后再走default的时候先走我们自己定义的default方法,然后在自己定义的default里可以写一些操作
	
6.小案例主要告诉我们在什么时候需要去考虑用派生,去继承类,重写里面的方法
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)


补充一个小知识:python解释器--->view--->appearance--->toolbar,找到toolbar并且勾选,可以后面用来返回上一次的页面。
'''

二、三大特性之封装

1.封装相关知识

1.封装其实就是将数据或者功能隐藏起来(包起来  装起来)

2.隐藏的目的不是让用户无法使用,而是给这些隐藏的数据'开设特定的接口' 让用户使用接口才可以去使用,我们在接口中'添加一些额外的操作' (换句话说就是找个中间商,赚个差价)

3.在'类的定义阶段'使用双下划线开头的名字,都是隐藏的属性,后续类和对象都无法直接获取
class Person:
    d_type = '人类'
    __school = '清华大学'
    def eat(self):
        print('今天是疯狂星期四,还好我不喜欢吃kfc,又省钱了')

obj = Person()
print(obj.__school)  # 无法访问
print(Person.__school)  # 无法访问
Person.__XXX = '信息学院'
print(Person.__XXX)  # 信息学院  是可以访问的

4.在python中不会真正的限制任何代码,隐藏的属性如果真的需要访问也可以,只不过需要做变形处理
	__变量名    _类名__变量名
class Person:
    d_type = '人类'
    __school = '清华大学'
    def eat(self):
        print('今天是疯狂星期四,还好我不喜欢吃kfc,又省钱了')

obj = Person()
print(Person.__dict__)

'''
{'__module__': '__main__', 
'd_type': '人类',
 '_Person__school': '清华大学',  # 名字变了形 ,可以通过,print(obj._Person__school)去访问,但是就失去了封装意义
 'eat': <function Person.eat at 0x0000024A5AD494C0>, 
 '__dict__': <attribute '__dict__' of 'Person' objects>, 
 '__weakref__': <attribute '__weakref__' of 'Person' objects>, 
 '__doc__': None,
  '__XXX': '信息学院'}
'''
ps:既然隐藏了 就不改使用变形之后的名字去访问 这样就失去了隐藏的意义

2.属性的封装

    
class Person:
    d_type = '人类'
    __school = '清华大学'
    __name = '张三'
    __age = 18

    def get_info(self):
        return f'''
        ------------------
        姓名:{self.__name}
        年龄:{self.__age}
        性别:{self.__school}
        ------------------
        '''

    def eat(self):
        print('今天是疯狂星期四,还好我不喜欢吃kfc,又省钱了')

obj = Person()
print(obj.get_info())
'''
        ------------------
        姓名:张三
        年龄:18
        性别:清华大学
        ------------------
'''
'''
封装的含义就是把一些东西隐藏掉,然后访问的时候不能直接访问,必须要通过提供的方法(封装接口),然后这个方法会返回数据,封装功能也是如此
'''

3.封装修改数据

上述封装的是属性
接下来是一个示例,封装修改数据
class Student(object):
    __school = '清华大学'
    def __init__(self, name, age):
        self.__name = name
        self.__age = age
    # 专门开设一个访问学生的数据的通道(接口)
    def check_info(self):
        print('''
            学生姓名:%s
            学生年龄:%s
        '''% (self.__name, self.__age))
    # 专门开设一个修改学生数据的通道(接口)
    def set_info(self, name, age):
        if len(name) == 0:
            print('用户不能为空')
            return
        if not isinstance(age, int):
            print('年龄必须为数字')
            return
        self.__name = name
        self.__age = age

stu1 = Student('jason', 18)

stu1.set_info('lisa', 24)
stu1.set_info('','我很大')  # 用户不能为空
"""
我们编写python很多时候都是大家墨守成规的东西 不需要真正的限制  ‘君子协定’
class A:
	_school = '清华大学'
	def _choice_course(self):
		pass
ps:当别人看到你的名字前面加了一个_,那么他就会考虑这个是不能直接用的,他会考虑有没有接口供他使用。
	同样方法也是,他会考虑这个时候不能随便的调用了,只能在内部使用
"""

三、property伪装

1.了解封装

可以简单的理解为 将方法伪装成数据
    数据只需要点名字:obj.name 
    方法不单单需要点名字,至少还需要加括号:obj.func()
伪装之后可以将func方法伪装成数据 obj.func

2.什么时候需要伪装?为什么要需要伪装?

 扩展知识:体质指数(BMI)=体重(kg)÷身高^2(m)
    
class Person:
    def __init__(self, name, weight, height):
        # 实例化
        self.name = name
        self.weight = weight
        self.height = height

    # 写一个方法,计算人的指数
    def BMI(self):
        return self.weight / (self.height ** 2)

p1 = Person('zhangran', 44, 1.60)
res = p1.BMI()
print(res)  # 17.187499999999996
'''
存在一个小问题:
    BMI应该是一个数据,不应该是一个功能,但是这个数据得需要通过计算才可以得到
怎么修改呢??
    class Person:
    def __init__(self, name, weight, height):
        # 实例化
        self.name = name
        self.weight = weight
        self.height = height

    # 这里是修改得部分,只需添加一直装饰器,但是不要有参数
    @@property
    def BMI():
        return self.weight / (self.height ** 2)

    p1 = Person('zhangran', 44, 1.60)
    # res = p1.BMI()
    # print(res)  # 17.187499999999996
    print(p1.BMI) # 17.187499999999996
'''
-----------------了解知识-------------------
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('jason')
# print(obj.name)
# obj.name = 666
# print(obj.name)
del obj.name
'''
伪装配套得俩个方法:@name.setter / @name.deleter
可以伪装的更彻底

'''

四、三大特性之多态

1.了解多态

多态:是指一种食物得多种形态
	例如:水:液态、固态、气态

2.多态的使用

class Animal(object):
    def spark(self):
        pass
class Cat(Animal):
    def miao(self):
        print('喵喵喵')
class Dog(Animal):
    def wang(self):
        print('汪汪汪')
class Pig(Animal):
    def heng(self):
        print('哼哼哼')

c1 = Cat()
d1 = Dog()
p1 = Pig()
c1.miao()  # 喵喵喵
d1.wang()  # 汪汪汪
p1.heng()  # 哼哼哼
'''
以上编写不符合多态性的概念,既然都是动物,那么叫叫的时候去调用同一个方法

一种事物有多种形态 但是相同的功能应该有相同的名字
这样的话 以后我无论拿到哪个具体的动物 都不需要管到底是谁 直接调用相同的功能即可
    无论你是鸡 鸭 猫 狗 猪 只要你想叫 你就调用固定的叫的功能
    
以下是多态性写法:
'''

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('哼哼哼')

c1 = Cat()
d1 = Dog()
p1 = Pig()
c1.spark()  # 喵喵喵
d1.spark()  # 汪汪汪
p1.spark()  # 哼哼哼

3.老早之前我们其实就接触了多态

l1 = [11, 22, 33, 44]
d1 = {'name': 'jason', 'pwd': 123, 'hobby': 'raed'}
t1 = (11, 22, 33, 44)
print(len(l1))
print(len(d1))
print(len(t1))
'''
三者都是容器类型,在统计数据个数的时候都是调用的都是len
那么这个就是多态性的理论
'''

4.多态的了解知识

'''------------------了解知识-------------------'''
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()

5.多态之鸭子类型

鸭子类型
	其实我们完全可以不依赖于继承,只需要制造出外观和行为相同对象,同样可以实现不考虑对象类型而使用对象,这正是Python崇尚的“鸭子类型”(duck typing)
	只要你长得像鸭子 走路像鸭子 说话像鸭子 那么你就是鸭子
    
 class Teacher:
     def run(self):pass
     def eat(self):pass
 class Student:
     def run(self):pass
     def eat(self):pass
    

扩展知识:

"""
操作系统
    linux系统:一切皆文件
        只要能够读写数据 那么就是文件
            内存
            硬盘
        class Txt: #Txt类有两个与文件类型同名的方法,即read和write
            def read(self):
                pass
            def write(self):
                pass
        
        class Disk: #Disk类也有两个与文件类型同名的方法:read和write
            def read(self):
                pass
            def write(self):
                pass
        
        class Memory: #Memory类也有两个与文件类型同名的方法:read和write
            def read(self):
                pass
            def write(self):
                pass
    python:一切皆对象
        只要你有数据 有功能 那么你就是对象
            文件名         文件对象
            模块名         模块对象       
"""

五、反射方法

1.反射的含义

	通过字符串来操作对象的数据方法

2.反射的四个主要方法

	hasattr():判断对象是否含有某个字符串对应的属性
	getattr():获取对象字符串对应的属性
 	setattr():根据字符串给对象设置属性
	delattr():根据字符串给对象删除属性

3.反射使用

class Student:
    school = '清华大学'

    def choice_course(self):
        print('选课')
stu = Student()

target_name = input('请输入你想要核查的名字>>>:')
print(hasattr(stu, target_name))
# hasattr,帮我们实现了变量名与字符串的转换

image-20220730124558699

class Student:
    school = '清华大学'

    def choice_course(self):
        print('选课')
stu = Student()
target_name = input('请输入你想要核查的名字>>>:')
print(getattr(stu, target_name))
'''
1.拿所给的字符串去所给的对象里面查找
2.查看是否存在一个变量名
3.如果存在的话就直接将它所绑定的值打印出来,如果是一个函数名那么加括号就可以使用了
4.如果输入一个不存在的名字,就会报错告诉我们,当前这个对象里面没有这个名字
'''

image-20220730125357524

# 需求:判断用户提供的名字在不在对象可以使用的范围内
# 方式一:利用异常处理
# try:
#     if stu.school:
#         print(f"True{stu.school}")
# except Exception:
#     print("没有属性")
# 这么写有缺陷,过于繁琐
'''
变量名school 与字符串school 区别大不大
    stu.school
    stu.'school'
两者虽然只差了引号 但是本质是完全不一样的 
'''
# 方式二:获取用户输入的名字 然后判断该名字对象有没有 上面的异常处理没有办法实现了 需要反射
while True:
    target_name = input('请输入你想要核查的名字>>>:')
    # 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('不好意思 您想要查找的名字 对象没有')

4.使用反射的小技巧

"""
以后只要在需求中看到了关键字
	....对象....字符串 
那么肯定需要使用反射
"""

5.反射实战案例

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()
# 用产生的对象去调用serve_forever()
obj.serve_forever()

image-20220730132000944

标签:__,name,self,多态,三大,print,property,class,def
来源: https://www.cnblogs.com/zxr1002/p/16534824.html