一、封装【private】

1、概念

广义的封装:函数和类的定义本身,就是封装的体现

狭义的封装:一个类的某些属性,在使用的过程 中,不希望被外界直接访问,而是把这个属性给作为私有的【只有当前类持有】,然后暴露给外界一个访问的方法即可【间接访问属性】

封装的本质:就是属性私有化的过程

封装的好处:提高了数据的安全性,提高了数据的复用性

2、属性私有化

如果想让成员变量不被外界直接访问,则可以在属性名称的前面添加两个下划线__,成员变量则被称为私有成员变量

私有属性的特点:只能在类的内部直接被访问,在外界不能直接访问

# 面向对象的特征:
# 1、封装
# 2、继承
# 3、多态(了解)

# 1、封装
#   封装了属性和方法
#   python中没有:private protect public

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()
	
	# 私有属性
	def __eat(self):
		print("eat")

# 对象实例化
p = Person('张三', '30', '男')
print(p.name)
# print(p.__age)	# 报错,__age是私有属性
print(p._sex)
p.run()

# P.__eat()	# 报错,__eat()是私有方法

# 这个方式可以调用私有属性或私有方法,但是不建议这么用
print(p._Person__age)
p._Person__eat()

3、get函数和set函数

get函数和set函数并不是系统的函数,而是自定义的,为了和封装的概念相吻合,起名为getXxx和setXxx

get函数:获取值

set函数:赋值【传值】

#3.get函数和set函数
class Person2():
 def __init__(self,name,age):
     self.name = name
     self.__age = age
     #特殊情况一
     self.__weight__ = 20.0
     #特殊情况二
     self._height = 155.0

 def myPrint(self):
     print(self.name,self.__age)

 # 书写私有属性age的get函数和set函数【通过自定义的函数进行私有属性的赋值和获取值,暴露给外界】
 """
 get函数和set函数并不是系统的函数,而是自定义的,为了和封装的概念相吻合,起名为getXxx和setXxx
 get函数:获取值
 set函数:赋值【传值】
 """
 #set函数:给成员变量赋值
 #命名方式:setXxx
 #特点:需要设置参数,参数和私有成员变量有关
 def setAge(self,age):
     #数据的过滤
     if age < 0:
         age = 0
     self.__age = age
 #get函数:获取成员变量的值
 #命名方式:getXxx
 #特点:需要设置返回值,将成员变量的值返回
 def getAge(self):
     return self.__age

 #注意:有几个私有属性,则书写几对get函数和set函数

p2 = Person2("abc",10)
p2.myPrint()   #abc 10
#print(p2.__age)
#间接的访问了私有的成员变量
print(p2.getAge())
p2.setAge(22)
print(p2.getAge())

p2.setAge(-20)
print(p2.getAge())

#总结:通过将属性私有化之后,然后提供get函数和set函数,外部代码就不能随意更改成员变量的值,这样在一定程度上保证了数据的安全性

#4.工作原理【了解】
#当编译器加载了程序之后,不能直接访问p2.__age,Python解释器把__age解释成_Person2__age
#p2.__age = 100
p2._Person2__age = 100
print(p2.getAge())

#5.特殊情况:尽量不要直接访问
#a.在一个变量的前后各加两个下划线,在Python中被认为特殊成员变量,将不再属于私有变量
#print(p2.__weight__)
#b.特殊变量
#print(p2._height)

#面试题:下面变量的含义
"""
xxx:普通的变量
_xxx:受保护的变量,不建议使用这种形式
__xxx:表示私有的,外界无法直接访问,只能通过暴露给外界的函数访问
__xxxx__:一般是系统的内置变量,比如:__name__,__solts__,自定义标识符的时候尽量不要使用这种形式
"""

4、@property装饰器

装饰器的作用:可以给函数动态添加功能,对于类的成员方法,装饰器一样起作用

Python内置的@property装饰器的作用:将一个函数变成属性使用

@property装饰器:简化get函数和set函数

使用:@property装饰器作用相当于get函数,同时,会生成一个新的装饰器@属性名.settter,相当于set函数的作用

作用:使用在类中的成员函数中,可以简化代码,同时可以保证对参数做校验

class Person:
	def __init__(self, name, wechat)
		self.name = name
		self.__wechat = wechat
	
	# getter间接获取私有属性
	# def get_wechat(self)
	#	return self.__wechat

	# setter 间接修改私有属性
	# def set_wechat(self, new_wechat):
	# self.__wechat = new_wechat

	@property	# 作用:让wechat函数可以当成属性来调用
	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('Tom', '110')
# print(p.get_wechat())
# p.set_wechat('007')
# print(p.get_wechat())

print(p.wechat)
p.wechat = '120'
print(p.wechat)

二、类方法和静态方法

类方法:使用@classmethod装饰器修饰的方法,被称为类方法,可以通过类名调用,也可以通过对象调用,但是一般情况下使用类名调用

静态方法:使用@staticmethod装饰器修饰的方法,被称为静态方法,可以通过类名调用,也可以通过对象调用,但是一般情况下使用类名调用

class Dog:
	age = 2
	def __init__(self, name):
		self.name = name
		
	def run(self):
		print("成员方法/公有方法")
	
	def __eat(self):
		print("私有方法:只能在当前类内部使用")
	
	# 类方法:
	# 1、可以用类和对象调用,推荐用类调用,可以节省内存
	# 2、类方法内部是不可以使用对象属性和其他成员方法或私有方法
	# 3、类方法内部使用其他类方法或类属性
	@classmethod
	def sleep(cls): # cls:class
		print("类方法:", cls == Dog)	# True
		print(cls.age)	# 调用类属性

	# 静态方法:
	# 1、可以用类和对象调用,推荐用类调用,可以节省内存
	# 2、静态方法内部是不可以使用对象属性和其他成员方法或私有方法
	# 2、 不建议去使用类属性和类方法,一般写成静态方法的就是一个普通函数,只是放在类里面
	@staticmethod
	def swim():
		print('静态方法')

# 对象实例化
d = Dog('啸天犬')
d.sleep() #类方法:True
Dog.sleep()	# 类方法:True

总结:实例方法【成员方法】、类方法以及静态方法之间的区别

a.语法上

实例方法:第一个参数一般为self,在调用的时候不需要传参,代表的是当前对象【实例】

静态方法:没有特殊要求

类方法:第一个参数必须为cls,代表的是当前类

b.在调用上

实例方法:只能对象

静态方法:对象 或者 类

类方法:对象 或者 类

c.在继承上【相同点】

实例方法、静态方法、类方法:当子类中出现和父类中重名的函数的时候,子类对象调用的是子类中的方法【重写】

三、继承

1、 概念

如果两个或者两个以上的类具有相同的属性或者成员方法,我们可以抽取一个类出来,在抽取的类中声明公共的部分

被抽取出来的类:父类,基类,超类,根类

两个或者两个以上的类:子类,派生类

他们之间的关系:子类 继承自 父类

注意:

a.object是所有类的父类,如果一个类没有显式指明它的父类,则默认为object

b.简化代码,提高代码的复用性

2、单继承

2.1 使用

简单来说,一个子类只能有一个父类,被称为单继承

语法:

父类:

class 父类类名(object):

类体【所有子类公共的部分】

子类:

class 子类类名(父类类名):

类体【子类特有的属性和成员方法】

说明:一般情况下,如果一个类没有显式的指明父类,则统统书写为object

# 父类:基类
class Ipad(object):
	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):
		super.__init__(price, color)
		self.size = size

	def health(self):
		print(self.price)
		# print(self.__color) # 不能使用父类的私有属性和私有方法,私有属性和私有方法不能继承

# 对象
# iphone = Iphone('5000','blue')
# print(iphone.price, iphone.color)
# iphone.movie()

# iwatch = Iwatch('1500', 'black', '1.8')
# print(iwatch.price, iwatch.size)
# iwatch.movie()
# iwatch.health()
2.2 特殊用法
# 子类中出现一个和父类同名的成员函数,则优先调用子类中的成员函数
# 子类的成员函数覆盖了父类中的同名的成员函数
s = Student("小明",9,90)
s.study()
s.show()

# 父类对象能不能访问子类中特有的成员函数和成员变量?----->不能
per = Person("gs",10)
# per.work()

#slots属性能否应用在子类中
#结论三:在父类中定义slots属性限制属性的定义,子类中是无法使用,除非在子类中添加自己的限制
#父类
class Student(object):
 __slots__ = ("name","age")

#子类
class SeniorStudent(Student):
 pass


s  = Student()
s.name = "zhangsan"
s.age = 10
#s.score = 90

ss = SeniorStudent()
ss.name = "lisi"
ss.age = 20
ss.score = 60

总结:

继承的特点:

a.子类对象可以直接访问父类中非私有化的属性

b.子类对象可以调用父类中非私有化的成员方法

c.父类对象不能访问或者调用子类 中任意的内容

继承的优缺点:

优点:

a.简化代码,减少代码的冗余

b.提高代码的复用性

c.提高了代码的可维护性

d.继承是多态的前提

缺点:

通常使用耦合性来描述类与类之间的关系,耦合性越低,则说明代码的质量越高

但是,在继承关系中,耦合性相对较高【如果修改父类,则子类也会随着发生改变】

3、多继承

一个子类可以有多个父类

语法:

class 子类类名(父类1,父类2,父类3.。。。):

类体

# 多继承
class Father:
	def __init__(self, name)
		self.name = name
	
	def run(self):
		print("会跑步")

# 父类
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('张三', 8, 1)
print(son.name, son.age, son.height)
son.run()
son.cook()

# mro算法:从左往右的继承链
# print(Son.__mro__)
# ==>(<class '__main__.Son'>,
# <class '__main__.Father'>,
# <class '__main__.Mother'>,
# <class 'object'>)


# 继承的使用场景
# 1、从多个不同的类中提取相同的属性和方法成为一个父类
# 2、直接继承父类的所有属性(除了私有属性和私有方法)

4、函数重新【override】

在子类中出现和父类同名的函数,则认为该函数是对父类中函数的重写

# 重写:方法重写
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('张三')
p.jump()

p2 = Player('王五')
p2.jump()
4.1 系统函数重写
__str__ & __repr__
class Animal(object):
 def __init__(self,name,age):
     self.name = name
     self.age = age

 #重写__str__函数,重写之后一般return一个字符串,有关于成员变量
 def __str__(self):
     return "name=%s age=%d"%(self.name,self.age)

 #重写__repr__,作用和str是相同的,优先使用str
 def __repr__(self):
     return "name=%s age=%d"%(self.name,self.age)

a = Animal("大黄",10)
print(a)   #<__main__.Animal object at 0x00000226A87AC240>
print(a.__str__())

#当一个类继承自object的时候,打印对象获取的是对象的地址,等同于通过子类对象调用父类中__str__
#当打印对象的时候,默认调用了__str__函数
#重写__str__的作用:为了调试程序

"""
总结:【面试题】
a.__str__和__repr__都未被重写的时候,使用对象调用的是__str__,此时__str__返回的是对象的地址
b.__str__和__repr__都被重写之后,使用对象调用的是__str__,此时__str__返回的是自定义的字符串
c.重写了__str__,但是没有重写__repr__,则使用对象调用的是__str__,此时__str__返回的是自定义的字符串
d.未重写__str__,但是重写了__repr__,则使用对象调用的是__repr__,此时,__repr__返回的是自定义的字符串
"""

# 使用时机:当一个对象的属性有很多的时候,并且都需要打印,则可以重写__str__,可以简化代码,调试程序

四、多态

一种事物的多种体现形式,函数的重写其实就是多态的一种体现

在Python中,多态指的是父类的引用指向子类的对象

# 封装,继承,多态
# 多态【了解】:在继承的基础上多个子类重写父类的同一个方法,在不同的子类中写不同的功能,
#      那么用父类的对象指向不同的子类,调用改方法,则可以实现不同的功能

# 父类
class Animal:
    def eat(self):
        pass

# 子类
class Dog(Animal):
    def eat(self):
        print("吃骨头")

class Cat(Animal):
    def eat(self):
        print("吃鱼")

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()
cat = Cat()
cow = Cow()

p = Person('小明')
p.keep(cat)


# 弱类型
#   a = 10
#   a = 'hello'
# 强类型
#   int a = 10
#   a = 'hello'   # 报错

总结:

简化代码,提高代码的可读性,可维护性

五、类中常用的属性

__name__
通过类名访问,获取类名字符串
不能通过对象访问,否则报错

__dict__
通过类名访问,获取指定类的信息【类方法,静态方法,成员方法】,返回的是一个字典
通过对象访问,获取的该对象的信息【所有的属性和值】,,返回的是一个字典

__bases__
通过类名访问,查看指定类的所有的父类【基类】
class Animal(object):
def __init__(self,arg):
  super(Animal, self).__init__()
  self.arg = arg


class Tiger(Animal):
age = 100
height = 200

def __init__(self,name):
  #super(Tiger, self).__init__(name)
  self.name = name

def haha(self):
  print("haha")

@classmethod
def test(cls):
  print("cls")

@staticmethod
def show():
  print("show")


if __name__ == "__main__":

#1.__name__
print(Tiger.__name__)  #Tiger

t = Tiger("")
#print(t.__name__)  #AttributeError: 'Tiger' object has no attribute '__name__'

#2.__dict__
print(Tiger.__dict__)  #类属性,所有的方法
print(t.__dict__)   #实例属性

#3.__bases__,获取指定类的所有的父类,返回的是一个元组
print(Tiger.__bases__)

六、单例设计模式

1 概念

什么是设计模式

经过已经总结好的解决问题的方案

23种设计模式,比较常用的是单例设计模式,工厂设计模式,代理模式,装饰模式

什么是单例设计模式

单个实例【对象】

在程序运行的过程中,确保某一个类只能有一个实例【对象】,不管在哪个模块中获取对象,获取到的都是同一个对象

单例设计模式的核心:一个类有且仅有一个实例,并且这个实例需要应用在整个工程中

2 应用场景

实际应用:数据库连接池操作-----》应用程序中多处需要连接到数据库------》只需要创建一个连接池即可,避免资源的浪费

3 实现

Python的模块就是天然的单例设计模式

模块的工作原理:

import xxx,模块被第一次导入的时候,会生成一个.pyc文件,当第二次导入的时候,会直接加载.pyc文件,将不会再去执行模块源代码

__new__():实例从无到有的过程【对象的创建过程】
# 设计模式: 23种
# 工厂模式,单例,代理,适配器,装饰模式,。。。
# 扩展:单例模式
# import math  只会导入一次

# 单例模式:让类只创建一个对象

class Person:

    # init:初始化时调用
    def __init__(self, name):
        print('__init__', name)
        self.name = name

    # 类属性
    instance = None
    # new:创建对象时调用
    @classmethod
    def __new__(cls, *args, **kwargs):
        print('__new__')

        if cls.instance == None:
            # 新建对象
            cls.instance = super().__new__(cls)

        return cls.indtance

# 对象
p1 = Person('李四')
p2 = Person('王五')
p3 = Person('赵六')

print(p1 == p2)
print(p1 == p3)
print(p1.name, p2.name, p3.name)    # 赵六 赵六 赵六