Python语法-类与实例

类定义

1
2
3
4
5
6
7
8
9
10
class Person:
def __init__(self, name, age):
self.name = name
self.age = age

def myfunc(self):
print("Hello my name is " + self.name)

p1 = Person("Bill", 63)
p1.myfunc()

属性

限制实例属性

为了达到限制的目的,Python允许在定义class的时候,定义一个特殊的__slots__变量,来限制该class实例能添加的属性:

1
2
3
4
5
6
7
class Student(object):
__slots__ = ('name', 'age') # 用tuple定义允许绑定的属性名称

s = Student() # 创建新的实例
s.name = 'Michael' # 绑定属性'name'
s.age = 25 # 绑定属性'age'
s.score = 99 # 绑定属性'score'

运行就会报错

1
2
3
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
AttributeError: 'Student' object has no attribute 'score'

类属性

1
2
3
4
5
6
7
8
9
10
11
class Person:
name = "person"

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


p1 = Person("Bill", 63)
print(Person.name)
print(p1.name)

结果

1
2
person
Bill

getattr/getattribute

1
2
3
4
5
6
7
8
class User:
def __getattribute__(self, item):
if (item == "age"):
return 0

def __getattr__(self, item):
if (item == "age"):
return 99

私有属性

1
2
3
4
5
6
7
8
9
10
11
12
13
14
class Person:
def __init__(self, name, age):
self.__name = name
self.age = age

def getName(self):
return self.__name


p1 = Person("Bill", 63)
print(p1.age)
print(p1.getName()) # 可以通过方法访问
print(p1._Person__name) # 这种方式依旧可以访问
print(p1.__name) # 这种方式是访问不了的

动态属性

1
2
3
4
5
6
7
8
9
10
11
12
13
14
class Person:

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

@property
def sex(self):
return "男"


p1 = Person("小明", 63)
print(p1.name)
print(p1.sex)

结果

1
2
小明

可以看出

动态属性就和vue的计算属性一样,可以像访问属性一样访问方法。

数据描述符/非数据描述符

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
import numbers


# 数据描述符
class IntField:
def __get__(self, instance, owner):
return self.value

def __set__(self, instance, value):
if not isinstance(value, numbers.Integral):
raise ValueError("need int value")
self.value = value

def __delete__(self, instance):
pass


# 非数据描述符
class NoDataField:
def __get__(self, instance, owner):
return 4


class User:
age = IntField()


if __name__ == "__main__":
u = User()
u.age = 10

这样如果赋值的时候不是数字,就会报错

这样就可以限制参数的类型了。

获取属性的顺序

获取属性的两种方式

1
2
3
4
u = User()
u.age
# 等效于
getattr(u, "age")

获取属性的优先级顺序

  1. 调用__getattribute__

  2. 类或者基类中定义的数据描述符

  3. 实例中的属性 user.__dict__["age"]

  4. 中的非数据描述符

  5. 类或基类的属性 User.__dict__["age"]

  6. 调用中的__getattr__方法

  7. 抛出AttributeError

查看实例的属性

1
2
3
4
5
6
7
8
9
10
11
class User:
def __init__(self, name, age):
self.__name = name
self.age = age


u = User("小明", 16)
print(u.__dict__) # 查看实例的属性
print(dir(u)) # 只能看属性的名称,不能看值,但是看到的属性比较全
print(User.__dict__) # 查看类的属性
print(dir(User)) # 查看类的属性

结果

1
2
3
4
{'_User__name': '小明', 'age': 16}
['_User__name', '__class__', '__delattr__', '__dict__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', '__gt__', '__hash__', '__init__', '__init_subclass__', '__le__', '__lt__', '__module__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__', '__weakref__', 'age']
{'__module__': '__main__', '__init__': <function User.__init__ at 0x017BD100>, '__dict__': <attribute '__dict__' of 'User' objects>, '__weakref__': <attribute '__weakref__' of 'User' objects>, '__doc__': None}
['__class__', '__delattr__', '__dict__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', '__gt__', '__hash__', '__init__', '__init_subclass__', '__le__', '__lt__', '__module__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__', '__weakref__']

类与对象

1
2
3
4
5
6
7
8
9
10
11
class User:
def __new__(cls, *args, **kwargs):
return super().__new__(cls, *args, **kwargs)

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


u = User("小明", 16)
print(getattr(u, "age"))

注意

__new__是用来把类生成对象,实在创建对象之前调用,如果没有返回,则__init__不会调用。

__init__是用来给对象初始化属性的。

枚举类

当我们需要定义常量时,一个办法是用大写变量通过整数来定义,例如月份:

1
2
3
4
5
6
JAN = 1
FEB = 2
MAR = 3
...
NOV = 11
DEC = 12

好处是简单,缺点是类型是int,并且仍然是变量。

更好的方法是为这样的枚举类型定义一个class类型,然后,每个常量都是class的一个唯一实例。Python提供了Enum类来实现这个功能:

1
2
3
from enum import Enum

Month = Enum('Month', ('Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'))

这样我们就获得了Month类型的枚举类,可以直接使用Month.Jan来引用一个常量,或者枚举它的所有成员:

1
2
for name, member in Month.__members__.items():
print(name, '=>', member, ',', member.value)

value属性则是自动赋给成员的int常量,默认从1开始计数。

如果需要更精确地控制枚举类型,可以从Enum派生出自定义类:

1
2
3
4
5
6
7
8
9
10
11
from enum import Enum, unique

@unique
class Weekday(Enum):
Sun = 0 # Sun的value被设定为0
Mon = 1
Tue = 2
Wed = 3
Thu = 4
Fri = 5
Sat = 6

@unique装饰器可以帮助我们检查保证没有重复值。

方法

实例方法

1
2
3
4
5
6
7
8
9
10
11
12
class Person:

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

def get_age(self):
return self.age


p1 = Person("小明", 63)
print(p1.get_age())

静态方法

1
2
3
4
5
6
7
8
9
10
11
12
13
class Person:
def __init__(self, name, age):
self.__name = name
self.age = age

@staticmethod
def getName():
return "123"


print(Person.getName()) # 可以通过方法访问
p1 = Person("Bill", 63)
print(p1.getName()) # 可以通过方法访问

注意

静态方法可以用类调用,也可以用示例调用。

类方法

1
2
3
4
5
6
7
8
9
10
11
12
13
class Person:

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

@classmethod
def getPerson(cls, name, age):
return cls(name, age)


p1 = Person.getPerson("小明", 63)
print(p1.name)

注意

类方法的主要好处在于生成类的实例的时候不用写类名,这样一旦类名发生变化,类方法中不用修改代码。

判断为空

1
if (p1 is None):

判断是否是类的实例

1
2
3
4
5
6
7
8
9
class Person:

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


p1 = Person("小明", 63)
print(isinstance(p1, Person))

删除属性或对象

1
2
3
4
5
a = object()
b = a
del a
print(b)
print(a)

结果

1
2
3
4
5
<object object at 0x017265B0>
Traceback (most recent call last):
File "main.py", line 5, in <module>
print(a)
NameError: name 'a' is not defined

我们会发现a被删除后,b依旧没有被销毁,这是因为对应每次被引用都会计数器+1,只有计数器为0,才会销毁。

继承

1
class Student(Person):

Python的类是可以多继承的

1
class C(A,B):

注意

开发时尽量不要用多继承

super

基本使用

1
2
3
4
5
6
7
8
9
10
11
12
13
class A:
def __init__(self) -> None:
print("A")


class B(A):
def __init__(self) -> None:
super().__init__()
print("B")


if __name__ == "__main__":
b = B()

结果

1
2
A
B

调用的顺序

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
class A:
def __init__(self) -> None:
print("A")
super().__init__()


class B(A):
def __init__(self) -> None:
print("B")
super().__init__()


class C(A):
def __init__(self) -> None:
print("C")
super().__init__()


class D(B, C):
def __init__(self) -> None:
print("D")
super().__init__()


print(D.__mro__)
d = D()

结果

1
2
3
4
5
(<class '__main__.D'>, <class '__main__.B'>, <class '__main__.C'>, <class '__main__.A'>, <class 'object'>)
D
B
C
A

可以看出

调用顺序是按照MRO的算法来调用的

使用类.__mro__可以查看调用顺序

属性和方法都是这个规则

抽象类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
import abc


class A(metaclass=abc.ABCMeta):
@abc.abstractmethod
def say(self):
pass


class B(A):
def __init__(self):
pass

def say(self):
pass


if __name__ == "__main__":
b = B()

注意

如果B不实现A的抽象方法就会报错。

动态创建类

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
def create_class01(ctype):
if (ctype == "user"):
class User:
name = "小明"

def say(self):
print(f"{self.name} say")

return User


def create_class02(ctype):
def say(self):
print(f"{self.name} say")

if (ctype == "user"):
User = type("User", (), {"name": "小明", "say": say})
return User


if __name__ == "__main__":
User = create_class02("user")
u = User()
print(u.name)
u.say()

结果

1
2
小明
小明 say

单例

很多初学者喜欢用全局变量,因为这比函数的参数传来传去更容易让人理解。确实在很多场景下用全局变量很方便。不过如果代码规模增大,并且有多个文件的时候,全局变量就会变得比较混乱。你可能不知道在哪个文件中定义了相同类型甚至重名的全局变量,也不知道这个变量在程序的某个地方被做了怎样的操作。

因此对于这种情况,有种更好的实现方式:
单例(Singleton)

单例是一种设计模式,应用该模式的类只会生成一个实例。

单例模式保证了在程序的不同位置都可以且仅可以取到同一个对象实例:如果实例不存在,会创建一个实例;如果已存在就会返回这个实例。因为单例是一个类,所以你也可以为其提供相应的操作方法,以便于对这个实例进行管理。

以下是实现方法:

  • 使用函数装饰器实现单例
  • 使用类装饰器实现单例
  • 使用 new 关键字实现单例
  • 使用 metaclass 实现单例

使用函数装饰器实现单例

以下是实现代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
def singleton(cls):
_instance = {}

def inner():
if cls not in _instance:
_instance[cls] = cls()
return _instance[cls]
return inner

@singleton
class Cls(object):
def __init__(self):
pass

cls1 = Cls()
cls2 = Cls()
print(id(cls1) == id(cls2))

输出结果:

1
True

在 Python 中,id 关键字可用来查看对象在内存中的存放位置,这里 cls1 和 cls2 的 id 值相同,说明他们指向了同一个对象。

代码中比较巧妙的一点是:

1
_instance = {}

使用不可变的类地址作为键,其实例作为值,每次创造实例时,首先查看该类是否存在实例,存在的话直接返回该实例即可,否则新建一个实例并存放在字典中。

使用类装饰器实现单例

代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
class Singleton(object):
def __init__(self, cls):
self._cls = cls
self._instance = {}
def __call__(self):
if self._cls not in self._instance:
self._instance[self._cls] = self._cls()
return self._instance[self._cls]

@Singleton
class Cls2(object):
def __init__(self):
pass

cls1 = Cls2()
cls2 = Cls2()
print(id(cls1) == id(cls2))

同时,由于是面对对象的,这里还可以这么用

1
2
3
4
5
6
7
class Cls3():
pass

Cls3 = Singleton(Cls3)
cls3 = Cls3()
cls4 = Cls3()
print(id(cls3) == id(cls4))

使用 类装饰器实现单例的原理和 函数装饰器 实现的原理相似,理解了上文,再理解这里应该不难。

使用 new 关键字实现单例模式

使用 new 方法在创造实例时进行干预,达到实现单例模式的目的。

1
2
3
4
5
6
7
8
9
10
11
12
class Single(object):
_instance = None
def __new__(cls, *args, **kw):
if cls._instance is None:
cls._instance = object.__new__(cls, *args, **kw)
return cls._instance
def __init__(self):
pass

single1 = Single()
single2 = Single()
print(id(single1) == id(single2))

在理解到 new 的应用后,理解单例就不难了,这里使用了

1
_instance = None

来存放实例,如果 _instanceNone,则新建实例,否则直接返回 _instance 存放的实例。

使用 metaclass 实现单例模式

同样,我们在类的创建时进行干预,从而达到实现单例的目的。

在实现单例之前,需要了解使用 type 创造类的方法,代码如下:

1
2
3
4
5
6
7
def func(self):
print("do sth")

Klass = type("Klass", (), {"func": func})

c = Klass()
c.func()

以上,我们使用 type 创造了一个类出来。这里的知识是 mataclass 实现单例的基础。

1
2
3
4
5
6
7
8
9
10
11
12
13
class Singleton(type):
_instances = {}
def __call__(cls, *args, **kwargs):
if cls not in cls._instances:
cls._instances[cls] = super(Singleton, cls).__call__(*args, **kwargs)
return cls._instances[cls]

class Cls4(metaclass=Singleton):
pass

cls1 = Cls4()
cls2 = Cls4()
print(id(cls1) == id(cls2))

这里,我们将 metaclass 指向 Singleton 类,让 Singleton 中的 type 来创造新的 Cls4 实例。