类的三大特性:多态,继承,封装,再加上一个反射
继承就不用再说了,现在说说多态
多态
多态其实在很多语言中都有,比如c++,但是呢python的多态比较宽松,emmmmmm怎么说呢,就是没那么严格的规定吧
那什么叫多态呢?多态就是调用同一个函数或者方法,在编译时是可以通过的,但是却无法得知具体的结果,需要在运行时才可以确定结果,这样的特性就叫做多态
多态有类方法的多态,也有函数的多态
类方法的多态:
类的多态:可以是同一个类的不同对象调用同一个方法的多态也可以是不同类的对象调用同一个类的方法的多态,下面这个是不同类的对象(但是继承了同一个父类)
class Water: def __init__(self,tem,zt): self.zt = zt self.tem = tem def show_info(self): if self.tem <= 0: print('%s度太冷了,水变成%s了!' % (self.tem,self.zt)) elif 0 < self.tem < 100: print('%s度刚刚好了,水依然还是%s!' % (self.tem, self.zt)) else: print('%s度太热了,水变成%s了!' % (self.tem, self.zt)) class WWater(Water): pass class Stream(Water): pass class Ice(Water): pass water = WWater(36,'水') ice = Ice(-19,'冰') stream = Stream(999,'水蒸气') water.show_info() //这个包括下面两个,是不是不同的类调用同一个方法得到的结果需要在运行时才唯一确定,在编译时是不可以确定的(用的是同一个父类的方法,所以用的是同一个方法) ice.show_info() stream.show_info()
结果
36度刚刚好了,水依然还是水!
-19度太冷了,水变成冰了!
999度太热了,水变成水蒸气了!
这里多讲一点东西哈,其实类的多态就是继承的具体体现而已,因为你写继承不可以为了继承而继承吧,你继承的东西肯定是要在子类的对象里用的呀,不然继承来干嘛?所以你在子类里调用父类的方法时就体现了类的多态,在编译时是可以通过的,但是结果却只有在运行时才唯一确定,比如上面的例子,所以说有些人认为,多态不应该和继承封装写在一起,因为多态也就是类的继承的具体体现而已,可能是由于这个类的特性贼他妈的重要,所以才把它也写出来
函数的多态:
先举一个python中内置得函数 ---> len()函数
a = [1,2,3,4] b = 'djh贼他妈的帅,帅得掉渣' c = ('djh','大帅哥','我爱你') print(len(a)) //调用同一个函数,但是结果在运行时才唯一确定,而且结果还不一定一样 print(len(b)) //len()函数本质上是调用 b.__len__() 方法 print(len(c))
结果
4
13
3
根据len()的原理,自定义一个类似的
class A:
def say_hi(self):
print('大家好')
class B:
def say_hi(self):
print('hello everyone')
def func(obj):
obj.say_hi()
return None
a = A()
b = B()
func(a) //运用相同的函数,在编译时没问题,可以随便通过,但是结果需要在运行时才唯一确定,但是呢,本质上是用了对象自己的say_hi()方法,不是同一个say_hi()方法,是各自自己的不同的,那这样叫不叫
//多态呢?欸 其实我也不知道,但感觉上应该可能或许可以勉勉强强叫多态,因为say_hi()虽然本质上不是同一个方法,但是呢却封装在func()函数里面,而a,b用的却是同一个函数func(),所以应该
//可以勉强叫多态,但是如果不封装,也就是直接用 a.say_hi() b.say_hi() 这样的话就应该不叫多态了,虽然名字相同但是却调用的不是同一个方法
func(b)
结果
大家好
hello everyone
封装
封装有多个层面的理解
1:类本身就是应该封装,类就相当于麻袋,将一些属性装进了类里面,这个是装,类名相当于绳子,将这个麻袋绑起来,这个是封,当然类名也是标签,通过类名也就是标签来找这个麻袋也就是这个类
举个例子:
class Water: def __init__(self,tem,zt): self.zt = zt self.tem = tem def show_info(self): if self.tem <= 0: print('%s度太冷了,水变成%s了!' % (self.tem,self.zt)) elif 0 < self.tem < 100: print('%s度刚刚好了,水依然还是%s!' % (self.tem, self.zt)) else: print('%s度太热了,水变成%s了!' % (self.tem, self.zt))
比如你写好了这个类,然后如果你需要在另一个文件里使用这个类,那直接通过import将这个类所在的模块导入就ok了,但是导入之后你只可以用这个类却看不到这个类面的任何东西,比如说你可以使用show_info()
这个方法,但是你只知道里面有这个方法并且你可以使用,但是这个方法这么实现的?逻辑是什么样的?原型是咋样的?你想看都看不到!
这就是第一层面也是最浅显的关于封装的理解
2:类中定义私有的属性,约定在类里面使用,不要再类外外面使用(为啥叫约定呢,因为如果你实在是要访问也是他妈的可以的,但是我们约定不访问)
以 ‘_’开头的变量
class Chinese: _name = 'china' //约定是类的私有属性,只在类内使用不在类外使用 def __init__(self,number): self._number = number //这个也是,约定是对象的私有属性,不在类外使用 def show_info(self): print('name:%s\ngdp:%s' % (self._name,self._number)) c = Chinese('15亿') c.show_info() print(c._name) //这里你硬要是调用python也没办法,因为我们是约定的,只是约定的,没有约束! print(c._number)//这里也一样 结果 name:china gdp:15亿 china 15亿
以‘__’开头的变量 -----> 和上面的一样,只是约定但没有约束,当然这个和上面的又有一点点区别
class Chinese:
__name = 'china'
def __init__(self,number):
self.__number = number
def show_info(self):
print('name:%s\ngdp:%s' % (self.__name,self.__number))
c = Chinese('15亿')
c.show_info()
# print(c.__name)//这样在类外直接调用是错的
# print(c.__number)
print(Chinese.__dict__)//可以看一下增加了什么变量
print(c.__dict__)
print(Chinese._Chinese__name)//以'__'开头的变量python会自动改个名字,变成'_'+Chinese(类名)+'__name'(以'__'开头的变量),具体看下面的属性字典,所以以‘__’开头的变量也没有完全做到属性私有
print(c._Chinese__number) //只是通过自动的改名字让类外无法直接调用,但是如果你知道了改名字的规则,在类外也是可以直接调用的,所以和"_"开头的变量一样,只是约定,约定,约定,而没有真正的约束
结果
name:china
gdp:15亿
{'__module__': '__main__', '_Chinese__name': 'china', '__init__': <function Chinese.__init__ at 0x00000256859A6730>,
'show_info': <function Chinese.show_info at 0x0000025686A539D8>, '__dict__': <attribute '__dict__' of 'Chinese' objects>,
'__weakref__': <attribute '__weakref__' of 'Chinese' objects>, '__doc__': None}
{'_Chinese__number': '15亿'}
china
15亿
3:真正意义上的私有属性,也就是真的相当于c++里面的私有成员,明确划分,哪些只可以在类内用不可以在类外用!
反射
python里一切皆对象,只要是对象就可以使用自省!!!
反射又叫自省,就是可以自动检查类本身的一些属性,有4个函数要记住,下面的4个方法都是对对象使用的(当然也可以是类,因为类也是对象,但自省本身就是对对象来说的呀)
//hasattr()函数
class sb: sb_attr = 'sb' def __init__(self,name): self.name = name def find_teacher(self): print('%s这个傻逼玩意就他妈会找老师.' % self.name) xld = sb('谢礼东') print(hasattr(xld,'find_teacher')) //有两个参数,第一个参数是对象,不是类,第二个参数是字符串,表示要找的对象的属性,第二个参数本质上就是属性字典里的key,同样大家都是字符串,查找的顺序和
print(hasattr(xld,'sb_attr'))//调用方法或者读取属性时的顺序一模一样,其实本质就是看可不可以调用这个方法而已,有这个属性返回True,没有就是False 结果 True True
//getattr()函数
class sb: sb_attr = 'sb' def __init__(self,name): self.name = name def find_teacher(self): print('%s这个傻逼玩意就他妈会找老师.' % self.name) xld = sb('谢礼东') a = getattr(xld,'name') //有3个参数,前两个和上面的hasattr()函数一样,如果找到会返回对应的值 fun = getattr(xld,'find_teacher')//如果是方法的话会返回内存地址 相当于 fun = xld.find_teacher 没有括号
#b = getattr(xld,'aaaaaa')//如果找的属性不存在这样写会报错
b = getattr(xld,'aaaaa','不存在这个属性呀') //第三个参数是在找不到属性时返回的,这样就不会像上面一样找不到就报错 print(a) print(fun) fun()
print(b) 结果 谢礼东 <bound method sb.find_teacher of <__main__.sb object at 0x000002159ACF3AC8>> 谢礼东这个傻逼玩意就他妈会找老师.
不存在这个属性呀
//setattr()函数
class sb: sb_attr = 'sb' def __init__(self,name): self.name = name def find_teacher(self): print('%s这个傻逼玩意就他妈会找老师.' % self.name) xld = sb('谢礼东') print(xld.__dict__) setattr(xld,'sb','sb') //第一个参数是对象,第二个参数相当于属性字典的key,也就是增加的属性的变量名,要写成字符串,第三个参数是值,也就是变量的值 #xld.sb = 'sb'//上面那个相当于这样写 print(xld.__dict__) 结果 {'name': '谢礼东'} {'name': '谢礼东', 'sb': 'sb'}
//delattr()函数
class sb: sb_attr = 'sb' def __init__(self, name): self.name = name def find_teacher(self): print('%s这个傻逼玩意就他妈会找老师.' % self.name) xld = sb('谢礼东') print(xld.__dict__) delattr(xld,'name') 相当于下面的那个 # del xld.name print(xld.__dict__)
结果
{'name': '谢礼东'}
{}
其实为啥要有反射机制呢?
因为有了反射机制就可以在大型的程序开发中每个程序员可以互不干扰的做自己的工作,比如说 alex 写的程序里需要用到 Bob 写的类,那是否意味着如果Bob没有将这个类写完,alex就无法写下面的代码呢?如果没有反射机制,原则上确实是无法进行,但是有反射机制的话,就可以用上面的4个方法配合 if else 语句来暂时跳过所需的这个类但又不影响下面写代码的工作,同时当Bob把这个类写好后,alex可以不做任何代码修改。举个例子吧。
#在 hello.py 文件的代码 class Test: def __init__(self,name): self.name = name def show_name(self): print(self.name) #在 5.py 文件的代码 from hello import Test t = Test('ttest') if hasattr(t,'show_name'): t.show_name() else: print('这个类没有这方法') if hasattr(t,'aaaaa'): //这里就是用了反射机制,如果没有这个方法,可以直接跳过先,不影响下面代码的书写,但如果一旦把这个方法被写好之后,可以不用修改现在写的代码,直接就调用这个方法,因为if条件 t.aaaaa() //会变成真
else: print('这个对象没有%s方法' % 'aaaaa') 结果 ttest 这个对象没有aaaaa方法
反射机制不单单可以用于检测从外面导入的模块,还可以检查自己目前正在写的模块,因为在一个大型的程序里,你会写很多很多的东西,比如你写到5000行时,你想用每个方法,但是你又不确定自己在上面有没有将这个方法实现,那按正常情况下是不是应该人工的从第1行一直看到5000行才可以检测究竟有没有实现这个方法,但这样的话效率是不是就低了,所以反射机制也可以用在自己目前写的这个模块里,但这需要先得到当前写的模块的对象!
a = set("asd") b = set("asd") import sys obj = sys.modules[__name__] //得到当前写的模块的对象 print(hasattr(obj,'aaaaa')) print(hasattr(obj,'b')) #结果 False True