一、封装
-
概念:一个类的某些属性,在使用的过程 中,不希望被外界直接访问,而是把这个属性给作为私有的【只有当前类持有】,然后暴露给外界一个访问的方法即可【间接访问属性】
-
封装的本质:就是属性私有化的过程
-
封装的好处:提高了数据的安全性,提高了数据的复用性
面向对象的特征:封装、继承、多态
1.1 属性私有化
- 如果想让成员变量不被外界直接访问,则可以在属性名称的前面添加两个下划线__(不能双下划线结尾的属性).成员变量则被称为私有成员变量
- 私有属性的特点:只能在类的内部通过函数方法直接被访问,在外界不能直接访问。(将私有属性放在函数方法中,对象通过调用该函数方法获取私有属性)
- 调用方法:将私有属性放在函数方法中,对象通过调用该函数方法获取私有属性。该种方法缺点:不能在调用方法的时候给变量赋值!只有在创建对象的时候赋值。
class Person1(): def __init__(self,name,age): self.name = name self.__age = age def myPrint(self): print(self.name,self.__age) p1 = Person1("abc",10) 调用方法一:通过调用方法 p1.myPrint() ---> abc 10 p1.name = "hello" # 修改类属性 #其实动态绑定属性,age和__age其实是两个不同的变量 p1.age = 222 # 这里只是增加了一个类属性,并没有修改私有化后的age p1.myPrint() print(p1.age) #AttributeError: 'Person1' object has no attribute '__age',私有化了,在外界不能直接访问 # print(p1.__age) 调用方法二:对象._类名__属性,不建议使用该方法 print(p1._Person1__age)
1.2 方法私有化
- 在函数方法名前加双下划线__,方法私有化后不能在外界直接调用,只能在类的非私有方法中调用(将私有方法当做私有属性定义在类中非私有方法中,对象通过调用该非私有方法来调用私有方法!)
class Person: def __init__(self,name,age,sex): self.name=name self.__age=age self.sex=sex def run(self): print(self.__age) self.__eat() # 私有方法:__eat(),双下划线开头的方法,不能在外边使用 def __eat(self): print('eat') p = Person('haha',30,'男') p.run()
1.3 get函数和set函数
- get函数和set函数并不是系统的函数,而是自定义的,为了和封装的概念相吻合,起名为getXxx和setXxx
- set函数:赋值【传值】
- get函数:获取值。需要设置返回值,将成员变量的值返回
- 工作原理:当编译器加载了程序之后,不能直接访问p2.__age,Python解释器把__age解释成_Person2__age,对象访问对象:p2_Person2__age
- 作用:调用私有属性,优点:对象在调用方法是可以重新给私有属性赋值。
class Person2(): def __init__(self,name,age): self.name = name self.__age = age def myPrint(self): print(self.name,self.__age) # 书写私有属性age的get函数和set函数【通过自定义的函数进行私有属性的赋值和获取值,暴露给外界】 #set函数:给成员变量赋值 #命名方式:setXxx def setAge(self,age): if age < 0: age = 0 self.__age = age #get函数:获取成员变量的值 #命名方式:getXxx def getAge(self): return self.__age p2 = Person2("abc",10) p2.myPrint() ---> abc 10 #间接的访问了私有的成员变量 print(p2.getAge()) ---> 10 p2.setAge(22) ---> 22 print(p2.getAge()) p2.setAge(-20) ---> 0 print(p2.getAge())
1.3.1 总结
- 通过将属性私有化之后,然后提供get函数和set函数,外部代码就不能随意更改成员变量的值,这样在一定程度上保证了数据的安全性。
- 有几个私有属性,则书写几对get函数和set函数。一对get\set函数也可同时获取几个私有属性,返回的值是元组。
1.3.2 特殊情况:尽量不要直接访问
-
在一个变量的前后各加两个下划线,在Python中被认为特殊成员变量,将不再属于私有变量。
print(p2.__weight__)
-
特殊变量
print(p2._height)
1.3.3 面试题:下面变量的含义
- xxx:普通的变量。
- _xxx:受保护的变量,不建议使用这种形式。
- _ _xxx:表示私有的,外界无法直接访问,只能通过暴露给外界的函数访问。
- _ _ xxxx_ _ :一般是系统的内置变量,比如: _ _ name_ _ ,_ _ solts _ _,自定义标识符的时候尽量不要使用这种形式。
1.4 @property装饰器
- @property装饰器的作用:将一个函数变成属性使用,简化get函数和set函数。使用在类中的成员函数中,可以简化代码,同时可以保证对参数做校验
- 使用:@property装饰器作用相当于get函数,同时,会生成一个新的装饰器@属性名.setter,相当于set函数的作用。
要调用几个私有属性就要创建几个@property装饰器对!
- 实例一:获取单个私有属性
class Person: def __init__(self,name,wechat): self.name = name self.__wechat = wechat @property # 作用:让wechat函数可以当成属性来调用.函数中一定要有return,函数当做属性调用 def wechat(self): return self.__wechat @wechat.setter def wechat(self,new_wechat): self.__wechat = new_wechat @property def photo(self): s = self.name + self.__wechat return s p = Person('hehe','110') print(p.wechat) ---> 110 p.wechat='120' print(p.wechat) ---> 120 print(p.photo) ---> hehe120
- 实例二:获取多个私有属性
class Person1(): def __init__(self,name,age): self.__name = name self.__age = age def myPrint(self): print(self.__name,self.__age) #注意:函数的命名=变量的名称 #作用:相当于get函数,设置返回值,将成员变量的值返回 @property def age(self): return self.__age #注意:函数的命名方式:需要和@property中函数的命名保持一致 #作用:相当于set函数,设置参数,给成员变量赋值 @age.setter def age(self,age): if age < 0: age = 0 self.__age = age @property def name(self): return self.__name @name.setter def name(self,name): self.__name = name p1 = Person1("abc",10) p1.myPrint() #abc 10 print(p1.age) #10 p1.age = 18 #相当于调用了set函数,将18传值,实质调用的是@age.setter修饰的函数 print(p1.age) #相当于调用了get函数,将成员变量的值获取出来,实质调用的是@peoperty修饰的函数 p1.name = "zhangsan" print(p1.name)
二、类方法和静态方法
2.1 类方法
-
使用@classmethod装饰器修饰的方法,被称为类方法,可以通过类名调用,也可以通过对象调用,但是一般情况下使用类名调用。
-
类方法特点:
1、可以用类和对象调用,推荐用类调用。可以节省内存
2、类方法内部是不可以使用对象属性和其他成员方法或私有方法
3、类方法内部可以使用其他类方法或类属性 -
说明:
a.必须有一个参数,这个参数一般情况下为cls,cls代表的是当前类,完全当做当前类使用(cls==类)
b.类方法是属于整个类的,并不是属于某个具体的对象,在类方法中禁止出现self
c.在类方法的内部,可以直接通过cls调用当前类中的属性和方法
d.在类方法的内部,可以通过cls创建对象
2.2 静态方法
-
使用@staticmethod装饰器修饰的方法,被称为静态方法,可以通过类名调用,也可以通过对象调用,但是一般情况下使用类名调用
-
静态方法特点:
1、可以用类和对象调用,推荐用类调用,可以节省内存
2、静态方法内部是不可以使用对象属性和其他成员方法或私有方法
3、也不建议去使用类属性和类方法,一般写成静态方法的就是一个普通函数,只是放在类里面 -
实例:
class Dog: age = 10 def __init__(self,name): self.name = name def run(self): print("成员方法/公有方法") def __eat(self): print("私有方法:只能在当前类内部使用") @classmethod def sleep(cls): #cls=class print("类方法:",cls == Dog) print(cls.age) @staticmethod def swim(): print("静态方法") d = Dog('哮天犬') # 调用类方法 d.sleep() ---> 类方法: True 10 Dog.sleep() ---> 类方法: True 10 # 调用静态方法 d.swim() Dog.swim()
2.3 实例方法【成员方法】、类方法以及静态方法之间的区别
- 语法上:
- 实例方法:第一个参数一般为self,在调用的时候不需要传参,代表的是当前对象【实例】
- 静态方法:没有特殊要求
- 类方法:第一个参数必须为cls,代表的是当前类
- 在调用上:
- 实例方法:只能对象
- 静态方法:对象 或者 类
- 类方法:对象 或者 类
- 在继承上【相同点】:
实例方法、静态方法、类方法:当子类中出现和父类中重名的函数的时候,子类对象调用的是子类中的方法【重写】在使用上,三种方法没有绝对区别!
三、继承
-
如果两个或者两个以上的类具有相同的属性或者成员方法,我们可以抽取一个类出来,在抽取的类中声明公共的部分 。被抽取出来的类就叫父类。
-
父类:父类,基类,超类,根类。object(根类,超类)是所有类的父类,如果一个类没有显式指明它的父类,则默认为object。
-
子类:子类,派生类。子类可以直接继承父类的所有属性(除私有属性和私有方法)
-
作用:简化代码,提高代码的复用性
3.1 单继承
- 一个子类只能有一个父类,被称为单继承。
class Ipad: def __init__(self,price): self.price = price def movie(self): print("看电影") # 子类:派生类 class Iphone(Ipad): def __init__(self,price,color): # 需要调用父类的init方法:对父类属性进行初始化 Ipad.__init__(self,price) # 方式一,显式调用 # super().__init__(price) # 方式二,隐式调用 self.color = color class Iwatch(Iphone): def __init__(self,price,color,size): Iphone.__init__(self,price,color) self.size = size def health(self): print(self.price) # 父类对象 p=Ipad(2000) p.movie() # 子类对象1 iphone = Iphone(1000,'green') print(iphone.price,iphone.color) iphone.movie() # 子类对象2 iwatch = Iwatch(2000,'yellow','2') print(iwatch.price,iwatch.size) iwatch.movie() iwatch.health()
3.1.1 总结
-
在子类的构造函数中调用父类的构造函数:
方式一(隐式调用):super(当前子类,self)._ _ init_ _(属性列表),super括号内参数可省略
方式二(显式调用):父类名. _ _ init _ _(self,属性列表) -
子类对象可以调用父类中的公开的成员方法(因为继承,私有方法除外)。
-
通过在子类的构造函数中调用父类的构造函数,子类对象可以直接访问父类中的成员变量(私有变量除外)。
-
子类中出现一个和父类同名的成员函数,则优先调用子类中的成员函数,子类的成员函数覆盖了父类中的同名的成员函数。
-
父类对象不能访问子类中特有的成员函数和成员变量。
-
在父类中定义slots属性限制属性的定义,子类中是无法使用,除非在子类中添加自己的限制。
3.2 多继承
- 一个子类可以有多个父类。
- 语法:class 子类类名(父类1,父类2,父类3.。。。):
如果多个父类中有相同的函数,通过子类的对象调用,调用的是哪个父类中的函数取决于在父类列表中出现的先后顺序
# 父类1 class Father: def __init__(self,name): self.name = name def run(self): print("跑步") # 父类2 class Mother: def __init__(self,age): self.age = age def cook(self): print("会做饭") # 子类 class Son(Father,Mother): def __init__(self,name,age,height): Father.__init__(self,name) Mother.__init__(self,age) # super(Son,self).__init__(name) # 继承Father # super(Father,self).__init__(age) # 继承Mother self.height = height # 对象 son = Son("哈哈",15,165) print(son.name,son.age,son.height) son.run() son.cook()
3.3 mro算法(了解):从左往右的继承链
-
print(Son.__mro__) # (<class '__main__.Son'>, <class '__main__.Father'>, <class '__main__.Mother'>, <class 'object'>)
3.4 重写【override】
- 在子类中出现和父类同名的函数,则认为该函数是对父类中函数的重写
3.4.1 系统函数的重新
-
_ _ str_ _ :将指定元素转换为字符串
-
重写_ _ str _ _函数,重写之后一般return一个字符串,有关于成员变量
-
重写_ _ str_ _的作用:为了调试程序
必须有返回字符串,作用是让打印的对象值是这里的返回值
def __str__(self): return f'姓名:{self.name},年龄:{self.age}'
-
_ _ repr _ :作用和用法与 _ str _ _相同,如果和str同时出现只会打印str
def __repr__(self): return f'姓名2:{self.name},年龄2:{self.age}'
-
使用时机:当一个对象的属性有很多的时候,并且都需要打印,则可以重写_ _ str _ _,可以简化代码,调试程序
-
总结:【面试题】
- _ _ str _ _ 和_ _ repr _ _ 都未被重写的时候,使用对象调用的是_ _ str _ _ ,此时 _ _ str _ _ 返回的是对象的地址。
- _ _ str _ _ 和_ _ repr _ _ 都被重写之后,使用对象调用的是_ _ str _ _ ,此时_ _ str _ _ 返回的是自定义的字符串。
- 重写了__str__,但是没有重写__repr__,则使用对象调用的是__str__,此时__str__返回的是自定义的字符串。
- 未重写__str__,但是重写了__repr__,则使用对象调用的是__repr__,此时,__repr__返回的是自定义的字符串。
3.4.2 自定义函数的重新
- 函数重写的时机:在继承关系中,如果父类中函数的功能满足不了子类的需求,则在子类中需要重写
class Person: def __init__(self,name): self.name = name def jump(self): print("跳2米远") class Player(Person): def __init__(self,name): super().__init__(name) # 把父类的jump方法重写了 def jump(self): print("跳4米远") # 对象 p = Person('baobao') p.jump() p2 = Player('haha') p2.jump()
3.5 总结
3.5.1继承的特点:
- 子类对象可以直接访问父类中非私有化的属性
- 子类对象可以调用父类中非私有化的成员方法
- 父类对象不能访问或者调用子类 中任意的内容
3.5.2继承的优缺点:
- 优点:
a.简化代码,减少代码的冗余
b.提高代码的复用性
c.提高了代码的可维护性
d.继承是多态的前提 - 缺点:
通常使用耦合性来描述类与类之间的关系,耦合性越低,则说明代码的质量越高但是,在继承关系中,耦合性相对较高【如果修改父类,则子类也会随着发生改变】
四、多态
- 多态:在继承的基础上,多个子类重写父类的同一个方法,在不同的子类中写不同的功能。函数的重写其实就是多态的一种体现。
- 用父类的对象指向不同的子类,调用该方法可以实现不同的功能。
子类对象可以是父类类型,但是,父类的对象不能是子类类型
- 多态作用:简化代码,提高代码的可读性,可维护性
class Animal: def eat(self): pass # 子类1 class Dog(Animal): def eat(self): print("吃骨头") # 子类2 class Cat(Animal): def eat(self): print("吃鱼") # 子类3 class Cow(Animal): def eat(self): print("吃草") # 补充 class Person: def __init__(self,name): self.name = name def keep(self,animal): print(f'{self.name}养了一只小动物') animal.eat() # 创建对象 dog = Dog() dog.eat() cat = Cat() cat.eat() cow = Cow() cow.eat() # p = Person("haha") p.keep(dog)
4.1 isinstance():判断一个对象是否属于某种类型(系统还是自定义的类型)
-
print(isinstance(a,list)) print(isinstance(b,Animal)) print(isinstance(c,Cat)) print(isinstance(c,Animal)) ----> True print(isinstance(b,Dog)) -----> False
五、类中的常用特殊属性(魔术方法)
5.1 __ name__
-
通过类名访问,获取类名字符串。不能通过对象访问,否则报错
print(Boy.__name__) ---> Boy print(__name__) ---> __main__
5.2 __ dict__
- 通过类名访问,获取指定类的信息【类方法,静态方法,成员方法】,返回的是一个字典
-通过对象访问,获取的该对象的信息【所有的属性和值】,返回的是一个字典print(b.__dict__) ---> {'name': 'haha', 'age': 10}
5.3 __ bases__
-
通过类名访问,查看指定类的所有的父类【基类】,返回的是一个元组
print(Boy.__bases__) ---> (<class '__main__.Man'>,)
5.4 __ module__
-
可通过类名和对象访问,查看所属的模块
print(b.__module__) ---> __main__ ,所属模块
5.5 __ class__
-
可通过类名和对象访问,查看所属类
print(b.__class__) ---> <class '__main__.Boy'>,对象所属的类
5.6 __ add__:运算符重载
-
def __add__(self,other): return self.age + other.age
六、单例设计模式【扩展】
-
23种设计模式,比较常用的是单例设计模式,工厂设计模式,代理模式,装饰模式
-
什么是单例设计模式:在程序运行的过程中,确保某一个类只能有一个实例【对象】,不管在哪个模块中获取对象,获取到的都是同一个对象
-
单例设计模式的核心:一个类有且仅有一个实例,并且这个实例需要应用在整个工程中
-
实际应用:数据库连接池操作----->应用程序中多处需要连接到数据库------>只需要创建一个连接池即可,避免资源的浪费
-
模块的工作原理:import xxx,模块被第一次导入的时候,会生成一个.pyc文件,当第二次导入的时候,会直接加载.pyc文件,将不会再去执行模块源代码。
class Person: def __init__(self,name): # print('__init__:',name) self.name = name instance = None @classmethod # new:创建对象是调用 def __new__(cls, *args, **kwargs): if cls.instance == None: # 新建对象 cls.instance = super().__new__(cls) return cls.instance p1 = Person('haha') p2 = Person('hehe') p3 = Person('yaya') print(p1 == p2) # True print(p1 == p3) # True print(id(p1.name),id(p2.name),id(p3.name))