Python基础(类与对象)
一、编程的两大思想
面向过程和面向对象
面向过程 | 面向对象 | |
---|---|---|
区别 | 事物比较简单,可以用线性的思维去解决 | 事物比较复杂,使用简单的线性思维无法解决 |
共同点:面向对象和面向过程都是解决实际问题的一种思维方式
二者相辅相成,并不是对立的,解决复杂问题,通过面向对象方式便于我们从宏观上把握事物之间复杂的关系,方便我们分析整个系统;具体到微观操作,仍然使用面向过程方式来处理
二、类
类别,分门别类,物以类聚,人类,鸟类,动物类,植物类…
- 类时多个类似事物组成的群体的统称。能够帮助我们快速理解和判断事物的性质
三、定义Python中的类
创建类的语法
class Student: #Student为类的名称(类名),由一个或多个单词组成,每个丹迪的首字母大写,其余小写
pass
class Student: #Student为类的名称(类名),由一个或多个单词组成,每个丹迪的首字母大写,其余小写
pass
class Student: #Student为类的名称(类名),由一个或多个单词组成,每个丹迪的首字母大写,其余小写
pass
print(id(Student))
print(type(Student))
print(Student)
输出:
1512775104816
<class 'type'> # 表明是class类型
<class '__main__.Student'>
类的组成
- 类属性
- 实例方法
- 静态方法
- 类方法
class Student:
native_pace='吉林' #直接写在类里的变量,称为类属性
def __init__(self,name,age):
self.name=name #self.name,称为实体属性,进行了一个赋值的操作,将局部变量的name的值赋给实体属性
self.age=age
#实例方法
def eat(self):
print('学生在吃饭...')
#静态方法
@staticmethod
def method():
print('我使用了staticmethod进行修饰,所以我是静态方法')
#类方法
@classmethod
def cm(cls):
print('我是类方法,因为我使用了classmethod进行修饰')
#在类之外定义的称为函数,在类之内定义的称为方法
def drink():
print('喝水')
四、对象的创建
- 对象的创建又称为类的实例化
- 语法:
- 实例名=类名
- 例子:
- stu=Student()
- 意义:有了实例,就可以调用类中的内容
#创建Student类的对象
stu1=Student('张三',20)
print(id(stu1))
print(type(stu1))
print(stu1)
输出:
1668138946320 #转换成十六进制就是 18464D44F10
<class '__main__.Student'>
<__main__.Student object at 0x0000018464D44F10>
查看类对象
print(id(Student))
print(type(Student))
print(Student)
输出:
2460596968352
<class 'type'>
<class '__main__.Student'>
对比上面实例对象和类对象的不同
五、使用对象
调用类的方法和属性
#创建Student类的对象
stu1=Student('张三',20)
stu1.eat() #对象名.方法名()
print(stu1.name) #调用实例对象的属性
print(stu1.age)
print('--------')
Student.eat(stu1) #这一行与上面调用eat()方法的功能相同,都是调用Student的eat()方法
#类名.方法名(类的对象)-->实际上就是方法定义处的self
输出:
学生在吃饭...
张三
20
--------
学生在吃饭...
对比两种调用方法的方式的不同,其实功能是相同的
六、类属性_类方法__类静态方法
类属性:类中方法外的变量称为类属性,被该类的所有对象所共享
类方法:使用@classmethod修饰的方法,使用类名直接访问的方法
静态方法:使用@staticmethod修饰的方法,使用类名直接访问的方法
print(Student.native_place) #访问类属性
Student.cm() # 调用类方法
Student.sm() # 调用静态方法
类属性的使用方式
#类似属性的使用方式
print(Student.native_pace)
stu1=Student('李四',20)
stu2=Student('王五',22)
print(stu1.native_pace)
print(stu2.native_pace)
#修改类属性
print('-------------------')
Student.native_pace='天津'
print(stu1.native_pace)
print(stu2.native_pace)
吉林
吉林
吉林
-------------------
天津
天津
表明类属性是共享的
类方法的使用方式
print('-----类方法的使用------')
Student.cm()
输出:
-----类方法的使用------
我是类方法,因为我使用了classmethod进行修饰
类方法
@classmethod
def cm(cls):
print('我是类方法,因为我使用了classmethod进行修饰')
类静态方法的使用
print('-----静态方法的使用------')
Student.method()
输出:
-----静态方法的使用------
我使用了staticmethod进行修饰,所以我是静态方法
静态方法
#静态方法
@staticmethod
def method():
print('我使用了staticmethod进行修饰,所以我是静态方法')
七、动态绑定属性和方法
python是动态语言,在创建对象之后,可以动态地绑定属性和方法
'''
动态绑定属性和方法
'''
class Student:
def __init__(self,name,age):
self.name=name
self.age=age
def eat(self):
print(self.name+'在吃饭')
stu1=Student('lisi',20)
stu2=Student('李四',30)
print(id(stu1))
print(id(stu2))
一个Student类可以创建N多个Student类的实例对象,每个实体对象的属性值不同
为某个实例对象动态绑定属性
stu1.gender='女'
print(stu1.gender)
输出:女
与java对比,属性可以在对象上动态增加
为实例对象绑定方法
类之外的方法被称为函数,将函数绑定给的对象
'''
动态绑定属性和方法
'''
class Student:
def __init__(self,name,age):
self.name=name
self.age=age
def eat(self):
print(self.name+'在吃饭')
stu1=Student('lisi',20)
stu2=Student('李四',30)
print(id(stu1))
print(id(stu2))
stu1.gender='女'
print(stu1.gender)
#绑定方法
def show():
print('定义在类之外的,称为函数')
stu1.show=show
stu1.show()
输出:
2243355037648
2243355037552
女
定义在类之外的,称为函数
八、面向对象的三大特征
- 封装:提高程序的安全性
- 将数据(属性)和行为(方法)包装到类对象中。在方法内部对属性进行类对象的外部调用方法。这样,无需关系方法内部的具体实现细节,从而降低复杂度。
- 在Python中没有专门的修饰符用于属性的私有,如果该属性不希望在类对象外部被访问,前边使用两个”_“。
- 继承:提高代码的复用性
- 多态:提高程序的可扩展性和可维护性
九、封装
class Car:
def __init__(self,brand):
self.brand=brand
def start(self):
print('汽车已启动...')
car=Car('宝马')
car.start()
print(car.brand)
输出:
汽车已启动...
宝马
这样就在类之外使用了封装的属性和方法
私有属性
class Student:
def __init__(self,age):
self.set_age(age)
def set_age(self,age):
if 0<=age<=120:
self.__age=age
else:
self.__age=18
def get_age(self):
return self.__age
stu1=Student(150)
stu2=Student(30)
print(stu1.get_age())
print(stu2.get_age())
私有属性
class Student:
def __init__(self,name,age):
self.name=name
self.__age=age #年龄不希望被外部使用,所以加了两个__
def show(self):
print(self.name,self.__age)
使用时:
stu1=Student('张三',21)
print(stu1.__age)
报错:
print(stu1.__age)
AttributeError: 'Student' object has no attribute '__age'
stu1=Student('张三',21)
# print(stu1.age)
stu1.show()
输出:
张三 21
在被隐藏的情况下查看私有属性
print(dir(stu1))
输出:
['_Student__age', '__class__', '__delattr__', '__dict__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', '__gt__', '__hash__', '__init__', '__init_subclass__', '__le__', '__lt__', '__module__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__', '__weakref__', 'name', 'show']
这样就可以看到有哪些属性和方法
没有找到__age属性,但是找到一个 ‘_Student__age’
print(stu1._Student__age)
输出:21
在类的外面依然可以使用,只是要麻烦一点
十、继承
语法格式
class 子类类名(父类1,父类2…) :
pass
- 如果一个类没有继承任何类,则默认继承object
- Python支持多继承
- 定义子类时,必须在其构造函数中调用父类的构造函数
定义类:
子类调用父类方法 super()
class Person(object):
def __init__(self,name,age):
self.name=name
self.age=age
def info(self):
print(self.name,self.age)
class Student(Person):
def __init__(self,name,age,stu_no):
super().__init__(name,age)
self.stu_no=stu_no
class Teacher(Person):
def __init__(self,name,age,teacherofyear):
super().__init__(name,age)
self.teacherofyear=teacherofyear
定义实例对象:
stu=Student('张三',20,'1001')
teacher=Teacher('李四',34,10)
调用:info()方法继承自Person类
stu.info()
teacher.info()
输出:
张三 20
李四 34
多继承
class A(object):
pass
class B(object):
pass
class C(A,B):
pass
与Java不同,Java只能单继承,而Python可以搞多继承
方法重写
- 如果子类对继承自父类的某个属性或方法不满意,可以在子类中对其(方法体)进行重新编写
- 子类重写后的方法中可以通过super().xxx() 调用父类中被重写的方法
class Person(object):
def __init__(self,name,age):
self.name=name
self.age=age
def info(self):
print(self.name,self.age)
class Student(Person):
def __init__(self,name,age,stu_no):
super().__init__(name,age)
self.stu_no=stu_no
def info(self):
super().info()
print(self.stu_no)
stu=Student('张三',25,'1001')
stu.info()
子类重写了父类的info()方法
输出:
张三 25
1001
十一、object类
-
object类是所有类的父类,因此所有类都有object类的属性和方法。
-
内置函数dir()可以查看指定对象的所有属性
-
Object有一个 " __ str __ () " 方法,用于返回一个对于“对象的描述” ,对应于内置函数str()
经常用于print()方法,帮我们查看对象的信息,所以我们经常会对 __ str __ () 进行重写
class Student: pass stu=Student() print(dir(stu))
查看对象的所有属性
['__class__', '__delattr__', '__dict__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', '__gt__', '__hash__', '__init__', '__init_subclass__', '__le__', '__lt__', '__module__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__', '__weakref__']
打印对象
print(stu)
输出:
<__main__.Student object at 0x000001EF126D58E0>
查看对象信息
先重写方法 __ str __ ()
class Student:
def __init__(self,name,age):
self.name=name
self.age=age
def __str__(self):
return '我的名字是{0},今年{1}岁'.format(self.name,self.age)
stu=Student('张三',26)
print(dir(stu))
print(stu)
输出:
['__class__', '__delattr__', '__dict__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', '__gt__', '__hash__', '__init__', '__init_subclass__', '__le__', '__lt__', '__module__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__', '__weakref__', 'age', 'name']
我的名字是张三,今年26岁
当你重写了 str 方法后,你再打印对象,就不会再打印对象的内存地址,而是调用 str 函数
str 方法经常重写,用于返回对象的描述
查看类型
print(type(stu))
<class '__main__.Student'>
会是创建时的子类类型,而不是object
十二、多态
简单地说,多态就是“具有多种形态”,它指的是:即便不知道一个变量所引用的对象到底是什么类型,仍然可以通过这个变量调用方法,在运行过程中根据变量所引用对象的类型,动态决定调用哪个对象中的方法。
class Animal(object):
def eat(self):
print('动物会吃')
class Dog(Animal):
def eat(self):
print('狗吃骨头')
class Cat(Animal):
def eat(self):
print('猫吃鱼')
class Person(object):
def eat(self):
print('人吃五谷杂粮')
#定义一个函数
def fun(obj):
obj.eat()
#开始调用函数
fun(Cat())
fun(Dog())
fun(Animal())
fun(Person())
输出:
猫吃鱼
狗吃骨头
动物会吃
人吃五谷杂粮
这样,就根据传入的对象类型,进行不同的输出
没有继承,也能实现多态,跟Java有区别
静态语言和动态语言
- 静态语言和动态语言关于多态的区别
- 静态语言实现多态的三个必要条件
- 继承
- 方法重写
- 父类引用指向子类对象
- 静态语言实现多态的三个必要条件
- 动态语言的多态崇尚“鸭子类型” 当看到一只鸟走起来像鸭子、游泳起来像鸭子、收起来也像鸭子,那么这只鸟就可以被称为鸭子。在鸭子类型中,不用关心对象时什么类型,到底是不是鸭子,只关心对象的行为。
十三、特殊方法和特殊属性
名称 | 描述 | |
---|---|---|
特殊属性 | __ dict __ | 获得类对象或实例对象所绑定的所有属性和方法的字典 |
以下为特殊方法 | __ len __ () | 通过重写 __ len __ () 方法,让内置函数len() 的参数可以是自定义类型 |
__ add __ () | 通过重写 __ add __ () 方法,可以使自定义对象具有 “+” 功能 | |
__ new __ () | 用于创建对象 | |
__ init __ () | 对创建的对象进行初始化 |
特殊属性
- __ dict __
class A:
pass
class B:
pass
class C(A,B):
def __init__(self,name,age):
self.name=name
self.age=age
def roar(self):
print('C is roar')
#创建C类的对象
x=C('Jack',20)
print(x.__dict__) #实例对象的属性字典
print(C.__dict__) #类对象的属性和方法字典
#该部分代码,后续的几个方法 里 x C 都指的这里面的
输出:
{'name': 'Jack', 'age': 20}
{'__module__': '__main__', '__init__': <function C.__init__ at 0x00000199020715E0>, 'roar': <function C.roar at 0x00000199022821F0>, '__doc__': None}
__ class __
print(x.__class__) #输出实例对象所属的类
输出:
<class '__main__.C'>
__ bases __
print(C.__bases__) # 输出类的父类的元祖,因为可能不止继承一个,只看上一级
输出:
(<class '__main__.A'>, <class '__main__.B'>)
__ base __
print(C.__base__) # 输出类的父类,如果有多个,就只输出第一个,只看上一级
输出:
<class '__main__.A'>
__ mro __
print(C.__mro__) # 输出类的层级关系
输出:
(<class '__main__.C'>, <class '__main__.A'>, <class '__main__.B'>, <class 'object'>)
__ subclasses __
print(A.__subclasses__()) # 输出类的子类
class D(A):
pass
print(A.__subclasses__())
输出:
[<class '__main__.C'>, <class '__main__.D'>]
输出的是子类的列表
特殊方法
-
__ add __()方法
- 让两个对象相加
a=20 b=100 c=a+b d=a.__add__(b) print(c) print(d)
输出:
120
120
这里相加的都是整数
但是,如果是两个自定义的实例对象呢
class Student:
def __init__(self,name):
self.name=name
# def __add__(self, other):
# return self.name + other.name
stu1=Student('张三')
stu2=Student('李四')
s= stu1+stu2
报错:
s= stu1+stu2
TypeError: unsupported operand type(s) for +: 'Student' and 'Student'
但是把 __ add __() 方法放开:
class Student:
def __init__(self,name):
self.name=name
def __add__(self, other):
return self.name + other.name
stu1=Student('张三')
stu2=Student('李四')
s= stu1+stu2
print(stu1+stu2)
print(stu1.__add__(stu2))
输出:
张三李四
张三李四
在定义过后,就可以相加了
要实现两个对象的加法运算,需要在两个对象的父类编写 __ add __() 特殊方法
__ len __() 方法
输出对象的长度
lst=[22,33,55,66]
print(len(lst))
print(lst.__len__())
输出:
4
4
len(lst) 这里使用的是内置函数,lst. __ len __() 列表对象本来就有这个方法
但是:问题是,自定义的对象没有
上面定义的 stu1 不能使用这个方法,必须自定义
class Student:
def __init__(self,name):
self.name=name
def __len__(self):
return len(self.name)
stu1=Student('张三')
stu2=Student('李四')
print(stu1.__len__())
输出:
2
stu2=Student('李四2')
print(stu2.__len__())
输出:
3
十四、__ new __ 与 __ init __ 演示创建对象的过程
class Person(object):
def __new__(cls, *args, **kwargs):
print('__new__被执行了,cls的id值为{0}'.format(id(cls)))
obj=super().__new__(cls)
print('创建的对象的id为:{0}'.format(id(obj)))
return obj
def __init__(self, name, age):
print('__init__被调用了,self的id值为:{0}'.format(id(self)))
self.name=name
self.age=age
print('object这个类对象的id为:{0}'.format(id(object)))
print('Person这个类对象的id为:{0}'.format(id(Person)))
#创建Person类的实例对象
p1=Person('张三',20)
print('p1这个Person类的实例对象的id:{0}'.format(id(p1)))
输出:
object这个类对象的id为:140715602066944
Person这个类对象的id为:3186528480864
__new__被执行了,cls的id值为3186528480864
创建的对象的id为:3186530217744
__init__被调用了,self的id值为:3186530217744
p1这个Person类的实例对象的id:3186530217744
十五、类的浅拷贝与深拷贝
变量的赋值操作
- 只是形成两个变量,实际上还是指向同一个对象
class CPU:
pass
class DISK:
pass
class Computer:
def __init__(self,cpu,disk):
self.cpu=cpu
self.disk=disk
#(变量的赋值)
cpu1=CPU()
cpu2=cpu1
print(cpu1,id(cpu1))
print(cpu2,id(cpu2))
输出:
<__main__.CPU object at 0x0000013C8AB45FD0> 1359536742352
<__main__.CPU object at 0x0000013C8AB45FD0> 1359536742352
其实这个过程中,只生成了一个CPU实例对象,然后把cpu1 指向 这个实例对象的地址赋值给了cpu2
也就是说cpu1,cpu2指向的都是同一个对象
浅拷贝
-
Python拷贝一般都是浅拷贝,拷贝时,对象包含的子对象内容不拷贝,因此,源对象和拷贝对象会引用同一个子对象
class CPU: pass class DISK: pass class Computer: def __init__(self,cpu,disk): self.cpu=cpu self.disk=disk #(变量的赋值) cpu1=CPU() cpu2=cpu1 print(cpu1,id(cpu1)) print(cpu2,id(cpu2)) #(2)类有浅拷贝 print('----------------------') disk=DISK() #创建一个硬盘类的对象 computer=Computer(cpu1,disk) # 创建一个计算机类的对象 #浅拷贝 import copy computer2=copy.copy(computer) print(computer,computer.cpu,computer.disk) print(computer2,computer2.cpu,computer2.disk)
输出:
<__main__.CPU object at 0x00000295212D6FD0> 2839530008528
<__main__.CPU object at 0x00000295212D6FD0> 2839530008528
----------------------
<__main__.Computer object at 0x00000295212D6970> <__main__.CPU object at 0x00000295212D6FD0> <__main__.DISK object at 0x00000295212D6F70>
<__main__.Computer object at 0x00000295212D6880> <__main__.CPU object at 0x00000295212D6FD0> <__main__.DISK object at 0x00000295212D6F70>
computer和computer2 指向的CPU和DISK实例对象是一样的,这两个子对象指向是不变的,这样的叫浅拷贝
深拷贝
-
使用copy模块的deepcopy函数,递归拷贝对象中包含的子对象,源对象和拷贝对象所有的子对象也不相同
#深拷贝 computer3=copy.deepcopy(computer) print(computer,computer.cpu,computer.disk) print(computer3,computer3.cpu,computer3.disk)
输出:
<__main__.Computer object at 0x00000252D49A6970> <__main__.CPU object at 0x00000252D49A6FD0> <__main__.DISK object at 0x00000252D49A6F70>
<__main__.Computer object at 0x00000252D49A6790> <__main__.CPU object at 0x00000252D49A64C0> <__main__.DISK object at 0x00000252D49A64F0>
可以看到,computer深拷贝后,computer3实例对象包含的cpu和disk对象地址都跟computer包含的不一样了,不仅是computer对象拷贝了,连子对象cpu和disk对象也拷贝了