1.面向对象
#问题:
①写代码时,什么时候用面向对象:处理比较复杂的角色之间的关系(代码量大,功能多的时候),如复杂的电商程序、公司/学校的人事管理的系统;提高代码的可读性,清晰度(无论是开发者还是调用者,都能明确的分辨出每个角色拥有的方法和属性);增强代码的可扩展性;增加了复用性;代码更加规范
②如何理解Python中一切皆对象:每一个基础数据类型都是对象,有属于自己的类,每个类特有的方法,只能属于本类的对象去使用
③类型和自定义类的关系:类型和类是一个东西,自定义的每一个类都是一个新的数据类型;当type(obj)时,如果obj是一个对象,那么它的type就是它的类型
④创建一个类:class 类名;python解释器读到这句话是创建类(语法级别);类也是被创建出来的,通过type创建类,即类型的类型就是类型,默认为【class A(metaclass=type)】,可以说type就是这个类的元类
class Student: pass s = Student() print(type(s)) #对象的类型是类 print(type(Student)) #类的类型就是type 输出: <class '__main__.Student'> <class 'type'>
#对于抽象类【class A(metaclass=ABCMeta)】可以说ABCMeta创建了这个A类,ABCMeta就是A的元类
from abc import ABCMeta class A(metaclass=ABCMeta):pass print(A) 输出: <class '__main__.A'>
总结:type是所有类的元类,object是所有类的父类;元类是用来创建类的
⑤创建一个对象:类名()叫做实例化;__new__()创造了一个对象的空间;__init__进行一些简单的初始化工作
1)类:class Leiming
①类是什么时候被记载的
#类是从头到尾加载到内存中的(所以类的执行和有无实例化没有关系),如果类中有函数,只加载函数名,函数中的内容,只有在调用函数时才执行
class A: name = "阿狸" print(name) def name(self): print("函数执行") 输出: 阿狸
②类名是什么时候生效的
#从头开始加载类,当把类中所有的变量加载完成后,才让类名去执行这个对象空间,所以在类中,使用类名去调用变量会报错
class A: name = "阿狸" print(A.name) #语法报错,只有在类中所以内容加载完成,才让类名执行这个内存空间
注:静态属性/静态字段/静态变量、动态属性/动态方法是存在类空间中的,不在对象空间中存,在对象空间中只从这些变量和方法的一个地址,需要时去类空间查找(即对象空间存的不是func的内存地址,而是存着func内存地址的一个变量的地址)
class A: name = "阿狸" def func(self):pass a = A() print(a.func) print(A.func) 输出:(两个地址不一样) <bound method A.func of <__main__.A object at 0x000001824E7B5EB8>> <function A.func at 0x0000018257E2B048>
2)对象
①可以通过指针找到类的空间中的内容
②对象本身内部也存储了一些只属于对象的属性(只存属性,不存方法)
③类创造对象的过程就是实例化的过程:构造为__new__;初始化为__init__
3)组合:一个类的对象作为哦另一个类对象的属性(即什么有什么的关系,如人有武器)
4)继承:主要为了节省代码;继承表示什么是什么的关系
①单继承和多继承
#单继承:如果子类的对象调用调用某个方法,如果子类有,就调用子类的,如果子类没有,就调用父类的,如果还没有就报错;如果子类中有但是想要调用父类的,那么可以使用两种方法:
super: 不用自己传self 格式:super(子类,self).方法名(除了self之外的参数)
父类名:需要传self 格式:父类名.方法名(self,…)
注:在任何类中调用的方法,都要仔细分辨一下这个self到底是谁的对象
class Father: def __init__(self): #self接受到的还是子类的对象,所以执行子类的func self.func() def func(self): print("In the Father...") class Son(Father): def func(self): print("In the son...") s = Son() 输出: In the son...
#多继承
新式类:默认继承object类,所以py3都是新式类;查找顺序遵循广度优先;可通过mro()方法查看继承顺序;在类的内部不用传子类名和self,如下super().func()
经典类:没有继承object的类;查找顺序遵循深度优先;没有mro()方法;需要类名和self,如下super(子类名,self).func()
②抽象类和接口类的区别
在python中接口类和接口类并没有特别大的区别,因为python是多继承的娥,而对于java而言,它是单继承的,只有抽象类中的方法,可以实现;但是接口中的所有方法只能写pass,接口支持多继承
抽象类:抽象类中的方法是可以实现的,但是只能单继承
接口类:可以单继承,但是这个类中的所有方法都不因该实现
因为python是多继承的,所以怎么简单怎么写,没必要遵循上述要求
5)封装
①广义的封装:把方法和属性都封装在一个类型中,定义一个规范来描述一类事物
②狭义的封装:私有化,只能在类的内部进行访问;__静态变量、私有方法、私有对象属性、私有的类方法、私有的静态方法;
#在内存中通过【_类名__名字】形式存储;
#在内的内部使用,就知道自己在哪个类中,所有在类的内部可以使用双下划线访问
#在子类中不可以访问父类的私有变量;对于私有变量和方法,不能再类的外部使用也不能被继承
4)多态
①鸭子类型:所以带同一方法的类都可以叫做鸭子类型;规范全凭自觉
②python中处处是多态
#多态:一个类型的多种形态,如每个子类是父类的一种形态,一个父类被多个子类去继承,就是一种多态
5)property
①property是一个装饰器函数,通过property可以将类中的方法伪装成属性/特性
②通过property将方法伪装成属性,可以使得程序逻辑性更加合理
③修改被property装饰的属性会调用这个装饰器的方法【@方法名.setter】,除了self外海有另一个参数:被修改的值
④删除被property装饰的属性会调用这个装饰器装饰的的方法【@方法名.deleter】,本质上属性是不能够被删除的,因为对象无法操作类中的静态属性(只是执行类装饰器装饰的方法)
class A: @property def name(self): return "ali" @name.deleter def name(self): print("执行被@name.delerer装饰的方法") a = A() del a.name #本质上对象不能删除静态属性,所以只是一个语法而已,没有实际意义 print(a.name) 输出: 执行被@name.delerer装饰的方法 ali
#可营造一个假象,高速用户把属性删除了(是个障眼法),这种方式比较偏其他语言,一般是将property和私有方法合用,这个时候更多的也会用到setter和deleter
class A: def __init__(self,name): self.__name = name @property def name(self): return self.__name @name.deleter def name(self): del self.__name a = A("阿狸") print(a.name) #阿狸 del a.name print(a.name) #AttributeError: 'A' object has no attribute '_A__name'
#只用property:一个方法的计算结果本身就是一个属性,但是这个属性会随着这个类/对象的基础变量的变化而变化,此时可以将这个方法设置为属性
class Circle: def __init__(self,r): self.r = r @property def area(self): return 3.14*self.r*self.r a = Circle(5) print(a.area) 输出: 78.5
#对于私有属性可以更改,但是可以对更改的值做约束,一是可以在类的内部再定义一个方法对私有属性进行更改,二是可以通过被装饰器【@方法名.setter】修饰的方法内部做约束
class A: def __init__(self,age): self.__age = age @property def name(self): return self.__age @name.deleter def name(self): del self.__age @name.setter def name(self,new_age): if type(new_age) is int: self.__age = new_age a = A(18) print(a.name) a.name = 20 #调用方法给属性改值 print(a.name) 输出: 18 20
6)classmethod
〇classmethod类方法的装饰器(内置函数)
①类方法,使用类名调用,默认传类名作为第一个参数
②不用对象命名空间中的内容,而用到了类命名空间中的变量(静态属性),或者类方法和静态方法,这种场景就用类方法
#例:一个商场的购物程序(第一版,有问题)
class Goods: __discount = 0.8 #活动促销前,打折率 def __init__(self,price): self.__price = price @property def price(self): return self.__price*self.__discount def change_discount(self,num): #此处相当于给对象空间添加了一个__discount新属性,而不是改变了类中的__discount私有静态属性 self.__discount = num apple = Goods(10) banana = Goods(8) print(apple.price,banana.price) #打折价格 apple.change_discount(1) #改变折扣,只改变的是apple的,而banana的没有变化 print(apple.price,banana.price) #打折后的价格 输出: 8.0 6.4 10 6.4
#若要以整个类的折扣为准,此时可以将对象调用私有静态属性改为以类名调用
class Goods: __discount = 0.8 #活动促销前,打折率 def __init__(self,price): self.__price = price @property def price(self): return self.__price*Goods.__discount def change_discount(self,num): #此处相当于给对象空间添加了一个__discount新属性,而不是改变了类中的__discount私有静态属性 Goods.__discount = num apple = Goods(10) banana = Goods(8) print(apple.price,banana.price) #打折价格 apple.change_discount(1) #改变折扣,只改变的是apple的,而banana的没有变化 print(apple.price,banana.price) #打折后的价格 输出: 8.0 6.4 10 8
#对于以上程序,虽然实现了功能,但是恢复折扣时,是以apple对象调用的改变所有的商品的折扣,而这个对象也没有涉及到所有和apple对象操作相关的类,所以此时可以将change_discoun方法设置为静态方法,此时不需要任何实例化,就可以对商品中的所有商品进行折扣
class Goods: __discount = 0.8 #活动促销前,打折率 def __init__(self,price): self.__price = price @property def price(self): return self.__price*Goods.__discount @classmethod def change_discount(cls,num): #此处相当于给对象空间添加了一个__discount新属性,而不是改变了类中的__discount私有静态属性 cls.__discount = num apple = Goods(10) banana = Goods(8) print(apple.price,banana.price) Goods.change_discount(1) #静态方法,通过类名调用 print(apple.price,banana.price) 输出: 8.0 6.4 10 8
#对于一个学生管理系统,假如有school 类、学生、老师、课程、班级四个类,对于登录这个方法来说,只有学生类、老师类(两个类基础school类)才会调用,此时可以将登录的方法写入schoole类,并设置为静态方法
7)staticmethod
①如果一个类中的方法,既不需要用到self中的资源,也不用cls中的资源,相当于一个普通的函数,但是由于某种原因,还要把这个方法放到类中,这个时候,就可以将这个方法写成一个静态方法
②某种原因有:完全想用面向对象编程,所有的函数都必须写到类中;某个功能确实是这个类的方法,但是却没有用到和这个类有关系的任何资源
8)反射
①从某个指定的命名空间中,用字符串数据类型的变量名来获取变量的值
②反射的分类
类名反射:静态属性、类方法、静态方法
对象反射:对象属性、方法
模块:反射模块中的方法
自己模块:反射自己模块
③方法:hasattr()、getattr()、setattr()、delattr()
④使用:使用反射时,变量名必须是拿到一个字符串的版本,可以从文件中、用户交互、网络传输中获取到字符串
2.抽象类和接口类
1)作用:规范子类当作必须实现某个方法
2)抽象类和接口类不能被实例化
3)python中有原生的实现抽象类的方法,但是没有原生实现接口类的方法
4)抽象类和接口类的区别
在python中接口类和接口类并没有特别大的区别,因为python是多继承的娥,而对于java而言,它是单继承的,只有抽象类中的方法,可以实现;但是接口中的所有方法只能写pass,接口支持多继承
①抽象类:抽象类中的方法是可以实现的,但是只能单继承
②接口类:可以单继承,但是这个类中的所有方法都不因该实现
因为python是多继承的,所以怎么简单怎么写,没必要遵循上述要求
3.内置方法(魔术方法/双下方法)
1)格式:__名字__
2)一般由内置函数、面向对象的特殊语法、python提高的语法糖调用
#所有的简单符号实现(加减乘除等),都对应了左右两个数据类型对应的一个方法,加:__add__ 减:__sub__ 乘:__mul__ 除:__div__
class Mytype: def __init__(self,s): self.s =s def __add__(self, other): return self.s.count('*') + other.s.count('*') obj1 = Mytype("ajdio****dnao") obj2 = Mytype("***sadi***") print(obj1.__add__(obj2)) print(obj1+obj2) 输出: 10 10
3)
①__str__ str(obj),要求obj必须实现__str__,要求这个方法的返回值必须是字符串
②__call__ 对象() 要求使用装饰器
③__len__ len(obj),要求obj必须实现了__len__方法;要求这个方法的返回值必须是数字
④__new__ 在实例化过程中,最先执行的方法,在执行init之前,用来创造一个对象
⑤__init__ 在实例化的过程中,在执行new方法之后,自动触发的一个初始化方法
4.__repr__方法
1)
引:对于str()方法,可以将数字转换为字符串,而对于repr()可以将字符串原形毕露
n = 123 n_new = str(n) s ="123" print(n_new) print(type(n_new)) print(repr(s)) 输出: 123 <class 'str'> '123'
repr()方法的内部就是通过__repr__实现
补充:格式化输入%s和%r的区别:%r是带引号输出
print("----%s----" % ("abc")) print("----%r----" % ("abc")) 输出: ----abc---- ----'abc'----
2)__repr__对应得是repr(obj)这个方法和%r这个用法;__repr__是__str__的备胎,如果有__str__方法,那么print %s 、str()等操作都去执行__str__方法,并且使用__str__的返回值;如果没有__str__,那么print %s 、str()都会执行repr
class A: def __init__(self,name): self.name =name def __str__(self): return "____%s____" %self.name def __repr__(self): return "____%r____" %self.name a = A("lol") print(str(a),repr(a)) print("%s | %r" %(a,a)) 输出: ____lol____ ____'lol'____ ____lol____ | ____'lol'____
注:如果__str__和__repr__两个方法只能使用一个,首先考虑__repr__
3)在子类中使用__str__,先找子类的__str__,若子类没有就往上找(只要父类不是object,就执行父类的__str___),但是如果除了object之外的父类都没有__str__方法,就执行子类的__repr__方法,如果子类也没有repr方法,还要向上继续找父类的__repr-__方法,若一直找不到,再执行object类中的__str__方法
class A: def __init__(self,name): self.name = name def __str__(self): return '**%s**'%self.name def __repr__(self): return self.name # class B(A): def __init__(self,name): self.name = name def __repr__(self): return '***' a = B('lol') print(a) print(str(a),repr(a)) print('%s | %r'%(a,a)) 输出: **lol** **lol** *** **lol** | ***