一、什么是类和对象
Python和Java一样,都是面向对象的编程语言,面向对象编程其实是一种封装代码的方法,把一些公共的属性或者方法封装到一个类中,然后再通过这个类可以创建多个对象,最后使用这些对象去调用这些封装起来的公共的属性或者方法。
- 类(Class):用来描述具有相同的属性和方法的对象的集合。类就好比现实生活中的印刷机的模板,这个模板是不能直接被使用的,模板的主要作用是用来创建对象的。
- 对象:是通过类创建出的实例。就好比是印刷机通过模板印刷出来的一张张试卷,这些试卷才是真正被学生们使用的,而代码中也是通过对象进行调用的。
- 方法:在类中定义的函数,和普通的函数所有不同的是,类方法必须有一个额外的第一个参数名称为
self
的参数,这个self
参数代表的是当前对象。 - 构造方法:是类中一个特殊的方法,方法名为
__init__
,主要用来创建类的对象实例的,该方法在创建实例的时候自动调用。可以不写构造方法,默认创建实例不做任操作,也可以在类中写__init__
方法,__init__
方法中可以带参数也可以不带参数,用来做一些初始化设置。 - 类变量:定义在类中且所有方法外部,是所有实例对象的共享变量。类变量可以通过
类名.类变量
或者对象.类变量
的方式进行引用,但是修改只能通过类名.类变量
的方式进行修改,不能通过对象.类变量
来修改。 - 实例变量:定义在类内部,方法内部,以
self.变量名
出现。实例变量属于实例对象私有,只能通过实例对象调用,不能通过类调用。 - 继承:指的是一个派生类(子类)可以继承基类(父类)的属性和方法。通俗点讲,就是你父亲有一栋房,如果你是你父亲的继承人,那么就等于你也拥有这套房,可以随时进去住。
- 方法重写:指的是子类可以改写继承的父类方法。通俗点讲,就是你觉得你父亲盖的这栋房子的装修不符合你的审美,你可以重新进行装修成你喜欢的样子。
二、类的定义以及对象的创建和使用
## 创建一个类
class Person:
class_id = 123456 ## 类变量
def __init__(self, sex): ## 构造方法
self.sex = sex ## 实例变量
def eat(self): ## 普通方法
self.food = "大米" ## 实例变量
print("吃的食物是" + self.food)
def run(self): ## 普通方法
self.class_id = "32145" ## 实例变量
## 通过类实例化对象
man = Person("男")
## 调用类变量,通过类名或者对象名都能调用
print(man.class_id) ## 123456
print(Person.class_id) ## 123456
## 修改类变量时,只能通过 类名.类变量 方式修改,所有实例对象都会随之改变
Person.class_id = 2222
print(man.class_id) ## 2222
print(Person.class_id) ## 2222
## 修改类变量时,如果通过 对象名.类变量 方式修改,是无法修改类变量的值的,其本质实际是给该对象新增了个实例变量
man.class_id = 2222
print(man.class_id) ## 2222
print(Person.class_id) ## 123456
## 调用实例变量
print(man.sex) ## 男
print(Person.sex) ## 报错,通过类名无法调用实例变量
print(man.food) ## 报错,因为man对象再此之前并没有调用eat()方法,所有此时并没有生成这个实例变量
## 调用方法
man.eat() ## 吃的食物是大米
print(man.food) ## 大米
## 实例变量和类变量可以同名,但这种情况下使用类对象将无法调用类变量,它会首选实例变量,这也是不推荐“类变量使用对象名调用”的原因
man.run()
print(man.class_id) ## 32145
## 使用对象名来修改实例变量的值的时候,不会影响类的其它实例化对象,更不会影响同名的类变量
woman = Person("女")
woman.sex = "woman"
print(woman.sex) ## woman
print(man.sex) ## 男
三、实例方法、类方法和静态方法
- 实例方法:通常情况下,在类中定义的方法默认都是实例方法,构造方法也属于一种特殊的实例方法。实例方法最大的特点就是,它最少也要包含一个 self 参数,用于绑定调用此方法的实例对象(Python 会自动完成绑定)。实例方法通常会用类对象直接调用,也支持使用类名调用实例方法,但此方式需要手动给 self 参数传值。
class Person:
def __init__(self, sex):
self.sex = sex
def eat(self):
self.food = "大米"
## 类对象直接调用
man = Person("男")
man.eat()
## 类名调用实例方法,但此方式需要手动给 self 参数传值
Person.eat(man)
- 类方法:类方法和实例方法相似,它最少也要包含一个参数,只不过类方法中通常将其命名为 cls,会自动将类本身绑定给 cls 参数,还有就是类方法需要使用@classmethod修饰符进行修饰。类方法推荐使用类名直接调用,当然也可以使用实例对象来调用(不推荐)。
class Person:
@classmethod
def info(cls):
print(name + ":" + sex)
# 使用类名直接调用类方法
Person.info()
#使用类对象调用类方法
p = Person()
p.info()
- 静态方法:其实就是在普通函数使用@staticmethod注解来修饰。静态方法没有类似 self、cls 这样的特殊参数,因此 Python 解释器不会对它包含的参数做任何类或对象的绑定。也正因为如此,类的静态方法中无法调用任何类属性和类方法。静态方法的调用,既可以使用类名,也可以使用类对象。
class Person:
@staticmethod
def info(name, sex):
print(name + ":" + sex)
## 使用类名调用
Person.info("Tom", "男")
## 使用类对象调用
p = Person("女")
p.info("xiaohong", "nv")
四、封装
封装指的是在设计类时,刻意地将一些属性和方法隐藏在类的内部,这样就无法在外部通过类对象直接访问,只能在本类中使用。封装的主要目的是w为了保证类内部数据结构的完整性,避免外部对内部数据的影响,提高了程序的可维护性。除此之外,对类进行良好的封装,还可以提高代码的复用性。
- 类如何进行封装
Python 类中的变量和函数在进行封装的时候不像Java封装的使用的(public、private)等修饰符,Python 没有使用修饰符,而是在变量名或者函数名左边添加__
双下划线来区分,没有__
则表示是公有的(类似 public 属性),有__
则表示是私有的(类似 private)。Java中封装使用的修饰符有四种,而Python 中只只有两种,要么是公有的,要么是私有的。公有属性的类变量和类函数,在类的外部、类内部以及子类中,都可以正常访问;私有属性的类变量和类函数,只能在本类内部使用,类的外部以及子类都无法使用。
class Test:
__id = 1
name = "haha"
def __get_id(self):
print(self.__id)
def set_id(self, id):
self.__id = id
print(self.__id)
五、继承
继承指的是子类(派生类)会继承父类(基类)属性和方法。Python 支持多继承,使用多继承经常需要面临的问题是,多个父类中包含同名的类方法该继承哪个父类的方法,Python 的处置措施是:根据子类继承多个父类时这些父类的前后次序决定,即排在前面父类中的类方法会覆盖排在后面父类中的同名类方法。
class Test1:
__id = 1
name = "Tom"
def __init__(self):
self.sex = "nan"
print("执行Test1")
def __get_id(self):
print("get Test1 中的id: " + str(self.__id))
def set_id(self, id):
self.__id = id
print("set Test1 中的id: " + str(self.__id))
class Test2:
__id = 2
name = "Jack"
def __get_id(self):
print("get Test2 中的id: " + str(self.__id))
def set_id(self, id):
self.__id = id
print("set Test2 中的id: " + str(self.__id))
## 创建一个子同时类继承Test1和Test2
class TestSub(Test1, Test2):
def __init__(self):
pass
## 创建一个子类对象
s = TestSub()
1.通过子类对象可以直接调用父类的公有属性和方法,在多继承且父类中存在同名属性或者同名方法时使用最左边的父类中的属性或者方法
print(s.name) ## Tom
s.set_id(3) ## set Test1 中的id: 3
2.子类中包含__init__方法时,如果不手动写名,子类不会去调用父类中的__init__()方法
print(s.sex) ## 会报错,AttributeError: 'TestSub' object has no attribute 'sex'
3.如需继续调用父类的 __init__() 方法,必须在子类的 __init__() 中添加对父类 __init__() 方法的调用,有两种方式
## 方式一:通过父类名直接调用
class TestSub(Test1, Test2):
def __init__(self):
Test1.__init__(self)
pass
## 方式二:通过super()方法调用
class TestSub(Test1, Test2):
def __init__(self):
super().__init__()
pass
4.父类与子类__init__方法的执行顺序,是按照子类的__init__方法从上往下顺序执行代码
class TestSub(Test1, Test2):
def __init__(self):
super().__init__()
print("执行TestSub")
pass
## 执行结果为:
执行Test1
执行TestSub
class TestSub(Test1, Test2):
def __init__(self):
print("执行TestSub")
super().__init__()
pass
## 执行结果为:
执行TestSub
执行Test1
5.重写父类的__init__方法
class TestSub(Test1, Test2):
def __init__(self):
print("执行TestSub")
super().__init__()
self.sex = "nv"
pass
## 通过子类对象调用重写过的父类属性
s = TestSub()
print(s.sex) ## nv
6.强制调用父类私有属性或者私有方法,子类不能够直接调用父类中的私有方法,
其实是编译器把这个方法的名字改成了"_父类名__私有方法名()"的形式,如果强制调用,可以如下所示:
class TestSub(Test1):
def get_id(self):
print(super()._Test1__id)
super()._Test1__get_id()
s = TestSub()
s.get_id()
## 调用结果如下:
1
get Test1 中的id: 1
六、多态
多态其实指的是父类可以存在多种类型的子类,调用的时候则是根据实际的类型去调用该类型的属性或者方法。就好比生活中可以把动物当作是一个父类,那么动物可以有很多种,比如:猫、狗它们都属于不同的种类的动物,这些不同的种类就可以称为多态。多态最主要的两个特征就是:
(1)继承:多态一定是发生在子类和父类之间;
(2)重写:子类重写了父类的方法。
1.首先定义一个动物类当作父类,并且定义动物都具有吃东西的方法
class Animal:
def eat(self):
print("动物都会吃东西")
2.再定义一个狗类继承动物类,并重写动物类的吃东西的方法,改成狗吃的是狗粮
class Dog(Animal):
def eat(self):
print("狗吃的是狗粮")
2.再定义一个猫类继承动物类,并重写动物类的吃东西的方法,改成猫吃的是猫粮
class Cat(Animal):
def eat(self):
print("猫吃的是猫粮")
## 当我们(相当于变量a)问动物会不会吃东西的时候,这时就会调用Animal类的eat()方法
a = Animal()
a.eat() ## 动物都会吃东西
## 当我们(相当于变量a)狗吃什么的时候,这时就会调用Dog类的eat()方法
a = Dog()
a.eat() ## 狗吃的是狗粮
## 当我们(相当于变量a)猫吃什么的时候,这时就会调用Cat类的eat()方法
a = Cat()
a.eat() ## 猫吃的是猫粮