类与实例的属性和方法及 decorator 的使用
· 笔记
11 min
类与实例的属性
在 Python 中,类与实例都是对象,因此它们都可以拥有属性.
类属性
类属性定义在类的内部,但在任何方法之外. 类属性属于类本身,在所有实例之间共享. 在不同的实例中访问类属性时,访问的是同一个值.
1class A:
2 cls_attr = 'class attribute'
3
4 def __init__(self):
5 self.inst_attr = 'instance attribute'
6
7 def print_class_attr(cls):
8 print(cls.cls_attr)
9
10inst1 = A()
11inst2 = A()
12
13print(A.cls_attr) # class attribute
14print(inst1.cls_attr) # class attribute
15print(inst2.cls_attr) # class attribute
在不同位置修改类属性的值会导致不同的结果. 如果直接在类上修改属性,则类与所有实例看到的值都会改变.
1A.cls_attr = 'modified class attribute'
2
3print(A.cls_attr)
4print(inst1.cls_attr)
5print(inst2.cls_attr)
而若在实例上修改属性,则只会影响该实例. 该实例会创建一个新的属性,遮蔽掉类属性,而其他实例仍然访问类属性.
1inst1.cls_attr = 'instance 1 attribute'
2
3print(A.cls_attr) # modified class attribute
4print(inst1.cls_attr) # instance 1 attribute
5print(inst2.cls_attr) # modified class attribute
实例属性
实例属性定义在类的方法中,通常在 __init__
方法中初始化. 实例属性属于实例本身,而不属于类.
1class B:
2 def __init__(self, value):
3 self.inst_attr = value
4
5print(hasattr(B, 'inst_attr')) # False
每个实例都有自己独立的实例属性. 修改一个实例的属性不会影响其他实例.
1b1 = B('value 1')
2b2 = B('value 2')
3
4print(b1.inst_attr) # value 1
5print(b2.inst_attr) # value 2
6
7b1.inst_attr = 'modified value 1'
8print(b1.inst_attr) # modified value 1
9print(b2.inst_attr) # value 2
Python 中的属性方法
Python 提供了内置函数 getattr()
、setattr()
、delattr()
和 hasattr()
来访问和操作对象的属性.
hasattr(obj, name) -> bool
: 检查对象obj
是否有名为name
的属性.1hasattr(A, 'cls_attr') # True 2hasattr(inst1, 'cls_attr') # True 3hasattr(inst1, 'non_existent_attr') # False
getattr(obj, name[, default]) -> value
: 获取对象obj
的名为name
的属性值. 如果属性不存在且提供了default
,则返回default
,否则引发AttributeError
.1getattr(b1, 'inst_attr') # 'modified value 1' 2getattr(b1, 'nonexistent_attr', 'default value') # 'default value'
setattr(obj, name, value) -> None
: 设置对象obj
的名为name
的属性值为value
. 如果属性不存在,则创建该属性.1setattr(inst1, 'new_attr', 42) 2print(inst1.new_attr) # 42
delattr(obj, name) -> None
: 删除对象obj
的名为name
的属性. 如果属性不存在,则引发AttributeError
.1delattr(inst1, 'new_attr') 2hasattr(inst1, 'new_attr') # False
property
装饰器
property
装饰器用于将类的方法转换为属性访问. 通过使用 @property
装饰器,可以定义只读属性. 可以通过 @PropertyName.setter
与 @PropertyName.deleter
定义属性的写入与删除行为.
1class C:
2 def __init__(self, value):
3 self._attr = value # private attribute
4
5 @property
6 def attr(self):
7 return self._attr
8
9 @attr.setter
10 def attr(self, value):
11 if value < 0:
12 raise ValueError("attr cannot be negative")
13 self._attr = value
14
15 @attr.getter
16 def attr(self, value):
17 del self._attr
18
19c = C(10)
20print(c.attr) # 10
21c.attr = 20
22print(c.attr) # 20
23c.attr = -5 # ValueError: attr cannot be negative
类与实例的方法
方法即定义在类中的函数,区别于普通函数,类中的方法分为以下几种类型.
实例方法
实例方法是类中最常见的方法类型. 在调用实例方法时,Python 会自动将实例本身作为第一个参数传递给方法,因此 PEP 8 规范建议将第一个参数命名为 self
以表示实例本身.
1class C:
2 key = 'value'
3
4 def __init__(self, name):
5 self.name = name
6
7 def greet(self):
8 return f"Hello, {self.name}!"
9
10c1 = C('Alice')
11c1.greet() # "Hello, Alice!"
实例方法可以通过 self.name
访问或修改实例属性,通过 ClassName.attr
/self.__class__.attr
访问或修改类属性.
1class C:
2 key = 'value'
3
4 def __init__(self, name):
5 self.name = name
6
7 def greet(self):
8 self.name = 'Doc. ' + self.name
9 return f"Hello, {self.name}!"
10
11 def greet_cls(self):
12 C.key = 'new value'
13 return f"Class key is now {C.key}"
14
15c1 = C('Alice')
16c1.greet() # "Hello, Alice!"
17c1.name
18
19c1.greet_cls()
20C.key # 'new value'
注意,修改类属性时,不能直接用
self.attr = value
,因为这会在实例上创建一个新的实例属性,遮蔽掉类属性.
类方法
类方法是绑定到类而非实例的方法. 类方法需要用 @classmethod
装饰器进行标记. 在调用类方法时,Python 会自动将类本身作为第一个参数传递给方法,因此 PEP 8 规范建议将第一个参数命名为 cls
以表示类本身.
1class D:
2 count = 0
3
4 def __init__(self):
5 D.count += 1
6
7 @classmethod
8 def get_instance_count(cls):
9 return cls.count
10
11d1 = D()
12d2 = D()
13D.get_instance_count() # 2
类方法可以通过 cls.attr
访问或修改类属性,但不能直接访问实例属性,因为类方法没有实例上下文.
静态方法
静态方法本质上是位于类命名空间中的普通函数. 静态方法需要用 @staticmethod
装饰器进行标记. 静态方法既不接收实例 (self
) 也不接收类 (cls
) 作为第一个参数,因此无法访问实例属性或类属性. 静态方法通常用于与类相关的辅助函数
1class E:
2 @staticmethod
3 def add(x, y):
4 return x + y
5
6E.add(3, 5) # 8
类与实例均可用于调用静态方法
1e = E()
2e.add(10, 20) # 30
魔术方法
魔术方法是 Python 中具有特殊名称和用途的方法. 为了提高代码的可读性与美观性,Python 提供了很多内置的魔术方法,允许我们通过重载这些方法来定制类的行为. 常见的魔术方法包括:
__init__(self, ...)
: 构造函数,在创建实例时调用,用于初始化实例属性.字符串表示相关
__str__(self)
: 定义实例的字符串表示形式,即控制str(obj)
和print(obj)
的输出.__repr__(self)
: 定义实例的官方字符串表示形式,通常用于调试,控制repr(obj)
的输出.
索引与切片相关
__len__(self)
: 定义实例的长度,控制len(obj)
的行为.__getitem__(self, key)
: 定义索引访问,控制obj[key]
的行为.__setitem__(self, key, value)
: 定义索引赋值,控制obj[key] = value
的行为.__delitem__(self, key)
: 定义索引删除,控制del obj[key]
的行为.
运算符重载相关
__add__(self, other)
: 定义加法运算,控制obj1 + obj2
的行为.__eq__(self, other)
: 定义相等比较,控制obj1 == obj2
的行为.__lt__(self, other)
: 定义小于比较,控制obj1 < obj2
的行为.
迭代器相关
__iter__(self)
: 定义迭代器,控制for item in obj
的行为.__next__(self)
: 定义迭代器的下一个元素,配合__iter__
使用.
属性访问相关
__getattr__(self, name)
: 定义当访问不存在的属性时的行为.__setattr__(self, name, value)
: 定义设置属性时的行为.__delattr__(self, name)
: 定义删除属性时的行为.
其他
__call__(self, ...)
: 使实例可调用,控制obj(...)
的行为.__enter__(self)
,__exit__(self, exc_type, exc_value, traceback)
: 定义上下文管理器,控制with
语句的行为.
通过重载这些魔术方法,我们可以让自定义类的实例表现得像内置类型一样,从而提高代码的可读性与美观性.
装饰器
装饰器本质上是一个特殊的函数,以函数作为输入与输出
1def decorator(func):
2 print("This is decorator")
3 return func
4
5@decorator
6def func():
7 print("This is func")
8
9func()
10# This is decorator
11# This is func
用装饰器装饰函数本质上即 func = decorator(func)
的简洁实现,这也是为什么装饰器要求以函数作为输入与输出.
一个常见的装饰器例子为计时器,通过在函数运行前后各记录一次时间戳,通过计算时间差得到函数的运行时间.
1import time
2
3def timer(func):
4 def wrapper(*args, **kwargs):
5 tic = time.time()
6 result = func(*args, **kwargs)
7 toc = time.time()
8 print(f"Function {func.__name__} took {toc - tic:.4f} seconds")
9 return result
10 return wrapper
11
12@timer
13def compute_square(n):
14 return [i * i for i in range(n)]
15
16compute_square(1000000) # Function compute_square took 0.0462 seconds
进一步地,可以通过再嵌套一层函数来给装饰器添加参数
1def repeat(num):
2 def decorator(func):
3 def wrapper(*args, **kwargs):
4 for _ in range(num):
5 result = func(*args, **kwargs)
6 return result
7 return wrapper
8 return decorator
9
10@repeat(3)
11def greet(name):
12 print(f"Hello, {name}!")
13
14greet("Alice")
15# Hello, Alice!
16# Hello, Alice!
17# Hello, Alice!
这本质上是执行了 greet = repeat(3)(decorator)()
.