面向对象的三大特性
面向对象的三大特性:继承 多态 封装
一. 继承
继承是一种创建新类的方式,在python中,新建的类可以继承一个或多个父类,
父类又可称为基类或超类,
新建的类称为派生类或子类
python中类的继承分为:单继承和多继承
class Foo: #父类Foo
def __init__(self, name, age):
self.name = name
self.age = age
def func(self):
print('foo')
class Son(Foo): #子类Son继承父类Foo
def __init__(self, name, age, sex): #使用super继承 父类__init__属性
super().__init__(name, age)
# super(Son,self).__init__(name,age)
# Foo.__init__(self,name,age)
self.sex = sex #派生属性
def func(self): #派生方法
super().func() #使用super()查看父类同名的方法
print('son')
s = Son('xxx', 22, 'man')
print(Son.__bases__) #使用__bases__查看从左到右的父类(<class '__main__.Foo'>,)
print(Son.__mro__) #使用__mro__ 查看多继承的顺序(<class '__main__.Son'>, <class '__main__.Foo'>, <class 'object'>)
1.2 抽象类
抽象类是一个特殊的类,它的特殊之处在于只能被继承,不能被实例化
抽象类 :Payment这个类是一个抽象类
抽象类做什么事儿 : 约束所有的子类 必须重写被abstractmethod装饰的方法名,给我们的代码指定规范
特点 : 抽象类不能实例化,只是作为具体的类的规范
抽象类长什么样
class 类名(metaclass = 'ABCMeta'):
@abstractmethod
def 规定的方法名(self):pass
@abstractmethod
def 规定的方法名(self):pass
@abstractmethod
def 规定的方法名(self):pass
from abc import ABCMeta,abstractmethod
class Payment(metaclass=ABCMeta): # 抽象类
@abstractmethod # 如果我必须要实现pay方法,那么我需要给pay加一个装饰器
def pay(self):
pass # 创建的这个pay并没有内容,
# 之所以写一个pay是为了提醒所有子类你一定要实现一个pay方法
@abstractmethod
def back(self):
pass
class Wechatpay(Payment):
def __init__(self,name,money):
self.name = name
self.money = money
def pay(self):
print('%s通过微信支付了%s元'%(self.name,self.money))
class Alipay(Payment):
def __init__(self,name,money):
self.name = name
self.money = money
def pay(self):
print('%s通过支付宝支付了%s元'%(self.name,self.money))
def back(self):
print('退款')
# 归一化设计
def pay(person):
person.pay()
alex = Alipay('alex',20000) #实例化 Wechatpay没有重写back方法所以不能实例化
pay(alex)
1.3 接口类
接口提取了一群类共同的函数,可以把接口当做一个函数的集合。然后让子类去实现接口中的函数
这么做的意义在于归一化,什么叫归一化,就是只要是基于同一个接口实现的类,那么所有的这些类产生的对象在使用时,从用法上来说都一样。
归一化,让使用者无需关心对象的类是什么,只需要的知道这些对象都具备某些功能就可以了,这极大地降低了使用者的使用难度。
class Alipay:
'''
支付宝支付
包含pay方法
'''
def pay(self,money):
print('支付宝支付了%s元'%money)
class Applepay:
'''
apple pay支付
包含pay方法
'''
def pay(self,money):
print('apple pay支付了%s元'%money)
class Wechatpay:
'''
wechatpay支付
包含pay方法
'''
def pay(self,money):
print('微信支付了%s元'%money)
def pay(payment,money):
'''
归一化 所有的类均包含pay方法
支付函数,总体负责支付
对应支付的对象和要支付的金额
'''
payment.pay(money)
p = Wechatpay() #归一化 不管实例化哪一个支付方法 均可正常执行
pay(p,200)
抽象类与接口类
抽象类 是单继承的规范
接口类 是多继承的规范
抽象类的本质还是类,指的是一组类的相似性,包括数据属性(如all_type)和函数属性(如read、write),而接口只强调函数属性的相似性。
抽象类是一个介于类和接口直接的一个概念,同时具备类和接口的部分特性,可以用来实现归一化设计
在python中,并没有接口类这种东西,即便不通过专门的模块定义接口,我们也应该有一些基本的概念。
二. 多态
在python当中 处处是多态,一切皆对象
鸭子类型
python当中写程序的一种特殊的情况
其他语言中 正常的我们说一个数据类型具有某个特点,通常是通过继承来实现
继承迭代器类,来证明自己本身是个迭代器
继承可哈希的类,来证明自己本事是可哈希的
但是所有的这些都不是通过继承来完成的
我们只是通过一种潜规则的约定,如果具有__iter__,__next__就是迭代器
如果具有__hash__方法就是可哈希
如果具有__len__就是可以计算长度的
这样数据类型之间的关系并不仅仅是通过继承来约束的
而是通过约定俗成的关系来确认的
三.封装
封装:隐藏对象的属性和实现细节,仅对外提供公共访问方式。
【好处】
1. 将变化隔离;
2. 便于使用;
3. 提高复用性;
4. 提高安全性;
【封装原则】
1. 将不需要对外提供的内容都隐藏起来;
2. 把属性都隐藏,提供公共方法对其访问。
广义上的封装 : 把方法和属性根据根据类别装到类中
狭义上的封装 : 私有化的方法和属性
方法\静态变量\实例变量(对象属性)都可以私有化
所谓的私有化 : 就是只能在类的内部可见,类的外部是不能访问或者查看的
实现方式:在python中使用双下划线将属性隐藏起来
3.1 private
私有属性:
lass Goods:
def __init__(self,name,price):
self.name = name
self.__price = price # 私有属性
def get_price(self):
print(self.__price) #在类的内部调用
apple= Goods('苹果',5)
print(apple.name)
apple.get_price() #可以使用类的方法查看属性
print(apple.__price) #报错
私有静态变量:
私有的静态变量
class Role:
__Country='China' # 静态变量的私有化
def func(self):
print(self.__Country)
print(Role.__Country) # 报错 : 因为不能再类的外部引用变量
Role().func() #使用类方法查看
私有方法:
import hashlib
class Auth:
def __init__(self,user,pwd):
self.username = user
self.password = pwd
def __md5_code(self):
md5 = hashlib.md5(self.username.encode('utf-8'))
md5.update(self.password.encode('utf-8'))
return md5.hexdigest()
def login(self):
if self.username == 'alex' and 'ee838c58e5bb3c9e687065edd0ec454f' == self.__md5_code():
return True
user = input('>>>')
pwd = input('>>>')
obj = Auth(user,pwd)
obj.__md5_code() # 报错的,私有的方法不能在类的外部被使用
obj._Auth__md5_code() # 不报错的
ret = obj.login()
if ret:
print('登陆成功')
# 私有化是怎么完成的?
class Goods:
def __init__(self,name,price):
self.name = name
self.__price = price # 私有属性
def get_price(self):
print(self.__price)
def set_num(self):
self.__num = 20
apple = Goods('苹果',5)
print(apple.__dict__)
print(apple._Goods__price) # 私有的形成
# 所有的私有的变化都是在类的[内部]定义的时候完成的
apple.__num = 10
print(apple.__dict__)
apple.set_num()
print(apple.__dict__)
执行结果:
私有属性在类的内部发生变形
这种自动变形的特点:
1.类中定义的__x只能在内部使用,如self.__x,引用的就是变形的结果。
2.这种变形其实正是针对外部的变形,在外部是无法通过__x这个名字访问到的。
3.在子类定义的__x不会覆盖在父类定义的__x,因为子类中变形成了:_子类名__x,而父类中变形成了:_父类名__x,即双下滑线开头的属性在继承给子类时,子类是无法覆盖的。
这种变形需要注意的问题是:
1.这种机制也并没有真正意义上限制我们从外部直接访问属性,知道了类名和属性名就可以拼出名字:_类名__属性,然后就可以访问了,如a._A__N
2.变形的过程只在类的内部生效,在定义后的赋值操作,不会变形
私有的属性可以被继承么?
class Foo:
def __init__(self):
self.__func()
def __func(self):
print('in foo')
class Son(Foo):
def __func(self):
print('in son')
Son() #-->in foo
私有的所有内容 :实例变量(对象属性),静态变量(类变量),方法都不能被子类继承
私有的 private 只能在类的内部使用 既不能被继承 也不能在类的外部使用
#例子
class User: def __wahaha(self): print('in user') class VipUser(User): def func(self): self.__wahaha() VipUser().func() # 报错,因为在命名空间中根本不存在一个_VipUser__wahaha
3.2 property
将一个类的函数定义成特性以后,对象再去使用的时候c1.area,根本无法察觉自己的area是执行了一个函数然后计算出来的,这种特性的使用方式遵循了统一访问的原则
class Circle:
def __init__(self,r):
self.r = r
@property # 把装饰的一个方法伪装成一个属性
def area(self):
return 3.14*self.r**2
@property
def perimeter(self):
return 2*3.14*self.r
c1 = Circle(5)
print(c1.area)
print(c1.perimeter)
一个静态属性property本质就是实现了get,set,delete三种方法
class Foo:
@property
def AAA(self):
print('get的时候运行我啊')
@AAA.setter
def AAA(self,value):
print('set的时候运行我啊')
@AAA.deleter
def AAA(self):
print('delete的时候运行我啊')
#只有在属性AAA定义property后才能定义AAA.setter,AAA.deleter
f1=Foo()
f1.AAA #get的时候运行我啊
f1.AAA='aaa' #set的时候运行我啊
del f1.AAA #delete的时候运行我啊
具体使用方法:
class Goods:
discount = 0.8 #折扣
def __init__(self,name,price):
self.name = name
# 原价
self.__price = 100
@property
def price(self):
# 实际价格 = 原价 * 折扣
return self.__price * self.discount
@price.setter
def price(self, value):
self.__price = value
@price.deleter
def price(self):
del self.__price
obj = Goods('xx',100)
print(obj.price) # 获取商品价格
obj.price=200 # 修改商品原价
print(obj.price)
del obj.price # 删除商品原价
print(obj.__dict__)
文件句柄操作:
# class File: # 盒子
# def __init__(self,path):
# self.__path = path
#
# @property
# def f(self):
# self.__f = open(self.__path,mode='w',encoding='utf-8')
# return self.__f
#
# @f.deleter
# def f(self):
# self.__f.close() #关闭文件
# del self.__f #删除句柄
#
#
# obj = File()
# obj.f # 文件句柄
# obj.f.write('balabala')
# del obj.f
classmethod
class Fruits:
__discount = 0.8
def __init__(self, name, price):
# print('init',self)
self.name = name
self.__price = price
@classmethod # 把一个方法从对象方法,变成一个类方法
def change_discount(cls,value): # cls始终指向的是类名
cls.__discount = value
@classmethod
def get_discount(cls):
return cls.__discount
print(Fruits.get_discount()) #-->0.8
Fruits.change_discount(1) #直接使用类方法不需实例化
print(Fruits.get_discount()) #-->1
staticmethod
class A:
a = 1
def func1(self):
print(1)
@staticmethod # 声明这个方法只是一个普通的不会使用任何和这个类中的变量相关的方法
def func(): # 此时 func是一个静态方法
print('既不操作和self相关的内容')
print('也不操作和类名相关的内容')
A.func() #通过类名调用 与普通函数无异
面向对象的反射:
反射 通过字符串属性名 得到真正的这个字符串的名字对应的对象的属性值
知识点:
所有的a.b都可以变成getattr(a,'b')
用字符串数据类型的变量名 来获取实际的变量值
用字符串数据类型的变量名 找到这个变量对应的内存地址
hasattr(对象名,'name' ):查看对象中是否有name这个属性或方法 返回bool值
getattr(对象名,'name' ):得到name这个属性或方法 相当于对象名.name 加上()可直接调用
setattr( 对象名,'name','value')没有name这个属性就创建一个新的 有的话就更改 对象名.name=value 只能添加属性 不能添加方法
delatter(对象名,'name) 删除name属性 (可以通过类名删除方法)
class Student:
def __init__(self,name):
self.name = name
def show_courses(self):
print('调用了 show courses')
def quit(self):
print('调用了 quit')
z = Student('zzz')
print(hasattr(z,'name')) #True
print(getattr(z,'name')) #zzz
print(getattr(z,'show_courses')()) #调用了 show courses
使用对象反射:
# 1使用对象反射
class Student:
def __init__(self,name):
self.name = name
def show_courses(self):
print('调用了 show courses')
def quit(self):
print('调用了 quit')
z = Student('zzz')
name = input('>>>')
if hasattr(z,name): #先查看是否有此属性后方法
func_attr = getattr(z,name)
if callable(func_attr): #有的话 看是否可调用
func_attr() #可调用就加上()调用
else:
print(func_attr) #不可调用就打印
else:
print('没有此属性')
print(z.__dict__) # {'name': 'zzz'}
setattr(z,'age',18) # 使用setattr添加属性
print(z.__dict__) # {'name': 'zzz', 'age': 18}
delattr(z,'name') # 使用delattr删除属性
print(z.__dict__) # {'age': 18}
使用类反射:
class A:
Country = '中国'
@classmethod
def show(cls):
print('国家 : ', cls.Country)
print(getattr(A, 'Country')) # 相当于print(A.Country)
getattr(A, 'show')() # 相当于A.show()
# setattr(A,'age',5000) #创建新属性
# print(getattr(A,'__dict__')) #查看类的属性方法
# delattr(A,'show') #删除类方法
使用模块反射:
import re ret1 = re.findall('\d+','2985urowhn0857023u9t4') ret2 = getattr(re,'findall')('\d','wuhfa0y80aujeiagu') #两种方式一样 import time print(getattr(time,'time')) # 等同于 time.time print(getattr(time, 'time')()) # 等同于 time.time()
反射本文件的内容:
只要是出现在全局变量中的名称(变量/函数/类名)都可以通过getattr(modules[__name__],字符串数据类型的名字)
# 语法
a = 1
b = 2
print(getattr(modules[__name__], 'a')) #1
# 函数名 def func(a, b): print('in func', a, b) getattr(modules[__name__], 'func') # func地址 getattr(modules[__name__], 'func')(1, 2) # func(1, 2) --> in func 1 2
# 类名
class Course:
def func(self):
print('in func')
print(Course) #in func
print(getattr(modules[__name__], 'Course')) # in func
getattr(modules[__name__], 'Course')() # 实例化的过程
getattr(getattr(modules[__name__], 'Course')(),'func')() # 相当于Course().func() -->in func
反射总结: