一、前言
在学习Python的类和对象之前,我们先回顾一下C++的类和对象是如何实现的?
我们通常会创建一个类,类中包含类的成员函数和成员变量,一般我们将想给外界暴露出来的接口函数设定为public,而成员变量和不想暴露给外界的函数设定为private。
下面是最简单的日期类:
class datetime
{
public:
datetime()
{}
~datetime()
{}
int year() { return _year;}
int month() { return _month;}
int day() { return _day;}
private:
int _year;
int _month;
int _day;
};
二、基本概念
类:
抽象出来的模版,不占用内存空间,就当成一个声明即可
实例化对象:
占有内存空间的类对象,每个类对象用到的方法都是一致的,但是类各自的数据可能不一致
类的方法:
无论是C++还是Python,类的方法的参数中都提供了指向实例化对象的关键字
C++中称其为this指针,形参与实参不需要显式传递,类方法中引用成员也不需要显式地写this-> xxx
Python用self表示,self是一个对实例化对象的引用,访问成员用 ‘ . ',需要在形参位置写self,在实参不需要传self,在访问类成员需要显式写 self.XXX
类的方法其它的特性与普通函数一致
三、Python类的实例化 - C++类的实例化
先回顾一下C++中类的实例化,C++的类提供了默认构造函数,或者是我们自己显式地去写构造函数
class datetime
{
public:
// 默认构造函数 -》 无参
datetime()
: _year(0)
, _month(0)
, _day(0)
{}
// 默认构造函数 -》 全缺省
datetime(int year = 2024, int month = 7, int day = 7)
: _year(year)
, _month(month)
, _day(day)
{}
// 我们不写构造函数,编译器自己生成的默认构造
// 构造函数
datetime(int year, int month, int day)
: _year(year)
, _month(month)
, _day(day)
{}
~datetime()
{}
int year() { return _year;}
int month() { return _month;}
int day() { return _day;}
private:
int _year;
int _month;
int _day;
};
Python的类的实例化跟C++的很相似,也是要通过构造函数来完成对每个实例化对象的初始化,只不过Python的构造函数是 __init__()来表示:
class Student(object):
# 参数与普通函数的参数设定形式一致,只不过类方法都多了self
def __init__(self, name, age):
self.name = name
self.age = age
s = Student('dd', 20)
print(s.name)
print(s.age)
四、实例属性和类属性
类属性
归整个类所有 , 每个实例化对象都具有该属性,属性包括成员变量与成员方法,但是每个实例化对象拿到的类属性都是独立的,实例化对象之间对类属性进行修改不影响彼此
如下在类内部定义的成员变量和成员方法就是类属性
class Animal(object):
name = '小丁'
def Run(self):
print('Animal is running ~ ')
a1 = Animal()
print(a1.name)
a2 = Animal()
print(a2.name)
实例属性
针对实例化对象设置的成员变量和成员方法,只可以被当前实例化对象使用;
如下是实例化对象中动态添加的成员变量和成员方法
from types import MethodType
class Student(object):
def __init__(self, name): # 设置名字属性
self.name = name
def printf(self):
print("%s的成绩是%d" % (self.name, self.score))
s = Student('Bob')
s.printf = MethodType(printf, s) # 给实例化对象绑定一个成员方法
s.score = 90 # 添加成员变量
s.printf()
限制可以添加的实例属性
使用__slots__变量来限制可以添加的实例属性,下面的代码就只允许我们添加name和age两个属性,其余属性不允许添加。
- 类属性的变量名不要与__slots__后的变量名冲突
- 只对当前类的实例起作用,不影响继承出来的派生类
- 在派生类中也定义__slots__变量,那派生类可添加的实例属性就是基类和派生类的总和
class Student(object):
__slots__ = ('name', 'age') # 限制Student类只允许有name和age两个属性
def __init__(self, name, age):
self.name = name
self.age = age
def printf(self):
print("%s is %d" % (self.name, self.age))
class Bob(Student):
pass
b = Bob('Bob', 20)
Bob.score = 90 # 不限制派生类的实例化对象的实例属性添加
print(Bob.score)
五、Python的类的自定义成员方法
与C++在思路上一致,类的成员方法无一不是对类的成员变量的增删查改,其实面向对象也基本就是这样的思路,比如人,你要给其一些关键的属性,也就是成员变量,再对成员变量做一些获取、更新的方法,这叫先描述;当一个对象设计好了,我们可以将其组织成不同的数据结构,比如顺序表、链表、哈希表等,来完成不同的功能,这叫再组织。
所以在上面想打印出Student的属性时,我们完全可以将其设计成一个方法,如果后续我们还需要设计其它方法,就直接在类中定义函数即可!
class Student(object):
def __init__(self, name, age): # 参数与普通函数的参数设定形式一致,只不过类方法都多了self
self.name = name
self.age = age
def print_student(self):
print("%s %d" % (self.name, self.age))
s = Student('dd', 20)
s.print_student()
六、访问限制
C++中对访问限制时有强制性规定的,private不允许任何外部来访问,只允许类的内部访问;public允许外部访问;protected其实是专门为继承来设定的,因为protected不允许外部访问,但允许派生类访问。
class Debug
{
public:
// 公有,允许任何人访问
private:
// 私有,只允许内部访问
};
但是Python中没有强制性规定访问的限制,一切都是靠程序员自觉。Python类中提供了一种规定,如果不想被外界访问,可以在成员前添加 ‘ __ ’
class Student(object):
def __init__(self, name, age):
self.__name = name
self.__age = age
def __print_student(self):
print("%s %d" % (self.__name, self.__age))
s = Student('dd', 20)
print(s.__age) #不允许访问
s.__print_student() # 不允许访问
【注】:
不能直接访问
__name
是因为Python解释器对外把__name
变量改成了_Student__name
,所以,我们仍然可以通过_Student__name
来访问__name
变量,那也就说明我们如果对类的实例化对象添加__name属性,是一个新的属性,因为之前我们认为的__name 已经是_Student__name了。但是强烈不推荐访问私有成员,每个Python解释器对私有成员的变量名更改都不一致。
七、继承
被继承的类为基类,继承基类的类为派生类,C++中派生类可以使用基类的protected、public的成员,无法使用基类private的成员(派生类继承了,但无法使用),C++中的继承方式分为public、protected、private。继承方式主要是改变基类成员的访问属性。
但是在Python中没有这些规定,Python中派生类可以继承基类的任何成员,只不过私有成员只在内部访问,公有成员可以在外部访问。
class Animal(object):
def Run(self):
print('Animal is running ~ ')
def __Eat(self):
print('Animal is eating~')
class Dog(Animal):
pass
class Cat(Animal):
pass
animal = Animal()
dog = Dog()
cat = Cat()
animal.Run()
dog.Run()
cat.Run()
八、多态
在C++中多态的条件:
- 必须是基类的指针或引用调用虚函数
- 派生类必须对基类的虚函数完成功能的重写
#include <iostream>
class Animal
{
public:
Animal(){}
~Animal(){}
virtual void Run()
{
std::cout << "Animal is running" << std::endl;
}
private:
};
class Dog : public Animal
{
public:
virtual void Run()
{
std::cout << "Dog is running" << std::endl;
}
private:
};
int main()
{
Animal* dog = new Dog();
dog->Run();
return 0;
}
在Python中就没有这么多规矩了,直接对基类的函数进行功能的重写就行
class Animal(object):
def Run(self):
print('Animal is running ~ ')
class Dog(Animal):
def Run(self):
print('Dgo is running ~')
animal = Animal()
dog = Dog()
animal.Run()
dog.Run()
九、@property
负责将一个成员方法变为属性调用,常用于set或get一个属性
class Student(object):
def __init__(self, name, age, score):
self.name = name
self.age = age
self.__score = score
@property # 读属性
def score(self):
return self.__score
@score.setter # 写属性
def score(self, score):
if not isinstance(score, int):
raise ValueError('score must be a integer')
if score < 0 or score > 100:
raise ValueError('socre must be in [0, 100]')
self.__score = score
s = Student('dd', 20, 80)
s.score = 60 # 转换为 socre(60)
print(s.score) # 转换为 score()
【注】:
属性的方法名不要和实例变量重名,否则在实际调用时,Python解释器会将其解释为方法,导致无限递归访问,从而使栈溢出。