蒙珣的博客

活好当下,做好今天该做的事情。

0%

Python类的方法

类对象与实例对象

他们在定义和使用中有所区别,而本质的区别是内存中保存的位置不同

  • 实例属性属于实例对象
  • 类属性属于类
1
2
3
4
5
6
7
8
9
10
11
12
13
14
class Province:
# 类属性
country = "中国"

def __init__(self, name):
# 实例属性
self.name = name

# 创建一个实例对象
obj = Province("山东省")
# 直接访问实例属性
print(obj.name)
# 直接访问类属性
Province.country

由上述代码可以看出【实例属性需要通过对象来访问】【类属性通过类访问】,在使用上可以看出实例属性和类属性的归属是不同的

其在内容的存储方式类似如下图:

上图中实例对象2,obj1 = Province("山西省") 山东省属于笔误

由此可见:

  • 类属性在内存中只保存一份
  • 实例属性在每个对象中都要保存一份

应用场景

  • 通过类创建实例对象,如果每个对象需要具有相同名字的属性,那么就使用类属性,用一份即可

实例方法、静态方法、类方法、特性方法

在 Python 的类里面,在类里面编写的特性函数称为方法,这些方法主要分为普通方法(实例方法),特性方法,静态方法,类方法

他们都归属于类,区别在于调用方式的不同

  • 实例方法:由对象调用;至少一个self参数,执行实例方式时,自动将调用该方法的对象赋值给self
  • 类方法:@classmethod由类调用;至少一个cls参数,执行类方法时,自动将调用该方法的类赋值给cls
  • 静态方法:staticmethod由类调用;无默认参数
  • 特性方法:@property由类调用;不能传入参数
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
class Foo:
def __init__(self, name):
self.name = name

def ord_func(self):
"""定义实例方法,至少有一个self参数"""
# print(self.name)
print("实例方法")

@classmethod
def class_func(cls):
"""定义类方法,至少有一个cls参数"""
print("类方法")

@staticmethod
def static_func():
"""定义静态方法,无默认参数"""
print("静态方法")

f = Foo("中国")
# 调用实例方法
f.ord_func()

# 调用类方法
Foo.class_func()

# 调用静态方法
Foo.static_func()

对比

  • 相同点:对于所有的方法而言,均属于类,所以在内存中也只保留一份
  • 不同点:方法调用者不同,调用方法时自动传入的参数不同

为什么会有类方法和静态方法

类方法作用

  • 修改类状态:类方法可以直接修改类的状态(类变量),而不需要通过类实例
  • 无需实例即可访问:有时,你可能需要执行与类相关的操作,但不需要创建类的实例。在这种情况下,类方法非常有用
  • 工厂方法:类方法常常用作工厂方法,用于创建和返回类的实例。由于它们与类本身紧密相关,因此它们可以在创建实例时执行一些与类相关的初始化或设置

静态方法作用

  • 组织代码:有时,你可能有一组与类紧密相关的函数,但这些函数并不需要访问类的状态或实例。将这些函数作为静态方法放在类中可以使代码更具组织性和可读性。
  • 保持命名空间整洁:将相关函数放在类中而不是模块级别可以使命名空间更整洁,并减少模块级别的名称冲突。
  • 访问类属性和其他方法:尽管静态方法不直接访问类的状态或实例,但它们仍然可以访问类的属性和其他方法(作为类本身的方法)

以下是类方法的使用场景

修改类状态以及无需实例即可访问

1
2
3
4
5
6
7
8
9
10
11
12
13
class Counter:
count = 0

@classmethod
def increment(cls, increment_by = 1):
print(f"Counter count: {cls.count}")
cls.count += increment_by
print(f"Counter increased by {increment_by}. New count: {cls.count}")

# 使用类方法增加计数器的值,无需创建实例
Counter.increment()
Counter.increment(5)
Counter.increment()

结果

1
2
3
4
5
6
Counter count: 0
Counter increased by 1. New count: 1
Counter count: 1
Counter increased by 5. New count: 6
Counter count: 6
Counter increased by 1. New count: 7

工厂方法

假设我们有一个Shape类,它是一个基类,用于创建不同类型的形状对象。我们可以使用类方法作为工厂方法,根据提供的参数返回不同类型的形状对象

关乎设计模式的内容,可以搜索下之前的文章

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
class Shape:  
@classmethod
def create(cls, shape_type, **kwargs):
if shape_type == 'circle':
return Circle(**kwargs)
elif shape_type == 'rectangle':
return Rectangle(**kwargs)
else:
raise ValueError(f"Unsupported shape type: {shape_type}")

class Circle(Shape):
def __init__(self, radius):
self.radius = radius

class Rectangle(Shape):
def __init__(self, width, height):
self.width = width
self.height = height

# 使用工厂方法创建形状对象,无需直接实例化特定类
circle = Shape.create('circle', radius=5)
rectangle = Shape.create('rectangle', width=3, height=4)

下面是关于静态方法的使用

组织代码

假设我们有一个MathUtils类,它包含了一些与数学相关的静态方法。这些静态方法不需要访问类的状态或实例,但它们都与数学计算相关,因此将它们放在同一个类中可以使代码更具组织性

1
2
3
4
5
6
7
8
9
10
11
12
class MathUtils:  
@staticmethod
def add(a, b):
return a + b

@staticmethod
def multiply(a, b):
return a * b

# 使用静态方法进行数学计算
result_add = MathUtils.add(3, 4) # 结果为 7
result_multiply = MathUtils.multiply(3, 4) # 结果为 12

保持命名空间整洁

如果我们有一些与特定主题相关的函数,将它们放在一个类中作为静态方法可以使模块级别的命名空间更整洁。例如,我们有一个与文件操作相关的工具集

1
2
3
4
5
6
7
8
9
10
11
12
13
14
class FileUtils:  
@staticmethod
def read_file(file_path):
with open(file_path, 'r') as file:
return file.read()

@staticmethod
def write_file(file_path, content):
with open(file_path, 'w') as file:
file.write(content)

# 使用静态方法进行文件操作
content = FileUtils.read_file('example.txt')
FileUtils.write_file('new_file.txt', 'Hello, world!')

访问类属性和其他方法

虽然静态方法不依赖于类的实例,但它们仍然可以访问类属性和其他类方法。这在某些情况下可能是有用的,尤其是当你需要在静态方法中调用类的其他功能时

1
2
3
4
5
6
7
8
9
10
11
12
13
class MyClass:  
class_variable = "I'm a class variable"

@classmethod
def class_method(cls):
return cls.class_variable

@staticmethod
def static_method():
return MyClass.class_variable, MyClass.class_method()

# 使用静态方法访问类属性和类方法
print(MyClass.static_method()) # 输出:('I'm a class variable', 'I'm a class variable')

在这个例子中,static_method是一个静态方法,但它通过MyClass类名直接访问了类属性class_variable和类方法class_method。这展示了静态方法如何在不依赖于类的实例的情况下访问类的其他部分

特性方法

@property

特性方法只具备只读属性,且调用特性方法不能有形参。

普通方法具备读和写的属性,且调用特性方法可以使用形参

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
class Person:

def __init__(self, name, age):
self.name = name
self.age = age

def setinfo(self, name, age):
if not isinstance(name, str):
raise TypeError('name不是字符串的数据类型')
if not isinstance(age, int):
raise TypeError('age不是整型的数据类型')

def info(self, city):
self.setinfo(self.name, self.age)
print(f"my name is {self.name}, and my age is {self.age}, from {city}")

@property
def property_info(self): # 不能传入参数
self.setinfo(self.name, self.age)
print(f"my name is {self.name}, and my age is {self.age}")

if __name__ == "__main__":
person = Person('william', 18)
person.info(city='xian')
person.property_info # 调用方法后面没有()

我们看一下实际上有那些使用了@property

seleniumfind_element(By.LINK_TEXT).text方法,使用的是特性方法

1
2
3
4
@property
def text(self) -> str:
"""The text of the element."""
return self._execute(Command.GET_ELEMENT_TEXT)["value"]

抽象类和抽象方法

场景介绍

假如我们现在实现了一个数据中台的开发,我们对外提供一个接口让不同组件通过这个接口进行访问数据库,来读取数据,我们给数据接口主要有2个功能,

  • 登录数据库
  • 读取数据
  • 执行SQL语句

可以想象,登录数据库这个功能在不同组件之间可以共用,不同组件只需要提供host、user、passwd即可,至于读取数据这是每个组件都必须单独实现的,可以声明为抽象方法,执行SQL语句也是每个子类需要实现的,可以声明为抽象的静态方法。

Python标准库中有一个模块abc可以实现抽象基类和抽象方法,它们的实现方式如下:

抽象基类:通过继承abc模块中的ABC类来实现抽象基类。

抽象方法:通过装饰器的方法来调用abc模块中abstractmethod方法来注解抽象基类的方法。

abstractmethod注解除了可以实现抽象方法外,还可以注解类方法(@classmethod)、静态方法(@staticmethod)、属性(@property)。

下面就先实现抽象基类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
from abc import ABC, abstractmethod

class Database(ABC):
def register(self, host, user, password):
print("Host : {}".format(host))
print("User : {}".format(user))
print("Password : {}".format(password))
print("Register Success!")

@abstractmethod
def query(self, *args):
"""
传入查询数据的SQL语句并执行
"""

@staticmethod
@abstractmethod
def execute(sql_string):
"""
执行SQL语句
"""

从抽象基类Database的实现可以看出,它共包含3个方法,其中register是每个子类都需要的,直接实现在抽象基类里,是一个普通的类方法。queryexecute只是在基类中进行类声明,给出了描述,但并没有实现,它限定了继承Database的子类必须实现这两个方法。

下面就来实现两个组件(子类)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
class Component1(Database):
def __init__(self, host, user, password):
self.register(host, user, password)

@staticmethod
def execute(sql_string):
print(sql_string)

def query(self, *args):
sql_string = "SELECT ID FROM db_name"
self.execute(sql_string)

class Component2(Database):
def __init__(self, host, user, password):
self.register(host, user, password)

@staticmethod
def execute(sql_string):
print(sql_string)

def query(self, *args):
sql_string = "SELECT ID FROM db_name"
self.execute(sql_string)

comp1 = Component1("00.00.00.00", "abc", "000000")
comp2 = Component2("11.11.11.11", "ABC", "111111")
comp1.query()
comp2.query()

# 输出结果
Host : 00.00.00.00
User : abc
Password : 000000
Register Success!
Host : 11.11.11.11
User : ABC
Password : 111111
Register Success!
SELECT ID FROM db_name
SELECT NAME FROM db_name

上述是通过Python标准库中abc模块实现了抽象基类,其实在Python中collections中也实现了抽象基类,numbers中也定义了有关数字对象的抽象基类,可见,抽象基类在Python中占据着至关重要的地位。