类特殊成员(属性与方法)
1. setattr()、getattr()、hasattr()
setattr()
、getattr()
和 hasattr()
是 Python 中用于处理对象属性的三个内建函数。
【setattr()
】
setattr()
函数用于设置对象的属性值。其基本语法为:
setattr(object, attribute, value)
object
是要设置属性的对象。attribute
是属性的名称。value
是要设置的属性值。
class Person:
pass
# 创建 Person 类的实例
person_instance = Person()
# 设置实例的属性值
setattr(person_instance, 'name', 'Alice')
setattr(person_instance, 'age', 25)
print(person_instance.name) # 输出: Alice
print(person_instance.age) # 输出: 25
【getattr()
】
getattr()
函数用于获取对象的属性值。其基本语法为:
getattr(object, attribute[, default])
object
是要获取属性的对象。attribute
是属性的名称。default
是可选参数,用于指定在属性不存在时的默认值。
class Person:
def __init__(self, name, age):
self.name = name
self.age = age
# 创建 Person 类的实例
person_instance = Person(name='Alice', age=25)
# 获取实例的属性值
name_value = getattr(person_instance, 'name')
age_value = getattr(person_instance, 'age')
print(name_value) # 输出: Alice
print(age_value) # 输出: 25
# 获取不存在的属性,提供默认值
gender_value = getattr(person_instance, 'gender', 'Unknown')
print(gender_value) # 输出: Unknown
【hasattr()
】
hasattr()
函数用于检查对象是否具有指定的属性。其基本语法为:
hasattr(object, attribute)
object
是要检查属性的对象。attribute
是要检查的属性名称。
class Person:
def __init__(self, name, age):
self.name = name
self.age = age
# 创建 Person 类的实例
person_instance = Person(name='Alice', age=25)
# 检查实例是否具有指定的属性
has_name = hasattr(person_instance, 'name')
has_gender = hasattr(person_instance, 'gender')
print(has_name) # 输出: True
print(has_gender) # 输出: False
这三个函数通常用于动态地操作对象的属性,特别是当属性的名称是在运行时确定的情况下。
2. init():初始化对象的实例
__init__()
是 Python 类中的一个特殊方法,用于初始化对象的实例。它是一个构造方法(constructor),在创建类的实例时自动被调用。__init__()
方法允许你在创建对象时执行一些必要的设置或初始化工作。
以下是 __init__()
方法的基本用法:
class MyClass:
def __init__(self, parameter1, parameter2, ...):
# 在这里进行初始化工作
self.attribute1 = parameter1
self.attribute2 = parameter2
# ...
# 创建类的实例
my_instance = MyClass(value1, value2, ...)
示例:
class Person:
def __init__(self, name, age):
self.name = name
self.age = age
# 创建 Person 类的实例
person_instance = Person("Alice", 25)
# 访问实例属性
print(person_instance.name) # 输出: Alice
print(person_instance.age) # 输出: 25
在这个例子中,__init__()
方法用于初始化 Person
类的实例,分别设置了 name
和 age
属性。
3. new():init()能力补充
new() 是一种负责创建类实例的静态方法,它无需使用 staticmethod 装饰器修饰,且该方法会优先 init() 初始化方法被调用。
那么,什么情况下使用 new() 呢?答案很简单,在 init() 不够用的时候。
例如,前面例子中对 Python 不可变的内置类型(如 int、str、float 等)进行了子类化,这是因为一旦创建了这样不可变的对象实例,就无法在 init() 方法中对其进行修改。
有些读者可能会认为,new() 对执行重要的对象初始化很有用,如果用户忘记使用 super(),可能会漏掉这一初始化。虽然这听上去很合理,但有一个主要的缺点,即如果使用这样的方法,那么即便初始化过程已经是预期的行为,程序员明确跳过初始化步骤也会变得更加困难。不仅如此,它还破坏了“init() 中执行所有初始化工作”的潜规则。
注意,由于 new() 不限于返回同一个类的实例,所以很容易被滥用,不负责任地使用这种方法可能会对代码有害,所以要谨慎使用。一般来说,对于特定问题,最好搜索其他可用的解决方案,最好不要影响对象的创建过程,使其违背程序员的预期。比如说,前面提到的覆写不可变类型初始化的例子,完全可以用工厂方法(一种设计模式)来替代。
4. repr():输出对象内部信息
我们经常会直接输出类的实例化对象,例如:
class CLanguage:
pass
clangs = CLanguage()
print(clangs)
程序运行结果为:
<__main__.CLanguage object at 0x000001A7275221D0>
通常情况下,直接输出某个实例化对象,本意往往是想了解该对象的基本信息,例如该对象有哪些属性,它们的值各是多少等等。但默认情况下,我们得到的信息只会是“类名+object at+内存地址”,对我们了解该实例化对象帮助不大。
那么,有没有可能自定义输出实例化对象时的信息呢?答案是肯定,通过重写类的 repr() 方法即可。事实上,当我们输出某个实例化对象时,其调用的就是该对象的 repr() 方法,输出的是该方法的返回值。
通过在类中重写这个方法,从而实现当输出实例化对象时,输出我们想要的信息。
案例配置:
class Point:
def __init__(self, x, y):
self.x = x
self.y = y
def __repr__(self):
return f"Point(x={self.x}, y={self.y})"
# 创建 Point 类的实例
point = Point(1, 2)
# 使用 repr() 函数获取对象的字符串表示
repr_str = repr(point)
print(repr_str) # 输出: Point(x=1, y=2)
# 直接打印对象,会调用对象的 __repr__() 方法
print(point) # 输出: Point(x=1, y=2)
上面案例通过__repr__输出对象中属性的一些信息,可以利用这些数值返回了解数据的信息。
5. str():输出提示信息
class Point:
def __init__(self, x, y):
self.x = x
self.y = y
def __str__(self):
return f"Point at ({self.x}, {self.y})"
# 创建两个 Point 类的实例
point1 = Point(3, 5)
point2 = Point(7, 10)
# 调用 __str__() 方法
str_representation1 = str(point1)
str_representation2 = str(point2)
# 直接使用 print() 函数,会自动调用 __str__() 方法
print(point1) # 输出: Point at (3, 5)
print(point2) # 输出: Point at (7, 10)
# 使用 f-string 输出字符串表示
print(f"The first point is {point1}.") # 输出: The first point is Point at (3, 5).
print(f"The second point is {point2}.") # 输出: The second point is Point at (7, 10).
__str__
方法的目的是为了在使用 print()
函数时提供一个友好、人类可读的字符串表示形式。如果没有定义 __str__
方法,那么 print()
函数会调用对象的 __repr__
方法,或者使用默认的输出方式。
6. issubclass和isinstance函数
- issubclass(cls, class_or_tuple):检查 cls 是否为后一个类或元组包含的多个类中任意类的子类。
- isinstance(obj, class_or_tuple):检查 obj 是否为后一个类或元组包含的多个类中任意类的对象。
issubclass
的应用场景
(1)检查继承关系: 在编写框架或库时,可以使用 issubclass
来检查一个类是否是某个基类的子类,以确保符合预期的继承关系。
class Shape:
pass
class Circle(Shape):
pass
# 在某个函数中检查是否是 Shape 的子类
def process_shape(shape_class):
if issubclass(shape_class, Shape):
# 处理 Shape 类或其子类的逻辑
pass
(2)动态选择实现: 在某些情况下,根据对象的类型来选择不同的实现。issubclass
可以帮助你动态选择适当的类。
class Shape:
def draw(self):
raise NotImplementedError
class Circle(Shape):
def draw(self):
print("Drawing a circle")
class Square(Shape):
def draw(self):
print("Drawing a square")
def draw_shape(shape):
if issubclass(type(shape), Shape):
shape.draw()
isinstance
的应用场景
(1)类型检查: 在函数中对传入的参数进行类型检查,确保参数是预期的类或类型。
def process_data(data):
if isinstance(data, (list, tuple)):
# 处理列表或元组的逻辑
pass
(2)多态实现: 允许对象根据其类型表现出不同的行为。这在设计模式中的多态概念中经常用到。
class Animal:
def make_sound(self):
pass
class Dog(Animal):
def make_sound(self):
print("Woof!")
class Cat(Animal):
def make_sound(self):
print("Meow!")
def animal_sound(animal_instance):
if isinstance(animal_instance, Animal):
animal_instance.make_sound()
(3)处理不同版本或类型的对象: 在处理对象集合时,可以使用 isinstance
来过滤或处理不同版本或类型的对象。
class Version1:
pass
class Version2:
pass
objects = [Version1(), Version2()]
for obj in objects:
if isinstance(obj, Version1):
# 处理 Version1 类型的对象
pass
elif isinstance(obj, Version2):
# 处理 Version2 类型的对象
pass
总体来说,这两个函数在处理多态性、动态类型和对象关系时非常有用。在编写灵活且可扩展的代码时,它们可以帮助你进行更动态的类型检查。
7. 生成器:按需生成值非一次性保存在内存
生成器(Generator)是 Python 中用于生成迭代器的一种特殊类型的对象。它允许你按需生成一系列值,而不是一次性生成并保存在内存中。生成器使用 yield
语句来产生值,而不是使用 return
。
def fibonacci_generator(n):
a, b = 0, 1
count = 0
while count < n:
yield a
a, b = b, a + b
count += 1
# 使用生成器获取斐波那契数列的前10个数字
fibonacci_gen = fibonacci_generator(10)
for number in fibonacci_gen:
print(number)
结果输出:
0
1
1
2
3
5
8
13
21
34
yield
语句的特性是会在暂停时保存整个生成器的状态,包括所有的局部变量的值。在下一次调用生成器时,它会从上一次 yield
语句的位置开始执行,并且所有的局部变量都会恢复到上一次暂停时的状态。
具体到这个斐波那契数列的例子,当执行到 yield a
时,生成器会返回当前的 a
值,然后暂停。在暂停时,a
和 b
的值会被保存,下一次调用生成器时,它会从上一次暂停的地方继续执行,而此时 a
和 b
的值是上一次暂停时的值。
所以,yield
语句不仅返回一个值,而且它会保存整个生成器的状态,包括所有的局部变量。这就是为什么在下一次调用时,a
和 b
的值会正确更新的原因。这种机制使得生成器可以在迭代过程中保持状态,而不是一次性生成所有的值。