封装
封装概述
- 将数据和操作组织到类中(属性和方法);
- 将数据隐藏起来,给使用者提供操作(方法)。使用者通过操作就可以获取或者修改数据(getter和setter);
- 通过访问控制,暴露适当的数据和操作给用户,该隐藏的隐藏起来(私有成员或保护成员)。
Python 类定义语法
在Python中,定义一个类的语法非常简洁和直观。下面是一个基本的类定义的语法:
class ClassName:
def __init__(self, parameter1, parameter2, ...): # 初始化方法(构造函数),用于创建类的实例并初始化属性
self.parameter1 = parameter1
self.parameter2 = parameter2
...
def method1(self, arg1, arg2, ...):
# 类的方法定义
# 方法体
...
def method2(self, arg1, arg2, ...):
# 另一个方法定义
# 方法体
...class关键字用于定义一个类。ClassName是类的名称,通常采用驼峰命名法(大驼峰,每个单词的首字母大写)。- 类的属性和方法定义在类的缩进块中。
__init__()方法是一个特殊的方法,称为构造函数,用于初始化类的对象。这个方法在创建类的实例时自动调用。self参数:在类的方法中,第一个参数通常被命名为self,它表示类的实例本身,可以用来访问和操作类的属性和方法。self是一个指向对象本身的引用,用于访问对象的属性和方法。- 类的方法定义与普通函数定义类似,但是第一个参数必须是
self,用于表示对象本身。 - 在类的方法中,可以通过
self来访问对象的属性和调用其他方法。
类的属性和方法
- 属性类似于对象的变量,它们用于存储对象的状态信息。
- 方法则类似于函数,它们定义在类中并且可以访问对象的属性,用于实现对象的行为或操作。
- 属性和方法都可以通过点运算符来访问。属性通常用于描述对象的特征或状态,而方法则用于实现对象的行为或操作。
_init_ 方法
__init__ 方法是Python类中的一个特殊方法,用于初始化新创建的对象。当你实例化一个类时,__init__ 方法会被自动调用,用于执行一些对象的初始化操作。
下面是关于 __init__ 方法的一些详解:
-
命名和双下划线:
__init__是一个特殊的方法,其名称前后都有双下划线。这是Python中约定的一种命名方式,表示这是一个特殊用途的方法。 -
初始化参数:
__init__方法的第一个参数通常是self,它代表要初始化的对象自身。除了self参数之外,你可以在__init__方法中定义其他参数,这些参数用于传递初始化对象时所需的信息。 -
对象属性的初始化:
__init__方法通常用于初始化对象的属性。在方法内部,你可以通过self来访问对象的属性,并对其进行赋值。 -
自定义初始化逻辑:你可以在
__init__方法中编写任何初始化逻辑,包括条件语句、循环等,以满足特定的对象初始化需求。 -
继承和重写:如果你的类继承自其他类,且子类中也定义了
__init__方法,则子类的__init__方法会覆盖父类的__init__方法。在子类的__init__方法中,你可以通过调用super().__init__()来显式调用父类的初始化方法,以确保父类的初始化逻辑也被执行。 -
返回值:
__init__方法不应该显式返回任何值,它的主要目的是在对象创建时进行初始化操作。如果__init__方法返回一个值,Python会忽略它。
下面是一个简单的例子,演示了 __init__ 方法的使用:
class Person:
def __init__(self, name, age):
self.name = name
self.age = age
## 创建一个新的 Person 对象
person1 = Person("Alice", 30)
## 访问对象的属性
print(person1.name) # 输出:Alice
print(person1.age) # 输出:30在这个例子中,__init__ 方法接受两个参数 name 和 age,并用它们来初始化 Person 对象的 name 和 age 属性。
注意事项
__init__方法中的self必须留下来
__init__方法的返回值必须为None
print(Person) # 不会调用__init__
print(Person()) # 会调用__init__,但这个实例打印后就消亡了
a = Person() # 会调用__init__self
self 指向的是当前实例自身
在Python中,self 是一个特殊的参数,用于表示类的实例本身。在类的方法定义中,第一个参数通常都是 self,它允许类的方法访问和操作对象的属性和方法。
下面是关于 self 的一些详解:
-
表示实例本身:
self参数表示调用该方法的实例本身。在调用类的方法时,Python会自动传递该实例作为第一个参数给方法,所以在方法定义时需要将self作为第一个参数。 -
属性和方法访问:通过
self,可以在类的方法中访问和操作对象的属性和方法。例如,self.attribute可以访问对象的属性,self.method()可以调用对象的方法。 -
创建实例变量:在
__init__方法中,通过self可以创建实例变量,这些变量对于该实例是特有的。
下面是一个简单的示例说明 self 的用法:
class MyClass:
def __init__(self, value):
self.value = value # 创建实例变量
def get_value(self):
return self.value # 访问实例变量
def set_value(self, new_value):
self.value = new_value # 修改实例变量
## 创建类的实例
obj = MyClass(10)
## 调用方法
print(obj.get_value()) # 输出:10
## 修改实例变量
obj.set_value(20)
print(obj.get_value()) # 输出:20在这个例子中,self 允许我们在方法中访问实例变量 value,并且可以在方法中修改它。
Python 类实例化和初始化
当类定义完成后,要使用类,就需要将类进行实例化。在Python中,实例化一个类包含两个主要阶段:实例化和初始化。
-
实例化(Instantiation):这是创建类的实例的过程。当你使用类名后跟括号
()创建一个类的实例时,Python会分配内存来存储该实例,并返回对这个实例的引用。这个过程创建了一个对象,并为对象分配了内存空间,但此时尚未初始化对象的属性。 -
初始化(Initialization):这是在实例化之后调用类的
__init__方法的过程。__init__方法是类的构造函数,用于初始化对象的属性和状态。在__init__方法内部,你可以为对象设置属性的初始值,以便对象在创建后处于一个有意义的状态。- 如果没有定义,会在实例化后隐式调用其父类
__init__方法又称为构造方法或构造器
让我们通过一个示例来说明这两个阶段:
class Person:
def __init__(self, name, age):
self.name = name
self.age = age
## 实例化一个Person对象
person1 = Person("Alice", 30)
## 实例化过程完成后,对象person1已经存在,但尚未初始化属性
## 初始化过程:调用__init__方法来初始化对象的属性
## person1.__init__("Alice", 30) # 这是隐式调用,无需手动调用
## 现在,person1对象的属性已经初始化
print(f"{person1.name} is {person1.age} years old.") # 输出: Alice is 30 years old.在这个示例中,首先进行了实例化,然后随后的初始化阶段通过调用__init__方法来为person1对象的属性赋值,从而使对象处于一个有用的状态。这两个阶段合在一起构成了创建类实例的完整过程。
PS:
在Python中,当你创建一个类的实例时,Python会自动调用该类的__init__方法作为初始化过程。__init__方法是类的构造函数,用于初始化类的属性。
在__init__方法中,通常会有一个特殊的形参,即self,它代表类的实例本身。self参数是必须的,并且是习惯性的命名方式,但你可以在方法定义中使用任何名称,尽管不建议这么做。其他形参是可选的,你可以在__init__方法中定义其他参数来接受外部传递的值,然后用这些值来初始化类的属性。
方法绑定
class Person:
def __init__(self, name, age=18):
self.name = name
self.age = age
def showage(self):
print("{} is {}.".format(self.name, self.age))
tom = Person('Tom', 20)
print(tom) # <__main__.Person object at 0x00000182B31CB190>
print(tom.name, tom.age) # Tom 20
tom.showage() # Tom is 20.- 调用函数showage()时,明明需要一个实参self,而未提供时并未报错,这是为什么?
在Python中,当你调用一个类的实例方法时,Python会自动为方法的第一个参数self提供实际的实例作为参数。这是一种语言特性,称为方法绑定(method binding)。
当你调用 tom.showage() 时,虽然你没有明确提供 self 参数,但Python会隐式地将 tom 这个实例作为 self 参数传递给 showage 方法。这是因为在类内部定义的方法,Python会自动将调用该方法的实例绑定到方法的第一个参数(通常命名为 self,但你可以使用任何名称)。这使得在方法内部能够访问该实例的属性和方法。
这种自动绑定的机制使得在调用实例方法时不需要显式传递 self 参数,因为Python会处理这个细节。你只需要关注方法的其他参数(如果有的话)以及方法内部的实现逻辑。
所以,虽然你的 showage 方法定义中有一个 self 参数,但在调用时不需要显式提供它,Python会自动将正确的实例传递给它。这就是为什么调用 tom.showage() 时不会报错的原因。
相关魔术方法
Python中的实例化过程包含了两个重要的魔术方法:__new__和__init__。
-
__new__方法:__new__方法是在实例化对象之前调用的,它负责创建并返回实例对象。通常情况下,你不需要显式地实现__new__方法,因为Python会提供默认的实现。如果你确实需要自定义对象的创建过程,你可以重写__new__方法。在__new__方法中,你可以创建实例并返回它,然后将该实例传递给__init__方法。 -
__init__方法:__init__方法是在对象已经被创建(由__new__方法返回)之后调用的,它负责初始化对象的属性。通常情况下,你会在__init__方法中设置对象的属性和状态。
以下是一个示例,演示了__new__和__init__方法的用法:
class MyClass:
def __new__(cls):
# 自定义__new__方法,创建实例并返回
instance = super(MyClass, cls).__new__(cls)
print(f"Creating an instance of MyClass: {instance}")
return instance
def __init__(self):
# 初始化方法,设置对象的属性
print(f"Initializing the instance: {self}")
self.value = 42
## 实例化MyClass对象,会调用__new__和__init__方法
obj = MyClass()
print(f"Value of obj: {obj.value}")在这个示例中,__new__方法被自定义,它创建了一个实例并返回它,然后__init__方法在对象创建后被调用,用于设置对象的属性。最终,对象obj被实例化,并可以访问其属性value。
无参实例化
“无参实例化” 意味着创建一个对象实例时,不需要传递任何参数给对象的构造函数。
在 Python 中,构造函数通常是类的 __init__ 方法,它用于初始化对象的属性和状态。
如果一个类的构造函数没有参数,那么创建该类的对象时可以省略参数值。
让我们通过一个示例来说明 “无参实例化” 的概念:
class MyClass:
def __init__(self):
self.value = 0
## 无参实例化,不需要传递参数给构造函数
obj = MyClass()
## 访问对象的属性
print(obj.value) # 输出: 0在上面的示例中,MyClass 类的构造函数 __init__ 没有任何参数,因此在创建 obj 对象时,我们可以简单地使用 MyClass(),而无需传递任何参数。
相反,如果构造函数有参数,那么在创建对象时需要提供与构造函数参数匹配的值。例如:
class MyOtherClass:
def __init__(self, initial_value):
self.value = initial_value
## 需要传递参数给构造函数
obj = MyOtherClass(42)
## 访问对象的属性
print(obj.value) # 输出: 42在这个示例中,MyOtherClass 类的构造函数 __init__ 接受一个参数 initial_value,因此在创建 obj 对象时,我们必须传递一个整数值作为参数。
注意事项
- 通常,每次实例化后获得的实例,是不同的实例,即使是使用同样的参数实例化,也得到不一样的对象。
—
示例 1
下面是一个简单的示例,演示了如何创建类的实例并使用类的方法:
## 定义一个类
class Dog:
# 构造函数
def __init__(self, name, age):
self.name = name
self.age = age
# 方法
def bark(self):
print(f"{self.name} says: Woof!")
## 创建类的实例(对象)
my_dog = Dog("Buddy", 3)
## 调用对象的方法
my_dog.bark()在这个示例中,我们定义了一个名为Dog的类,它具有一个构造函数__init__()用于初始化对象的属性name和age,以及一个方法bark()用于模拟狗叫声。然后,我们通过调用Dog类的构造函数创建了一个名为my_dog的实例,并使用.运算符调用了对象的bark()方法。
类的方法通常可以访问对象的属性,并且可以在方法中执行与对象相关的操作。在调用类的方法时,Python会自动将调用对象传递给方法的第一个参数(通常命名为self),因此在方法内部可以通过self来访问对象的属性和调用其他方法。
示例 2
以下是一个简单的示例,展示了一个包含构造函数和两个方法的类定义:
class Person:
def __init__(self, name, age):
self.name = name
self.age = age
def introduce(self):
print(f"Hello, my name is {self.name} and I'm {self.age} years old.")
def celebrate_birthday(self):
self.age += 1
print(f"Happy birthday! Now I'm {self.age} years old.")
## 创建Person类的对象
person = Person("Alice", 30)
## 调用对象的方法
person.introduce()
person.celebrate_birthday()这个示例定义了一个名为Person的类,具有两个属性name和age,以及两个方法introduce()和celebrate_birthday()。然后,创建了一个Person类的对象,并调用了对象的方法。
示例:最低要求
class Person:
def __init__(self, name, age=18):
self.name = name
self.age = age
def show_age(self):
return self.age
@classmethod
def show_cls_name(cls):
return cls.__name__
azheng = Person('azheng')
print(azheng.show_age()) # 18
print(Person.show_age(azheng)) # 18
print(azheng.show_cls_name()) # Person
print(Person.show_cls_name()) # Person实现工具类(类方法和静态方法)
除了普通的实例方法外,Python还支持类方法和静态方法。
- 类方法使用
@classmethod装饰器定义,第一个参数通常命名为cls,表示类本身; - 而静态方法使用
@staticmethod装饰器定义,不需要传递类或对象的引用。
这些方法的调用方式与实例方法类似,但可以直接通过类名来调用。
class MyClass:
@classmethod
def class_method(cls):
print("This is a class method.")
@staticmethod
def static_method():
print("This is a static method.")
## 调用类方法
MyClass.class_method() # This is a class method.
## 调用静态方法
MyClass.static_method() # This is a static method.总的来说,使用类的方法需要先创建类的实例,然后通过实例来调用方法;同时也可以通过类名来调用类方法和静态方法。
@classmethod
@classmethod 是 Python 中用于定义类方法的装饰器。类方法是一种绑定到类而不是实例的方法,因此可以在不创建实例的情况下被调用。通常情况下,类方法的第一个参数是类本身,通常命名为 cls,而不是常规的 self。
-
在Python中,类方法的第一个参数通常被命名为
cls,这是一种约定俗成的命名方式,但其实你可以用任何名称。cls是指当前类的引用,它允许在类方法内部访问类的属性和调用其他类方法。通常情况下,你会看到cls作为第一个参数,以表明这是一个类方法。当你调用一个类方法时,Python会自动将类本身传递给这个方法的第一个参数,因此你可以在方法内部使用
cls参数来操作类的属性和方法。这个约定让代码更易读和易于理解,因为在看到
cls参数时,你就知道这是一个类方法,并且你可以通过它来访问类级别的东西。
@classmethod 的主要用途包括:
-
替代构造函数:有时候我们希望有不同的方式来创建类的实例,而不仅仅是通过
__init__方法。类方法可以提供这样的功能,它们可以接受不同的参数,并返回一个新的类实例。 -
访问或修改类的属性:由于类方法是绑定到类本身的,因此它们可以轻松地访问类的属性或方法,无需创建类的实例。
-
在类的层次结构中提供一致的接口:通过在基类中定义类方法,子类可以继承这些方法,并在需要时进行覆盖或扩展。
下面是一个示例来说明 @classmethod 的用法:
class MyClass:
count = 0
def __init__(self, name):
self.name = name
MyClass.count += 1
@classmethod
def get_count(cls):
return cls.count
@classmethod
def create_instance(cls):
return cls("Anonymous")
## 创建 MyClass 的实例
obj1 = MyClass("Object 1")
obj2 = MyClass("Object 2")
## 调用类方法获取实例数量
print(MyClass.get_count()) # 输出: 2
## 使用类方法创建新的实例
obj3 = MyClass.create_instance()
print(obj3.name) # 输出: Anonymous在上面的示例中,get_count 类方法用于获取 MyClass 类的实例数量,而 create_instance 类方法用于创建一个匿名的 MyClass 实例。通过 @classmethod 装饰器,这些方法可以直接通过类名调用,而不需要通过实例调用。
MyClass.create_instance() 是怎么创建新实例的?
MyClass.create_instance() 方法创建新实例的过程是通过调用类的构造函数来完成的。在示例中,create_instance() 方法内部调用了 cls("Anonymous"),其中 cls 就是指代类本身,即 MyClass。这相当于调用 MyClass("Anonymous"),从而创建了一个新的 MyClass 实例,并将其返回。
因此,create_instance() 方法的作用是提供一种简单的方式来创建新实例,并且可以灵活地接受参数进行初始化。在这个示例中,参数 “Anonymous” 用于初始化新实例的 name 属性,但具体的初始化逻辑可以根据需要进行修改。
实例变量和类变量
class Person:
age = 3 # 类变量是类的变量,是类的所有实例共享的属性或方法
def __init__(self, name):
self.name = name # 实例变量是每一个实例自己的变量,是自己独有的
tom = Person('tom')
jerry = Person('jerry')
print(tom.name, tom.age) # tom 3
print(jerry.name, jerry.age) # jerry 3
Person.age = 100
print(tom.name, tom.age) # tom 100
print(jerry.name, jerry.age) # jerry 100类的特殊属性
__name__
在Python中,__name__是一个特殊的属性,用于确定模块的名称。当一个Python文件被直接执行时,__name__属性的值为__main__;而当它被导入到另一个模块中时,__name__的值为该模块的名称。
这个特性在编写可重用的模块时非常有用。当一个模块被导入时,你可能不希望某些代码块被执行,而只希望在该模块被直接执行时执行。通过检查__name__属性,你可以编写这样的代码块。
例如,考虑以下Python文件 example.py:
def main():
print("This is the main function.")
if __name__ == "__main__":
main()当你直接运行 example.py 时,__name__的值将是__main__,因此main()函数会被调用并打印"This is the main function."。但如果你将 example.py 作为一个模块导入到另一个Python文件中,则__name__的值将是example,main()函数不会被自动调用。
这种用法使得一个模块可以同时作为独立程序执行,也可以作为一个库被导入到其他程序中使用。
__class__
在Python中,__class__是一个特殊的属性,用于获取一个对象所属的类。当你调用这个属性时,它返回一个对象的类对象(也就是类本身)。
这个属性通常用于动态地获取对象的类信息,特别是在面向对象编程中。它允许你在运行时检查对象所属的类,并且可以根据类来执行相应的操作。
以下是一个简单的例子来说明__class__的用法:
class MyClass:
pass
obj = MyClass()
print(obj.__class__) # 输出:<class '__main__.MyClass'>在这个例子中,obj是MyClass类的一个实例,当我们调用obj.__class__时,它返回MyClass类的引用。
__class__属性在某些情况下也可以用于动态地创建新的对象。例如,你可以通过获取对象的类,然后使用类来实例化一个新对象。这在某些设计模式和元编程中非常有用。
需要注意的是,虽然__class__属性可以被访问,但它是一个只读属性,不能被修改。
__dict__
在Python中,__dict__是一个特殊的属性,它是一个字典,包含了一个对象的所有属性和方法。字典中的键是属性或方法的名称,值是对应的属性值或方法对象。
当你访问一个对象的__dict__属性时,你可以动态地查看和修改对象的属性和方法,而不需要使用点语法(.)来访问它们。
示例1:
class Person:
age = 3
def __init__(self, name):
self.name = name
tom = Person('tom')
print(tom) # <__main__.Person object at 0x000002AEB5B0A740>
print(tom.__class__, type(tom), tom.__class__ is type(tom)) # <class '__main__.Person'> <class '__main__.Person'> True
print(tom.__class__.__name__, type(tom).__name__) # Person Person
print(tom.__class__.__dict__) # {'__module__': '__main__', 'age': 3, '__init__': <function Person.__init__ at 0x0000029032C1A4D0>, '__dict__': <attribute '__dict__' of 'Person' objects>, '__weakref__': <attribute '__weakref__' of 'Person' objects>, '__doc__': None}
print(tom.name) # tom
print(tom.__dict__) # {'name': 'tom'}示例2:
class Person:
age = 3
height = 170
def __init__(self, name, age=18):
self.name = name
self.age = age
tom = Person('Tom')
jerry = Person('Jerry', 20)
Person.age = 30
print(Person.age, tom.age, jerry.age) # 30 18 20
print(Person.height, tom.height, jerry.height) # 170 170 170
jerry.height = 175
print(Person.height, tom.height, jerry.height) # 170 170 175
tom.height += 10
print(Person.height, tom.height, jerry.height) # 170 180 175
Person.height += 15
print(Person.height, tom.height, jerry.height) # 185 180 175
Person.weight = 70
print(Person.weight, tom.weight, jerry.weight) # 70 70 70
print(tom.__dict__['height']) # 180
print(tom.__dict__['weight']) # weight示例3:
下面是一个简单的例子来说明__dict__的用法:
class MyClass:
def __init__(self, x, y):
self.x = x
self.y = y
def add(self):
return self.x + self.y
obj = MyClass(3, 4)
print(obj.__dict__) # 输出:{'x': 3, 'y': 4}在这个例子中,obj.__dict__返回一个字典,其中包含了对象obj的所有属性和它们的值。
__dict__属性的主要用途之一是动态地添加、删除或修改对象的属性。你可以直接操作__dict__字典来实现这些操作,而不必使用点语法。例如:
obj.z = 5 # 添加一个新属性
print(obj.__dict__) # 输出:{'x': 3, 'y': 4, 'z': 5}
del obj.x # 删除一个属性
print(obj.__dict__) # 输出:{'y': 4, 'z': 5}需要注意的是,__dict__属性只包含了对象的实例属性和方法,而不包括类属性和方法。类属性和方法存储在类的__dict__属性中。
__qualname__
在Python中,__qualname__是一个特殊的属性,用于获取一个类或函数的限定名称(qualified name)。限定名称包括了类或函数的整个层次结构,以点号分隔。
__qualname__属性通常在反射(reflection)和元编程(metaprogramming)中使用,以便动态地获取类或函数的完整名称。
下面是一个简单的例子来说明__qualname__的用法:
class MyClass:
def method(self):
pass
print(MyClass.__qualname__) # 输出:MyClass
print(MyClass.method.__qualname__) # 输出:MyClass.method在这个例子中,MyClass.__qualname__返回类MyClass的限定名称,而MyClass.method.__qualname__返回类方法method的限定名称。
__qualname__属性的主要用途之一是在日志记录和调试信息中使用。通过在类或函数中包含限定名称,可以帮助你更清楚地了解代码的结构和层次关系。
需要注意的是,对于在模块中定义的顶级类或函数,__qualname__属性与__name__属性通常是相同的。但是,对于嵌套类或函数,它们的限定名称会包括它们所属的所有父类或函数的名称。
示例:类 print
class Person:
"""A Example of Class"""
x = 'abc' # 类属性
def showme(self): # 方法,也是类属性
"showme method"
return __name__ # 返回类的名称
print(Person) # <class '__main__.Person'>
print(Person.showme) # <function Person.showme at 0x000001FBD519A4D0>,类属性
print(Person.__name__) # Person,类名字
print(Person.__class__, type(Person)) # <class 'type'> <class 'type'>
print(Person.showme.__qualname__) # Person.showme
print(Person.__doc__) # A Example of Class,类文档
print(Person.showme.__doc__) # showme method,类方法文档print(Person):这将打印Person类的字符串表示,通常显示类的名称以及其所属的模块(在这个例子中,类定义在__main__模块中),所以输出是<class '__main__.Person'>。print(Person.showme):这将打印Person类的showme方法的字符串表示,包括其内存地址。输出类似于<function Person.showme at 0x000001FBD519A4D0>。print(Person.__name__):这将打印Person类的 名称,所以输出是Person。print(Person.__class__, type(Person)):这将打印Person类的类,也就是type,因为Person本身就是一个类,所以两者输出都是<class 'type'>。print(Person.showme.__qualname__):这将打印showme方法的限定名称(qualified name),限定名称通常是一个字符串,格式为类名.方法名。在这个例子中,Person.showme是showme方法的限定名称,所以输出是Person.showme。print(Person.__doc__):这将打印Person类的文档字符串。在你的示例中,Person类的文档字符串是"A Example of Class",它描述了这个类的用途或概要。print(Person.showme.__doc__):这将打印showme方法的文档字符串。在你的示例中,showme方法的文档字符串是"showme method",它提供了关于这个方法的简要描述。
打补丁(Monkey patching)
在 Python 中,“monkey patching” 通常用于动态地在运行时更改类或模块的行为,通常用于临时修复问题或者在没有源代码访问权限的情况下修改程序行为(但最常用的还是增强和扩展原有代码)。
注意事项:
- 副作用:猴子补丁可能会导致意外行为和副作用,特别是在大型代码库或过度使用时。
- 调试:它可能会使代码更难理解和调试,因为从源代码中可能无法清楚地了解对象的行为。
- 文档:在进行猴子补丁时,适当的文档至关重要,以确保其他人(以及未来的自己)了解修改内容。
猴子补丁的替代方案:
- 子类化:与其直接修改类,不如考虑子类化和覆盖方法。
- 依赖注入:显式传递依赖项,而不是依赖全局状态,这样可以更轻松地替换行为。
应谨慎使用猴子补丁,并在可能的情况下考虑替代方案,以保持代码清晰度并避免意外行为。
示例1:
打补丁前
t2.py
class Person:
def __init__(self, name):
self.name = name
def get_score(self):
return {'History': 66, 'Chinese': 77, 'English': 88}t1.py
from t2 import Person
if __name__ == '__main__':
tom = Person('Tom')
print(tom.get_score()) # {'History': 66, 'Chinese': 77, 'English': 88},假设t2模块有缺陷,需要都改为100分打补丁
t3.py
- 补丁代码
def get_score(self):
return {'History': 100, 'Chinese': 100, 'English': 100}t2.py
- 不变
class Person:
def __init__(self, name):
self.name = name
def get_score(self):
return {'History': 66, 'Chinese': 77, 'English': 88}t1.py
from t2 import Person
from t3 import get_score
def monkeypatch4Person(): # 猴子补丁函数
Person.get_score = get_score
monkeypatch4Person() # 执行该函数则打补丁,反之亦然
if __name__ == '__main__':
tom = Person('Tom')
print(tom.get_score()) # {'History': 100, 'Chinese': 100, 'English': 100}示例2:
假设您有一个简单的模块math_utils.py,其中包含一个函数add:
## math_utils.py
def add(a, b):
return a + b现在,您想要猴子补丁此函数以始终返回0。您可以通过以下方式实现:
import math_utils
def patched_add(a, b):
return 0
math_utils.add = patched_add
## 现在,调用math_utils.add(2, 3)将始终返回0访问控制
在Python中,封装是面向对象编程的一个重要概念,它允许你将数据隐藏在类内部,只暴露特定的接口给外部使用。封装有三种级别:公开(public)、保护(protected)和私有(private)
Python中的封装性通过使用私有属性和方法(以双下划线__开头)来实现,从而隐藏对象的内部实现细节,只暴露必要的接口。
在Python中,封装是一种面向对象编程的重要概念,它通过限制对类的属性和方法的访问来隐藏类的内部实现细节,从而提高了代码的安全性和可维护性。
Python中的封装通常通过以下两种方式实现:
-
私有属性和方法:可以通过在属性名或方法名前面添加两个下划线
__来将其标记为私有的。私有属性和方法只能在类的内部访问,无法从类的外部直接访问。但是,在Python中并没有严格的私有性限制,而是通过名称修饰来实现的,因此仍然可以通过一定的方式访问私有属性和方法,但是不建议这样做,因为违反了封装的原则。 -
属性和方法的访问器(Getter和Setter方法):可以通过定义Getter和Setter方法来控制对属性的访问和修改。Getter方法用于获取属性的值,Setter方法用于设置属性的值,并可以在设置值之前进行一些额外的逻辑检查或处理。
下面是一个简单的示例,演示了如何在Python中实现封装:
class BankAccount:
def __init__(self, balance=0):
self.__balance = balance # 私有属性
def deposit(self, amount):
if amount > 0:
self.__balance += amount
def withdraw(self, amount):
if 0 < amount <= self.__balance:
self.__balance -= amount
else:
print("Insufficient balance.")
def get_balance(self):
return self.__balance # Getter方法
## 创建对象
account = BankAccount(1000)
## 尝试直接访问私有属性(不推荐)
## print(account.__balance) # 这行代码会导致AttributeError
## 使用Getter方法获取账户余额
print("Current balance:", account.get_balance())
## 使用方法进行存款和取款
account.deposit(500)
account.withdraw(200)
## 输出更新后的余额
print("Updated balance:", account.get_balance())在这个示例中,BankAccount类包含了一个私有属性__balance,以及对该属性进行操作的方法deposit()和withdraw()。为了避免直接访问私有属性,我们提供了一个Getter方法get_balance()用于获取账户余额。通过这种方式,我们实现了对类的内部实现细节的封装,用户只能通过提供的接口来访问和修改属性值,从而提高了代码的安全性和可维护性。
__ 私有成员
两个下划线开头的成员,为私有成员。
私有属性
未使用私有属性前,年龄的增长不受控制:
class Person:
def __init__(self, name, age=18):
self.name = name
self.age = age
def showage(self):
return self.age
tom = Person("Tom")
tom.age += 999 # 可直接修改实例属性,不受控制
print(tom.showage()) # 1017,超出合理范围通过__将age设置为私有属性,实现控制:
class Person:
def __init__(self, name, age=18):
self.name = name
self.__age = age
# 类中定义增长年龄的方法
def growup(self, i=1):
if i > 0 and i < 150:
self.__age += i
else:
print('Age out of range')
def showage(self):
return self.__age
tom = Person("Tom")
## tom.age += 999 # AttributeError: 'Person' object has no attribute 'age',将age定义为私有属性后,无法直接修改该属性
tom.growup(999) # Age out of range,收到类中方法的限制
tom.growup(10) # 定义合理区间
print(tom.showage()) # 28私有方法
class Person:
def __init__(self, name, age=18):
self.name = name
self.__age = age
# 定义为私有方法
def __growup(self, i=1):
if i > 0 and i < 150:
self.__age += i
else:
print('Age out of range')
def showage(self):
self.__growup(10) # 只能在类内部使用该私有方法
return self.__age
tom = Person("Tom")
## tom.__growup(10) # AttributeError: 'Person' object has no attribute '__growup',将growup定义为私有方法后,无法在外部使用该方法
print(tom.showage()) # 28_ 保护成员
保护成员指的是在Python类中以一个下划线开头的成员。这意味着它们可以在类的外部使用,但是按照约定,它们应该被视为受保护的,不应该直接被外部代码访问或修改。
- 保护成员只是python程序员间共同的约定。
让我展示一个简单的例子:
class MyClass:
def __init__(self):
self._protected_var = 10 # 这是一个保护成员
def get_protected_var(self):
return self._protected_var
def set_protected_var(self, value):
self._protected_var = value
## 创建一个对象
obj = MyClass()
## 从外部访问保护成员
print(obj.get_protected_var()) # 输出: 10
## 直接访问保护成员(不推荐)
print(obj._protected_var) # 输出: 10
## 直接修改保护成员(不推荐)
obj._protected_var = 20
print(obj.get_protected_var()) # 输出: 20
## 使用类提供的方法修改保护成员(推荐)
obj.set_protected_var(30)
print(obj.get_protected_var()) # 输出: 30在这个例子中,_protected_var 是一个保护成员。尽管我们可以从外部直接访问和修改它,但是为了遵循最佳实践和约定,最好使用类提供的方法来操作保护成员。
类属性装饰器 @property
在Python中,使用property装饰器可以将类的方法转换为类的属性,这样可以实现对属性的访问和设置时调用特定的方法。
这个装饰器通常用于定义getter、setter和deleter方法,以便在访问、设置和删除属性时执行自定义的逻辑。
示例1
class Person:
def __init__(self, name, age=18):
self.name = name
self.__age = age
@property
def age(self):
return self.__age
@age.setter
def age(self, value):
self.__age = value
@age.deleter
def age(self):
del self.__age
tom = Person('Tom')
## getter:
print(tom.age) # 18
## setter:
tom.age = 50
print(tom.age) # 50
## deleter:
del tom.age
print(tom.age) # AttributeError: 'Person' object has no attribute '_Person__age'示例2
下面是一个简单的示例来详解property装饰器的用法:
class Circle:
def __init__(self, radius):
self._radius = radius
@property # getter
def radius(self):
return self._radius
@radius.setter
def radius(self, value):
if value <= 0:
raise ValueError("Radius must be positive")
self._radius = value
@radius.deleter
def radius(self):
del self._radius
## 创建一个Circle对象
circle = Circle(5)
## 访问radius属性,实际上调用了radius()方法
print(circle.radius) # 输出: 5
## 设置radius属性,实际上调用了radius.setter修饰的方法
circle.radius = 10
print(circle.radius) # 输出: 10
## 尝试设置无效的半径值,会触发ValueError
try:
circle.radius = -1
except ValueError as e:
print(e) # 输出: Radius must be positive
## 删除radius属性,实际上调用了radius.deleter修饰的方法
del circle.radius
## 尝试访问已删除的属性会引发AttributeError
try:
print(circle.radius)
except AttributeError as e:
print(e) # 输出: 'Circle' object has no attribute '_radius'在上面的示例中,@property装饰器用于将radius方法转换为radius属性,@radius.setter用于定义设置属性时的逻辑,@radius.deleter用于定义删除属性时的逻辑。这样,通过访问radius属性、设置radius属性和删除radius属性时,实际上调用了相应的方法,从而实现了对属性的自定义控制。
对象的销毁 __del__ 方法
在Python中,__del__ 方法是一个特殊的方法,用于定义对象销毁时的行为。当对象的引用计数归零时(即没有任何变量指向该对象时),Python 解释器会自动调用对象的 __del__ 方法。这个方法可以用来执行一些清理工作,如释放资源、关闭文件等。但需要注意的是,__del__ 方法的调用时机并不是确定的,因为对象的销毁通常由垃圾回收机制来管理,而不是简单地由引用计数控制。
以下是一个简单的示例来详解__del__ 方法的用法:
class MyClass:
def __init__(self, name):
self.name = name
print(f"{self.name}对象已创建")
def __del__(self):
print(f"{self.name}对象已销毁")
## 创建两个对象
obj1 = MyClass("Object 1")
obj2 = MyClass("Object 2")
## 删除一个对象的引用
del obj1
## 手动触发垃圾回收
import gc
gc.collect()
## 输出:
## Object 1对象已创建
## Object 2对象已创建
## Object 1对象已销毁在上面的示例中,当对象 obj1 的引用被删除时,Python 解释器自动调用了 obj1 的 __del__ 方法。
注意事项
通常情况下,不建议在类中实现 __del__ 方法。虽然 __del__ 方法提供了一个在对象被销毁时执行清理操作的机会,但由于其调用时机不确定,可能会导致一些意想不到的行为,特别是在涉及到循环引用或复杂的对象生命周期管理时。
下面是一些原因:
-
不确定的调用时机:
__del__方法的调用是由Python的垃圾回收机制决定的,而不是由引用计数控制的。这意味着你不能确定对象何时会被销毁,也就无法保证__del__方法何时会被调用。 -
循环引用问题:如果存在循环引用,
__del__方法可能不会被及时调用,从而导致资源泄露。垃圾回收器无法处理循环引用的情况,因为它们会阻止对象的引用计数归零。 -
不可预测的行为:在
__del__方法中执行的操作可能会影响其他对象,导致不可预测的行为。比如,如果在__del__方法中关闭一个文件,但在此之后仍然有其他地方在尝试使用这个文件对象,那么可能会导致错误。 -
可替代方案:通常情况下,更好的做法是使用上下文管理器(
with语句)来管理资源。上下文管理器确保资源在不再需要时被及时释放,而不需要依赖于垃圾回收机制。
尽管如此,在某些特定情况下,如封装底层资源或在对象销毁时需要执行必要的清理工作时,__del__ 方法可能是一个合适的选择。但在大多数情况下,最好避免使用它,而是选择更可靠、更可控的资源管理方式。
方法重载(overload)
方法重载(Method Overloading)是面向对象编程中的概念,指的是在同一个类中可以定义多个同名方法,但这些方法的参数类型、参数个数或参数顺序不同。在调用这些方法时,根据传入的参数不同,会自动匹配到对应的方法进行调用。
注意:python没有方法重载,也不需要方法重载!!
在 Python 中,方法重载并不是严格意义上的特性,因为 Python 不支持直接定义多个同名方法。但可以通过一些技巧来模拟方法重载的效果,比如利用默认参数、*args 和 **kwargs 来处理不同的参数情况。
下面是一个示例来说明方法重载的模拟实现:
class Calculator:
def add(self, a, b):
return a + b
def add(self, a, b, c):
return a + b + c
## 创建一个 Calculator 对象
calc = Calculator()
## 调用不同版本的 add 方法
print(calc.add(2, 3)) # 报错,因为只定义了带三个参数的 add 方法
print(calc.add(2, 3, 4)) # 输出: 9在上面的示例中,Calculator 类中定义了两个同名的 add 方法,一个接受两个参数,另一个接受三个参数。由于 Python 只会保留最后一个定义的方法,因此调用 add 方法时会直接调用最后一个定义的版本,而不管传入的参数个数。
需要注意的是,这种方法并不是真正意义上的方法重载,而是通过方法覆盖来模拟的。因此,在 Python 中,更推荐使用默认参数、可变参数、关键字参数等特性来处理不同参数情况,而不是依赖于方法重载。
_new_ 方法
在 Python 中,__new__ 方法是用于创建对象实例的特殊方法。与 __init__ 方法不同,__new__ 方法是在对象实例化之前调用的。它负责创建对象,并返回一个新的对象实例。__new__ 方法通常用于定制不可变类型的对象创建过程,例如内置类型(如 str、int、tuple 等)。
下面是关于 __new__ 方法的一些详解:
-
作用:
__new__方法主要负责对象的创建过程。它是一个类方法,通常使用cls作为第一个参数(代表当前类),并返回一个新的对象实例。 -
调用时机:
__new__方法是在对象实例化之前调用的,它在__init__方法之前执行。因此,__new__方法用于创建对象实例,而__init__方法用于初始化对象实例。 -
参数:
__new__方法的参数包括当前类(通常命名为cls)和其他与对象创建相关的参数。__new__方法可以接收任意数量的额外参数,并将它们传递给基类的__new__方法(如果有的话)。 -
返回值:
__new__方法应该返回一个新的对象实例。通常情况下,它会调用基类的__new__方法来创建对象,然后对新对象进行一些额外的处理,最后返回新对象。如果__new__方法返回的是一个与当前类不同的对象实例,则__init__方法不会被调用。 -
定制不可变类型:
__new__方法通常用于定制不可变类型的对象创建过程。通过在__new__方法中控制对象的创建过程,可以实现对对象的创建和初始化过程进行更加灵活的控制。
下面是一个简单的示例,演示了如何使用 __new__ 方法来定制对象创建过程:
class MySingleton:
_instance = None
def __new__(cls, *args, **kwargs):
if cls._instance is None:
cls._instance = super().__new__(cls, *args, **kwargs)
return cls._instance
def __init__(self, value):
self.value = value
## 创建 MySingleton 的实例
singleton1 = MySingleton(10)
singleton2 = MySingleton(20)
print(singleton1.value) # 输出:10
print(singleton2.value) # 输出:10
print(singleton1 is singleton2) # 输出:True,因为是单例模式在上面的示例中,__new__ 方法被用来实现单例模式,确保只创建一个 MySingleton 的实例。
不可变类型的对象:
在 Python 中,不可变类型的对象指的是在创建之后不能被修改的对象。这意味着一旦创建了不可变类型的对象,就不能对其进行修改,任何修改操作都会创建一个新的对象。Python 中的一些内置类型,如整数 (int)、浮点数 (float)、字符串 (str)、元组 (tuple) 等,都是不可变类型的对象。
以下是不可变类型对象的一些特点和意义:
-
值不可变:不可变类型的对象的值在创建后不能被修改。例如,对于整数对象
x = 5,x的值始终是5,不能被修改为其他值。 -
对象身份不可变:不可变类型的对象在创建后其对象身份(即内存地址)不能被修改。这意味着不可变类型的对象在创建后始终指向相同的内存地址,不能被重新赋值为其他对象。
-
线程安全:由于不可变类型的对象不能被修改,因此在多线程环境下不需要担心对象的修改会导致线程安全问题。这使得不可变类型的对象在并发编程中更加安全。
-
哈希性:不可变类型的对象通常是可哈希的,即它们可以作为字典的键或集合的元素。由于对象的值不可变,因此可以使用对象的哈希值作为其在字典或集合中的唯一标识符。
总的来说,不可变类型的对象具有固定的值和对象身份,不能被修改,因此在某些情况下更加安全和可靠。它们在 Python 中广泛应用于各种场景,例如表示常量、哈希表的键等。