目录
一、类和对象
二、类的定义和数据属性
三、特殊的类属性
四、类的成员方法
五、类的访问控制
六、类的构造与析构
七、类的继承
7.1 单继承
7.2 多继承
7.3 多继承中的二义性问题
7.4 使用`super`
一、类和对象
“高内聚,低耦合”是软件工程中追求的核心原则,其核心理念是每个模块或组件应专注于完成自己的任务,并尽量减少对外部代码的依赖。
在不同的编程范式中,这一原则都有所体现:
- **面向过程编程**:一个函数应尽可能独立地完成一项任务。
- **面向对象编程**:一个对象应尽量自主处理自己的事务,减少对其他对象的依赖。
面向对象编程相较于面向过程编程的一个显著优势在于,它将数据和操作数据的方法封装在一起,自然地实现了高内聚。类作为面向对象编程的基本构建块,是这一思想的具体体现。
类和对象的关系:类是对现实世界中实体的抽象,而对象是类在内存中的具体实例。Python作为一种支持面向对象编程的语言,同样引入了类的概念。在Python中:
- 类中的变量被称为“成员变量”或“数据属性”。
- 类中的函数被称为“成员函数”或“成员方法”。
此外,Python中习惯将对象称为实例,强调了对象是类的一个具体实现。
二、类的定义和数据属性
在 Python 中,可以通过class 关键字定义自己的类,然后通过自定义的类创建实例对象。
类和实例都有自己的数据属性。类数据属性是属于类的,所有实例共享这些属性。实例数据属性是属于单个实例的,每个实例都有自己的副本。
class Student(object):
count = 0 # 类数据属性
books = [] # 类数据属性
def __init__(self, name, age):
self.name = name # 实例数据属性
self.age = age # 实例数据属性
objStudent1 = Student("xiaoming", 19)
在这个例子中:
1. **类数据属性**:
- `count`:这是一个类数据属性,所有`Student`类的实例共享这个属性。
- `books`:这也是一个类数据属性,所有`Student`类的实例共享这个属性。
2. **实例数据属性**:
- `name`:这是一个实例数据属性,每个`Student`实例都有自己的`name`属性。
- `age`:这也是一个实例数据属性,每个`Student`实例都有自己的`age`属性。
类数据属性和实例数据属性的主要区别在于它们的共享方式和作用范围:
- **类数据属性**:这些属性在所有实例之间共享。如果你修改了一个类数据属性,所有实例都会看到这个修改。
- **实例数据属性**:这些属性是每个实例独有的。修改一个实例的实例数据属性不会影响其他实例。
例如,如果你创建多个`Student`实例并修改它们的`name`或`age`属性,每个实例都会有自己独立的`name`和`age`值。但是,如果你修改了`Student`类的`count`或`books`属性,所有实例都会看到这个修改。
总结一下:
- `count` 和 `books` 是类数据属性。
- `name` 和 `age` 是实例数据属性。
在Python中,属性分为两种类型:实例属性和类属性。
实例数据属性:
-
在构造函数
__init__
中定义,并以self
作为前缀。 -
实例属性属于单个实例(对象),只能通过对象名来访问。
类数据属性:
-
在类内部直接定义,不以
self
作为前缀。 -
类属性属于类本身,可以通过类名来访问和修改。
-
尽管类属性也可以通过对象来访问,但不推荐这样做,因为这可能导致类属性值的不一致。
总结类数据属性和实例数据属性:
-
类数据属性属于类本身,可以通过类名进行访问和修改。
-
类数据属性也可以被类的所有实例访问和修改。
-
在类定义之后,可以通过类名动态添加类数据属性,新增的类属性也被类和所有实例共享。
-
实例数据属性只能通过实例来访问。
-
在实例生成后,还可以动态添加实例数据属性,但这些实例数据属性只属于该实例。
三、特殊的类属性
在Python中,类有一些特殊的属性,这些属性提供了关于类的额外信息或功能。以下是一些常见的特殊类属性:
1. **`__name__`**:
- 返回类的名称。
class MyClass:
pass
print(MyClass.__name__) # 输出: MyClass
2. **`__module__`**:
- 返回定义类的模块的名称。
class MyClass:
pass
print(MyClass.__module__) # 输出: __main__(如果在主模块中定义)
3. **`__dict__`**:
- 返回类的命名空间,包含类的属性和方法。
class MyClass:
class_var = 1
def __init__(self):
self.instance_var = 2
print(MyClass.__dict__)
# 输出: {'__module__': '__main__', 'class_var': 1, '__init__': <function MyClass.__init__ at 0x...>, ...}
4. **`__bases__`**:
- 返回一个包含类的所有基类的元组。
class BaseClass:
pass
class MyClass(BaseClass):
pass
print(MyClass.__bases__) # 输出: (<class '__main__.BaseClass'>,)
5. **`__doc__`**:
- 返回类的文档字符串(如果定义了的话)。
class MyClass:
"""This is a docstring for MyClass."""
pass
print(MyClass.__doc__) # 输出: This is a docstring for MyClass.
6. **`__class__`**:
- 返回实例所属的类。
class MyClass:
pass
obj = MyClass()
print(obj.__class__) # 输出: <class '__main__.MyClass'>
7. **`__mro__`**:
- 返回一个包含类的继承顺序的元组,即方法解析顺序(Method Resolution Order)。
class A:
pass
class B(A):
pass
class C(B):
pass
print(C.__mro__) # 输出: (<class '__main__.C'>, <class '__main__.B'>, <class '__main__.A'>, <class 'object'>)
这些特殊属性提供了关于类的结构和行为的详细信息,有助于更好地理解和操作类及其实例。
四、类的成员方法
在Python中,类可以定义三种不同类型的方法:实例方法、静态方法和类方法。每种方法都有其特定的用途和行为。
1. **实例方法(Instance Methods)**:
- 实例方法是类中最常见的方法类型。
- 实例方法的第一个参数通常是`self`,它代表类的实例。
- 实例方法可以访问和修改实例属性,并且可以调用其他实例方法。
- 实例方法只能通过实例来调用。
class MyClass:
def __init__(self, value):
self.value = value
def instance_method(self):
print(f"Instance method called with value: {self.value}")
obj = MyClass(10)
obj.instance_method() # 输出: Instance method called with value: 10
2. **静态方法(Static Methods)**:
- 静态方法使用`@staticmethod`装饰器来定义。
- 静态方法没有`self`参数,因此不能访问或修改实例属性。
- 静态方法与类和实例无关,通常用于与类相关但不需要访问实例或类属性的功能。
- 静态方法可以通过类或实例来调用。
class MyClass:
@staticmethod
def static_method():
print("Static method called")
MyClass.static_method() # 输出: Static method called
obj = MyClass()
obj.static_method() # 输出: Static method called
3. **类方法(Class Methods)**:
- 类方法使用`@classmethod`装饰器来定义。
- 类方法的第一个参数通常是`cls`,它代表类本身。
- 类方法可以访问和修改类属性,并且可以调用其他类方法。
- 类方法可以通过类或实例来调用。
class MyClass:
class_var = 0
@classmethod
def class_method(cls):
print(f"Class method called with class var: {cls.class_var}")
MyClass.class_method() # 输出: Class method called with class var: 0
obj = MyClass()
obj.class_method() # 输出: Class method called with class var: 0
总结:
- **实例方法**:用于操作实例属性,通过实例调用。
- **静态方法**:与类和实例无关,通过类或实例调用。
- **类方法**:用于操作类属性,通过类或实例调用。
实例方法其实就是最为普通的成员函数。类方法与静态方法似乎没有什么差别,一般不需要访问类成员或者实例成员可以使用静态方法,需要访问类成员的使用类方法。这些方法类型提供了灵活性,使得代码更加模块化和易于维护。
五、类的访问控制
在Python中,确实没有像其他一些编程语言那样的严格访问控制关键字(如`private`、`protected`)。但是,Python社区通过一些命名约定来实现类似的效果,以表明变量、函数或方法的访问级别。以下是这些约定的详细解释:
1. **单下划线(`_`)**:
在模块级别,以单下划线开头的变量或函数通常被视为模块私有的。这意味着当你使用`from module import *`语句时,这些以单下划线开头的名称不会被导入。
- 在类中,以单下划线开头的实例变量或方法通常被视为“受保护的”,即不鼓励外部直接访问,但技术上仍然可以访问。
# 模块级别
_private_var = 42
def _private_function():
return "This is a private function"
# 类中
class MyClass:
def __init__(self):
self._protected_var = 10
def _protected_method(self):
return "This is a protected method"
2. **双下划线(`__`)**:
在类中,以双下划线开头的实例变量或方法会被Python解释器进行名称改写(name mangling),以实现一定程度的私有化。具体来说,解释器会在名称前加上`_ClassName`,从而使得这些成员在类外部难以直接访问。
class MyClass:
def __init__(self):
self.__private_var = 20
def __private_method(self):
return "This is a private method"
obj = MyClass()
# 以下访问方式会引发AttributeError
# print(obj.__private_var)
# print(obj.__private_method())
# 但可以通过改写后的名称访问
print(obj._MyClass__private_var) # 输出: 20
print(obj._MyClass__private_method()) # 输出: This is a private method
3. **特殊变量**:
以双下划线开头和结尾的变量(如`__init__`、`__str__`)是Python中的特殊方法或属性,通常用于实现特定的语言功能(如构造函数、字符串表示等)。这些变量可以直接访问,并且不是私有变量。
class MyClass:
def __init__(self):
self.__special_var__ = 30
obj = MyClass()
print(obj.__special_var__) # 输出: 30
总结:
- **单下划线**:模块级别的私有化,类中的受保护成员。
- **双下划线**:类中的私有成员(通过名称改写实现)。
- **双下划线开头和结尾**:特殊变量,用于实现特定的语言功能。
这些约定有助于提高代码的可读性和维护性,但它们并不提供真正的访问控制,因为Python解释器不会阻止你访问这些“私有”或“受保护”的成员。
六、类的构造与析构
在Python中,类的构造函数和析构函数是两个特殊的方法,它们分别用于对象的初始化和资源释放。以下是关于这两个方法的详细解释:
### 构造函数
**构造函数**:在Python中,构造函数是`__init__`方法。它用于在创建对象时初始化对象的属性。
当用户定义一个类时,可以显式地定义`__init__`方法来设置对象的初始状态。
如果用户没有定义`__init__`方法,Python会提供一个默认的构造函数,该构造函数不执行任何操作。
构造函数属于类,而不是单个对象。每个对象在创建时都会调用类的构造函数。
class MyClass:
def __init__(self, value):
self.value = value
obj = MyClass(10) # 调用构造函数 __init__
print(obj.value) # 输出: 10
### 析构函数
**析构函数**:在Python中,析构函数是`__del__`方法。它用于在对象被销毁之前释放对象占用的资源。
当对象的引用计数变为零时,Python的垃圾回收机制会自动调用`__del__`方法。
如果用户没有定义`__del__`方法,Python会提供一个默认的析构函数,该析构函数不执行任何操作。
析构函数同样属于类,而不是单个对象。每个对象在销毁时都会调用类的析构函数。
class MyClass:
def __init__(self, value):
self.value = value
def __del__(self):
print(f"Object with value {self.value} is being destroyed")
obj = MyClass(10)
del obj # 调用析构函数 __del__,输出: Object with value 10 is being destroyed
总结:
- **构造函数**:`__init__`方法,用于对象的初始化。
- **析构函数**:`__del__`方法,用于对象的资源释放。
需要注意的是,虽然Python提供了默认的构造函数和析构函数,但在实际编程中,通常不需要显式地定义析构函数,因为Python的垃圾回收机制会自动处理对象的内存管理。只有在需要执行特定资源释放操作时,才需要定义`__del__`方法。
七、类的继承
在Python中,继承是一种机制,允许一个类(子类)继承另一个类(父类)的属性和方法。Python支持单继承和多继承。以下是关于继承的一些详细解释和示例:
7.1 单继承
单继承是指一个子类只继承一个父类。语法如下:
class ParentClass:
def __init__(self, parent_attr):
self.parent_attr = parent_attr
def parent_method(self):
print(f"Parent method called with attribute: {self.parent_attr}")
class SubClass(ParentClass):
def __init__(self, parent_attr, sub_attr):
super().__init__(parent_attr) # 调用父类的构造函数
self.sub_attr = sub_attr
def sub_method(self):
print(f"Sub method called with attribute: {self.sub_attr}")
# 实例化子类
obj = SubClass("parent_value", "sub_value")
obj.parent_method() # 输出: Parent method called with attribute: parent_value
obj.sub_method() # 输出: Sub method called with attribute: sub_value
7.2 多继承
多继承是指一个子类继承多个父类。语法如下:
class ParentClassA:
def __init__(self, attr_a):
self.attr_a = attr_a
def method_a(self):
print(f"Method A called with attribute: {self.attr_a}")
class ParentClassB:
def __init__(self, attr_b):
self.attr_b = attr_b
def method_b(self):
print(f"Method B called with attribute: {self.attr_b}")
class SubClass(ParentClassA, ParentClassB):
def __init__(self, attr_a, attr_b, sub_attr):
ParentClassA.__init__(self, attr_a) # 调用父类A的构造函数
ParentClassB.__init__(self, attr_b) # 调用父类B的构造函数
self.sub_attr = sub_attr
def sub_method(self):
print(f"Sub method called with attribute: {self.sub_attr}")
# 实例化子类
obj = SubClass("value_a", "value_b", "sub_value")
obj.method_a() # 输出: Method A called with attribute: value_a
obj.method_b() # 输出: Method B called with attribute: value_b
obj.sub_method() # 输出: Sub method called with attribute: sub_value
7.3 多继承中的二义性问题
在多继承中,如果多个父类有同名的方法或属性,Python会按照方法解析顺序(Method Resolution Order, MRO)来决定调用哪个方法。Python使用C3线性化算法来确定MRO,通常是广度优先的顺序。
class A:
def method(self):
print("A's method")
class B(A):
def method(self):
print("B's method")
class C(A):
def method(self):
print("C's method")
class D(B, C):
pass
obj = D()
obj.method() # 输出: B's method
在这个例子中,`D`类继承自`B`和`C`,而`B`和`C`都继承自`A`。由于`D`的MRO是`[D, B, C, A]`,所以`obj.method()`会调用`B`类的方法。
7.4 使用`super`
在子类中,可以使用`super()`函数来调用父类的方法,这样可以避免直接引用父类的名称,使代码更加灵活和可维护。
class ParentClass:
def __init__(self, parent_attr):
self.parent_attr = parent_attr
def parent_method(self):
print(f"Parent method called with attribute: {self.parent_attr}")
class SubClass(ParentClass):
def __init__(self, parent_attr, sub_attr):
super().__init__(parent_attr) # 调用父类的构造函数
self.sub_attr = sub_attr
def sub_method(self):
print(f"Sub method called with attribute: {self.sub_attr}")
super().parent_method() # 调用父类的方法
# 实例化子类
obj = SubClass("parent_value", "sub_value")
obj.sub_method() # 输出: Sub method called with attribute: sub_value
# 输出: Parent method called with attribute: parent_value
通过使用`super()`,可以确保在多继承的情况下,正确地调用父类的方法。
总结:
在Python中,继承机制允许一个类(子类)继承另一个或多个类(父类)的属性和方法。Python支持单继承和多继承。
通过继承,子类可以继承父类的属性,并且可以使用内置函数`issubclass()`来判断一个类是否是另一个类的子类或子孙类。
### 继承后的构造函数
在处理继承时,需要特别注意初始化函数`__init__`的行为。如果子类没有定义自己的`__init__`函数,父类的`__init__`函数会被自动调用。然而,如果子类定义了自己的`__init__`函数,但没有显式调用父类的`__init__`函数,父类的属性将不会被初始化。
如果子类定义了自己的`__init__`函数并显式调用了父类的`__init__`函数,那么子类和父类的属性都会被正确初始化。
### 使用`super`
在子类中,通常会定义与父类相同的属性(数据属性和方法),以实现子类特有的行为。子类会继承父类的所有属性和方法,并且可以覆盖父类中同名的属性和方法。
有时需要在子类中访问父类的属性,这时可以使用`super()`函数。`super()`函数提供了一种方便的方式来调用父类的方法,而不需要直接引用父类的名称。
### 多继承问题
Python允许使用多继承,只需在子类定义时列出所有父类即可:
class CTest(CA, CB):
pass
通过这种方式,一个类可以同时继承自多个类。然而,多继承可能会导致二义性问题,例如:
- 当两个父类有同名函数或变量时。
- 当出现菱形继承时。
Python通过广度优先的顺序来解决这些二义性问题,即按照方法解析顺序(Method Resolution Order, MRO)来寻找同名的函数或变量。Python使用C3线性化算法来确定MRO,确保方法调用的顺序是明确的。
总结来说,Python的继承机制提供了灵活的方式来构建类之间的关系,但需要注意构造函数的调用、使用`super()`函数以及处理多继承时的二义性问题。