Python 类
1 类的创建和类的实例
Python 是一种面向对象的编程语言,其中类是非常重要的概念。
类是一种用户定义的数据类型,它代表着一类具有相同属性和方法的对象的集合。
实例则是类的具体实现,是类的一个个体,可以使用类定义的属性和方法。
- 定义类:使用关键字
class - 创建一个类的对象:可以通过类名后面加括号来创建一个实例
__init__()作为类的构造方法,用来初始化类的实例,self表示类的实例本身。self的名字并不是规定死的,也可以使用this,但是最好还是按照约定使用self。
- 其他方法则按照正常函数的形式定义。
- 类可以定义属性和方法,属性是类的数据成员,方法是类的函数成员。
- 类的方法与普通的函数只有一个特别的区别:它们必须有一个额外的第一个参数名称, 按照惯例它的名称是 self。
class Dog:
def __init__(self, name, breed, age):
self.name = name
self.breed = breed
self.age = age
my_dog = Dog('Buddy', 'Golden Retriever', 6)
上面的代码中,“Dog”类表示狗类,它包含名字、品种和年龄这三个属性。
创建了一个 “my_dog” 的实例,该实例有名为 “Buddy” 的名字、品种为 “Golden Retriever”、年龄为 6 岁。
此时,my_dog 变量就代表了一个狗类的实例,可以通过访问它的属性来获取相应的信息,例如:
print(my_dog.age)
这将打印出 6。同时,也可以使用对象的方法来修改属性或进行其他操作:
class Dog:
def __init__(self, name, breed, age):
self.name = name
self.breed = breed
self.age = age
def bark(self):
print('Woof woof!')
my_dog = Dog('Buddy', 'Golden Retriever', 6)
my_dog.bark() # 输出 “Woof woof!”
2 类的属性
在 Python 中,类的属性可以被理解为类的变量。
Python 的公有属性、私有属性以及实例属性、静态属性之间是存在关联的,具体关系如下:
-
公有属性:指没有加前缀双下划线
__的属性,可以在类内外被访问,也可以被继承和重写。 -
私有属性:指加了前缀双下划线
__的属性,只能在类内被访问和修改,而在类外部无法访问或修改。 -
实例属性:指定义在
__init__方法中,以self.属性名的形式定义的属性,每个实例都独立拥有一个自己的实例属性,它们随实例创建和销毁。 -
静态属性:指在类下直接定义的属性,可以使用类名直接访问,它们是类的属性,每个实例都共享一个静态属性。
公有属性和私有属性是属于对象或类中的实例属性或静态属性的一种访问方式,
也就是说,公有属性和私有属性可以同时作为实例属性和静态属性存在。
对于 Python 中的公有属性和实例属性的关系,可以通过实例的 self.属性名 来访问和修改;
而对于 Python 中的私有属性,则需要在属性名前面加上双下划线"__",才能被认定为私有属性,无法通过实例调用,只能通过类内部的方法进行访问和修改。对于静态属性,则是直接定义在类下,可以使用类名进行访问和修改。
2.1 实例属性和类属性
类的属性有两种类型:实例属性和类属性。
| 类型 | 定义 | 特点 |
|---|---|---|
| 实例属性 | 类的实例的属性 | 各个实例相互之间不产生影响 |
| 静态属性/类属性 | 类本身的属性 | 类的所有实例都共享一份类属性,各个实例相互之间会产生影响 |
| 属性 | 实例属性 | 静态属性 |
|---|---|---|
| 定义方式和作用 | 定义在实例方法中,仅对当前实例有效 | 定义在类中,对所有实例共享同一份 |
| 访问方式 | 通过实例进行访问 | 通过类名进行访问 |
| 访问时用的是哪个对象属性 | 实例属性值 | 只有一个静态属性值,共用同一个 |
| 生命周期 | 随着实例的创建和销毁而创建和销毁 | 随着类的创建和销毁而创建和销毁 |
| 作用域 | 实例内部作用域内 | 类的内部作用域内 |
| 示例 | self.name = '张三' (在 init 中) | name = '李四' (在类中) |
2.1.1 实例属性
实例属性是指属于某个对象的属性,每个对象都有自己独立的实例属性,相互之间不会产生影响。
实例属性的赋值一般在类的初始化方法 __init__ 中进行,也可以在对象创建之后通过赋值来添加实例属性。
例如,我们定义一个人类 Person,可以在初始化方法中定义实例属性 name 和 age,如下所示:
class Person:
def __init__(self, name, age):
self.name = name
self.age = age
2.1.2 静态属性/类属性
- 类属性是指属于整个类的属性,所有对象共享一份类属性,相互之间会产生影响。
- 类属性一般在类的定义中直接定义,也可以在类的其他方法中添加。
- 类的属性是反映类内部状态的变量,可以在类的定义中定义和修改,也可以在类的其他方法中访问和使用。
- 理解好类的属性对于深入理解 Python 面向对象编程非常重要。
例如,我们为人类 Person 添加一个类属性 species,表示人类的种类,定义如下:
class Person:
species = 'human' # 定义类属性
def __init__(self, name, age):
self.name = name
self.age = age
def get_species(self): # 定义实例方法
return self.species
- 在类属性的访问和修改上,可以通过类名或对象名来访问。
例如,我们可以通过 Person.species 来访问类属性,也可以通过 person1.species 来访问对象 person1 的类属性。
- 类属性的修改同样可以通过类名或对象名来实现。
如果通过对象名修改类属性,则会创建一个同名的实例属性,该实例属性会覆盖掉类属性的值,不会改变类属性的值。
例如,我们修改 person1.species 的值为 'homo erectus',则会创建一个实例属性 species,它的值为 'homo erectus',但是不会改变类属性 Person.species 的值。
2.2 公有属性和私有属性
在 Python 中,类的属性(或者成员变量)也可以分为公有属性和私有属性。
| 属性类型 | 声明方式 | 外部访问方式 | 内部访问方式 |
|---|---|---|---|
| 公有属性 | 未加任何修饰符 | 直接访问 | 直接访问 |
| 私有属性 | 在属性名前面加双下划线 __ | 不能直接访问,可以通过访问方法间接访问 | 可以直接访问 |
2.2.1 公有属性
公有属性是指可以从类的外部直接访问和修改的属性,一般在类的构造方法中用 self 关键字来定义,例如:
class Student:
def __init__(self, name, age):
self.name = name # 公有属性
self.age = age # 公有属性
在这个例子中,name 和 age 都是公有属性,我们可以通过实例对象直接访问和修改它们的值:
s = Student('Lucy', 20)
print(s.name) # 'Lucy'
s.age = 21
print(s.age) # 21
2.2.2 私有属性
私有属性是指不能从类的外部直接访问和修改的属性。在 Python 中,可以通过在属性名前面加上双下划线 __ 来定义私有属性,例如:
class Student:
def __init__(self, name, age):
self.__name = name # 私有属性
self.__age = age # 私有属性
在这个例子中,__name 和 __age 都是私有属性,我们无法直接访问和修改它们的值:
s = Student('Lucy', 20)
print(s.__name) # AttributeError: 'Student' object has no attribute '__name'
s.__age = 21 # AttributeError: 'Student' object has no attribute '__age'
这是因为 Python 解释器会自动将属性名 __name 和 __age 替换成 _Student__name 和 _Student__age,从而防止属性被意外访问和修改。
虽然无法直接访问和修改私有属性,但是我们可以通过类内部定义的方法来访问和修改它们的值,例如:
class Student:
def __init__(self, name, age):
self.__name = name # 私有属性
self.__age = age # 私有属性
def get_name(self):
return self.__name
def set_age(self, age):
self.__age = age
在这个例子中,我们定义了一个 get_name() 方法来获取私有属性 __name 的值,定义了一个 set_age() 方法来修改私有属性 __age 的值。这样,我们就可以通过这些方法来访问和修改私有属性的值:
s = Student('Lucy', 20)
print(s.get_name()) # 'Lucy'
s.set_age(21)
print(s._Student__age) # 21
需要注意的是,这种方式虽然可以访问和修改私有属性的值,但是完全破坏了封装的原则,不建议频繁使用。在实际编程中,一般优先使用公有属性,只在有特殊需求的时候才使用私有属性。并且,Python 中也没有真正的私有属性,只有一种约定俗成的方法来模拟私有属性的效果,这也是 Python 的一种“鸭子类型”的体现。
3 类的方法
Python 的实例方法、类方法和静态方法是类中的三种不同类型的方法,与公共方法和私有方法的关系是有些类似的。
-
实例方法是定义在类中、需要传入
self参数的方法,可以访问实例属性和类属性,也可以修改它们。这种方法是最常用的一种方法。 -
类方法是通过
@classmethod装饰器定义的方法,第一个参数为cls(代表类本身),可以访问和修改类属性,但不能访问实例属性。类方法可以通过类本身调用,也可以通过实例调用,实例调用时会自动将该实例的类传递给cls参数。 -
静态方法是通过
@staticmethod装饰器定义的方法,与类和实例无关,不能访问实例属性和类属性。静态方法与函数类似,只不过它们是定义在类中的。 -
公共方法是可以在类内外部通过实例或者类名调用的方法,不需要使用任何修饰符限制访问权限。
-
私有方法需要在方法名前添加双下划线声明为私有方法,只能在类内部被访问和使用。
因此,可以说实例方法、类方法和静态方法是三种不同的方法类型,而公共方法和私有方法是访问权限的控制,它们之间没有直接的关联。
3.1 实例方法、类方法和静态方法
在 Python 中,类的方法分为三类:普通方法、类方法和静态方法。
| 方法类型 | 定义方式 | 调用方式 | 传递的参数 | 访问权限 |
|---|---|---|---|---|
| 实例方法/普通方法 | 定义时不带任何装饰器,第一个参数为 self | 通过实例对象调用 | 默认传递实例对象本身作为 self 参数(隐式传递) | 可以访问和修改实例属性和类属性 |
| 类方法 | 定义时用 @classmethod 装饰器修饰,第一个参数为 cls | 通过类对象或实例对象调用 | 默认传递类对象本身作为 cls 参数(隐式传递) | 可以访问和修改类属性,但不能访问和修改实例属性 |
| 静态方法 | 定义时用 @staticmethod 装饰器修饰,无需指定特殊的参数 | 通过类对象或实例对象调用 | 不隐式传递任何额外参数 | 既不能访问和修改类属性,也不能访问和修改实例属性 |
需要注意的是,所有的类方法和静态方法都是无法直接访问实例属性的,因为它们不需要隐式传递实例对象作为参数。
反之,实例方法可以访问和修改实例属性和类属性,因为它总是通过隐式的方式传递实例对象作为第一个参数。 在实际编程中,可以根据实际需要选择合适的方法类型来完成相应任务。
隐式传递: 在 Python 中,实例方法和类方法的第一个参数通常被命名为
self或cls,它们会自动传递给方法。这种参数传递的方式称为隐式传递(implicit passing),因为在调用方法时不需要显式地传递这些参数,Python 会自动处理这个过程。 例如,当你使用obj.method()调用对象的实例方法时,Python 会自动将对象本身作为第一个参数self隐式传递给方法,从而让我们能够在方法中通过self参数访问对象的实例属性。 隐式传递在 Python 中是非常常见的,它可以帮助我们省去很多冗杂的代码,并更加方便地调用方法。 但需要注意的是,在方法定义时必须显式声明这些参数,才能保证正确的隐式传递。否则,在方法调用时可能会出现参数不匹配的错误。
3.1.1 实例方法/普通方法
实例方法/普通方法(instance method)是最常见的类方法,它的第一个参数必须是 self,代表类的实例对象。这样我们才能在方法中访问实例对象的属性和方法。比如,我们可以定义一个简单的类 Person,并在其中定义一个普通方法 greet,用于问候人:
class Person:
def __init__(self, name):
self.name = name
def greet(self):
print(f"Hello, my name is {self.name}!")
在这个例子中,我们定义了一个类 Person,并在其中定义了一个方法 greet,第一个参数为 self。在方法的实现中,我们通过 self.name 访问了实例对象的属性 name。这样,我们就可以通过下面的方式创建实例对象,并调用 greet 方法:
person = Person('Tom')
person.greet() # 输出 "Hello, my name is Tom!"
3.1.2 类方法
类方法是指跟类相关而不是跟对象相关的方法,用 classmethod 装饰器来定义。在类方法中,第一个参数必须是 cls,它不是对象,而是类本身。类方法可以访问类的私有属性,也可以修改。
类方法(class method)是针对整个类对象进行操作的,并且第一个参数必须是 cls,代表类对象本身。可以使用类名来调用类方法,也可以使用类的实例对象来调用。比如,我们可以在 Person 类中定义一个类方法 count,用于记录已经创建的实例对象个数:
class Person:
count = 0
def __init__(self, name):
self.name = name
Person.count += 1
def greet(self):
print(f"Hello, my name is {self.name}!")
@classmethod
def get_count(cls):
return cls.count
在这个例子中,我们添加了一个类属性 count,并在 __init__ 方法中对此进行了修改。可以看到,在类方法 get_count 中,我们通过 cls.count 访问类属性 count。这样,我们就可以通过下述方式来统计实例对象的个数:
person1 = Person('Tom')
person2 = Person('Jerry')
print(Person.get_count()) # 输出 2
class MyClass:
class_attribute = "This is a class attribute"
def __init__(self):
self.instance_attribute = "This is an instance attribute"
@classmethod
def class_method(cls, x):
print("This is a class method, and the class attribute is:", cls.class_attribute)
print("The parameter is:", x)
MyClass.class_method(123) # 调用类方法,输出:This is a class method, and the class attribute is: This is a class attribute The parameter is: 123
3.1.3 静态方法
静态方法是指在类中定义的,不强制要求传递类或实例的方法。用 staticmethod 装饰器来定义。静态方法既不是类方法也不是实例方法,就像普通的函数一样,可以直接通过类名调用,也可以通过实例调用。
静态方法(static method)不需要 self 或 cls 参数,因此静态方法不能访问实例属性或类属性。和类方法一样,静态方法可以通过类名来调用,也可以通过类的实例对象调用。比如,我们可以在 Person 类中定义一个静态方法 add,用来将两个数字相加:
class Person:
@staticmethod
def add(a, b):
return a + b
在这个例子中,我们使用 @staticmethod 装饰器将 add 方法声明为静态方法。在方法实现中,我们直接返回了两个数的和。这样,我们就可以通过下述方式调用静态方法:
print(Person.add(1, 2)) # 输出 3
总之,类的方法是定义在类中的函数,用来实现类的功能。其中,普通方法必须包含 self 参数,类方法必须包含 cls 参数,而静态方法不需要包含 self 或 cls 参数。
class MyClass:
class_attribute = "This is a class attribute"
def __init__(self):
self.instance_attribute = "This is an instance attribute"
@staticmethod
def static_method():
print("This is a static method, and the class attribute is:", MyClass.class_attribute)
MyClass.static_method() # 调用静态方法,输出:This is a static method, and the class attribute is: This is a class attribute
my_instance = MyClass()
my_instance.static_method() # 调用实例方法,输出:This is a static method, and the class attribute is: This is a class attribute
通过上述例子可以看到,静态方法和类方法都是属于类的方法,并不依赖于类的实例化对象。但是类方法必须传递第一个参数 cls,而静态方法不需要传递类或实例的参数。
3.2 公共方法和私有方法
| 类型 | 命名规则 | 可见性 | 调用方式 |
|---|---|---|---|
| 公有方法 | 以小写字母开头 | 在类的内部和外部都可以被访问 | 使用实例对象或类对象调用 |
| 私有方法 | 以双下划线开头 | 仅在类的内部可以被访问 | 只能在类的内部调用 |
需要注意的是,虽然 Python 中有私有方法这一概念,但并不会在语言层面上强制限制私有方法的访问,而是通过“属性名重整”机制来实现私有化。
在类定义中,以两个下划线开头的方法名会自动在前面添加一个下划线和类名,变成一个长名称,这个名称就成为了实际的方法名。
因此,如果在类的外部访问一个私有方法,需要使用重整后的名称。
在 Python 中,类的方法包括公有方法和私有方法。
- 公有方法是指可以从类的外部直接访问和使用的方法。也就是说,公有方法的访问权限不受限制,无需特殊的访问约定。
- 私有方法则需要在方法名前面加上双下划线 "
__" 声明。私有方法的访问权限只限于类内部,在类的外部无法直接访问和使用。这种访问权限的控制可以帮助我们隐藏一些类的内部实现细节,避免外部代码的非法访问和篡改。
下面是一个 Python 类的例子,其中定义了一个公有方法和一个私有方法:
class Person:
def __init__(self, name, age):
self.__name = name
self.age = age
# 公有方法
def show(self):
print("My name is", self.__name)
self.__say_hello()
# 私有方法
def __say_hello(self):
print("Hello, World!")
在这个例子中,show 方法是一个公有方法,可以在类的外部直接使用。而 __say_hello 方法则是一个私有方法,只能在类的内部使用。
在调用私有方法时,需要使用特殊的语法来访问,即在方法名前面加上双下划线 "__"。例如,在 show 方法中调用 __say_hello 方法可以使用以下语法:
self.__say_hello()
需要注意的是,由于 Python 中对于双下划线 "__" 的私有属性和方法在名称前加"_类名"进行了实现,所以不能直接 outside._类名__私有属性/方法 来访问。
3.3 类专有的方法
一个类创建的时候,就会包含一些方法,主要有以下方法:
类的专有方法:
| 方法 | 说明 |
|---|---|
__init__ | 构造函数,在生成对象时调用 |
__del__ | 析构函数,释放对象时使用 |
__repr__ | 打印,转换 |
__setitem__ | 按照索引赋值 |
__getitem__ | 按照索引获取值 |
__len__ | 获得长度 |
__cmp__ | 比较运算 |
__call__ | 函数调用 |
__add__ | 加运算 |
__sub__ | 减运算 |
__mul__ | 乘运算 |
__div__ | 除运算 |
__mod__ | 求余运算 |
__pow__ | 乘方 |
获取类的相关信息,可以使用如下的方法:
type(obj):来获取对象的相应类型;isinstance(obj, type):判断对象是否为指定的 type 类型的实例;hasattr(obj, attr):判断对象是否具有指定属性/方法;getattr(obj, attr[, default])获取属性/方法的值, 要是没有对应的属性则返回 default 值(前提是设置了 default),否则会抛出 AttributeError 异常;setattr(obj, attr, value):设定该属性/方法的值,类似于 obj.attr=value;dir(obj):可以获取相应对象的所有属性和方法名的列表:
3.3.1 构造函数
在 Python 中,构造函数是用于初始化一个类实例的方法。它被称为构造函数的原因是,它在创建新对象时的工作类似于建筑物的构造。
构造函数是另一种特殊的方法,它使用双下划线(__)作为前缀和后缀,并且通常被命名为__init__。当你创建类的实例时,这个构造函数会被自动调用,用来初始化这个实例。
构造函数是一个可选的方法,所以你不需要为每个类都定义一个构造函数。如果你没有定义一个自己的构造函数,Python 将为你提供一个默认构造函数,它不做任何事情。
当你定义一个构造函数时,你可以将参数传递给它,以便在实例化新对象时设置实例的属性。通常,构造函数的参数包括 self 和其他参数。self 参数是一个指向实例本身的引用,在构造函数中用于访问实例属性和方法。
以下是一个示例狗类的构造函数的代码:
class Dog:
def __init__(self, name, age):
self.name = name
self.age = age
在这个狗类中,__init__() 构造函数被定义为接受两个参数:名字和年龄。它使用这些参数来初始化实例属性 name 和 age。
当你创建了一个新的 Dog 实例时,你必须为这些参数提供值,如下所示:
my_dog = Dog('Fido', 3)
在这个例子中,我们将 'Fido' 和 3 作为参数传递给构造函数,以创建一个名为 my_dog 的新 Dog 实例。使用构造函数将这些值分配给实例的 name 和 age 属性。
这就是 Python 构造函数的基本概念和用法。它允许你为类的实例提供自定义初始化逻辑,以便更好地满足你的应用程序的需求。
3.3.2 析构函数
Python 中的析构函数是一种特殊的函数,也被称为 __del__ 函数。它在对象被解释器销毁时自动执行,用于释放这个对象所占用的资源。当一个对象不再被引用时,Python 解释器会自动调用其析构函数。
具体来说,析构函数通常被用于清理对象的资源,比如关闭数据库连接,释放内存等。对象销毁时会自动执行析构函数,这可以避免在程序运行过程中出现资源泄露等问题。
Python 中的析构函数通常不需要手动调用。当对象的引用计数为零,也就是对象不再被引用时,析构函数会被自动调用。需要注意的是,由于 Python 的垃圾回收机制是基于引用计数的,因此循环引用可能导致析构函数无法正确执行,在处理循环引用时需要特别注意。
下面是一个简单的析构函数的示例代码:
class MyClass:
def __del__(self):
print("Object is destroyed")
在此示例中,当 MyClass 的对象被销毁时,Python 解释器会自动调用 __del__ 函数并输出 "Object is destroyed"。
4 类的继承
在 Python 中,类的继承是指子类继承父类的属性和方法。子类可以在不重写任何父类方法的情况下,继承并执行父类中的方法。
Python 中类的继承是面向对象编程中一个重要的特性,它允许我们定义一个类作为另一个类的扩展,并继承其属性和方法。
4.1 继承语法
继承的语法非常简单,只需要在子类定义时在类名后加上父类名即可:
class ParentClass:
pass
class ChildClass(ParentClass):
pass
在上述示例中,ChildClass 继承了 ParentClass 的所有属性和方法。
4.2 继承种类
在 Python 中,类的继承有三种种类:单继承、多继承和多层继承。
| 类型 | 描述 | 继承方式 | 应用场景 |
|---|---|---|---|
单继承 | 一个子类只有一个直接的父类 | 使用单个类的名称 | 简单继承关系、子类仅需要继承一个父类的方法和属性 |
多继承 | 一个子类有多个直接的父类 | 使用多个类的名称,用逗号隔开 | 需要从多个父类中继承不同特性,需要灵活的继承体系 |
抽象基类继承 | 定义接口规范,供子类继承和实现 | 使用 abc 模块定义抽象基类 | 定义规范接口,约束子类的实现方式 |
需要注意的是,Python 语言本身不强制要求使用某一种继承方式,开发人员可以根据具体的需求和设计要求进行选择。但一般来说,单继承是最简单也最常见的继承方式,多继承需要慎重考虑,避免出现多重继承导致的命名冲突和复杂度高的问题,而抽象基类的继承则更多地用于接口和规范的定义。
4.2.1 单继承
单继承是指一个子类只继承一个父类的属性和方法。示例如下:
class ParentClass:
def parent_method(self):
print("This is a method from ParentClass.")
class ChildClass(ParentClass):
def child_method(self):
print("This is a method from ChildClass.")
c = ChildClass()
c.parent_method() # This is a method from ParentClass.
c.child_method() # This is a method from ChildClass.
在上述示例中,ChildClass 继承了 ParentClass 的 parent_method() 方法,并且可以在子类中访问和使用。
4.2.2 多继承
多继承是指一个子类继承多个父类的属性和方法。在 Python 中,多继承的语法是在定义子类时指定多个父类,并用逗号分隔它们。示例如下:
class ParentClass1:
def parent_method1(self):
print("This is a method from ParentClass1.")
class ParentClass2:
def parent_method2(self):
print("This is a method from ParentClass2.")
class ChildClass(ParentClass1, ParentClass2):
def child_method(self):
print("This is a method from ChildClass.")
c = ChildClass()
c.parent_method1() # This is a method from ParentClass1.
c.parent_method2() # This is a method from ParentClass2.
c.child_method() # This is a method from ChildClass.
在上述示例中,ChildClass 继承了两个父类的属性和方法。
4.2.3 多层继承
多层继承是指一个子类继承一个父类,而这个父类本身也是另一个父类的子类。示例如下:
class GrandParentClass:
def grandparent_method(self):
print("This is a method from GrandParentClass.")
class ParentClass(GrandParentClass):
def parent_method(self):
print("This is a method from ParentClass.")
class ChildClass(ParentClass):
def child_method(self):
print("This is a method from ChildClass.")
c = ChildClass()
c.grandparent_method() # This is a method from GrandParentClass.
c.parent_method() # This is a method from ParentClass.
c.child_method() # This is a method from ChildClass.
在上述示例中,GrandParentClass 是 ParentClass 的父类,而 ParentClass 又是 ChildClass 的父类。
4.3 访问父类的属性和方法
可以,子类可以访问父类公有属性、公有方法,但无法直接访问父类私有属性、私有方法。
| 父类公有属性 | 父类私有属性 | 父类公有方法 | 父类私有方法 | |
|---|---|---|---|---|
| 子类访问 | 可以 | 无法访问 | 可以 | 无法访问 |
class ParentClass:
parent_public_var = "This is a public variable in parent class."
__parent_private_var = "This is a private variable in parent class."
def parent_public_method(self):
print("This is a public method from ParentClass.")
def __parent_private_method(self):
print("This is a private method from ParentClass.")
class ChildClass(ParentClass):
def child_method(self):
print(self.parent_public_var) # 访问父类的公有属性
print(self.parent_public_method()) # 访问父类的公有方法
print(self._ParentClass__parent_private_var) # 访问父类的私有属性,要加上 _ParentClass
print(self._ParentClass__parent_private_method()) # 访问父类的私有方法,要加上 _ParentClass
print("5--", self.__parent_private_var) # 访问父类的私有属性,报错
print("6--", self.__parent_private_method()) # 访问父类的私有方法,报错
c = ChildClass()
c.child_method()
在上述示例中,我们在子类 ChildClass 中访问了父类 ParentClass 的公有属性和方法、私有属性和方法,并给出了相应的访问方式。
需要注意的是,访问私有属性和方法时需要使用类名前缀加两个下划线 "__",否则会出现 AttributeError。
在 Python 中,要访问父类的属性和方法有两种方式:使用 super() 函数和使用父类的类名。
4.3.1 使用 super() 函数
super() 函数可以用于获取当前子类继承自父类的实例,从而调用父类的方法或属性。其用法如下:
class Parent:
def __init__(self, name):
self.name = name
self.age = 50
class Child(Parent):
def __init__(self, name, grade):
super().__init__(name) # 调用父类的 __init__ 方法
self.grade = grade
p = Child("Tom", 4)
print(p.name) # 访问父类的属性
在子类的 __init__ 方法中,使用 super().__init__(name) 调用父类的 __init__ 方法,从而完成对父类的属性进行初始化。这种方式可以确保子类的属性和父类的属性均被正确初始化。
4.3.2 使用父类的类名
在 Python 中,也可以使用父类的类名来调用父类的方法或属性,其用法如下:
class Parent:
def __init__(self, name):
self.name = name
self.age = 50
class Child(Parent):
def __init__(self, name, grade):
Parent.__init__(self, name) # 调用父类的 __init__ 方法
self.grade = grade
p = Child("Tom", 4)
print(p.name) # 访问父类的属性
在子类的 __init__ 方法中,使用 Parent.__init__(self, name) 调用父类的 __init__ 方法,完成对父类的属性进行初始化。这种方式不推荐使用,因为如果子类继承自多个父类,那么使用父类的类名访问父类的方法会比较麻烦。此时需要使用 super() 函数来代替。
4.4 修改父类的属性和方法
4.4.1 修改父类的属性
在 Python 中,如果要修改父类的属性,一般有以下两种方式:
4.4.1.1 直接修改父类属性
如果父类的属性是公有(即没有使用双下划线开头),可以通过直接访问父类的属性来修改其值,例如:
class Parent:
x = 1 # 父类属性
class Child(Parent):
pass
p = Parent()
print(p.x) # 输出 1
c = Child()
print(c.x) # 输出 1
Parent.x = 2 # 直接修改父类属性值
print(p.x) # 输出 2
print(c.x) # 输出 2,因为 Child 类继承自 Parent 类,所以也会受到影响
需要注意的是,如果父类的属性是私有(即使用双下划线开头),则不能直接访问和修改该属性,因为私有属性是无法被子类继承和访问的。
4.4.1.2 重写父类属性
如果不想修改父类的属性值,而是想在子类中重新定义同名的属性,可以使用方法重写(Override)的技术来实现,例如:
class Parent:
x = 1 # 父类属性
class Child(Parent):
x = 2 # 子类重新定义的属性值
p = Parent()
print(p.x) # 输出 1
c = Child()
print(c.x) # 输出 2,因为 Child 类重新定义了 x 属性
Parent.x = 3 # 修改父类属性值
print(p.x) # 输出 3
print(c.x) # 输出 2,因为子类重写父类属性后,不会受到父类属性值的影响
需要注意的是,如果在子类中重写了父类的属性后,实例访问该属性时,会优先使用子类的属性值,而不是父类的属性值。
4.4.2 修改父类的方法
4.4.2.1 重写
如果你的父类方法的功能不能满足你的需求,你可以在子类重写你父类的方法,
Python 类中的方法重写(Method Overriding)指的是在子类中重新定义或修改从父类继承而来的方法,使得子类对象能够按照自己的需求进行特定的操作。在方法重写过程中,子类方法覆盖了父类中同名的方法,并改变了它的行为。
方法重写的常见应用场景是子类具有和父类相同的方法名和参数列表,但是含义或实现不同,这时就需要使用方法重写来覆盖父类的默认实现。
下面是一个简单的示例代码来演示方法重写:
class Animal:
def make_sound(self):
print("The animal makes a sound.")
class Cat(Animal):
def make_sound(self):
print("The cat meows.")
animal = Animal()
animal.make_sound() # The animal makes a sound.
cat = Cat()
cat.make_sound() # The cat meows.
在上面的代码中,Animal 类定义了一个 make_sound 方法,该方法输出了一句话“the animal makes a sound”。Cat 类继承了 Animal 类,并重写了 make_sound 方法,使得其输出了一句话“the cat meows”,这就是方法重写的示例。
总之,方法重写是一种重要的面向对象编程的特性,使得子类可以定制化自己独有的行为,并且可以覆盖父类的默认实现。
4.4.2.2 使用 super() 函数
在继承中,我们可能需要在子类中重写或修改父类的方法,但是又需要保留父类原来的实现。为了实现这个目标,我们可以使用 super() 函数调用父类的方法。super() 函数可以让我们在子类中调用父类的方法,从而实现方法的重写和修改,并保留父类原来的实现。示例如下:
class ParentClass:
def parent_method(self):
print("This is the original implementation of parent_method.")
class ChildClass(ParentClass):
def parent_method(self):
super().parent_method() # 调用父类的 parent_method() 方法
print("This is the modified implementation of parent_method from ChildClass.")
c = ChildClass()
c.parent_method()
在上述示例中,ChildClass 中的 parent_method() 方法重写了父类 ParentClass 中的 parent_method() 方法,但是又调用了父类的实现,并在此基础上实现了新的行为。
4.5 子类的类型判断
使用 isinstance() 函数,判断 class 的类型
#!/usr/bin/env python3
# -*- coding: UTF-8 -*-
class User1(object):
pass
class User2(User1):
pass
class User3(User2):
pass
if __name__ == '__main__':
user1 = User1()
user2 = User2()
user3 = User3()
# isinstance()就可以告诉我们,一个对象是否是某种类型
print(isinstance(user3, User2))
print(isinstance(user3, User1))
print(isinstance(user3, User3))
# 基本类型也可以用isinstance()判断
print(isinstance('两点水', str))
print(isinstance(347073565, int))
print(isinstance(347073565, str))
输出的结果如下:
True
True
True
True
True
False
5 类的多态
多态的概念是指对不同类型的变量进行相同的操作,它会根据对象(或类)类型的不同而表现出不同的行为。
事实上,我们经常用到多态的性质,比如:
>>> 1 + 2
3
>>> 'a' + 'b'
'ab'
对两个整数进行 + 操作,会返回它们的和,对两个字符进行相同的 + 操作,会返回拼接后的字符串。
也就是说,不同类型的对象对同一消息会作出不同的响应。
Python 中的多态是指不同的类对象通过调用相同的方法名,可以得到不同的结果。Python 之所以支持多态,是因为所有的类对象都是从 object 类派生出来的,因此默认都会有一些 magic method,例如 __str__、__repr__、__add__、__lt__等,可以被调用,改变类对象的默认行为。
通过实现 Python 类的多态,可以带来很多的好处。在编写代码时,我们可以利用这个特性来提高代码的可读性、可扩展性和可维护性,从而更好地满足不同的需求。
5.1 实现多态
实现 Python 类的多态需要满足两个前提条件:继承和重写。在 Python 中,继承是通过在类定义时使用括号引入父类实现的。子类继承了父类的属性和方法,在子类中可以对方法进行重写,给方法的默认实现加上一些个性化的东西。
例如,我们定义一个 Pet 类和其两个子类 Dog 和 Cat。在 Pet 类中定义了一个 speak() 方法,用于输出一个动物叫声的字符串。而在 Dog 和 Cat 子类中,我们可以通过重写这个方法,对生成的动物叫声字符串进行个性化的修改。由于 Dog 和 Cat 都继承了 Pet,所以在输出它们的叫声字符串时会有不同的表现,这就体现了 Python 类的多态。
下面是代码示例:
class Pet:
def __init__(self, name):
self.name = name
def speak(self):
pass
class Dog(Pet):
def speak(self):
return "汪汪!"
class Cat(Pet):
def speak(self):
return "喵喵!"
dog = Dog("小黑")
cat = Cat("花花")
print(dog.speak()) # 输出:汪汪!
print(cat.speak()) # 输出:喵喵!
注意:
- 父类定义的方法名不能和子类中的方法名相同,否则会被子类中的定义覆盖。
- 子类实例调用方法时,将会优先调用子类中自己的方法,如果子类中没有实现,才会从父类中寻找。
类的多态的优点:
- 提高代码的灵活性和可扩展性,使得同一段代码可以适用于不同的类实例,从而更好地满足不同的需求。
- 可以减少代码重复,提高代码的可维护性和可读性。
- 可以降低程序的复杂度,提高程序的可理解性。