一、面向对象的特性
1. 回顾封装
Python不依赖语言的特性去封装数据,而是通过遵循一定的数据属性或函数属性的命名来达到封装的效果。任何以单下划线开头的名字都应该是内部的、私有的。封装的意义在内部业务逻辑的数据隐藏。Python真正意义上的封装是用类的内外访问来区分的。并且它与Java的private属性有很大的区别,Python并没有强制性的拒绝外部类对私有属性的访问。换句话说,以单下划线或双下划线开头的私有属性仅仅只是一种约定。
(1)第一层封装: 使用以单下划线开头的私有属性。对象在调用时可直接使用“对象名 . 私有属性名”;
(2)第二层封装:使用以双下划线开头的私有属性。对象在调用时不能直接使用“对象名 . 私有属性名”。需要在双下划线开头的私有属性前加上“_类名”,如:“对象名 . _类名 . 私有属性名”。
(3)第三层封装:使用自定义的函数来提供接口。明确区分内外,实现内部业务逻辑的封装,并像java的setter和getter方法一样给出外部程序的使用自定义的接口函数,让该接口函数返回一个值私有的性。
2. Python的继承
Python的继承与C++的多继承非常相似,它的继承关系会被解释器解释为一个MRO列表,该MRO列表就是一个简单的所有基类的线性顺序列表。MRO列表的构造是通过一个C3线性化算法来实现的。Python和java一样,所有类的最终基类是Object。继承从某种角度上来讲是有害的,继承将类与类之间耦合到一块了。这大大的破坏了系统的封闭原则。继承真正有意义的是接口式的继承。Python提供的接口式的继承。Python 的继承顺序可由类使用__mro__查看。解释器查看顺序遵循以下三条规则:
(1)子类会先于父类被检查
(2)多个父类会根据他们在列表中的顺序进行检查
(3)如果对下一个类存在两个合法的选择,选择第一个父类
class Person: """ 描述人的类 """ def __init__(self, name, gender, age): self.name = name self.gender = gender self.age = age def printinfo(self): print("人类") class Student(Person): """ 学生的类 """ def __init__(self, name, gender, age, school): """ 使用super调用父类的构造函数时,参数中不能出现self,因为 super已经默认给出了self,如果此时加上self会报错。而报错 的原因是多了一个参数。使用super()调用父类的函数使程序更 加灵活。当然也可以使用以下耦合的调用模式。 Person.__init__(self, name, gender, age) """ super().__init__(name, gender, age) self.school = school def ptintinfo(self): print(self.name, self.gender, self.age, self.school) s = Student("Macky", "女", 18, "麻省理工") s.ptintinfo()
3. 多态性
Python的多态和java的多态一样。多态的概念指出了对象如何通过他们共有的属性及函数来访问,不需要考虑他们之间的具体的类。
class Person: def __init__(self, name, age): self.name = name self.age = age def behavior(self): if self.age >= 0 and self.age <= 18: print("%s是未成年人"%self.name) else: print("%s是成年人"%self.name) class Adult(Person): pass class Pupil(Person): pass #实现多态的简单例子 def func(obj): obj.behavior() #初始化Adult和Pupil adult = Adult("小明",3) pupil = Pupil("小华",20) #调用func函数 func(adult) func(pupil)
二、duck类型
python的duck类型,很多人把它称为反射。反射的概念是由Smith在1982年首次提出的,主要是指程序可以访问、检测和修改它本身状态或行为的一种能力(自省)。这一概念的提出很快引发了计算机科学领域关于应用反射性的研究。它首先被程序语言的设计领域所采用,并在Lisp和面向对象方面取得了成绩。反射展示了某对象在运行期间是如何取得自己的状态的。如果传一个对象给你,你可以查出他的所有能力,那是极好的。如果Python不支持某种形式的反射功能,dir和type内置函数,将很难工作。还有那些特殊的属性,像__dict__、__name__及__doc__。反射的好处在于它可以事先定义好接口,接口只有在被完成后才会真正执行,这实现了即插即用,这其实是一种“后期绑定”,你可以事先把主要的逻辑写好(只定义接口),然后后期再去实现接口的功能。python的反射比起java的反射更加灵活,用着用着就能体会到。
duck提供了四个非常重要的函数,这四个函数贼鸡儿六。
hasattr
判断指定对象中有没有一个name字符串对应的方法或属性,如果有可以继续使用getatt
getattr
获取对象的属性值或这函数的地址。当获取对象的属性值不存在时返回None,而函数不存在时报错。如果要想函数不存在时不报错能并且能通知我们函数不存在,恶可以使用default参数。该函数的功能与对象调用属性相同。如:getattr(x, 'y') <==> x.y
setattr
为对象x的属性y设置或修改一个值,当属性不存在时,会将setattr设置的不存在的属性向对象的字典中追加。如:setattr(x, y, v) <==> x.y =v
delattr
删除对象的属性。如:delattr(x, y) <==> del x.y
class Person: def __init__(self, name, gender, age): self.name = name self.gender = gender self.age = age def eating(self): print("在吃饭") def sleep(self): print("在睡觉") person = Person("Handy","女",18) #使用hasattr判断对象中是否存在属性name print(hasattr(person, "name")) #输出:True #使用getattr获取对象的属性值 print(getattr(person, "name")) #输出:Handy #使用getattr获取对象的函数地址 print(getattr(person, "eating"))#输出:eatting()的地址 #运行函数 getattr(person, "eating")() #输出:在吃饭 #使用getattr的default参数 print(getattr(person, "eatingfood", "函数不存在")) #输出:函数不存在 #使用setattr修改一个值 setattr(person, "name", "Lily") print(person.__dict__) #向对象的字典追加一个不存在的值 setattr(person, "ID", "001") print(person.__dict__) #增加函数1 setattr(person, "func", lambda x : x+1) print(person.func) print(person.func(10)) #增加函数2 setattr(person, "func", lambda self : self.name+"是可以这样做的") print(person.func) print(person.func(person)) #使用delattr删除对象的属性 delattr(person, "ID") print(person.__dict__)
三、类的重要属性
在类中包含着很多默认的内置属性,当我们创建一个类时,如果有必要,我们可以进行重载。Python为我们提供了标准数据类型,以及丰富的内置函数,其实在很多场景下我们都需要基于标准数据类型来定制我们自己的数据类型,新增或者改写方法,这就用到了继承/派生知识。
attr系列
_ _getattr_ _ 属性不存在时自动触发
_ _setattr_ _ 设置属性时自动触发
_ _delattr_ _ 删除属性时自动触发
_ _getattribute_ _ 程序调用时(无论属性存在与否),当它使用raise关键字抛出AttributeError异常时,由_ _getattr_ _捕捉处理
class Test: def __init__(self, book_name): self.book_name = book_name #属性不存在时会自动触发 def __getattr__(self, item): print("访问的属性不存在时执行的操作") #初始化时会执行 def __setattr__(self, key, value): if type(value) is str: #value先转换一下在设置,这样比较好玩 self.__dict__[key] = value.upper() print("已经设置") else: print("参数必须是str") #删除属性时会自动触发 def __delattr__(self, item): print("执行删除属性【%s】的操作"%item) #测试 test = Test("从你的世界路过") #输出:已经设置 print(test.__dict__) #输出:{'book_name': '从你的世界路过'} del test.book_name #输出:执行删除属性【book_name】的操作 print(test.__dict__) #输出:{'book_name': '从你的世界路过'}
__del__
当对象在内存中被释放时,自动触发执行。如果产生的对象仅仅只是python程序级的(用户级),那么无需定义_ _del_ _,如果产生的对象的同时还会向操作系统发起系统调用,即一个对象有用户级与内核级两种资源,比如(打开一个文件,创建一个数据库链接),则必须在清除对象的同时回收系统资源,这就用到了_ _del_ _。需要注意的是,我们删除实例的属性时是不会的执行_ _del_ _的,只有在删除实例时才会触发_ _del_ _。系统在回收内存时也会触发它。
比如当我们创建数据库类时,用该类实例化出数据库链接对象,对象本身是存放于用户空间内存中,而链接则是由操作系统管理的,存放于内核空间内存中。当程序结束时,python只会回收自己的内存空间,即用户态内存,而操作系统的资源则没有被回收,这就需要我们重载del,在对象被删除前向操作系统发起关闭数据库链接的系统调用,回收资源。
class Person: def __init__(self, name): self.name = name def __del__(self): print("执行删除实例的操作") #测试 person = Person("SB") del person.name #不执行 del person #执行删除实例的操作
__slots__
Python为每一个实例提供了一个独自的字典。我们常用的“.”运算符在底层其实操作的是字典。__slots__对所有的实例对象取消了字典,实例使用一种更加紧凑的内部表示,通过一个很小的固定大小的数组来构建,而不是为每个实例定义一个字典,这跟元组或列表很类似。类变量列出的属性名在内部被映射到这个数组的小标上。它限制了实例对象的访问操作,当访问的属性不存在时,会直接报错。
当我们一个类有很多的实例时,每一个实例都会有一个单独的内存空间,如果使用__slots__确实是能够减少资源空间的占用,但是python很多特性都依赖于普通的基于字典的实现。而使用类变量的弊端是不再支持基于字典的特性,比如多继承等等。
class Person: __slots__ = ["name","age"] #测试 person = Person() print(Person.__slots__) #输出:['name', 'age'] person.name = "小明" person.age = 18 print(person.name) #输出:小明 print(person.age) #输出:18 #输出:AttributeError: 'Person' object has no attribute'__dict__' print(person.__dict__)
__format__
__format__内置属性是一种自定义的格式化。通常 Python自带的format格式化通常无法满足我们的需求,因此我们需要对其进行修改。
#定义格式字典 format_dic={ 'ymd':'{0.year}{0.mon}{0.day}', 'm-d-y':'{0.mon}-{0.day}-{0.year}', 'y:m:d':'{0.year}:{0.mon}:{0.day}' } class Date_format: def __init__(self,year,mon,day): self.year=year self.mon=mon self.day=day def __format__(self, format_spec): if not format_spec or format_spec not in format_dic: format_spec='ymd' fm=format_dic[format_spec] return fm.format(self) #测试 date = Date_format(2016,12,26) print(format(date,'ymd')) #输出:20161226 print(format(date,'y:m:d')) #输出:2016:12:26 print(format(date,'m-d-y')) #输出:12-26-2016 print(format(date,'m-d:y')) #输出:20161226
__call__
我们知道构造函数的执行是由创建实例对象触发的,即:对象 = 类名()。Python中一切皆对象,我们创建的类本身也是一个对象。内置属性__call__的执行是由于对象创建后被作为函数使用而触发的。重载__call__的意义在于将对象作为函数使用,__call__不影响对象的生命周期,不影响一个对象的构造和析构。
class Test: def __init__(self, name): self.name = name print("执行__init__") def __call__(self, *args, **kwargs): print("执行__call__") #测试 test = Test("SB") #输出:执行__init__ test() #输出:执行__call__
__next__ 和 __iter__
在迭代器中我们说实现了迭代器协议的对象称为可迭代对象。而迭代器协议强调对象必须提供一个next或_ _next_ _()方法,并且执行该方法只有两种决策,要么返回迭代中的下一项,要么者引起一个StopIteration异常,以终止迭代。其实这个协议也不过是通过内置属性实现的。我们知道一个普通的对象是无法对它进行遍历的,但是Python提供了_ _next_ _和_ _iter_ _这两个内置属性供我们重载对象。通过这两个内置属性的使用使得普通对象可变为可迭代对象。而迭代器的实现也基于此。
class Test: def __init__(self, start, stop): self.start = start self.stop = stop def __iter__(self): return self def __next__(self): if self.start >= self.stop: raise StopIteration("索引越界") num = self.start self.start += 1 return num #测试 test = Test(1,10) for i in test: #遍历对象 print(i, end=' ') ######################################################################################################## #斐波那契数 class Fib: def __init__(self): self._a=0 self._b=1 def __iter__(self): return self def __next__(self): self._a,self._b=self._b,self._a + self._b return self._a #测试 f1=Fib() for i in f1: if i > 100: break print('%s ' %i,end='')
__str__ 和 __repr__
当我们使用str函数并以对象作为参数的时候,返回的是一个对象的地址及所属类。它的默认执行路径是:str( obj ) ==> obj._ _str_ _()。str 与 repr 都用于控制输出,当他们在程序中共存时,repr不会执行;当程序中只有repr时,虽然它被用于解释器,但是在控制台会执行,因为str找不到,所以它是str的替代品。
class Person: #系统默认输出对象的地址及所属类 def __str__(self): return "修改系统默认的内置属性" #测试 person = Person() print(person) #输出:修改系统默认的内置属性 ########################################################################################### class Student: def __init__(self, name, age): self.name = name self.age = age def __str__(self): return "名字是%s,年龄是%s"%(self.name, self.age) def __repr__(self): return "执行__repr__" stu = Student("小明",18) print(stu) #输出:名字是小明,年龄是18