一、派生实战演练
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](https://s2.loli.net/2022/07/30/q9g6YEOx7iRDBz8.png)
class Student:
school = '清华大学'
def choice_course(self):
print('选课')
stu = Student()
target_name = input('请输入你想要核查的名字>>>:')
print(getattr(stu, target_name))
'''
1.拿所给的字符串去所给的对象里面查找
2.查看是否存在一个变量名
3.如果存在的话就直接将它所绑定的值打印出来,如果是一个函数名那么加括号就可以使用了
4.如果输入一个不存在的名字,就会报错告诉我们,当前这个对象里面没有这个名字
'''
![image-20220730125357524](https://s2.loli.net/2022/07/30/m1J8e7lcDUAhBIV.png)
# 需求:判断用户提供的名字在不在对象可以使用的范围内
# 方式一:利用异常处理
# 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](https://s2.loli.net/2022/07/30/sYCJqMfB27RX9xD.png)