目录
1、前言
在Python中,面向对象编程(Object-Oriented Programming,简称OOP)是一种程序设计范式,它通过创建类(Class)和对象(Object)来组织代码。而面向对象编程有几个基本的特性:封装,继承,多态。
2、类和对象
前言中提到了面向对象编程是通过类和对象来组织代码,那么肯定要先来了解下这两个最重要的概念,类和对象。
- 类,是一种用于创建对象的蓝图或抽象的模板。比如Animal类,Car类等。
- 对象,根据抽象模板创建出来的具体的对象(或称该对象是类的实例),每个对象都拥有相同的方法,但各自数据可能不同。比如根据Animal类创建的对象有cat,dog等。
2.1、定义类
要定义一个类,使用关键字class,后跟类的名称。类中通常包含属性(变量)和方法(函数)。
python
class Animal:
def __init__(self, name, foot):
self.name = name
self.foot = foot
通过class关键字定义了动物类Animal,同时绑定了2个基本属性name和foot。此后通过该类创建的对象,都必须强制传入这两个属性。类中定义的__init__方法是该类的构造方法,也就是构造该实例的时候需要传入name和foot两个属性共同创建该对象。
注:init 前后是两个_
2.2、定义方法
定义完类之后,我们需要给当前类定义一个方法。面向对象前面提到了其中有一个特性便是封装,而定义的方法便是要对我们的变量数据进行封装。我们在类中定义的name和foot属性,只有在Animal类内部才能访问,其他类是访问不到的。
python
class Animal:
def __init__(self, name, foot):
self.name = name
self.foot = foot
def eat(self):
print(f"{self.name} is eating {self.foot}")
与普通方法相比,类中定义的方法第一个参数永远是self,表示当前自身的实例。但是在调用的时候,与普通函数调用一样,不需要传入self变量。上述示例中定义了动物类的吃饭的方法。
这样从外部看Animal类,就只需要知道,创建实例需要给出name和foot,而如何打印,都是在Animal类的内部定义的,这些数据和逻辑被"封装"起来了,调用很容易,但却不用知道内部实现的细节。
2.3、创建对象
定义完类和方法之后,我们需要创建该类的实例对象。
python
# 创建猫和狗的对象
cat = Animal("cat", "fish")
dog = Animal("dog", "bone")
# 调用方法
cat.eat()
dog.eat()
结果打印:
2.4、访问控制
在Python中,类的访问控制是通过属性和方法的命名规约来实现的,与Java不同,它并不是通过严格的访问修饰符。
2.4.1、公共变量
公有成员在类的内外部都可以访问。默认情况下,类的属性和方法都是公有的。
python
class Animal:
def __init__(self, name, foot):
self.name = name
self.foot = foot
def eat(self):
print(f"{self.name} is eating {self.foot}")
cat = Animal("cat", "fish")
dog = Animal("dog", "bone")
print(cat.name) # name为公共变量,在类外部可以访问
2.4.2、私有变量
私有在类变量的外部是不可访问的,只能在类的内部访问。在Python中,可以通过在属性或方法名称前面添加两个下划线(如__money)来定义私有成员。
python
class Animal:
def __init__(self, name, foot):
self.name = name
self.foot = foot
self.__money = 500 # 存款金额,动物怎么会有存款金额,不告诉你
def eat(self):
print(f"{self.name} is eating {self.foot}")
cat = Animal("cat", "fish")
dog = Animal("dog", "bone")
print(cat.name) # name为公共变量,在类外部可以访问
运行结果:
虽然直接访问私有变量无法访问,但是可以通过定义类方法进行访问。
python
class Animal:
def __init__(self, name, foot):
self.name = name
self.foot = foot
self.__money = 500 # 存款金额,动物怎么会有存款金额,不告诉你
def eat(self):
print(f"{self.name} is eating {self.foot}")
def getMoney(self):
return self.__money
cat = Animal("cat", "fish")
dog = Animal("dog", "bone")
print(cat.name) # name为公共变量,在类外部可以访问
# print(cat.__money) # __money为私有变量,在类外部无法访问
print(cat.getMoney()) # __money为私有变量,但是可以通过定义类方法访问
运行结果:
2.4.3、保护成员
保护成员在命名时,通常使用单下划线作为前缀(如_age)。虽然在语法上是可以访问的,但也建议通过公有方法进行访问。
python
class Animal:
def __init__(self, name, foot):
self.name = name
self.foot = foot
self.__money = 500 # 存款金额,动物怎么会有存款金额,不告诉你
self._age = 30 # 年龄,虽然动物的年龄不是很私密,但是直接问年龄毕竟不太好
def eat(self):
print(f"{self.name} is eating {self.foot}")
def getMoney(self):
return self.__money
cat = Animal("cat", "fish")
dog = Animal("dog", "bone")
# print(cat.name) # name为公共变量,在类外部可以访问
# print(cat.__money) # __money为私有变量,在类外部无法访问
# print(cat.getMoney()) # __money为私有变量,但是可以通过定义类方法访问
print(cat._age)
运行结果:
但是PyCharm上是会提示你正在访问的_age是受保护的变量,不推荐直接访问。
我们可以使用类方法进行访问:
python
class Animal:
def __init__(self, name, foot):
self.name = name
self.foot = foot
self.__money = 500 # 存款金额,动物怎么会有存款金额,不告诉你
self._age = 30 # 年龄,虽然动物的年龄不是很私密,但是直接问年龄毕竟不太好
def eat(self):
print(f"{self.name} is eating {self.foot}")
def getMoney(self):
return self.__money
def getAge(self):
return self._age
cat = Animal("cat", "fish")
dog = Animal("dog", "bone")
# print(cat.name) # name为公共变量,在类外部可以访问
# print(cat.__money) # __money为私有变量,在类外部无法访问
# print(cat.getMoney()) # __money为私有变量,但是可以通过定义类方法访问
print(cat._age)
print(cat.getAge())
运行结果:
2.4.4、总结
- 公开变量(public)。普通变量,普通命名,如name和foot。所有类都可以访问。
- 私有变量(private)。内部变量,双下划线(__xxx)命名,如__money。只有类内部才可以访问,类外部无法访问。
- 受保护变量(protected)。单下划线(_xxx)命名,如_age。虽然在语法上是可以访问的,但也建议通过公有方法进行访问。
- 特殊变量。此外还有一种特殊命名方式,前后都是双下划线(xxx),如前面提到的__init__方法。这种变量是可以直接访问的,并不是私有变量。这些命名约定通常具有特殊的含义,用于表示特殊用途的属性或方法。以下是一些常见的双下划线名称:
-
- init。:用于在创建对象时进行初始化操作,是构造方法。
- del。用于在对象被销毁(垃圾回收)前进行清理操作。
- str。用于定义对象的字符串表示,通过str(obj)调用。
- repr。用于定义对象的"官方"字符串表示,通过repr(obj)调用。
这些命名规范并不是强制性的,而是一种约定俗成的做法。通过这些规范,其他开发者可以更容易地理解你的代码,并且遵循这些规范有助于提高代码的可维护性。同时,使用文档来描述类的成员和其用途也是非常重要的。
3、封装
前面介绍了类和对象后,继续来介绍面向对象的几大特性。首先就是封装。封装是一种将数据和操作封装在类中的机制,通过定义公共接口,隐藏内部实现的细节。在Python中,可以使用私有变量(以双下划线开头)来实现封装。
python
class Animal:
def __init__(self, name, foot):
self.name = name
self.foot = foot
def eat(self):
return f"{self.name} is eating {self.foot}"
cat = Animal("cat", "fish")
dog = Animal("dog", "bone")
print(cat.eat())
print(dog.eat())
4、继承
继承是一种允许一个类(子类)继承另一个类(父类)的属性和方法的机制。子类可以重用父类的代码,并且可以添加或修改功能。当我们定义一个class的时候,可以从某个现有的class继承,新的class称为子类(Subclass),而被继承的class称为基类、父类或超类(Base class、Super class)。比如上面定义的Animal类中,有一个call()方法,表示动物的叫声。
Python中继承的语法为:class 子类(继承的父类)。如:
python
class Animal:
def __init__(self, name, foot):
self.name = name
self.foot = foot
def call(self):
return "哈哈哈" # 父类默认的动物叫声,哈哈哈。应该不可能
# Dog类继承了animal,重写了叫声为汪汪汪
class Dog(Animal):
def call(self):
return f"{self.name} eating {self.foot} and call 汪汪汪" # 不对,小狗的叫声应该为汪汪汪
dog = Dog("dog", "bone")
print(dog.call())
其中Dog继承了Animal,Dog为子类,Animal为父类。Dog重写了父类Animal中的call方法。我们根据Dog类创建出来的实例,调用的call方法,优先调用子类方法。
运行结果:
从中也可以看出,子类继承父类后,也可以直接调用父类的属性变量,如name和foot。那么来试下私有变量和保护变量是否可以访问:
python
class Animal:
def __init__(self, name, foot):
self.name = name
self.foot = foot
self.__money = 500
self._age = 30
def call(self):
return "哈哈哈" # 父类默认的动物叫声,哈哈哈。应该不可能
# Dog类继承了animal,重写了叫声为汪汪汪
class Dog(Animal):
def call(self):
# return f"{self.name} eating {self.foot} and call 汪汪汪" # 不对,小狗的叫声应该为汪汪汪
return f"[{self._age}]岁的[{self.name}]吃着[{self.foot}], 开心的汪汪汪" # 不对,小狗的叫声应该为汪汪汪
# return f"[{self._age}]岁的[{self.name}]吃着[{self.foot}],数着老爹的[{self.__money}]存款,开心的汪汪汪" # 不对,小狗的叫声应该为汪汪汪
dog = Dog("dog", "bone")
print(dog.call())
没错,私有变量一样访问不了。
此外,继承可以多级下来,爷爷->爸爸->儿子->孙子......。而这些类,追溯到底,都是继承了python的object类。我们平时写的class Animal其实应该是class Animal(object),只是默认这个都会省略掉。
python
class Animal(object):
pass
5、多态
其实多态往往是跟随着继承而得到的好处。多态允许使用相同的接口(方法名)来处理不同类型的对象,即不同的类可以对相同的方法名做出相应。
python
class Animal(object):
pass
# Dog类继承了animal,重写了叫声为汪汪汪
class Dog(Animal):
def call(self):
return "小狗叫声:汪汪汪" # 不对,小狗的叫声应该为汪汪汪
class Cat(Animal):
def call(self):
return "小猫叫声:喵喵喵"
class Dark(Animal):
def call(self):
return "小鸭叫声:嘎嘎嘎"
def call(animal):
return animal.call()
dog = Dog()
cat = Cat()
dark = Dark()
print(call(dog))
print(call(cat))
print(call(dark))
你会发现,新增一个Animal的子类,不必对call()做任何修改,实际上,任何依赖Animal作为参数的函数或者方法都可以不加修改地正常运行,原因就在于多态。多态的好处就是,当我们需要传入Dog、Cat......时,我们只需要接收Animal类型就可以了,因为Dog、Cat......都是Animal类型,然后,按照Animal类型进行操作即可。由于Animal类型有call()方法,因此,传入的任意类型,只要是Animal类或者子类,就会自动调用实际类型的call()方法,这就是多态的意思。
多条可以让调用方无需关注细节,只需关注调用即可。而这也就是传说中的"开闭"原则。
6、小结
面向对象编程是如今最重要的一种编程模式,也是python中很重要的一个章节。牢记面向对象的几个重要概念:类,对象,属性方法,封装,继承,多态。