Python基础学习(25)super方法 封装 property装饰器 反射
一、今日内容大纲
- super 方法(继承内容补充)
- 封装
- property 装饰器
- 反射
二、super 方法
super 方法会按照 MRO(Method Resolusion Order) 顺序来寻找当前类的下一个类,因为经典类不具有 mro 方法,所以经典类不支持 super 方法。由于单继承情况的 MRO 就是从父至子依次排序,所以在单继承时 super 方法在单继承情况下就是寻找父类。
# super方法
class A:
def func(self):
print('A')
class B(A):
def func(self):
super().func()
print('B')
class C(B):
def func(self):
super().func()
print('C')
C().func() # A B C
class A:
def func(self):
print('A')
class B(A):
def func(self):
super().func()
print('B')
class C(A):
def func(self):
super().func()
print('C')
class D(B, C):
def func(self):
super(C, C()).func()
print('D')
D().func() # A B C D
# 日常使用情况
class User:
def __init__(self, name):
self.name = name
class VIPUser(User):
def __init__(self, name, level):
# User.__init__(self, name) # 这样的话可以不用继承User类
# super().__init__(name)
super(VIPUser, self).__init__(name)
self.level = level
tiabai = VIPUser('太白',6)
print(tiabai.__dict__) # {'name': '太白', 'level': 6}
三、封装
封装,其实就是把属性或者方法装起来;可以细分为广义封装和狭义封装:
- 广义:把属性和方法装起来,外面就不能直接调用了,要通过类的名字来调用(类其实就属于广义上的封装);
- 狭义:把属性和方法藏起来,外面不能调用,只能在内部偷偷调用。
下面我们来主要介绍狭义的封装,狭义的封装具有三种使用情况:
- 不让用户看到也不让用户修改;
- 可以让用户看到但不让用户修改;
- 可以让用户看到也可以让用户修改,但必须遵守一系列规则。
类中变量具有三种级别:
- public 公有的 类内外都可以使用,父类子类也都可以使用(Python 支持);
- protect 保护的 类内能用,父类子类也都可以使用,但类外不可以使用(Python 不支持);
- private 私有的 类内能用,其他地方都不可以使用(Python 支持)。
我们今天介绍的狭义封装就是依靠私有变量实现的,那么类都可以四有哪些内容呢?
- 私有静态变量
- 私有实例变量
- 私有绑定方法
如在平时的用户登录操作中,用户的密码不能随随便便被外部调用,所以我们利用私有变量隐藏起这个属性:
# 隐藏属性方法:
class User:
def __init__(self, name, password):
self.user = name
self.__password = password # 给一个属性前面加上了双下划线的时候,这个属性就变成了私有的
# 官方称其为私有变量
alex = User('alex', 'sbsbsb')
# print(alex.__password) # AttributeError: 'User' object has no attribute '__password'
如果我们希望实现用户只能看到不能修改,可以在类内定义方法来实现读取私有变量:
# 所有的私有内容或者名字都不能在类的外部调用,只能在内部使用了
# 如果一定要返回私有变量,可以通过定义绑定方法的方式返回
# 这种绑定方法返回私有变量的情况,主要是想让用户看,但是不让用户该的情况
class User:
def __init__(self, name, password):
self.user = name
self.__password = password # 私有的实例变量/私有的对象属性
def get_pwd(self): # 表示的时用户不能改只能看
return self.__password
def change_pwd(self): # 表示用户必须调用我们自定义的修改方式来进行变量的修改
pass
alex = User('alex', 'sbsbsb')
print(alex.get_pwd()) # sbsbsb
# 返回经hashlib加密的变量
import hashlib
class User:
def __init__(self, name, password):
self.user = name
self.__pwd = password # 私有的实例变量
def __get_md5(self): # 私有的变量
md5 = hashlib.md5(self.user.encode('utf-8'))
md5.update(self.__pwd.encode('utf-8'))
return md5.hexdigest()
def getpwd(self):
return self.__get_md5()
alex = User('alex', 'sbsbsb')
print(alex.getpwd())
那么,从内部实现的角度来说,为什么类内的变量名字加了下划线就不能从外部调用了呢?
class User:
__Country = 'China'
def func(self):
print(self.__Country) # 在类的内部使用的时候,自动地把当前这句话所在的类的名字拼在私有变量钱完成变形
print(User.__dict__)
# {'__module__': '__main__', '_User__Country': 'China', 'a': 'b', '__dict__': <attribute '__dict__' of 'User' objects>, '__weakref__': <attribute '__weakref__' of 'User' objects>, '__doc__': None}
# __Country -> _User__Country
# 所以其实非要找的话是找得到的
# print(User._User__Country) # China
# 在类的外部根本不能定义私有的概念
# User.__aaa = 'bbb' # 这是不可以运行的
我们知道,私有的内容是不可被继承的,那么这是什么原因呢?这是因为私有的内容在类内调用的时候会自动填补上_classname
所以即使子类定义了同名私有变量,但是其实实际上定义的变量/函数名是不同的,所以私有的变量/函数只能在类内使用:
# 私有的内容能不能被子类使用呢?
class Foo(object):
def __init__(self):
self.func()
def func(self):
print('in Foo')
class Son(Foo):
def func(self):
print('in Son')
Son() # in Son
# 私有的内容不可以被继承!
class Foo(object):
def __init__(self):
self.__func() # 表面上在寻找__func,实际上是在寻找_Foo__func
def __func(self):
print('in Foo')
class Son(Foo):
def __func(self):
print('in Son')
Son() # in Foo
四、property 装饰器
-
property 装饰器
为了方便 property 装饰器的理解,我们先引入一个之前举过的例子,圆类:
from math import pi class Circle: def __init__(self, radius): self.radius = radius def area(self): return pi * self.radius ** 2 c1 = Circle(5) print(c1.radius) # 获取半径 print(c1.area()) # 获取面积
我们可以看到,圆在此类中获取半径和面积的方法是截然不同的,因为一个是内部方法,一个是实例属性;但是在我们正常逻辑理解中,圆的半径和面积应该都是属于名词,都应该是圆的属性,这样我们就可以利用 property 装饰器,将一个类内的内部方法伪装成属性在外部使用:
from math import pi class Circle: def __init__(self, radius): self.radius = radius @property def area(self): return pi * self.radius ** 2 c1 = Circle(5) print(c1.radius) # 获取半径 print(c1.area) # 获取面积
为了能更轻松理解 property 装饰器,我们再举两个例子:
# property装饰器的应用场景1: import time class Person: def __init__(self, name, birth): self.name = name self.birth = birth @property def age(self): # 装饰的这个方法,不可以有参数 return time.localtime().tm_year - self.birth taibai = Person('taibai', 1998) print(taibai.age) # property的应用场景2:和私有属性合作 class User: def __init__(self, user, password): self.user = user self.__password = password @property def pwd(self): return self.__password alex = User('alex', 'sbsbsb') print(alex.pwd) # sbsbsb # 售货例子:苹果的打折 class Goods: discount = 0.8 def __init__(self, name, origin_price): self.name = name self.__price = origin_price @property def price(self): return self.__price * self.discount apple = Goods('apple', 5) print(apple.price) # 4.0
-
property_name.setter 装饰器
当我们需要在外部修改苹果的价格时,单纯的使用 property 装饰器伪装的内部方法会暴露,所以我们必须再伪装一个同名内部方法来完成其修改的操作:
class Goods: discount = 0.8 def __init__(self, name, origin_price): self.name = name self.__price = origin_price @property def price(self): return self.__price * self.discount @price.setter def price(self, new_value): if isinstance(new_value, float): # 起到约束数据类型的作用 self.__price = new_value apple = Goods('apple', 5) print(apple.price) # 调用的是被@property装饰的price apple.price = 10.0 # 调用的是被setter装饰的price print(apple.price) # 8.0
-
property_name.deleter 装饰器
虽然已经支持修改和调用,但是如果我们在外部删除苹果的价格时,内部方法仍然会暴露,所以我们可以再伪装一个同名内部方法来完成其删除的操作:
# 我们要删除苹果的价格的时候,又会产生新需求,这时我们需要deleter class Goods: discount = 0.8 def __init__(self, name, origin_price): self.name = name self.__price = origin_price @property def price(self): return self.__price * self.discount @price.setter def price(self, new_value): if isinstance(new_value, float): # 起到约束数据类型的作用 self.__price = new_value @price.deleter def price(self): if isinstance(): # 起到约束数据类型的作用 del self.__price apple = Goods('apple', 5) del apple.price # 并不能真的删除什么东西,只是调用被装饰的方法而已
五、反射
-
getattr()
内部函数反射其实就是利用字符串数据类型的名字,来操作这个名字对应地函数/实例变量/绑定方法等。如我们举一个日常编写代码中的问题:
name = 'alex' age = 123 # birth # sex # job # phone # ...这么多怎么办? n = input('>>>') if n == 'name': print(name) elif n == 'age': print(age) # ...
有些时候,你明明知道一个变量的字符串数据类型的名字,你想直接调用它但是调用不到,这时可以利用反射,反射支持以下多种情况:
- 反射对象的实例变量;
- 反射对象的静态变量/绑定方法等;
- 反射模块中的所有变量:包括被导入模块,当前执行的 py 文件(脚本)。
class Person: def __init__(self, name, age): self.name = name self.age = age def qqxing(self): print('qqxing') alex = Person('alex', 83) wusir = Person('wusir', 74) print(alex.name) print(alex.age) ret1 = getattr(alex, 'name') ret2 = getattr(wusir, 'name') print(ret1) # alex print(ret2) # wusir ret = getattr(wusir, 'qqxing') print(ret) # <bound method Person.qqxing of <__main__.Person object at 0x00000226CAC4DBE0>> ret() # qqxing
这样,反射的基本用法已经介绍完了,我们现在举一个之前写过的例子:支付程序
# 实际使用情况:归一化设计 class Payment: def pay(self, money): raise NotImplementedError('请在子类中重写同名pay方法') # 然后将其他的支付函数继承这个Payment类 class Alipay(Payment): def __init__(self, name): self.name = name def pay(self, money): dic = {'uname': self.name, 'price': money} # 想办法调用支付宝支付 url链接 把dic传过去 print('%s通过支付宝支付%s成功' % (self.name, money)) class Wechat(Payment): def __init__(self, name): self.name = name def pay(self, money): dic = {'username': 'alex', 'money': 200} # 想办法调用微信支付 url链接 把dic传过去 print('%s通过微信支付%s成功' % (self.name, money)) class Applepay(Payment): def __init__(self, name): self.name = name def pay(self, money): dic = {'name': self.name, 'qian': money} # 想办法调用苹果支付 url链接 把dic传过去 print('%s通过苹果支付%s成功' % (self.name, money)) def pay(name, price, mode): if mode == 'Wechat': obj = Wechat(name) elif mode == 'Alipay': obj = Alipay(name) elif mode == 'Applepay': obj = Applepay(name) obj.pay(price) pay('alex', 400, 'Wechat') # alex通过微信支付400成功 pay('alex', 500, 'Alipay') # alex通过支付宝支付500成功 pay('alex', 500, 'Applepay') # alex通过苹果支付500成功
我们现在要利用反射来解决
pay()
函数中冗长的代码,但是这些变量并没有封装在模块里,该如何反射呢?我们先研究个小问题抛砖引玉:# 在本模块里前面没有点要怎么解决呢? import sys import temp # 这是我们随手自己写的一个模块 # 两者等价 print(sys.modules['temp'].Alipay) # <class 'temp.Alipay'> print(temp.Alipay) # <class 'temp.Alipay'> # 两者等价 print(getattr(temp, 'Alipay')) print(getattr(sys.modules['a'], 'Alipay'))
所以我们从上面的小例子可以知道,如果想反射当前执行的脚本,可以使用下面的方法:
wahaha = 'wahaha' print(getattr(sys.modules['__main__'], 'wahaha')) # wahaha
这样,
pay()
函数的优化问题就很好解决了:# 所以可对原先的归一化函数进行这样的修改 def pay(name, price, mode): ret = getattr(sys.modules['__main__'], mode) obj = ret(name) obj.pay(price) pay('alex', 400, 'Wechat') # alex通过微信支付400成功 pay('alex', 500, 'Alipay') # alex通过支付宝支付500成功 pay('alex', 500, 'Applepay') # alex通过苹果支付500成功
要点小结:
# 使用反射 # 1.反射对象的实例变量 # 2.反射对象的静态变量/绑定方法等 # 3.反射模块中的所有变量:包括被导入的模块,当前执行的py文件-脚本 class A: Role = 'fashi' def __init__(self): self.name = 'alex' self.age = 84 def func(self): print('wahaha') return 666 a = A() # 实例变量 print(getattr(a, 'name')) # alex # 静态变量/绑定方法 print(getattr(a, 'func')()) # wahaha 666 print(getattr(a, 'Role')) # fashi # 反射导入模块 # import module_name # getattr(module_name, 'variable_name') # 反射当前文件 getattr(sys.modules['__main__'], 'variable_name')
-
hasattr()
内部函数和callable()
内部函数反射在日常使用中,我们会发现,因为无法判断该变量/方法是否存在的原因,总会出现报错:
class A: Role = 'fashi' def __init__(self): self.name = 'alex' self.age = 84 def func(self): print('wahaha') return 666 a = A() # print(getattr(a, 'gender')) # AttributeError: 'A' object has no attribute 'gender'
这时我们可以在反射的使用中假如
hasattr()
和callable()
来判断反射内容是否存在以及是否可以调用,来防止报错的发生:class A: Role = 'fashi' def __init__(self): self.name = 'alex' self.age = 84 def func(self): print('wahaha') return 666 a = A() if hasattr(a, 'gender'): # 判断是否拥有属性,防止报错 print(getattr(a, 'gender')) if hasattr(a, 'func'): if callable(getattr(a, 'func')): # 判断是否可以调用,防止报错 print(getattr(a, 'func')()) # wahaha 666