(Python):元类(metaclass), Type类,类实例,dataclass

文章目录

    • 类实例
      • 类成员变量与成员变量的区别
    • Type类
    • Metaclass
      • ABCMeta
    • dataclass

类实例

在python中,所有的类都是一个实例,也就是说,所有的类其实都是由自己的属性和方法的。
这里就需要区分清楚一件事,类实例和对象。类实例指的是类本身,而对象则是类的某一个实例。

class Test:
	pass
x = Test()
print(Test, x)

其中x是Test的对象,而Test本身是类实例。

类成员变量与成员变量的区别

既然类本身是一个实例,那么类实例本身也有自己的成员变量。我们厂有一种错觉认为类实例的成员变量会被分配给每一个该类的对象,实则不然。

类成员变量属于类的属性,类的对象虽然可以访问,但是无法修改,一但对象尝试修改,Py就会自动的在对象中创建一个同名成员变量,然后赋值。此时这个对象不再访问类实例的成员变量。看下面的例子:

class test:
	x = False
	def __init__(self):
		self.y = 2

上述中x是类变量,y是成员变量。他们有如下规则:

  1. 类变量可以通过,类和实例访问,但只允许通过类修改
  2. 如果通过实例修改类变量,则会自动在实例的作用于域中创建一个对应的成员变量
class test:
    ok = True
    @classmethod
    def change_ok(cls):
        cls.ok = False

x, y = test(), test()

print('x, y分别是', x.ok, y.ok)
x.change_ok()
print('x, y分别是', x.ok, y.ok)
x.ok = True
print('x, y分别是', x.ok, y.ok)
x.change_ok()
print('x, y分别是', x.ok, y.ok)
"""
output:
x, y分别是 True True
x, y分别是 False False
x, y分别是 True False
x, y分别是 True False
"""

我们一步一步来看输出:

  1. 首先由于x和y都是test的实例,所以在访问ok时都访问的是类变量ok
  2. 改变类变量ok,由于x和y都访问的是类变量,所以两者同时该变
  3. 为修改x的ok为True,此时Python自动为x的成员变量区域创建一个ok变量,从此之后x不再访问公共类变量ok,而是自己独有的成员变量ok。
  4. 改变类变量ok,此时x已经不会再跟着变了,因为x有了自己的成员变量ok

类变量只能通过类对象访问,而类对象通过:类名.xxx,cls.xxx的方式使用。类对象全局唯一。

Type类

上面讲完了类实例,那么问题来了,Py的类都是一个实例,那么谁创造这些实例呢?
没错,就是Type类创造的,Type类创造了所有的类实例,所有的类实例都是Type的一个instance,看下面代码:

class Test:
    pass
print(isinstance(object, type))
print(isinstance(Test, type))

输出结果均为True,可以见得所有的类都是type类的一个对象。那么是否可以通过type类来创建一个新的类呢?
当然可以!
我们可以通过如下方法来动态的创建一个类

type(类名, (父类们), 所有属性)

举个例子:

@classmethod
def speak(cls, x):
	print(x)
T = type('Myclass', (object,), {'speak': speak})
T.speak(123123)
print(T)
"""
output:
123123

"""

可以看到我们通过type方法创建了一个类Myclass,还为其设置了一个类方法speak。而这里类名,父类,类属性都是通过参数传入的,所以这是一个动态创建类的过程。

我们甚至可以进一步的把它扩展。我们可以把一个类存在文本文件中,我们通过读取文本文件来创建类。

x = """
@classmethod
def speak(cls, x):
    print(x)
"""
attr_ = dict()
exec(x, globals(), attr_)

T = type('Myclass', (object,), attr_)
T.speak(123123)
print(T)

exec可以动态的执行文本代码,第一个参数是代码,第二个参数需要传入一个变量作用区域(字典),第三个参数是变异的结果存储的结果(一个Mapping类型)。然后把这个结果传给type就创建出来的一个类。通过这种方式,我们甚至可以在线的创建一些类,更新一些操作。

Metaclass

每一个类都会有一个Metaclass,这个Metaclass就是用来创建自身类的实例的,如果没有指明Metaclass,那么这个值默认就是type。这也就是为什么每一个类的实例都是type的对象的原因。

那么,我们是否可以定义属于自己的Metaclass呢?当然是可以的,只需要继承type类即可。作为Metaclass,其负责的内容是创建类对象,所以我们需要重写__new__方法。例如:

class MyMetaclass(type):
    @classmethod
    def __new__(mcs, *args, **keywords):
        return super().__new__(*args) # 创建类实例

class Test(metaclass=MyMetaclass):# (使用MyMetaclass作为元类)
    pass

由于这里我们是在创建一个类,所以我们可以搞一些小动作,比如给其添加一些类属性:

class MyMetaclass(type):
    @classmethod
    def __new__(mcs, *args, **keywords):
        class_ = super().__new__(*args)
        class_.meta_type = 'MyMetaclass'
        return class_

class Test(metaclass=MyMetaclass):
    pass

print(Test.meta_type)
"""
MyMetaclass
"""

我们还可以像Metaclass中传入参数,使得在创建类时多一些选择

class MyMetaclass(type):
    @classmethod
    def __new__(mcs, *args, **keywords):
        class_ = super().__new__(*args)
        for key, value in keywords.items():
            setattr(class_, key, value)
        return class_

class Test(metaclass=MyMetaclass, name='test'):
    pass
print(Test.name)

上述的输出结果为test。

ABCMeta

ABCMeta是官方提供的一个抽象方法元类,使用它我们就可以定义类接口。其使用方法如下:

  1. 类Test使用ABCMeta为元类
  2. 使用abstractmethod,abstractstaticmethod等装饰器来装饰Test的成员方法,使其成为抽象方法。

这样拥有抽象方法的Test类就无法实例化,而想要实例化就必须继承且实现对应的类

from abc import ABCMeta, abstractmethod, abstractproperty


class Test(metaclass=ABCMeta):
    @abstractmethod
    def speak(self, x):...

    @abstractproperty
    def name(self):...

Test()
"""
Traceback (most recent call last):
  File "e:\PythonProject\main.py", line 68, in        
    Test()
TypeError: Can't instantiate abstract class Test with abstract methods name, speak
"""

如果想要使用这个函数,就必须继承然后实现,这就使得我们可以强迫使用者实现某些方法。

from abc import ABCMeta, abstractmethod, abstractproperty


class Test(metaclass=ABCMeta):
    @abstractmethod
    def speak(self, x):...

    @abstractproperty
    def name(self):...


class RealTest(Test):
    def speak(self, x):
        print(x)

    @property
    def name(self):
        return 'RealTest'

RealTest()

此时RealTest就可以正常的实例化了,因为所有的抽象方法,属性都已经被实现。

dataclass

dataclass是py提供的一种数据接口,我们可以通过它来定义数据类,我们通过类装饰器的方式来装饰类就可以把类变成数据类

from dataclasses import dataclass, field

@dataclass()
class MyData:
    age: int
    name: str = 9

print(MyData(10))
"""
MyData(age=10, name=9)
"""

数据类内部,我们可写上若干种数据的定义,然后在创建时只需要输入参数就可以创建。
dataclass为我们写好了各种常见的操作,例如__eq__, __rper__, __str__等。

from dataclasses import dataclass, field

@dataclass()
class MyData:
    age: int
    name: str = 9

print(MyData(10) == MyData(10))
"""
True
"""

我们还可以指定这些参数是否可以被二次修改,例如:

from dataclasses import dataclass, field

@dataclass(frozen=True)
class MyData:
    age: int
    name: str = 9

x = MyData(10)
x.age = 100
"""
Traceback (most recent call last):
  File "e:\PythonProject\main.py", line 45, in 
    x.age = 100
  File "", line 4, in __setattr__
dataclasses.FrozenInstanceError: cannot assign to field 'age'
"""

此时加了frozen=True参数,所以修改就会报错。

我们还可以隐藏某些我们不想让用户看到的参数,此时使用filed函数。

from dataclasses import dataclass, field

@dataclass
class MyData:
    age: int
    name: str = field(default='Mydata', init=False, hash=True, repr=False)

x = MyData(10)

上述代码使用field函数给了name变量值,同时设置其不能通过对象来赋值,也不能被print打印出来,但是可以参与hash。

除此之外,dataclass还提供了__post__init__操作为初始化之后的操作进行一些后处理

from dataclasses import dataclass, field
@dataclass(order=True) # 设置该Class支持排序
class MyData:
    age: int
    name: str = 9
    other_info: list = field(default=None, init=False, repr=True, hash=True)

    def __post_init__(self): # 后续操作,根据age处理斌生成other_info
        if self.age >= 18:
            self.other_info = "this person has matured"
        else:
            self.other_info = "this person has not matured"


x = MyData(10, 'asd')
y = MyData(20, 'asssd')
print(sorted([y, x])) # 排序MyData
"""
[MyData(age=10, name='asd', other_info='this person has not matured'), MyData(age=20, name='asssd', other_info='this person has matured')]
"""

最后一点就是,dataclass还支持排序操作,按照的排序的优先级是变量的顺序。

你可能感兴趣的:(python,开发语言)