一、基本概念
类(class):用来描述具有相同属性和方法的对象的集合。它定义了该集合中每个对象所共有的属性和方法;
对象:对象是类的实例,例如:人是一个类,小明是一个人,类的一个特例;
实例变量:通过构造方法初始化的属性,必须实例化之后才能使用,与实例绑定;
类变量:定义在类中的变量,不需要实例化就可以使用,与类绑定;
成员方法:类中定义的函数;
类方法:通过装饰器@classmethod
定义的函数,与类变量类似;
静态方法:通过装饰器@staticmethod
定义的函数,因为没有第一个初始参数,所以不能访问类的成员变量和成员方法;
方法重写:如果从父类继承的方法不能满足子类的需求,可以对其进行改写,这个过程叫做方法的覆盖(override),也称为方法的重写;
继承:指一个派生类(Derived Class)继承基类(Base Class)的成员变量和成员方法。继承也允许把一个派生类的对象作为一个基类对象对待。
二、类
2.1、类的定义
语法:
class [类名]:
[语法块]
程序示例:
class EmptyClass:
pass
2.2、类的使用
使用类之前需要实例化列,类实例化后会成为对象,创建对象和创建变量类似,需要先声明对象是哪个类,同时指明变量名称。
class EmptyClass:
pass
empty = EmptyClass()
print(type(empty))
2.3、类的构造方法
在创建实例时,很多类可能需要有特定的初始状态,所以一个类可以定义一个特殊的方法,叫做构造函数。在Python中,构造函数就是类的__init__(self)
(注意双下划线)方法。
class Dog:
def __init__(self):
print("汪汪汪!");
dog = Dog()
注意:
- 当一个类定义了
__init__
方法,类在实例化时会自动调用__init__
方法,用于创建新的类实例; - 在Python中,除了静态方法和类方法外,所有方法第一个参数都是"self",不能漏掉;
- 第一个参数"self"指的是实例本身,相当于C++中的"this"指针;
- 构造方法的返回值必须是"None",否则虽然解释时不报错,但是实例化的时候会报错;
2.4、类中的属性
在构造方法中可以初始化一些属性,例如:
class Dog:
def __init__(self, name):
self.name = name
self.age = 3
dog = Dog("旺财")
print(dog.name)
print(dog.age)
注意:
- 属性必须用
self
加点的方式赋值,不能直接定义变量。直接定义变量的生命周期只会在函数内,函数执行完变量就会被销毁;
2.5、类中的方法
在类中定义的函数称之为方法,例如:
class Dog:
def __init__(self, name):
self.name = name
def play(self):
print("汪汪汪!我是",self.name)
dog = Dog("旺财")
dog.play()
注意:
- 类中的方法和函数定义的方法基本相同,但是方法必须要定义在类中,并且第一个参数必须是"self";
2.6、私有属性
在构造函数中定义了属性,实例可以轻松地获取和修改属性的值。但是有时候我们需要限定实例随意地修改属性,这时候就要用到私有属性。
定义私有属性
只需要在定义属性名字的时候使用双下划线开头
即可,Python解释器就人为这个属性是私有的,外部不能随便访问这个属性。
class Dog:
def __init__(self, name):
self.__name = name
def play(self):
print("汪汪汪!我是",self.__name)
dog = Dog("旺财")
dog.play()
# 错误,私有属性不能用对象访问,需要通过成员方法访问
print(dog.__name)
私有属性常用于限制
用户随意
的修改属性值
,而需要遵循成员方法定下的规矩修改,例如:
class Dog:
def __init__(self, name):
self.__name = name
self.__age = None
print(self.__name,"生成成功")
def set_age(self, age):
if not isinstance(age,int):
print("输入的年龄必须是数字")
return False
if age <=0:
print("年龄必须大于0!")
return False
self.__age = age
def play(self):
print("汪汪汪!我今年", self.__age, "岁")
dog = Dog("旺财")
dog.set_age("Hello")
dog.set_age(-20)
dog.set_age(3)
dog.play()
在这个例子中,__age
是私有属性,实例化后只能通过set_age
方法设置年龄。在set_age
方法中,我们限制了__age
只能是int,并且其值要大于0,有效地限制了实例化后数据的内容,保证了__age
是一个有效可用的数据。
2.7、私有方法
私有方法和私有属性类似,只能在类内部被调用,实例不能直接调用,私有方法的定义
同样是以双下划线
作为方法名的开头
。
class Dog:
def __init__(self, name):
self.__name = name
self.__age = None
print(self.__name,"生成成功")
def set_age(self, age):
if not isinstance(age,int):
print("输入的年龄必须是数字")
return False
if age <=0:
print("年龄必须大于0!")
return False
self.__age = age
def __test(self):
print("这是一个私有方法")
print("只能在类内部调用")
print("对象无法调用")
def play(self):
print("汪汪汪!我今年", self.__age, "岁")
dog = Dog("旺财")
dog.set_age(3)
dog.play()
# 错误,不能通过对象调用私有方法
dog.__test();
三、继承
继承,是一种对类进行分层级划分的概念。继承的基本思想是在一个类的基础上制定出一个新的类,这个新的类不仅可以继承原来类的属性和方法,还可以增加新的属性和方法。原来的类称为父类,新的类称为子类。
语法:
class Animal:
pass
class Dog(Animal):
pass
class Cat(Animal):
pass
定义要从哪个类继承,只需要在定义子类的名字后面的括号中填入父类名字,如果有多个父类,则用逗号,
隔开。
注意:
- 在继承中,如果子类定义了构造方法,则父类的构造方法
__init__
不会被自动调用,需要在子类的构造函数中专门调用:super(Dog, self).__init__("旺财")
; - 子类不能继承父类中的私有方法,也不能调用父类的私有方法;
四、多态
继承可以帮助我们重复使用代码,但是有时候子类的行为不一定完全和父类一样,例如:
class Animal:
def say(self):
print("Animal")
class Dog(Animal):
pass
class Cat(Animal):
pass
dog = Dog()
dog.say()
cat = Cat()
cat.say()
# 输出
# Animal
# Animal
我们的目的是想让Dog
和Cat
在调用say方法是输出不同的内容,狗应该“汪汪汪!”,猫应该“喵喵喵”,可以用过重写(覆盖)父类方法来实现:
class Animal:
def say(self):
print("Animal")
class Dog(Animal):
def say(self):
print("汪汪汪!")
class Cat(Animal):
def say(self):
print("喵喵喵")
dog = Dog()
dog.say()
cat = Cat()
cat.say()
# 输出
# 汪汪汪!
# 喵喵喵
多态:当子类和父类存在相同的方法时,子类的方法会覆盖父类的方法,这样代码在运行时总会调用子类的方法,这就是多态。多态的意思就是多种形态,多态意味着即使不知道变量所引用的对象是什么类型,也能对对象进行操作,对象会根据类的不同而表现出不同的行为。
判断是不是某个对象,可以使用isinstance(对象, 类)
函数,例如:isinstance(dog, Dog)
,是则返回True,否则返回False;
子类对象既是子类对象,又是父类对象,所以常用父类对象作为函数形参,这样可以实现不修改函数就能直接被子类使用,例如:
class Animal:
def say(self):
print("Animal")
class Dog(Animal):
def say(self):
print("汪汪汪!")
class Cat(Animal):
def say(self):
print("喵喵喵")
def animal_say(animal:Animal):
animal.say()
dog = Dog()
cat = Cat()
animal_say(dog)
animal_say(cat)
# 输出
# 汪汪汪!
# 喵喵喵
五、鸭子类型
在程序设计中,鸭子类型(Duck Typing)
是动态类型的一种风格。在这种风格中,一个对象有效的语义,不是由继承自特定的类或实现特定的接口决定的,而是由当前方法和属性的集合决定的。
在鸭子类型中,我们关注的不是对象的类型本身,而是它如何使用。例如:
在不使用鸭子类型的语言中,我们可以编写一个函数,使它接受一个为鸭子的对象,并调用它的“走和叫”方法;
在使用鸭子类型的语言中,这样的一个函数可以接受一个任意类型的对象,并调用它的“走和叫”方法;
如果这些需要被调用的方法不存在,那么将引发一个运行时的错误。任何拥有这样正确的“走和叫”方法的对象都可以被函数接受。
例如:
class Dog:
def say(self):
print("汪汪汪!")
class Cat:
def say(self):
print("喵喵喵")
def animal_say(animal):
animal.say()
dog = Dog()
cat = Cat()
animal_say(dog)
animal_say(cat)
在这个例子中,Dog
和Cat
并没有从Animal
继承下来,但是都各自实现了say
方法,这也是多态的一种实现方式。因为Python是一门动态语言,在函数没有被执行的时候,程序并不能断定数据的类型(虽然可以用函数注释做限制,但是这并不是强制性的),只有执行到相关的语句时才真的去调用相关的方法。虽然我们没有使用“继承”,但是仍然可以实现多态。
六、类变量和实例变量
实例变量
是在构造方法中初始化的属性,实例变量必须实例化之后才能使用,相当于绑定在实例上;
类变量
是定义在类中的变量,不需要实例化就能直接使用,相当于绑定在类上;
注意:
- 类变量也可以被实例调用,但是实例不能修改类变量,如果实例对类变量进行修改,Python解释器会新建一个同名的成员变量来代理实例中的类变量;
class Dog:
# 类变量
a = 10
def __init__(self, name):
# 实例变量
self.__name = name
self.__age = None
print(self.__name,"生成成功")
def set_age(self, age):
if not isinstance(age,int):
print("输入的年龄必须是数字")
return False
if age <=0:
print("年龄必须大于0!")
return False
self.__age = age
def play(self):
print("汪汪汪!我今年", self.__age, "岁")
print(Dog.a)
dog = Dog("旺财")
print(dog.a)
七、静态方法与类方法
静态方法和类变量有点类似,静态方法在定义类时就已经被分配定义好了。静态方法并不绑定类也不绑定实例,相当于给方法添加一个前缀。定义静态方法将引入一个新的概念 – 装饰器。
7.1、静态方法
class Animal:
name = "动物"
# 注意,装饰器和函数定义之间不能有空行
@staticmethod
def play():
print("playing")
Animal.play()
注意:
- 定义静态方法的语法就是在定义函数的上面一行(不能有空行)添加一句
@staticmethod
; - 静态方法不再有第一个默认参数
self
,所以静态方法本身也不能调用成员变量和成员方法; - 静态方法不需要实例化之后使用,和类变量一样直接使用即可,其他的和一般函数没有任何区别;
7.2、类方法
类方法,就是该方法绑定在定义的类上面,而不是绑定在实例上。
class Animal:
name = "动物"
# 注意,装饰器和函数定义之间不能有空行
@classmethod
def play(cls):
print(cls.name, "playing")
Animal.play()
注意:
- 定义静态方法的语法就是在定义函数的上面一行(不能有空行)添加一句
@classmethod
; - 和静态方法不同的是,类方法和成员方法一样都有一个初始的参数,但是这个参数不同于成员方法;
- 成员方法的第一个参数指向的是实例,而类方法指向的则是定义的类本身,所以类方法可以读取和修改类变量;