封装、property的使用、多态、反射
今日学习内容总结
在昨日的学习中,我们已经开始对面向对象的三大特性开始学习。首先学习的是继承,继承就是用来描述类与类之间的数据的从属关系的。使用继承可以减少代码冗余,提升编程效率。而今天的学习内容首先是对昨日的派生类做一个补充,然后是对面向对象的其他特性进行学习。
派生类的实际应用
在昨日的学习中我们说过,继承中无需重新编写原来的类的情况下对这些功能进行扩展或者重写的子类,叫做派生类。派生类的实际应用案例:
import datetime
import json
class MyJsonEncoder(json.JSONEncoder):
def default(self, o):
# 形参o就是即将要被序列化的数据对象
# print('重写了', o)
'''将o处理成json能够序列化的类型即可'''
if isinstance(o,datetime.datetime):
return o.strftime('%Y-%m-%d %X')
elif isinstance(o, datetime.date):
return o.strftime('%Y-%m-%d')
return super().default(o) # 调用父类的default(让父类的default方法继续执行 防止有其他额外操作)
d1 = {'t1': datetime.datetime.today(), 't2': datetime.date.today()}
res = json.dumps(d1, cls=MyJsonEncoder)
print(res) # {"t1": "2022-04-08 20:00:09", "t2": "2022-04-08"}
在上述操作中,我们想把字典中的date序列化,但是json不能序列化python所有的数据类型,只能是一些基本数据类型。而我们通过继承json.JSONEncoder类,重写序列化方法。在研究源码发现报错的方法叫 default :raise TypeError("Object of type '%s' is not JSON serializable" % o.class.name) 。所以我们就写了一个 MyJsonEncoder 类来继承 JSONEncoder
类然后重写default方法。
封装
面向对象的三大特性之一,封装。封装的目的是保护隐私。把不想让别人知道的代码或者方法封装起来。让内部直接访问,外部无法访问的功能。
封装,就是隐藏,将类中的某些名字'隐藏'起来,不让外界直接调用,而隐藏的目的是为了提供专门的通道去访问,在通道内可以添加额外的功能:
class People:
__star = 'earth111111111111'
__star1 = 'earth111111111111'
__star2 = 'earth111111111111'
__star3 = 'earth111111111111'
def __init__(self, id, name, age, salary):
print('----->', self.__star)
self.id = id
self.name = name
self.age = age
self.salary = salary
def get_id(self):
print('我是私有方法啊,我找到的id是[%s]' % self.id)
def get_star(self):
print(self.__star)
p1=People('123123123123','alex','18',100000000) # -----> earth111111111111
print(p1.__star) # 'People' object has no attribute '__star'
print(p1._People__star)
p1.get_star() # earth111111111111
封装名字,其实就是在变量名的前面加上两个下划线 __ 并且封装的功能只能在类定义阶段生效。在python的类中,封装其实也不是绝对的 仅仅是做了语法上的变形而已。实际上你用 print(People.dict) 之后会发现,被封装的名字都变形为 _类名__变量名 。并且可以使用,这就是python的封装不绝对的原因。
通过上述代码你会发现,我们专门开设了一个访问封装变量的通道,这个通道叫做接口。因为在外部我们无法直接访问类里面的被封装的变量,需要相应的接口来允许类外部间接操作数据。
封装的目的是为了隔离复杂度,我们虽然通过 print(People.dict) 方式看见了变形后的变量名,但是我们不能直接访问,看到了封装就表示这个属性需要通过特定的通道接口去访问。
property的使用
property就是将方法伪装成数据,@property最大的好处就是在类中把一个方法变成属性调用,起到既能检查属性,还能用属性的方式来访问该属性的作用。
实用案例:
# 有时候很多数据需要经过计算才可以获得,但是这些数据给我们的感觉应该属于数据而不是功能,比如BMI指数
class Person(object):
def __init__(self, name, height, weight):
self.__name = name
self.height = height
self.weight = weight
@property
def BMI(self):
# print('%s的BMI指数是:%s' % (self.name, self.weight / (self.height ** 2)))
return '%s的BMI指数是:%s' % (self.__name, self.weight / (self.height ** 2))
p1 = Person('jason', 1.83, 77)
# 这里不是方法调用,而是直接打印数据的方式使用
print(p1.BMI) # jason的BMI指数是:22.99262444384723
多态
多态,是面向对象的三大特性之一,意思是在以封装和继承为前提不同的子类调用相同的方法,产生不同的结果。
多态的特点:
1、只关心对象的实例方法是否同名,不关心对象所属的类型;
2、对象所属的类之间,继承关系可有可无;
3、多态的好处可以增加代码的外部调用灵活度,让代码更加通用,兼容性比较强;
4、多态是调用方法的技巧,不会影响到类的内部设计。
多态的应用案例:
class Animal(object):
def speak(self):
pass
class Cat(Animal):
def speak(self):
print('喵喵喵')
class Dog(Animal):
def speak(self):
print('汪汪汪')
class Pig(Animal):
def speak(self):
print('哼哼哼')
c1 = Cat()
d1 = Dog()
p1 = Pig()
c1.speak() # 喵喵喵
d1.speak() # 汪汪汪
p1.speak() # 哼哼哼
多态性的好处在于增强了程序的灵活性和可扩展性,比如通过继承Animal类创建了一个新的类,实例化得到的对象obj,可以使用相同的方式使用obj.speak()。
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
p1=Person() # 若子类中没有一个名为talk的方法则会抛出异常TypeError,无法实例化
由多态性衍生出一个鸭子类型理论:只要你看着像鸭子,走路像鸭子,说话像鸭子,那么你就是鸭子。比如Linux系统中的一切皆文件,内存可以存取数据,硬盘也可以存取数据等等,那么所有人都是文件。
反射
在做程序开发中,我们常常会遇到这样的需求:需要执行对象里的某个方法,或需要调用对象中的某个变量,但是由于种种原因我们无法确定这个方法或变量是否存在,这是我们需要用一个特殊的方法或机制要访问和操作这个未知的方法或变量,这中机制就称之为反射。
反射其实就是通过字符串来操作对象的数据和功能,而反射中我们需要掌握四种方法:
1. hasattr():判断对象是否含有字符串对应的数据或者功能
2. getattr():根据字符串获取对应的变量名或者函数名
3. setattr():根据字符串给对象设置键值对(名称空间中的名字)
4. delattr():根据字符串删除对象对应的键值对(名称空间中的名字)
hasattr
使用案例:
class Person(object):
def __init__(self,name):
self.name = name
def talk(self):
print("%s正在交谈"%self.name)
p = Person("laowang")
print(hasattr(p,"talk")) # True。因为存在talk方法
print(hasattr(p,"name")) # True。因为存在name变量
print(hasattr(p,"abc")) # False。因为不存在abc方法或变量
getattr
使用案例:
class Person(object):
def __init__(self,name):
self.name = name
def talk(self):
print("%s正在交谈"%self.name)
p = Person("laowang")
n = getattr(p,"name") # 获取name变量的内存地址
print(n) # laowang
f = getattr(p,"talk") # 获取talk方法的内存地址
f() # laowang正在交谈 调用talk方法
# getattr中第三个参数的用法
s = getattr(p,"abc","not find")
print(s) # not find 因为abc在对象p中找不到,本应该报错,属性找不到,但因为修改了找不到就输出not find
setattr
使用案例:
def abc(self):
print("%s正在交谈"%self.name)
class Person(object):
def __init__(self,name):
self.name = name
p = Person("laowang")
setattr(p,"talk",abc) # 将abc函数添加到对象中p中,并命名为talk
p.talk(p) # laowang正在交谈 调用talk方法,因为这是额外添加的方法,需手动传入对象
setattr(p,"age",30) # 添加一个变量age,复制为30
print(p.age) # 30
delattr
使用案例:
class Person(object):
def __init__(self,name):
self.name = name
def talk(self):
print("%s正在交谈"%self.name)
p = Person("laowang")
delattr(p,"name") # 删除name变量
print(p.name) # 'Person' object has no attribute 'name' 此时将报错
# 不能用于删除方法
反射使用口诀:以后只要在业务中看到关键字:对象 和 字符串(用户输入、自定义、指定) 那么肯定用反射。

学习内容总结
浙公网安备 33010602011771号