一.面向对象编程的三大特性
(一)继承
1.继承:子类继承父类的所有属性,若子类中有与父类属性相同的,由于作用域的影响,当调用该属性时会得到子类的该属性值。
用一句话总结继承:给了类(子类)一个特权,能去别的类(父类)的属性字典里找其属性。
2.继承分为单继承和多继承。
class Foo: 单继承 name = "cwt" class Bar(Foo): age = 20 print(Bar.name)>>>>cwt
class Foo: name = "cwt
class Bar: 多继承 age = 20 class People(Foo,Bar): gender = "male" print(People.name) >>>>cwt print(People.age) >>>>20
3.继承的含义:
(1)继承基类的方法,且做出自己的改变或扩展(减少代码重用)。即把有相同属性的属性整合,做成一个基类。
class Animal: def eat: print("需要吃东西") def sleep: print("需要睡觉") class Cat(Animal): def feature: print("我是猫,我会抓老鼠") class Bid(Animal): def feature: print("我是鸟,我会飞")
(2)声明某个子类兼容于某基类,定义一个接口类,子类继承接口类,并且实现。也称为借口继承。
例子:在python中,文件的共有属性就是都可读可写,所以定义下面的基类来声明,每个都要有读和写的功能
class All_file: def read(self): pass def write(self): pass
上面的例子并不能达到声明的功能,当你没有读功能或写功能时,并不会有什么反应。所以完善如下图
class All_file(metaclass=abc.ABCMeta): @abc.abstractmethod def read(self): pass @abc.abstractmethod def write(self): pass class Disk(All_file): def read(self): pass d1 = Disk() 》》》》》》报错(由于所创建的类中少了一个write功能,所以实例化时就会报错) class Disk(All_file): def read(self): pass def write(self): pass d1 = Disk() 》》》》》正常运行
上面这种接口继承也称为归一化设计。这种才是继承的主要用处。
4.继承顺序:
(1.)深度优先:经典类都用该方法
(2.)广度优先:新式类都用该方法
在python3中d都是用这种方法,且可以调用 类名.__mor__的方法查看继承顺。
总结:在python2中父类是什么类型的,子类就是什么类型。
5。在子类中调用父类属性的方法
(1)就是在父类的属性字典里找。(这不是一种好的方法,一般别用)
class People: def __init__(self,name,age): self.name = name self.age = age def run(self): pass class Foo(People): def __init__(self, name, age, gender ): self.name = name self.age = age self.gender = gender 子类中__init__中有几个是跟父类一样的,这样就是在写重复代码。 所以可以应用在子类中调用父类的方法 class Foo(People): def __init__(self, name, age, gender): People.__init__(self,name,age) self.gender = gender
(2)用super来调用父类的属性。
class People: def __init__(self,name,age): self.name = name self.age = age def run(self): pass class Foo(People): def __init__(self, name, age, gender): super().__init__(name, age) self.gender = gender
(二).多态
1.含义:对象通过他们共同的属性和动作来操作及访问,而不需要考虑他们具体的类
2.多态是一种绑定关系,只有运行时才成为多态。
class People: def __init__(self,name,age): self.name = name self.age = age def run(self): pass class L(People): pass class K(People): pass l1 = L("cwt",20) k1 = K("ccc",18) l1.run() 》》》》 k1.run() 》》》》这两步操作就称为多态(两个来
(三)封装:
1.含义:明确区分内外,内部实现逻辑,外部只能调用其方法,无法访问其内部逻辑。
2.第一种简单意义的封装:如在执行文件中调用另一个文件中定义的类(People),那么你只能调用到People中的方法,但你不知道People这个类中的逻辑,这就是简单意的封装。
3.python中的两种约定:
(1)以 _xxx 命名的属性,使用者不应该去调用(这只是一种约定,看你自己尊不遵守)
(2)以 _ _xxx 命名的属性,内部可以调用,但使用者无法调用到该属性。当是,这并不是python不让你调用,本质上是python的内在系统帮你把这个重命名了。 自动帮你改成 _类名_ _ xxx 。
class Foo: def __init__(self,x): self.__x = x def tell(self)
print(__x)
f1 = Foo(1) print(f1.__x) 》》》》报错 print(f1._Foo__x) >>>>>1
tell() >>>>>>1 内部能调用
4.真正意义的封装:把你不想别人知道的逻辑封装起来,但后期若是很多人需要用,你需要设接口,让使用者能调用到该东西。
class Foo: def __init__(self,x): self.__x = x def tell(self): print(__x) tell函数就是一个接口,本质上是利用在 内部能调用到
二。其它小知识点:
1.反射(自省):指程序可以访问,检测和修改它本身状态或行为的一种能力。有以attr4种方法
(1)hasattr(object, name) >>检测object中是否含有name属性
(2)getattr(object,name,default = None) >>访问object的属性name,有该属性就会返回其值,若没有则会报错,但当你为default赋值时,没有就会输出default的值。
(3)setattr(object,x,y) >>为object添加属性 "x" = "y"
(4)delattr(object,x) >>删除object中的属性x
用途:当在写一个软件时,不同功能由不同的人写的,当你 需要用到别人写的功能时,你并不知道对方代码是否都写好了,这是你就可以用反射。
文件名:test.py class Room: def __init__(self,width,leight,height): self.width = width self.leight = leight self.height = height def area(self): room_area = self.width * self.leight return room_area
from test.py import Roomdef volume(height): r1 = Room(20,20) if hasattr(Room, "area"): room_volume = height * r1.area() return room_volume else: setattr(r1,"new_area", lambda width,leight:width *leight) room_volume = height * r1.new_area(20,20) return room_volume
.补充:
1.导入动态模块:有时,我们得到的模块路径是一个字符串,所以不能直接用from "xxxx" import xxx 这样只会报错
(1)__import__("文件名")
(2)用模块来解决
模块路径:kk.t import importlib m = importlib.import_module("kk.t") m.test()
2.如何把当前文件当作模块导入当前文件中:
import sys obj = sys.modules[__name__]
2.类的内置 attr 方法(注意:类的内置方法自己不设置时,是由默认的,当你设置后,就会按你自己设置的输出)
(1)_getattr_ ,访问的属性不存在时就会触发该函数
(2)_setattr_,设置属性时就会触发该函数,如实例化会触发__init__,__init__函数下有如 self.name = name的就会触发__serattr__
(3)_delattr_,删除属性时就会触发该函数。
这3个内置函数,与类都无关,只与实例有关:
class Foo: x = 1 y = 2 def __setattr__(self, key, value): print("触发setattr") def __getattr__(self, item): print("触发getattr") def __delattr__(self, item): print("触发delattr") print(Foo.x) 不会触发__getattr__ Foo.z = 3 不会触发__setattr__ del Foo.y 不会触发__delattr__
class Foo: x = 1 y = 2 def __init__(self,name): self.name = name def __setattr__(self, key, value): print("触发setattr") def __getattr__(self, item): print("触发getattr") def __delattr__(self, item): print("触发delattr") f1 = Foo("cwt") 》》》。触发__setattr__ f1.z = 3 》》》》触发__setattr__ del f1.y 》》》》触发_delattr__ print(f1.age) >>>由于f1中没有该属性所以触发__getattr__
如果在类中自己写了这3个函数,而没有写东西的话,实例后,实例的属性字典是空的。简单模拟系统的默认操作
class Foo: def __init__(self,name): self.name = name def __setattr__(self, key, value): self.__dict__ [key] = value def __getattr__(self, item): print("没有该属性") def __delattr__(self, item): self.__dict__ .pop(item) 只能用字典的操作来进行操作,如果用点的方式来进行操作,会进入死循环。 因为用点的方法总会触发_setattr_
这几个方法可以用来对属性的格式进行设置。如不让使用者删除:_delattr_函数下什么都不写,只写个提示
def __delattr__(self, item): print("不可删除")
3.item系列的方法
(1)_getitem_ 访问属性时,无论属性是否存在,都会触发该函数
(2)_setitem_ 创建属性时触发
(3)_delitem_ 删除属性时触发
以上3种方法的操作都必须是用字典的操作方法才会触发
class Foo: def __init__(self,name): self.name =name def __getitem__(self, item): print("触发getitem") def __setitem__(self, key, value): print("触发setitem") def __delitem__(self, key): print("触发delitem") f1 = Foo("cwt") f1["name"] 》》》触发_getitem__ f1["age"]=20 》》》触发__setitem__ f1["name"] 》》》触发__delitem__
4.包装标准类型:可以定制自己需要的数据类型
(1)包装的一个特性,继承。
class List(list): def append(self, val): print("不可对该列表进行增值") l1 = List("abc") print(l1) 》》》》["a","b","c"] l1.append(2) >>>>不可对该列表进行增值
上述代码,定义了一个不能添加元素的列表类,其它功能与原来一模一样。
(2)包装的另一特性:授权
import time class Open: def __init__(self, file_name, mode="r", encoding="utf8"): self.file = open(file_name, mode=mode, encoding=encoding) self.mode = mode self.encoding = encoding def __getattr__(self, item): return getattr(self.file, item) def write(self,line): t = time.strftime("%Y:%m:%d %X") self.file.write("%s %s" % (t, line)) f = Open("test", "r", encoding="utf8") f.write("hello world ") f.write("hello world") print(f.read())
授权简单来说其实 就是,把本来就有的类实例化后,作为一个属性值,赋给某个属性。如上图中,把open实例化后,赋给file,这个file就有原来open这个类所有的功能。
5.isinstence(obj,cls)和issubclass(sub,super)
(1)isinstence 用来判断obl是否是类cls的实例化
(2)issubclass 用来判断sub 是否是super的子类
6.__getattribute__和__getattr__
(1)__getattribute__ 无论属性是否存在都会触发该函数执行。
(2)__getattr__ 当访问的属性不存在时会触发该函数。
class Foo: def __init__(self,name): self.name = name def __getattribute__(self, item): print("getattribute") def __getattr__(self, item): print("getattr") p1 = Foo("cwt") p1.name 》》》》》getattribute p1.age >>>>>>>getattribute 可见若两个函数同时存在,则永远只会触发__getattribute__
class Foo: def __init__(self, name, age): self.name = name self.age = age def __getattribute__(self, item): print("getattribute") p1.age >>>>属性不存在的话会报错,若该操作以下还有代码得话则不会执行了
.
. 这些代码都不会执行了。所以,一般 __getattribute__和__getattr__都是一起用的,但同时存在不是不会执行__getattr__吗?请往下继续看
.
class Foo: def __init__(self, name, age): self.name = name self.age = age def __getattribute__(self, item): print("getattribute") raise AttributeError ("属性不存在") def __getattr__(self, item): print("getattr") p1 = Foo("cwt",20)
》》》》getattribute 》》》》属性不存在
》》》》getattr
p1.age >>>首先触发__getattribute__,由于该属性不存在,所以会执行__getattribute_函数下的raise,实行了raise后,raise会传给__getattr__一个执行的指示。所以触发__getattr__
7.改变对象的字符串显示
(1.)假如类Foo实例化后得到实例f1,print(f1)>>>会得到<__main__.Foo object at 0x02C6EDD0> 得到的这个就是对象的字符串显示。
若你不设置,系统就会以默认值输出即类似于<__main__.Foo object at 0x02C6EDD0>。
(2)两个方法
①__str__:在pycharm等的环境下使用的。
②__repr__:这个是在python的解释器中应用的,但如果不是再解释器的环境下设置该属性时,你只写了__repr__那么系统会自动以这个来代替__repr__.若两个都有,那么只会调用__str__
class Foo: def __str__(self): return "我是一个实例" def __repr__(self): return "我是一个对象" f1 = Foo() print(f1) >>>我是一个实例
class Foo: def __repr__(self): return "我是一个对象" f1 = Foo() print(f1) >>>我是一个对像
8.format()本质上是在调用类中的__format__函数属性。即自己不设置,系统会以默认形式输出。
(1)自定制格式化(format方法)
class Data: def __init__(self,year,mon,day): self.year = year self.mon = mon self.day = day def __format__(self, form): form_dict = {"ymd":"{0.year}{0.mon}{0.day}", "y:m:d":"{0.year}:{0.mon}:{0.day}", "y-m-d":"{0.year}-{0.mon}-{0.day}"} if not form or form not in form_dict: form = "ymd" f = form_dict[form] return f.format(self) d1 = Data(2019,8,15) print(d1.__format__("y:m:d"))
9._slots_属性,该属性为一个类变量。
类的属性字典是共享的,不过实例的属性字典是独享的,即你每创建一个实例,就会生成一个实例的属性字典。
假如有一个只有一两个属性的类,不过需要实例很多次,那么就会生成很多属性字典,就会占用很多内存,这时可以用_slots_来代替属性字典
即不设__init__函数,因为这个本质上就是在创建属性字典。而改成_slots_
class Foo: __slots__ = "name" f1=Foo() 》》》实例化后并不会产生属性字典,而是于类公用一个__slots__ f1.name = "cwt"
print(f1.name)
print(f1.__dict__)>>>会报错,没有这个
>>>cwt
有多个属性可以用列表存放,且实例过程,只能设跟类的__slots__里有的属性
class Foo: __slots__ = ["name","age"] f1=Foo() f1.name = "cwt" f1.age=20
f1.gender="male >>>>>报错
10.其它内置函数
(1)__del__,也称为析构方法。
作用:当对象在内存中被释放时,会自动触发该函数。(即当代码全部读完后,就会触发该函数)
class Foo: def __del__(self): print("清除完毕") f1=Foo() print("123") 》》》123 》》》清除完毕
(2)__doc__属性:查看类的文档字符串。
class Foo: "我是一个类" def __init__(self,x): self.x = x f1=Foo(1) print(f1.__doc__ ) 》》》我是一个类
注意:该属性不能被继承,因为每定义一个新类,系统会自动将__doc__=None设置进属性字典里
class Bar: "我是一个类" pass class Foo(Bar): def __init__(self,x): self.x = x f1=Foo(1) print(f1.__doc__ ) >>>None
(3)__call__属性:对象后加个()就会触发该函数。
class Foo: def __init__(self,x): self.x = x def __call__(self, *args, **kwargs): print("我执行了") f1 = Foo(1) f1() 》》我执行了
11.迭代器协议
(1)迭代器协议是由类中的__iter__和__next__构成的
(2)执行next()操作时,本质上就是触发__next__
(3)解释for循环:我们知道数据类型如 list 本身并不是可迭代对象,但却能进行for循环。原理:
for i in f1 ①f1 变成 iter(f1) 然后触发__iter__,将 f1 变成可迭代对象 ②自动触发next()进而触发__next__
例子
class Feibo: def __init__(self): self._a = 1 self._b = 1 def __iter__(self): return self def __next__(self): # 1 1 2 3 5 self._a, self._b = self._b, self._a + self._b return self._a f1 = Feibo() print(f1.__next__()) 1 print(f1.__next__()) 2 print(f1.__next__()) 3 print(f1.__next__()) 5 print(f1.__next__()) 8
12.描述符
(1)含义:就是一个新式类,这个类至少要实现__get__ , __set__ , _delete__中的一个。
(2)数据描述符:实现了__set__和__get__ 的称为数据描述符,其它称为非数据描述符。
(3)基本的形式及参数
class Foo: def __get__(self, instance, owner): pass def __set__(self, instance, value): pass def __delete__(self, instance): pass class Bar: x = Foo() def __init__(self,n): self.x = n b1 = Bar(2) Foo即为一个描述符 instance = b1 owner = Bar self = x
class Foo: def __init__(self,key): self.key = key def __get__(self, instance, owner): return instance.__dict__[key] def __set__(self, instance, value): instance.__dict__ [self.key] = value def __delete__(self, instance): return instance.__dict__.pop(self.key) class Bar: x = Foo("name") def __init__(self,n): self.x = n b1 = Bar("cwt") print(b1.name) >>>>触发__get__ b1.age = 20 >>>>触发__set__ del b1.age >>>>触发__delete__
(4)优先级
类属性 > 数据描述符
数据描述符 > 实例属性
实例属性 > 非数据描述符
13.类的装饰器。与函数的装饰器是一样的。
def Foo(obj) : obj.x=1 obj.y=2 return obj @Foo class Bar: pass
14.上下文管理协议:
(1)含义:能符合with的操作 如 with open ("file_name") as f
(2)方法:__enter__(self) 和__exit__(self, exc_type,exc_val,exc_tb)
class Open: def __init__(self,name): self.name = name def __enter__(self): print("进入") return self def __exit__(self, exc_type, exc_val, exc_tb): print("已经退出") with Open("file") as f: ①左边的 with Open 会触发__enter__并把返回值传给f print(123) ②当with 语句下的代码都执行完了之后就会自动触发__exit__ print(222) >>> 进入 123 222 已经退出
class Open: def __init__(self,name): self.name = name def __enter__(self): print("进入") return self def __exit__(self, exc_type, exc_val, exc_tb): print("已经退出") with Open("file") as f: print(123) print(x) 当with以下的代码有发生错误,则会报错并执行__exit__ print(1111) 从错误的代码块开始,以下的都不会执行
print(33333) >>> 进入 Traceback (most recent call last): 123 已经退出 File "E:/PycharmProjects/untitled/untitled2/study-python/pracrer.py", line 259, in <module> print(x) NameError: name 'x' is not defined
class Open: def __init__(self,name): self.name = name def __enter__(self): print("进入") return self def __exit__(self, exc_type, exc_val, exc_tb): print("已经退出") return True》》》》》》》》》》》》》》》》》》》》加上这一步,当with中的代码发生错误时,自动执行__exit__,但不会报错,所以不会影响with之外的外码 》》》》》》》》》 with Open("file") as f: print(123) print(x) print(1111) print(123) 》》》 进入 123 已经退出 123