python类与对象(二)

文章发布时间:

最后更新时间:

文章总字数:
2k

预计阅读时间:
8 分钟

一.类方法

  • 类方法属于类,指定由类来访问,但类和该类的实例都可以调用类方法

  • 类方法需要使用@classmethod装饰器定义

  • 类方法至少有一个形参用于绑定类,约定为 cls

  • 类方法不能访问此类创建的对象的实例属性,只能访问类属性

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
# 来一个类方法
class arknights:
x = 1


def fun(self):
print(self.x)
# 来个换行
print()

# 类方法定义,其中cls为形参,隐式传对象进去
@classmethod
def fun2(cls):
print('fun2')
print(cls.x)
print(cls)
# 来个换行
print()


a = arknights()

# 普通方法
a.fun()

# 普通方法,这里通过类调用得传个对象进去,因为fun里面打印的是self.x,必须是有x的对象(包括类自己)才行
arknights.fun(arknights)


# 类方法可以通过类名调用
arknights.fun2()

# 类方法可以通过实例调用,实际上隐式传进去的参数是类
a.fun2()

输出:

1
2
3
4
5
6
7
8
9
10
11
1

1

fun2
1
<class '__main__.arknights'>

fun2
1
<class '__main__.arknights'>

举个用得到类方法的案例,常常用作工厂方法,即批量生产

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
class Single:
isinstance = None


# 单例,创建一个实例
@classmethod
def share_instance(cls):
if cls.isinstance is None:
cls.isinstance = cls()
return cls.isinstance
else:
return cls.isinstance

# 这里的方法实际上返回了一个对象,保存在isinstance中
a = Single.share_instance()
a1 = Single.share_instance()

print(id(a), id(a1))

输出:

1
2844097892272 2844097892272
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
# 批量创建对象
class Person:
def __init__(self, name, age):
self.name = name
self.age = age

@classmethod
def create_person(cls,count = 10):
people = []
for i in range(count):
# 这里的f-string方法也不要忘了
people.append(cls(f"Person {i}", i))
return people


# 直接创建了20个对象,但都没有数据,分别是Person 0, Person 1, Person 2, ..., Person 19
Person.create_person(20)

输出:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
[<__main__.Person at 0x29632b56210>,
<__main__.Person at 0x29632bd28a0>,
<__main__.Person at 0x29632bd09e0>,
<__main__.Person at 0x29632d278c0>,
<__main__.Person at 0x29632d27890>,
<__main__.Person at 0x29632d243b0>,
<__main__.Person at 0x29632d27ad0>,
<__main__.Person at 0x29632d24410>,
<__main__.Person at 0x29632d27170>,
<__main__.Person at 0x29632d27a10>,
<__main__.Person at 0x29632d26c90>,
<__main__.Person at 0x29632d27410>,
<__main__.Person at 0x29632d25790>,
<__main__.Person at 0x29632d26690>,
<__main__.Person at 0x29632d27830>,
<__main__.Person at 0x29632d24ad0>,
<__main__.Person at 0x29632d264e0>,
<__main__.Person at 0x29632d270e0>,
<__main__.Person at 0x29632d25af0>,
<__main__.Person at 0x29632d26300>]

二.静态方法

  • 使用@staticmethod装饰器定义

  • 不需要self和cls参数

  • 通过类或类实例调用

  • 可以访问类属性,不能访问实例属性

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
class arknights:
x = 10

# 静态方法
@staticmethod
#小括号内不会有隐式传参,都是自定义传参或者不传
def fn():
print(arknights.x)

a = arknights()

# 对象与类都可访问
a.fn()
arknights.fn()

a.x = 20
# 但是静态方法只能访问类内部的数据
a.fn()

输出:

1
2
3
10
10
10

三.构造方法

  • 其他编程语言中构造方法与初始化方法一样

  • python中用于负责对象的创建和内存分配

  • 通常不需要显式地定义 new()方法,Python会直接调用基类 object 的 new()方法。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
class MyClass:
# 这一排实际上都不用写,创建对象的时候会自动调这个方法
def __new__(cls, *args, **kwargs):
print("调用 __new__ 方法,创建对象")
# 用父类的构造方法创建对象
return super().__new__(cls)

# 这个代码表示初始化创建的对象空间,实际上python里两个是分开的,但是前面的会自动调用
def __init__(self, value):
print("调用 __init__ 方法,初始化对象")
self.value = value


# 创建对象
obj = MyClass(10)

输出:

1
2
调用 __new__ 方法,创建对象
调用 __init__ 方法,初始化对象

那把构造方法改了会怎么样?试试看

1
2
3
4
5
6
7
8
9
class MyClass:
def __new__(cls, *args, **kwargs):
print("调用 __new__ 方法,创建对象")
# 用父类的构造方法创建对象,但是创建的对象变成一个固定列表
return [1,2,3]

# 这里创建的对象全是列表
a = MyClass()
print(a)

输出:

1
2
调用 __new__ 方法,创建对象
[1, 2, 3]

其实也就是把默认的构造方法改了,一般不写这个方法的时候就是默认创建对象,写了之后按你的方式构建对象。

四.魔术方法

是一种特殊的方法,用双下划线包裹,例如__init__,允许自定义类的行为.

  • str(self): 定义对象的字符串表示形式,可通过str(x)或print(x)调用。例如,可以返回一个字符串,描述对象的属性。
1
2
3
4
5
6
7
class A:
def __str__(self):
# 将原来那一长串对象的表示变成'hello'
return 'hello'

a = A()
print(a)

输出:

1
hello
  • repr(self): 定义对象的“官方”字符串表示形式,通常用于调试。可通过repr(object)调用
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
class A:

def __str__(self):
return 'hello'

def __repr__(self):
return 'world'

a = A()
print(a)

# 如果类内部没有设置str方法,就会默认调用repr方法
# 如果设置了,则优先调用str方法,可以用repr(x)来调用repr方法
re = repr(a)
print(re)

输出:

1
2
hello
world
  • add(self, other): 定义对象相加的行为,使对象可以使用 + 运算符相加
1
2
3
4
5
6
7
8
9
class A:
x = 1

# 它让对象与某个数据的相加变成自定义的表达式
def __add__(self, other):
return self.x + other
a = A()
a.x = 2
print(a + 1)

输出:

1
3
  • sub(self, other): 定义对象相减的行为,使对象可以使用 - 运算符相减
1
2
3
4
5
6
class A:
x = 1
def __sub__(self,other):
return self.x - other
a = A()
print(a - 2)

输出:

1
-1
  • eq(self, other): 定义对象相等性的行为,使对象可以使用 == 运算符比较
1
2
3
4
5
6
7
class A:
x = 1
def __eq__(self, value):
return self.x == value.x
a = A()
b = A()
print(a == b)

输出:

1
True

可以看到,a、b明明是不同的对象但相等,因为__eq__方法被重写了,所以==运算符会调用这个方法,让两对象数值大小作比较。

  • lt(self, other): 定义对象小于其他对象的行为,使对象可以使用 < 运算符比较
1
2
3
4
5
6
class A:
x = 1
def __lt__(self, other):
return self.x < other
a = A()
print(a < 4)

输出:

1
True
  • gt(self, other): 定义对象大于其他对象行为,使对象可以使用 > 运算符比较
1
2
3
4
5
6
class A:
x = 1
def __gt__(self, other):
return self.x > other
a = A()
print(a > 4)

输出:

1
False
  • len(self): 定义对象的长度,可通过len(object)调用。通常在自定义容器类中使用
1
2
3
4
5
6
7
8
9
class A:
x = 1
L = [1,2,3]
# 使得对象的长度表示为你定义的东西
def __len__(self):
return len(self.L)

a = A()
print(len(a))

输出:

1
3
  • getitem(self, key): 定义对象的索引操作,使对象可被像列表或字典一样索引。例如,object[ key ]
1
2
3
4
5
6
7
8
9
10
11
class A:
x = 1
L = [1,2,3]

def __getitem__(self, key):
print(key)
# 定义索引的返回值形式
return 100
a = A()
# 里面索引的结果就是getitem方法返回的结果
a[1]

输出:

1
2
3
1

100
  • setitem(self, key, value): 定义对象的赋值操作,使对象可像列表或字典一样赋值。例如,object[ key ] = value
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
class A:
x = 1
L = [1,2,3]

def __getitem__(self, key):
print(key)
return self.L[key]

def __setitem__(self, key, value):
# 定义索引的赋值形式
self.L[key] = value


a = A()

# 动用setitem方法
a[1] = 4
# 里面索引的结果就是getitem方法返回的结果
print(a[1])

输出:

1
2
1
4
  • delitem(self, key): 定义对象的删除操作,使对象可像列表或字典一样删除元素,例如,del object[ key ]
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
class A:
x = 1
L = [1,2,3]

def __delitem__(self, key):
del self.L[key]
print('delitem')


a = A()
# 原始方法
a.L.remove(1)
print(a.L)

# 魔术方法del
del a.L[0]
print(a.L)

输出:

1
2
[2, 3]
[3]
  • call(self, other) 是一个特殊的方法,它允许一个对象像函数一样被调用
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
class A:
x = 1
L = [1,2,3]


# 指定被当成函数调用应该如何运行
def forward(self):
print('forward')

# 函数调用的入口
def __call__(self, *args, **kwds):
print(self.x)
self.forward()
print('被调用了')


a = A()
a()
# 换行

print()

# 甚至可以直接调里面设置的函数
a.forward()

# 看看是不是真的能当函数调用
callable(a)

输出:

1
2
3
4
5
6
1
forward
被调用了
forward

True