在 Python 中,类(Class)和对象(Object)是面向对象编程(OOP)的两个主要方面。
一、类(Class)
类是创建对象的蓝图。我们可以把类想象成一个模板,它定义了一种新的数据类型。一旦定义了类,我们就可以根据该类创建对象。
类由一组属性(用来描述对象的特性,例如颜色、大小等)和一组方法(用来描述对象的行为,例如跑、跳等)组成。
# 定义一个简单的类
class Dog:
def __init__(self, name):
self.name = name
def bark(self):
return "Woof!"
在这个例子中,Dog就是一个类。它有一个属性(name)和一个方法(bark),__init__是这个类的构造方法用于初始化一些属性或完成一些初始化的任务。
二、对象(Object)
对象是类的实例。基于类的定义,我们可以创建出多个具有相同属性和方法的对象。
# 创建一个Dog类的对象
my_dog = Dog("Fido")
# Accessing the 'name' attribute of the 'my_dog' object
print(my_dog.name) # 输出: Fido
# Calling the 'bark' method of the 'my_dog' object
print(my_dog.bark()) # 输出: Woof!
在这个例子中,my_dog 就是一个对象,它是 Dog 类的一个实例。我们可以访问它的属性 name,并调用它的方法 bark。
要注意的是,每个对象都拥有自己的属性和方法,这些属性和方法都是独立于其他对象的。也就是说,如果我们创建了一个新的 Dog 对象 another_dog = Dog("Spot"),another_dog 的 name 属性就会有自己独立的值,不会影响到 my_dog 的 name 属性的值,反之亦然。
三、类方法
在 Python 中,类方法是那些被类对象而非其实例对象调用的方法。Python 的类层级结构中的每层类定义都可以增加正常的方法和类方法。类方法在 Python 中使用 @classmethod 装饰器和特殊的第一参数来声明。(其实就是java的静态方法)调用不需要对类进行初始化。
class MyClass:
@classmethod
def class_method(cls, arg1, arg2):
print(f"Calling class_method with {arg1} and {arg2}")
# 调用类方法
MyClass.class_method("Hello", "World")
这里的 class_method() 是一个类方法,第一个参数 cls 是类本身,和实例方法中的 self 类似。@classmethod 是一个装饰器,用来指示 class_method() 是一个类方法。
类方法的一个常见用途是定义可以创建类实例的工厂方法。例如:
class Person:
def __init__(self, name, age):
self.name = name
self.age = age
@classmethod
def from_birth_year(cls, name, birth_year):
return cls(name, datetime.date.today().year - birth_year)
person = Person.from_birth_year("Bob", 1990)
在这个示例中,from_birth_year() 是一个类方法,它使用人的出生年份计算年龄,然后创建并返回一个 Person 类的实例。
四、魔术方法
Python中的魔术方法(Magic Methods)是一种特殊的方法,它们以双下划线开头和结尾,例如__init__,__str__,__add__等。这些方法允许您自定义类的行为,以便与内置Python功能(如+运算符、迭代、字符串表示等)交互。
以下是一些常用的Python魔术方法:
__init__(self, ...): 初始化对象,通常用于设置对象的属性。
__str__(self): 定义对象的字符串表示形式,可通过str(object)或print(object)调用。例如,您可以返回一个字符串,描述对象的属性。
__repr__(self): 定义对象的“官方”字符串表示形式,通常用于调试。可通过repr(object)调用。
__len__(self): 定义对象的长度,可通过len(object)调用。通常在自定义容器类中使用。
__getitem__(self, key): 定义对象的索引操作,使对象可被像列表或字典一样索引。例如,object[key]。
__setitem__(self, key, value): 定义对象的赋值操作,使对象可像列表或字典一样赋值。例如,object[key] = value。
__delitem__(self, key): 定义对象的删除操作,使对象可像列表或字典一样删除元素。例如,del object[key]。
__iter__(self): 定义迭代器,使对象可迭代,可用于for循环。
__next__(self): 定义迭代器的下一个元素,通常与__iter__一起使用。
__add__(self, other): 定义对象相加的行为,使对象可以使用+运算符相加。例如,object1 + object2。
__sub__(self, other): 定义对象相减的行为,使对象可以使用-运算符相减。
__eq__(self, other): 定义对象相等性的行为,使对象可以使用==运算符比较。
__lt__(self, other): 定义对象小于其他对象的行为,使对象可以使用<运算符比较。
__gt__(self, other): 定义对象大于其他对象的行为,使对象可以使用>运算符比较。
下面是一个简单的例子:
class ComplexNumber:
def __init__(self, real, imag):
self.real = real
self.imag = imag
def __add__(self, other):
return ComplexNumber(self.real + other.real, self.imag + other.imag)
def __repr__(self):
return f"ComplexNumber({self.real}, {self.imag})"
# 使用自定义的 `__add__` 魔术方法
c1 = ComplexNumber(1, 2)
c2 = ComplexNumber(2, 3)
c3 = c1 + c2 # ComplexNumber(3, 5)
# 使用自定义的 `__repr__` 魔术方法
print(c1) # ComplexNumber(1, 2)
在这个例子中,我们定义了一个表示复数的 ComplexNumber 类,并为它定义了一些魔术方法:__init__ 构造函数用于初始化,__add__ 实现了加法操作,__repr__ 则用于显示实例的字符串表示。
五、继承
在 Python 中,继承是面向对象编程的一个基本概念。子类(派生类)可以继承父类(基类)的属性和方法。
以下是 Python 继承的简单示例:
# 父类
class Animal:
def __init__(self, name):
self.name = name
def speak(self):
raise NotImplementedError("Subclass must implement this abstract method")
# 子类
class Dog(Animal):
def speak(self):
return 'Woof!'
# 子类
class Cat(Animal):
def speak(self):
return 'Meow!'
a = Animal('Fred')
d = Dog('Fido')
c = Cat('Whiskers')
print(d.speak()) # 输出: 'Woof!'
print(c.speak()) # 输出: 'Meow!'
在这个例子中,Animal 类是 Dog 类和 Cat 类的父类。当你为 Dog 或 Cat 实例调用 speak 函数时,它们会执行属于各自类的 speak 方法。这是因为这些子类重写(override)了父类的 speak 方法。
如果子类没有实现父类的某个方法,那么将会使用父类的实现。这就是继承的一个主要优点:如果所有的动物都有相似的行为,但每种动物执行这些行为的方式又各不相同,那么你可以在父类中定义一个默认行为(可能会抛出 NotImplementedError 异常),并且在每个子类中提供专门的行为。
请注意这个例子还展示了 Python 对于抽象方法的支持。方法 Animal.speak() 等同于一个抽象方法,其目的是要被子类重写。如果有任何未提供 speak() 实现的 Animal 子类实例调用了 speak(),Python 将抛出 NotImplementedError 异常
六、多继承
在 Python 中,一个类可以同时继承多个父类。这种特性就叫做多重继承。
Python 的多重继承通过在类定义时提供多个父类,且父类之间用逗号分隔即可实现。下面是一个例子:
# 父类 A
class A:
def __init__(self):
super().__init__()
self.name = "Class A"
# 父类 B
class B:
def __init__(self):
super().__init__()
self.name = "Class B"
# 多重继承
class C(A, B):
def __init__(self):
super().__init__()
# 创建 C 的对象
object_c = C()
# 输出: Class A
print(object_c.name)
在这个例子中,Class C 继承了 Class A 和 Class B。我们可以看到当 super().__init__() 被调用时,Python 使用一个称为方法解析顺序(Method Resolution Order, MRO)的算法来确定应该首先调用哪个父类的方法。上面的例子中,Class A 在 Class B 之前列出,所以 Class A 的 __init__() 方法被首先调用。
请注意,多重继承有其复杂性,并且可能导致一些难以预见和调试的问题,因此应谨慎使用。如果能采用继承和组合的方式来实现同样的功能,通常建议避免使用多重继承。
七、方法重写
在 Python 中,方法重写是面向对象编程中的一个重要概念。当子类需要改变从父类继承的某个方法的行为时,就需要使用方法重写(也叫方法覆盖)。下面是一个例子:
class Animal:
def speak(self):
return '...'
class Dog(Animal):
def speak(self):
return 'Woof'
class Cat(Animal):
def speak(self):
return 'Meow'
a = Animal()
d = Dog()
c = Cat()
print(a.speak()) # 输出: '...'
print(d.speak()) # 输出: 'Woof'
print(c.speak()) # 输出: 'Meow'
在这个例子中,Dog 类和 Cat 类都重写了从 Animal 类继承的 speak() 方法。
方法重写的一个主要目的是让子类可以展现出与父类不同的行为。在上述例子中,虽然所有的类都有 speak 方法,但每种类 speak 的方式都各不相同。
注意:如果子类想在保持父类方法的基础上添加一些新的行为,你可以使用 super() 函数来调用父类的方法。例如:
class Dog(Animal):
def speak(self):
original_speech = super().speak()
return f'{original_speech} - this is modified by Dog class. Now I say Woof'
在这个重写的 speak() 方法中,我们首先通过 super().speak() 调用 Animal 类的原始 speak 方法,然后返回一个修改后的字符串。
八、私有属性与方法
在 Python 中,按照约定,以单下划线开头的变量和方法被视为私有的。这只是一种约定,并没有真正阻止外部访问,例如:
class MyClass:
def __init__(self):
self._my_private_var = 0
obj = MyClass()
print(obj._my_private_var) # 输出: 0
对于需要严格控制访问的属性或方法,Python 提供了一种方法使其变得“更私有”。这种方法是通过双下划线(__)前缀来实现。例如:
class MyClass:
def __init__(self):
self.__my_really_private_var = 0
obj = MyClass()
print(obj.__my_really_private_var) # 抛出 AttributeError
在这个例子中,试图直接访问变量 __my_really_private_var 将会抛出 AttributeError。Python 实际上修改了变量的名称,你可以通过 obj._MyClass__my_really_private_var 仍然可以访问,但这通常不建议这样做,因为这种方法并不是公开的 API。
这种带有两个下划线的名称修改(name mangling)也适用于方法名称,即如果一个方法以 __ 开头,那么在类外部是无法直接访问的。