目录
- 类的定义
- 类属性与类实例(class objects and instance objects)
- 类属性
- 类实例(类的初始化)
- 类方法
- 类的访问控制
- 类的继承与多态
- 类的继承
- 类的多(重)继承
- 类的多态
Python中的类(Class)是面向对象编程(OOP)的核心概念之一。它为创建对象提供了一种蓝图或模板。类提供了一种将数据和功能捆绑在一起的手段。创建一个新类会创建一个新类型的对象,允许创建该类型的新实例。每个类实例都可以附加属性来维持其状态。类实例也可以有方法(由其类定义)来修改其状态。
类的定义
定义类非常简单,使用class
关键字进行定义:
class ClassName:
pass
在对类进行命名时,建议使用驼峰命名法,即当多个单词组合在一起时,每个单词的首字母大写。(在命名变量时,通常使用小写字母,并使用下划线作为单词之间的分隔符)类的命名应该与其功能相关,即具有一定的描述性。另外,除非常见术语,一般不建议缩写。
类属性与类实例(class objects and instance objects)
类属性
类属性是附加到类上的变量,它们被该类的所有实例共享。这意味着当更改类属性时,这个更改会反映在该类的所有实例上。比如:
class MyClass:
class_attribute = "This is a class attribute"
print(MyClass.class_attribute) # 输出:"This is a class attribute"
对于所有的类的实例,上例中的class_attribute
属性都一致的。
类实例(类的初始化)
类实例是根据类的定义创建的对象。每个实例都有自己的属性和方法,并且与其他实例独立。实例属性是附加到类实例上的变量。特别的,python类提供了self
关键字用于区分实例属性还是类属性。 比如:
class MyClass:
class_attribute = "This is a class attribute"
def __init__(self, name):
self.instance_attribute = name
# 创建类的实例
obj1 = MyClass("Instance 1") # 调用def __init__()初始化类
obj2 = MyClass("Instance 2")
print(obj1.class_attribute) # 输出:"This is a class attribute"
print(obj1.instance_attribute) # 输出:"Instance 1"
print(obj2.instance_attribute) # 输出:"Instance 2"
带有self
的,全部为类实例的属性。在程序中,如果obj1的类实例属性,这不会影响obj2的实例属性。但是,如果在任何一个实例中修改类属性,即示例中的class_attribute
,所有示例中的该属性都会被修改。例如:
obj1.class_attribute = "Modified through instance"
print(obj1.class_attribute) # 输出:"Modified through instance"
print(obj2.class_attribute) # 输出:"Modified class attribute"
print(MyClass.class_attribute) # 输出:"Modified class attribute"
类方法
类的方法一般指类所包含的函数,但是值得注意的是,一个类可以没有函数,充当一个数据容器,类似于字典。在构建类的方法时,所有函数都要以self
作为函数的第一个‘变量’,以说明该函数为类的函数。下面给出一个例子:
class Example:
def __init__(self, value):
self.attribute = value
def display(self):
print(self.attribute)
@staticmethod
def show():
print('staticmethod')
函数__init__
是类的内置函数,用于初始化一个类。一个类中所有的数据变量都应该在该函数中进行初始化。display
函数是类的函数,可以访问类的数据并执行特定操作。
example=Example('test')
example.display() # 调用display 函数,输出'test'
如果在类的方法中不使用self
作为第一个变量,那么当该函数调用类实例的数据时,将会报错。如果一个函数不需要调用类中的数据,或者想不创建实例就调用该方法时,可以使用staticmethod
方法。如上例中的show
方法。
example.show() # 输出为'staticmethod'
Example.show() # 输出为'staticmethod'
类的访问控制
在面向对象的编程中,访问控制指的是对对象的属性和方法的访问权限的管理。正确的访问控制可以保证对象的状态不会被外部不恰当地修改,从而确保数据的完整性和安全性。Python 并没有像 Java 或 C++ 那样的严格访问修饰符(如 private
、protected
和 public
)。但 Python 通过命名约定和一些特性提供了访问控制的机制。
- 公开(Public):
- 这是默认的访问控制级别。
- 属性或方法的名称没有任何前导下划线。
- 可以从类的任何地方、类的任何实例或类的任何子类进行访问。
- 示例:
name
- 受保护(Protected):
- 通过一个前导下划线表示(例如
_name
)。 - 通常意味着它只应该在该类和其子类中使用。
- 但需要注意的是,这只是一个约定。Python 不会真正阻止你在类的外部访问这些属性或方法。
- 示例:
_protected_attribute
- 私有(Private):
- 通过两个前导下划线表示(例如
__name
)。 - Python 会对这些属性或方法进行名称修饰,这使得它们不能从外部直接访问。
- 名称修饰涉及到在前面加上
_classname
(其中 classname 是当前的类名)。 - 示例:
__private_attribute
- 虽然可以通过名称修饰来访问私有属性,但这是不推荐的,因为这违反了封装的原则。
class MyClass:
def __init__(self):
self.public_attribute = "I'm a public attribute"
self._protected_attribute = "I'm a protected attribute"
self.__private_attribute = "I'm a private attribute"
def get_private_attribute(self):
return self.__private_attribute
obj = MyClass()
print(obj.public_attribute) # 正常访问
print(obj._protected_attribute) # 可以访问,但不建议在类外部这样做
# print(obj.__private_attribute) # 报错:AttributeError
print(obj.get_private_attribute()) # 通过公开方法访问私有属性
print(obj._MyClass__private_attribute) # 不建议,但可以通过名称修饰访问
Python 提供的访问控制主要是基于约定和程序员的自律,而不是语言层面的强制性规则。尽管如此,理解和正确使用这些机制是编写健壮和可维护的代码的关键。
类的继承与多态
在面向对象的编程中,继承和多态是两个核心概念,它们允许程序员编写更加模块化、可扩展和可维护的代码。
类的继承
继承是一种机制,允许一个类(子类/派生类)继承另一个类(父类/基类)的属性和方法。
- 代码复用:子类可以复用父类的代码,这减少了重复的代码。
- 扩展:子类可以扩展或修改它从父类继承的行为。
示例:
# 定义一个基类
class Animal:
def __init__(self, name):
self.name = name
def speak(self):
pass
def get_name(self):
print(self.name)
# 定义一个派生类
class Dog(Animal):
def speak(self):
return f"{self.name} says Woof!"
class Cat(Animal):
def speak(self):
return f"{self.name} says Meow!"
dog = Dog("Buddy")
print(dog.speak()) # 输出:"Buddy says Woof!"
dog.get_name() # 输出“Buddy”
在上述例子中,子类Dog
和Cat
继承了Animal
,所以Animal
的数据变量和函数都被继承了下来。但是,由于子类Dog
和Cat
重写了speak
函数,所以在子类的实例调用speak
函数时,都将调用之类的speak
函数,而非Animal
的speak
函数。
类的多(重)继承
顾名思义,类的多继承是指一个类可以从多个父类继承属性和方法。使用多继承的优缺点如下:
- 优点:
- 可以用于创建一个集成了多个类功能的新类。
- 有助于代码复用,因为可以从多个源继承行为和属性。
- 缺点:
- 菱形问题(Diamond Problem):当两个父类都继承自一个祖先类,并且一个子类同时继承这两个父类时,可能会出现歧义。如果两个父类提供了相同的方法,子类可能会混淆它应该继承哪个方法。但是按照python内部的继承顺序可以解决这一缺点,难处在与用户是否可以正确理解并使用该顺序。
- 可能导致代码的复杂性增加,使其更难理解和维护。
class A:
def method(self):
print("A method")
class B(A):
def method(self):
print("B method")
class C(A):
def method(self):
print("C method")
class D(B, C):
pass
d = D()
d.method() # “B method"
在使用多继承时,python是使用的Method Resolution Order, MRO机制确定继承顺序。Method Resolution Order (MRO)
是 Python 中用于支持多重继承的一个机制。当一个类是从多个父类继承过来的,MRO 确定了当调用一个方法时应该从哪个父类继承这个方法。Python 中的 MRO 遵循三个主要的原则:
- 子类优先于父类:如果子类覆盖了某个方法或属性,那么它应该在父类之前被考虑。
- 多个父类会按照它们在列表中的顺序被考虑:也就是说,如果有多个父类,那么第一个父类的方法或属性将首先被考虑,然后是第二个,依此类推。
- 一旦确定了父类的顺序,这个顺序应该是一致的,不应该发生变化。
为了确定类的 MRO,Python 使用了一个叫做 C3 线性化的算法。这个算法确保所有的子类都会在它们的父类之前被考虑,并保证父类的顺序与它们在代码中的定义顺序一致。
考虑以下的类结构:
class A: pass
class B(A): pass
class C(A): pass
class D(B, C): pass
对于 D
类的 MRO,它首先会考虑 D
本身,然后是它的第一个父类 B
,然后是 B
的父类 A
,然后才是第二个父类 C
,然后是 C
的父类(但由于 A
已经被考虑过了,所以它不会再次出现)。因此,D
的 MRO 将是:D -> B -> C -> A
如何查看 MRO
- 使用
mro()
方法:
print(D.mro())#[<class '__main__.D'>, <class '__main__.B'>, <class '__main__.C'>, <class '__main__.A'>, <class 'object'>]
- 使用内置的
help()
函数:
help(D)
在输出的顶部,你会看到 “Method resolution order” 部分,它列出了 MRO。
MRO 的问题可能在多重继承中变得复杂,尤其是当有多个类与相同的父类有关系时。这就是为什么,在设计面向对象的应用程序时,多重继承应该谨慎使用。如果不小心,可能会遇到难以预测的行为和“菱形继承”的问题。
类的多态
多态是面向对象编程中的四大基本特性之一,其他三个特性是封装、继承和抽象。多态来自于希葛亚文中的"poly"(意为"多")和"morph"(意为"形状"或"形态"),所以字面上,它意味着"多种形态"。
在面向对象编程中,多态的概念是指:使用一个接口来代表多种形式的数据。更具体地说,多态允许我们定义在多个类中的方法,这些方法具有相同的名称但具有不同的实现。多态具有以下特点:
- 接口的统一:多态为不同的对象提供了一个统一的接口。这意味着我们可以使用相同的方法名来调用不同对象的方法。
- 动态绑定:在运行时,程序会根据对象的实际类型来决定调用哪个类中的方法。
Python 是动态类型语言,这意味着多态是其核心的一部分。在 Python 中,多态主要通过鸭子类型实现,这意味着我们不关心对象是什么类型,只关心它能做什么。让我们考虑一个简单的例子,其中有几种不同的动物,每种动物都有自己的声音。
class Dog:
def sound(self):
return "Woof!"
class Cat:
def sound(self):
return "Meow!"
class Bird:
def sound(self):
return "Chirp!"
虽然每个类都有一个 sound
方法,但实现方式各不相同。现在,我们可以编写一个函数来展示多态的行为:
def animal_sound(animal):
return animal.sound()
d = Dog()
c = Cat()
b = Bird()
print(animal_sound(d)) # 输出: Woof!
print(animal_sound(c)) # 输出: Meow!
print(animal_sound(b)) # 输出: Chirp!
在上面的示例中,animal_sound
函数不关心其参数的数据类型,只要该参数具有一个名为 sound
的方法。这就是多态的魔力。利用好多态,可以:
- 代码重用:你可以创建统一的接口,然后让多个类实现该接口,而不必为每个类编写单独的功能。
- 扩展性:添加新的类或方法非常容易,而不需要修改已有的代码。
- 灵活性:多态允许程序在运行时动态地确定特定的方法和对象,这为开发提供了很大的灵活性。