python中的面向对象的学习主要是类和对象。
在面向对象编程的世界里,程序中的数据和操作数据的函数是一个逻辑上的整体,我们称之为对象,对象可以接收消息,解决问题的方法就是创建对象并向对象发出各种各样的消息;通过消息传递,程序中的多个对象可以协同工作,这样就能构造出复杂的系统并解决现实中的问题。
面向对象编程:
把一组数据和处理数据的方法组成对象,把行为相同的对象归纳为类。通过封装隐对象的内部细节,通过继承实现类的特化和泛化,通过多态实现基于对象类型的动态分派。
对象(object)、类(class)、封装(encapsulation)、继承(inheritance)、多态(polymorphism)。
类是对象的蓝图和模板,对象是类的实例,是可以接受消息的实体
类:多个具有特殊功能的个体的集合 例如:人类 狗 猫
对象:在一个类中,一个具有特殊功能的个体,能够帮忙解决某件特定的事情,也被称为实例[instance]
两者之间的关系:类用于描述某一类对像的共同特征,而对象是类的具体的存在
思考问题:先有类还是先有对象?
类的定义
语法:
class类名():
类体
说明:
a.Python中使用class关键字定义类
b.类名只要是一个合法的标识符即可,但是要求:遵循大驼峰命名法则【首单词的首字母大写,不同单词之间首字母大写】
c.通过缩进区分类体
d.类体一般包含两部分内容:属性和方法(属性就是描述一些静态信息的,比如人的姓名\年龄\性别等等,方法:一般用函数表示,用来实现具体的功能)
class Dog():
# 类属性
name = "局长"
sex = "公"
def __init__(self, name=None):
if name:
self.name = name
# 类方法
def eat(self):
print(self.name, "吃肉!")
def say(self):
print("我是吼的方法")
# 使用示例
dog1 = Dog()
dog2 = Dog("旺财")
dog1.eat()
dog2.eat()
类中的方法和变量的定义
类中的方法和变量是为了描述事物的行为和特征
类中定义的方法被称为:成员方法
类中定义的变量被称为成员变量,也被称为属性 [os.name]
成员变量:类具有的特征
成员方法:类具有的行为
类存在的意义:拥有相同特征和行为的对像可以抽取出来一个类,类的存在是为了创建一个具体
的对象
类中方法和属性的使用
创建对象【实例化对象】
已知类,通过类创建对象对象的创建过程被对象的实例化过程
语法:变量名=值
对象名 = 类名()
class Dog():
# 类属性
name = "局长"
sex = "公"
# 类方法
def eat(self):
print(Dog.name, "吃肉!")
def say(self):
print("我是吼的方法")
# 通过Dog类创建对象
labuladuo = Dog()
# 通过对象访问方法
labuladuo.eat()
labuladuo.say()
# 通过对象访问属性
print(labuladuo.name)
print(labuladuo.sex)
总结:
访问变量采用:对象名.属性名
访问方法采用:对象名.方法名(参数列表)
构造函数
采用上面的方式创建对象【直接给成员变量赋值】,很多的类一般倾向于创建成有初始状态
__init__:构造函数【作用:创建对象,给对象的成员变量赋初始值】
构造函数:构造器
调用的时机:当一个对象被创建的时候,第一个被自动调用的函数
per = person()
语法:
def __init__(self,args1,args2....)
函数体
说明:
a.之前的写法中并没有显式的定义init函数,说明系统默认提供了一个无参的构造函数
b.args1,args2...一般设置的形参列表和成员变量有关。
class GirlFriend:
# 构造函数参数是对象相关的属性
def __init__(self, name, age):
# 对象属性
self.name = name
self.age = age
print("构造函数的触发时机是:当创建对象的时候自动")
# 对象方法
def say(self):
print(self.name, "原神,启动!!!")
def sing(self):
print("第五人格,启动!!!")
# 当创建对象的时候,会自动调用__init__()
n = GirlFriend("py小王子", 21)
n.say()
析构函数
与构造函数正好相反,当对象被销毁的时候自动调用的函数,被称为析构函数
__del__:删除变量: del 变量名,此时可以触发析构函数的调用
使用情景:清理工作,比如关闭数据库,关闭文件等析构函数的应用场景:
关闭数据库 保存文件
内存回收的方式:
1.当对象在某个作用域中调用完毕,在跳出其作用域的同时析构函数会被调用一次,这样可以用来释放内存空间。
2.当使用del删除对象的时候,也会调用该对象的析构函数,相当于手动释放内存
class GirlFriend:
# 对象方法
def sing(self):
print("可是困住我青春的人,始终没有回头看我一眼。")
# 析构函数:触发时机是当对象被删除时,会被自动调用,释放内存
def __del__(self):
print("脚本运行结束,释放内存")
# 当创建对象的时候,会自动调用__init__()
n = GirlFriend()
n.sing()
print("我是最后执行的一句代码了!")
# 可以添加下面这行代码来手动触发析构函数(不过在脚本正常结束时也会自动触发)
# del n
封装
1.概念
广义的封装:函数和类的定义本身,就是封装的体现
狭义的封装:一个类的某些属性,在使用的过程中,不希望被外界直接访问,而是把这个属性给
作为私有的【只有当前类持有】,然后暴露给外界一个访问的方法即可【间接访问属性】
封装的本质:就是属性私有化的过程
封装的好处:提高了数据的安全性,提高了数据的复用性
2.属性私有化和方法私有化
如果想让成员变量不被外界直接访问,则可以在属性名称的前面添加两个下划线,成员变量则被称为私有成员变量
私有属性的特点:只能在类的内部直接被访问,在外界不能直接访问
class Girl:
def __init__(self, name, sex, height):
self.name = name
self.sex = sex
self.height = height
# 比如我的青春停留年龄,在外面不能轻易的访问,需要把年龄设置为私有属性__
self.__age = 18
def say(self):
print("哥哥,我坐你旁边,姐姐不会生气吧!")
# 在类的内部可以访问私有属性
def sayAge(self, boyFriend):
if boyFriend == "py小王子":
print(f"{self.name}偷偷的告诉{boyFriend}说:你的朋友太多了,这显示的我不是很重要了")
else:
print("年少之忆,也可叹,只是当时已惘然呐")
# 私有方法
# 接吻
def __kiss(self):
print("一吻定终身!")
# 类中可以访问私有方法
def love(self, relationship):
if relationship == "情侣关系":
self.__kiss()
else:
print("离开")
is_girl = Girl("她", "美女", 165)
print(is_girl.name)
print(is_girl.sex)
print(is_girl.height)
# print(is_girl.age) # 将age设置为私有属性后,外部不能直接访问
is_girl.say()
is_girl.sayAge("py小王子")
is_girl.love("未知关系")
私有属性:
1.写法:在属性的前面加两个下划线:__age
2.用法:只能在类的内部访问,不能在类的外部访问可以在类的内部设置一个外部访问的接口(这个接口一般会做各种条件判断,满足后才能访问),让外部获取私有属性的值
私有方法:
1.写法:在方法的前面加两个下划线:__kiss()
2.用法:只能在类的内部访问,不能在类的外部访问。私有方法一般是用来在类的内部实现某些功能的,对于外部来说没有实质的意义。这种方法一般定义为私有方法。
get函数和set函数
get函数和set函数并不是系统的函数,而是自定义的,为了和封装的概念相吻合,起名为getXxx
和setXxx
- get函数:获取值
- set函数:赋值【传值】
# 第一种访问和设置私有属性的方式 get和set函数
class Girl:
def __init__(self, name, age):
self.name = name
self.__age = age
# 访问私有属性
# 命名规则:get+私有属性名(属性名单词首字母大写)
def getAge(self):
return self.__age
# 设置私有属性
# 命名规则:set+私有属性名(属性名单词首字母大写)
def setAge(self, age):
self.__age = age
zhi = Girl("她", 21)
print(zhi.name)
# 不能直接访问私有属性,以下代码是错误的:
# print(zhi.__age)#AttributeError: 'Girl' object has no attribute '__age'
# 访问私有属性
age = zhi.getAge()
print(age) # 21
# 设置私有属性
zhi.setAge(18)
print(zhi.getAge()) # 18
@property装饰器
装饰器的作用:可以给函数动态添加功能,对于类的成员方法,装饰器一样起作用
Python内置的@property装饰器的作用:将一个函数变成属性使用
@property装饰器:简化get函数和set函数
使用:
@property装饰器作用相当于get函数,同时,会生成一个新的装饰器
@属性名.settter,相当于set函数的作用
作用:使用在类中的成员函数中,可以简化代码,同时可以保证对参数做校验
# 第一种访问和设置私有属性的方式 get和set函数
class Girl:
def __init__(self, name, age):
self.name = name
self.__age = age
# 访问私有属性
# 命名规则:get+私有属性名(属性名单词首字母大写)
def getAge(self):
return self.__age
# 设置私有属性
# 命名规则:set+私有属性名(属性名单词首字母大写)
def setAge(self, age):
self.__age = age
# 通过装饰器@property获取私有属性age
# 相当于getAge()
@property
def age(self):
return self.__age
# 通过装饰器设置私有属性
@age.setter
def age(self, new_age):
self.__age = new_age
zhi = Girl("她", 21)
print(zhi.getAge()) # 21
zhi.setAge(18)
print(zhi.getAge()) # 18
print()
print(zhi.age) # 18 通过装饰器访问私有属性,访问格式:对象名.私有属性名
zhi.age = 19 # 通过装饰器设置私有属性,格式:对象名.私有属性名=值
print(zhi.age) # 19
动态属性
在Python中,我们可以动态为对象添加属性,这是Python作为动态类型语言的一项特权,代码如下所示。需要提醒大家的是,对象的方法其实本质上也是对象的属性,如果给对象发送一个无法接收的消息,引发的异常仍然是AttributeError。
class Student(object):
def __init__(self, name, age):
self.name = name
self.age = age
stu = Student('py小王子', 21)
# 为Student对象动态添加sex属性
stu.sex = '男'
print(stu.__dict__)
如果不希望在使用对象时动态的为对象添加属性,可以使用Python的__slots__魔法。对于Student类来说,可以在类中指定__slots__ = ('name', 'age'),这样Student类的对象只能有name和age属性,如果想动态添加其他属性将会引发异常。
class Student:
__slots__ = ('name', 'age')
def __init__(self, name, age):
self.name = name
self.age = age
stu = Student('py小王子', 21)
# AttributeError: 'Student' object has no attribute 'sex'
stu.sex = '男'
print(stu.__dict__)
类方法和静态方法
类方法:使用@classmethod装饰器修饰的方法,被称为类方法,可以通过类名调用,也可以通过对象调用,但是一般情况下使用类名调用
静态方法:使用@staticmethod装饰器修饰的方法,被称为静态方法,可以通过类名调用,也可以通过对象调用,但是一般情况下使用类名调用类方法:
1.通过@classmethod装饰器修饰的方法就是类方法
2.类方法可以使用类名或者对象调用。但是一般情况下使用类名调用类方法(节省内存)
3.没有self,在类方法中不可以使用其他对象的属性和方法(包括私有属性和私有方法)
4.可以调用类属性和其他的类方法,通过c1s来调用
5.形参的名字cls是class的简写,可以更换,只不过是约定俗成的写法而已
6.cls表示的是当前类
class Animal():
# 类属性
name = "牧羊犬"
# 对象属性
def __init__(self, name, sex):
self.name = name
self.sex = sex
@classmethod
def run(cls):
print("我是类方法")
print(cls.name)
print(cls == Animal) # cls表示的是当前类
"""
静态方法:
1.通过@staticmethod装饰器修饰的方法就是静态方法
2.通过类名或者对象名都可以调用静态方法(推荐使用类名调用)
3.静态方法形式参数中没有cls,在静态方法中不建议调用(类属性\类方法静态方法)
4.静态方法一般是一个单独的方法,只是写在类中
"""
# 静态方法
@staticmethod
def eat():
print("我是静态方法")
# 创建对象
dog = Animal('中华土狗', '公')
# dog.run() # 对象也可以调用类方法,但一般推荐用类名调用类方法
Animal.run() # 类名调用类方法
Animal.eat() # 类名调用静态方法
总结:实例方法【成员方法】、类方法以及静态方法之间的区别
a.语法上
实例方法:第一个参数一般为self,在调用的时候不需要传参,代表的是当前对象【实例】
静态方法:没有特殊要求
类方法:第一个参数必须为cls,代表的是当前类
b.在调用上
实例方法:只能对象
静态方法:对象或者类
类方法:对象或者类
c在继承上【相同点】
实例方法、静态方法、类方法:当子类中出现和父类中重名的函数的时候,子类对象调用的是子类中的方法【重写】
注意:注意区分三种函数的书写形试,在使用,没有绝对的区分对象方法、类方法、静态方法都可以通过类名.方法名的方式来调用,区别在于方法的第一个参数到底是普通对象还是类对象,还是没有接受消息的对象
类中的常用属性
__name__
通过类名访问,获取类名字符串
不能通过对象访问,否则报错
__dict__
通过类名访问,获取指定类的信息[类方法,静态方法,成员方法],返回的是一个字典
通过对象访问,获取的该对象的信息【所有的属性和值】,返回的是一个字典
__bases__
通过类名访问,查看指定类的所有的父类【基类】
class Animal(object):
def __init__(self, name, sex):
self.name = name
self.sex = sex
def eat(self):
print("吃")
animal = Animal("二哈", "公狗")
# __name__—通过类名访问获取当前类的类名,不能通过对象访问
print(Animal.__name__) # Animal
print()
# __dict__—以字典的形式返回类的属性和方法以及对象的属性
print(Animal.__dict__) # 以字典的形式显示类的属性和方法
print()
print(animal.__dict__) # 以字典的形式显示对象的属性{'name': '二哈', 'sex': '公狗'}
print()
# __bases__ 获取指定类的父类返回的是一个元组
print(Animal.__bases__) # (<class 'object'>,)
print()
# 魔术方法:__str__()和__repr__
class Person(object):
def __init__(self, name, age):
self.name = name
self.age = age
def swim(self):
print("游泳的方法")
# __str__()触发时机:当打印对象的时候,自动触发。一般用它来以字符串的形式返回对象的相关信息,必须使用return返回数据
def __str__(self):
return f"姓名是:{self.name}, 年龄是:{self.age}"
# __repr__()作用和__str__()类似,若两者都存在,执行__str__()
def __repr__(self):
return f"姓名是:{self.name}, 年龄是:{self.age}"
zhi = Person("她", 18)
print(zhi)
继承
概念
如果两个或者两个以上的类具有相同的属性或者成员方法,我们可以抽取一个类出来,在抽取的
类中声明公共的部分
被抽取出来的类:父类,基类,超类,根类
两个或者两个以上的类:子类,派生类
他们之间的关系:子类继承自父类
父类的属性和方法子类可以直接使用。
注意:
a.object是所有类的父类,如果一个类没有显式指明它的父类,则默认为object
b.简化代码,提高代码的复用性
单继承
简单来说,一个子类只能有一个父类,被称为单继承
语法:
父类:class 父类类名 (object):
类体【所有子类公共的部分】
子类:
class 子类类名 (父类类名):
类体【子类特有的属性和成员方法】
说明:一般情况下,如果一个类没有显式的指明父类,则统统书写为object
# 最简单的继承
# 父类
class Person(object):
def say(self):
print("说话的方法")
# 子类
class Boy(Person): # 定义一个子类,将父类的类名传进去子类就继承了父类
def eat(self):
print("子类自己的吃饭的方法")
boy = Boy()
boy.eat() # 子类调用自己的方法
boy.say() # 子类调用父类的方法
# 有构造函数的单继承
# 父类
class Animal(object):
def __init__(self, name, sex):
self.name = name
self.sex = sex
def eat(self):
print("所有的动物都有捕食的技能")
# 子类
class cat(Animal):
def __init__(self, name, sex, tail): # 先继承父类的属性,再重构
# 1.经典的写法
# Animal.__init__(self, name, sex) # 继承父类的构造方法
# 2.隐式的继承父类的构造函数
super().__init__(name, sex)
self.tail = tail # 定义子类自己的属性
def catchMouse(self):
print("猫抓老鼠")
cat_obj = cat("波斯猫", "母", "长尾巴")
print(cat_obj.name)
print(cat_obj.sex)
print(cat_obj.tail)
cat_obj.eat()
cat_obj.catchMouse()
总结:
继承的特点:
a.子类对象可以直接访问父类中非私有化的属性
b.子类对象可以调用父类中非私有化的成员方法
c.父类对象不能访问或者调用子类中任意的内容
继承的优缺点:
优点:a.简化代码,减少代码的沉余
b.提高代码的复用性
c.提高了代码的可维护性
d.继承是多态的前提
缺点:
通常使用耦合性来描述类与类之间的关系,耦合性越低,则说明代码的质量越高
但是,在继承关系中,耦合性相对较高【如果修改父类,则子类也会随着发生改变】
多继承
一个子类可以有多个父类
语法:
class子类类名(父类1,父类2,父类3...):
类体
# 父亲类
class Father(object):
def __init__(self, surname):
self.surname = surname
def make_money(self):
print("钱难挣!")
# 母亲类
class Mother(object):
def __init__(self, height):
self.height = height
def eat(self):
print("一言不合,就干饭!")
# 子类
class Son(Father, Mother): # 子类继承多个父类时,在括号内写多个父类名称即可
def __init__(self, surname, height, weight):
# 继承父类的构造函数
Father.__init__(self, surname)
Mother.__init__(self, height)
self.weight = weight
def play(self):
print("就这么厉害!")
son = Son("黄", 178, 160)
print(son.surname)
print(son.height)
print(son.weight)
son.make_money()
son.eat()
son.play()
多态
一种事物的多种体现形式,函数的重写其实就是多态的一种体现
在Python中,多态指的是父类的引用指向子类的对象
# 多态:在继承的基础上,(多个子类继承一个父类,并且重写父类的一个方法),去调用子类的方法可以实现不同的功能。
# 父类
class Animal():
def eat(self):
print("吃的方法")
# 子类
class Fish(Animal):
def eat(self):
print("大鱼吃小鱼,小鱼吃虾米")
class Dog(Animal):
def eat(self):
print("狼行千里吃肉,狗走万里吃粑粑!")
class Cat(Animal):
def eat(self):
print("猫爱吃鱼!")
# 严格意义的多态:使用对象调用eat方法
class Person():
def feed(self, animal):
animal.eat()
fish = Fish()
dog = Dog()
cat = Cat()
# 最简单的多态的体现
fish.eat()
dog.eat()
cat.eat()
# 严格意义的多态的体现
person = Person()
person.feed(dog)
person.feed(cat)
首先定义了Animal类作为父类,Fish、Dog、Cat作为子类重写了eat方法,体现了多态性。然后Person类中的feed方法接受一个Animal类型的对象并调用其eat方法,通过实例化不同的子类对象并传入Person类的feed方法,进一步展示了多态的应用。
这种多态机制使得代码更加灵活和可扩展,当需要添加新的Animal子类时,只要重写eat方法,Person类的feed方法就可以正确地处理新的子类对象,而不需要修改Person类的代码
- 在父类和子类中出现了函数重名的情况,会调用子类的函数,子类和父类函数重名的现象叫做重载(重写)
- 不同的子类之间调用和父类相同的方法,调用的都是自己的方法,这就是多态的一种体现。
单例设计模式
1.概念
什么是设计模式
经过已经总结好的解决问题的方案
23种设计模式,比较常用的是单例设计模式,工厂设计模式,代理模式,装饰模式
什么是单例设计模式
单个实例【对象】
在程序运行的过程中,确保某一个类只能有一个实例【对象】,不管在哪个模块中获取对象,获取到的都是同一个对象单例设计模式的核心:一个类有且仅有一个实例,并且这个实例需要应用在整个工程中
应用场景
实际应用:数据库连接池操作一》
应用程序中多处需要连接到数据车一一》只需要创建一个连接池即可,避免资源的浪费
模块
Python的模块就是天然的单例设计模式
模块的工作原理:
import xxx,模块被第一次导入的时候,会生成一个.pyc文件,当第二次导入的时候,会直接加载.pyc文件,将不会再去执行模块源代码
使用new
__new__():实例从无到有的过程【对象的创建过程】
class Person(object):
# __init__对象初始化属性时,自动触发
def __init__(self, name):
print("__init__")
self.name = name
# 定义一个类属性,接收创建好的对象
instance = None
@classmethod
def __new__(cls, *args, **kwargs):
print("__new__")
# 如果类属性的instance=None表示该类未创建过对象
if cls.instance == None:
cls.instance = super().__new__(cls)
return cls.instance
p = Person("陈梦")
p1 = Person("陈梦")
p2 = Person("陈梦")
print(p == p1 == p2)
1. 设计目的
这段 Python 代码实现了一个简单的单例模式。单例模式是一种设计模式,其目的是确保一个类只有一个实例,并提供一个全局访问点来获取这个实例。
2.
__new__
方法
__new__
是一个特殊的类方法,在创建对象实例时被调用,它是对象实例化过程中第一个被调用的方法,负责创建对象并返回该对象。- 在
Person
类的__new__
方法中:
- 首先打印
"__new__"
,这用于展示方法的调用顺序。- 然后检查类属性
instance
是否为None
。如果是None
,表示当前类还没有创建过实例。此时,通过super().__new__(cls)
创建一个新的实例,并将其赋值给cls.instance
。这里的super().__new__(cls)
是调用父类(object
类)的__new__
方法来创建对象。- 最后,无论是否新创建了实例,都返回
cls.instance
。这意味着后续每次创建Person
类的对象时,都会得到同一个实例。3.
__init__
方法
__init__
方法在对象创建后被调用,用于初始化对象的属性。- 在
Person
类的__init__
方法中:
- 首先打印
"__init__"
,这里可以看到它在每次创建对象(实际上是获取单例实例)时都会被调用。- 然后将传入的参数
name
赋值给对象的self.name
属性。4. 创建对象和验证单例
- 代码创建了三个
Person
对象:p
、p1
和p2
,它们都使用相同的参数"陈梦"
。- 当创建这些对象时,由于
__new__
方法的实现,实际上只创建了一个Person
类的实例。每次调用Person("陈梦")
都会返回同一个实例。- 最后,
print(p == p1 == p2)
用于验证这三个对象是否是同一个实例。由于单例模式的实现,这里的输出是True
,表明p
、p1
和p2
是同一个对象。
经典案例
案例1:定义一个类描述数字时钟。
import time
# 定义数字时钟类
class Clock(object):
"""数字时钟"""
def __init__(self, hour=0, minute=0, second=0):
"""初始化方法
:param hour: 时
:param minute: 分
:param second: 秒
"""
self.hour = hour
self.min = minute
self.sec = second
def run(self):
"""走字"""
self.sec += 1
if self.sec == 60:
self.sec = 0
self.min += 1
if self.min == 60:
self.min = 0
self.hour += 1
if self.hour == 24:
self.hour = 0
def show(self):
"""显示时间"""
return f'{self.hour:0>2d}:{self.min:0>2d}:{self.sec:0>2d}'
# 创建时钟对象
clock = Clock(00, 00, 00)
while True:
# 给时钟对象发消息读取时间
print(clock.show())
# 休眠1秒钟
time.sleep(1)
# 给时钟对象发消息使其走字
clock.run()
案例2:两点之间距离计算
class Point(object):
"""屏面上的点"""
def __init__(self, x=0, y=0):
"""初始化方法
:param x: 横坐标
:param y: 纵坐标
"""
self.x, self.y = x, y
def distance_to(self, other):
"""计算与另一个点的距离
:param other: 另一个点
"""
dx = self.x - other.x
dy = self.y - other.y
return (dx * dx + dy * dy) ** 0.5
def __str__(self):
return f'({self.x}, {self.y})'
p1 = Point(3, 5)
p2 = Point(6, 9)
print(p1, p2)
print(p1.distance_to(p2))
总结
“路漫漫其修远兮,吾将上下而求索”
恭喜你学会了面向对象,快去试试吧!!!