多态
什么是多态?
一种事物/一个类的多种形态。
例如:动物有多种形态:人、猪、狗;文件有多种形态:执行文件、文本文件;水有多种形态:液态水、固态水、水蒸气
import adc
class File(metaclass=abc.ABCMeta):
@abc.abstractmethod
def click(self):
pass
class Text(File):
def click(self):
print('open file')
class ExeFile(File):
def click(self):
print('execute file')
多态性:多个不同类对象响应同一个方法,返回不同的结果
多态性的好处?
1、增加了程序的灵活性:以不变应万变,不论对象千变万化,使用者都是同一种方式去调用。如:fun(animal)
2、增加了程序的可扩展性:通过继承Animal类创建一个新类,使用者无需更改代码,还是用fun(animal)调用
'''多态性:一种调用方式,不同的执行效果(多态性)
def func(obj):
obj.run()
func(peo1)
func(pig1)
func(d1)
# 多态性依赖于:继承
# 多态性:定义统一的接口
'''
class Cat(Animal): # 属于动物的另外一种形态:猫
def talk(self):
print('say miao')
def func(animal): # 对于使用者来说,自己的代码根本无需改动
animal.talk()
cat1 = Cat() # 实例出一只猫
func(cat1) # 甚至连调用方式也无需改变,就能调用猫的talk功能
多态小结
多态:同一种事物的多种形态,动物分为人类,猪类(在定义角度)
多态性:一种调用方式,不同的执行效果(多态性)
封装
什么是封装?
隐藏对象的属性和实现细节,仅对外提供公共访问方式
先装后封:
装:把数据和方法装到类里面。
封:把类里面的数据和方法封起来,不让人访问。
封装原则:将不需要对外提供的内容都隐藏起来。把属性都隐藏,提供公共方法对其访问。
为什么要封装:
1、保护隐私,比如:你买了个充气娃娃,你能让人知道吗?这时候就把你买的娃娃封装起来,然后自己偷偷用。
2、隔离复杂度,比如:你有尿尿这个功能(方法),你不需要知道你尿是经历了多少化学反应才流出来的,你只需要掏出你的接口直接用尿这个功能就行,这不就隔离了复杂度嘛。
封装的两个层面:
无论那种层面的封装,都要对外界提供接口,访问你内部隐藏的内容。使用者都只能通过这个接口访问。
1、创建类和对象会分别创建二者的名称空间,我们只能使用类名.或者对象.的方式访问名称空间里面的名字,这本身就是一种封装。
2、把某些属性隐藏起来(或者说定义成私有的),只在类内部使用,外部无法访问。或者留下少量接口,供外部访问。
重要:在python中,使用双下划线 __的方式实现隐藏属性(设置成私有)
重要:类中所有双下划线开头的名称如 __X 会自动变形 _类名__X 的形式
私有属性:用双下划线开头的属性
class A:
__N = 0 #变形为_A__N
def __init__(self):
self.__X = 10 #变形成self._A__X
def __foo(self): #变形成:_A__foo
pass
def bar(self):
self.__foo() #只有在类内部可以通过__foo访问。
#注意:A.__N访问不到,A._A__N是可以访问到的,即这种操作并不是严格意义上的限制外部访问,仅仅只是一种语法意义上的变形
print(A.__dict__) # {'__module__': '__main__', '_A__N': 0, '__init__': <function A.__init__ at 0x1005704d0>, '_A__foo': <function A.__foo at 0x100570830>, 'bar': <function A.bar at 0x1004c2b00>, '__dict__': <attribute '__dict__' of 'A' objects>, '__weakref__': <attribute '__weakref__' of 'A' objects>, '__doc__': None}
print(A._A__N) # 0
a = A()
print(a._A__N) # 0
print(a.__N) # AttributeError: 'A' object has no attribute '__N'
自动变形的特点:
1、类中定义的 __x 只能在内部使用,如 self.__x, 引用的就是变形的结果
2、这种变形是针对外部的变形,在外部无法通过 self.__x 这个名字访问到。
3、在类中定义的 __x 不会覆盖父类定义的 __x, 因为子类中的 __x 变形成了 _子类__x, 父类中的 __x 则变形成了 _父类__x
所以子类定义的私有属性是无法覆盖父类的。因为变形成了不同的属性
在继承中,父类如果不想让子类覆盖自己的方法,可以将方法定义为私有的
class A:
def __fa(self): #在定义时,变形为_A__fa
pritn('from A')
def test(self):
self.__fa() #类内部调用自己的私有方法,实际上为self._A__fa()
class B(A):
def __fa(self):
print('from B')
b = B()
b.test() # from A
封装在于明确区分内外,让类使用者可以修改封装内的东西而不影响外部调用。
#类的设计者
class Room:
def __init__(self,name,owner,width,length,high):
self.name=name
self.owner=owner
self.__width=width
self.__length=length
self.__high=high
def tell_area(self): #对外提供的接口,隐藏了内部的实现细节,此时我们想求的是面积
return self.__width * self.__length
#使用者
>>> r1=Room('卧室','egon',20,20,20)
>>> r1.tell_area() #使用者调用接口tell_area
#类的设计者,轻松的扩展了功能,而类的使用者完全不需要改变自己的代码
class Room:
def __init__(self,name,owner,width,length,high):
self.name=name
self.owner=owner
self.__width=width
self.__length=length
self.__high=high
def tell_area(self): #对外提供的接口,隐藏内部实现,此时我们想求的是体积,内部逻辑变了,只需求修该下列一行就可以很简答的实现,而且外部调用感知不到,仍然使用该方法,但是功能已经变了
return self.__width * self.__length * self.__high
#对于仍然在使用tell_area接口的人来说,根本无需改动自己的代码,就可以用上新功能
>>> r1.tell_area()
property属性
什么是property属性?
property属性是一种特殊的属性。使用@property装饰器可以将一个函数当做数据属性来使用。
class A:
def __init__(self, w, h):
self.__w = w
self.__h = h
@property
def bmi(self):
return self.__w / self.__h ** 2
a = A(67, 1.76)
print(a.bmi) # 21.62964876033058
import math
class Circle():
def __init__(self, radius):
self.__radius = radius
@property
def area(self):
return math.pi * self.__radius ** 2
@property
def perimeter(self):
return 2 * math.pi * self.__radius
c = Circle(10)
print(c.area)
print(c.perimeter)
#注意:此时的特性area和perimeter不能被赋值
c.area=3 #为特性area赋值
'''
抛出异常:
AttributeError: can't set attribute
'''
为什么要用property?
将一个类的函数定义成特性以后,对象使用的时候obj.name,根本无法察觉自己的name是执行了一段函数然后就算出来的。这种特性的使用方式遵循了统一访问的原则。
property属性是无法修改的。python中提供了 @定义成特性的函数.setter 装饰器来修改,提供了 @定义成特性的函数.deleter 装饰器来防止删除。
class Foo:
def __init__(self, name):
self.__name = name
@property
def name(self):
return self.__name
@name.setter
def name(self, value):
if not isinstance(value, str):
raise TypeError('输入的名字必须是字符串类型的')
self.__name = value
@name.deleter
def name(self):
raise TypeError('特性name不能被删除')
f = Foo('大帅比')
print(f.name)
f.name = '我是你爹'
print(f.name)
f.name = 10 #TypeError: 输入的名字必须是字符串类型的
del f.name # TypeError: 特性name不能被删除
'''
注意:只有在函数被 @property 装饰器修饰成一个特性以后,才能使用 函数名。setter, 函数名.dellter
'''
@classmethod
类的绑定方法,当类里面的函数不需要用到对象的属性时可以使用类绑定方法
class Classmethod_Demo():
role = 'dog'
@classmethod
def func(cls):
print(cls.role)
Classmethod_Demo.func()
@staticmethod
静态方法,当类里面的函数不需要用到类属性、对象属性时,可以使用静态方法
class Staticmethod_Demo():
role = 'dog'
@staticmethod
def func():
print("当普通方法用")
Staticmethod_Demo.func()