当实例化一个类时,python都做了什么?

原文: Understanding Object Instantiation and Metaclasses in Python

在这篇文章中, 我们将探讨python实例化对象时整个过程. 从基础的创建对象开始, 逐步深入理解特殊方法, 例如__new__, __init____call__. 我们还将理解到元类(Metaclass)在python创建一个对象中所起到的作用. 尽管这些都是一些高级的话题, 但在本篇文章会从易到难逐步谈到这些话题, 所以即使是新手也能够理解.

object基类

在python3, 所有的类隐式(implicitly)继承于内建(built-in)object基类. object类提供许多公共方法(common methods), 比如__init__, __str____new__, 这些公共方法可以被其子类覆写(overrideen), 考虑下面代码:

1
2
class Human:
pass

在这段代码中, Human类并没有定义有任何属性和方法. 然而, 在默认情况下, Human类会继承object类, 其object类的所有方法和属性都会被继承. 我们可以使用dir方法来查看Human类所拥有的方法和属性.

dir函数会返回一个列表, 列表中包含该类所拥有的属性和方法.

1
2
3
4
5
6
7
8
dir(Human)

# Output:
['__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__']

dir函数输出Human类所拥有的方法和属性, 其中, 绝大部分从object类继承过来的方法和属性都是被子类使用的. python提供了__bases__属性给每一个类, 能够获取该类都继承哪些父类.

__bases__属性记录该子类所继承的所有父类的列表

1
2
3
print(Human.__bases__)

# Output: (<class 'object'>,)

上述输出展示了Human类的父类, 我们通过调用dir方法来查看object类的所有属性和方法.

1
2
3
4
5
6
7
dir(object)

# Output:
['__class__', '__delattr__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__',
'__getattribute__', '__gt__', '__hash__', '__init__', '__init_subclass__', '__le__',
'__lt__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__',
'__sizeof__', '__str__', '__subclasshook__']

上述对Human类的定义可以等价于下面的代码. 在这里, 我们显式(emplicitly)继承object基类. 尽管你可以显式继承object基类, 但实际上可以不需要.

1
2
class Human(object):
pass

object基类提供了__init____new__方法用于创建和实例一个类. 稍后我们会探讨__init____new__的相关细节.

Object and types in Python

python是一门面向对象的程序语言. 在python的世界中, 一切都是对象或实例. 类, 函数(function), 甚至是基础数据类型如整型(integer), 浮点(float)都可以是众多类中的对象. 每一个对象由其类实例化获得的. 为了晓得这个类或这个对象的类, python为我们提供了type函数和定义在该对象自身的__class__属性.

让我们去理解type函数对基础类型带来什么帮助,比如int和float.

1
2
3
4
5
6
7
8
9
# A simple integer data type
a = 9

# The type of a is int (i.e., a is an object of class int)
type(a) # Output: <class 'int'>

# The type of b is float (i.e., b is an object of the class float)
b = 9.0
type(b) # Output: <class 'float'>

不像其他语言, 在python中, 9是int类中的一个对象, 被变量a引用, 同样的, 9.0是float类的一个对象, 被变量b引用.

type函数可用于查看类型或该对象所属的类. 它接受一个我们想知道对象作为第一个参数并返回该对象所属的类或类型

我们也可以使用该对象的__class__属性来查看该对象的所属类.

__class__是对象的一个属性, 记录了在该对象被创建时所属于的类

1
2
a.__class__   # Output: <class 'int'>
b.__class__ # Output: <class 'float'>

通过上述例子, 现在知道type方法和__class__属性能够为用户定义的Human类提供帮助. 考虑下面对Human的定义.

1
2
3
4
5
6
# Human class definition
class Human:
pass

# Creating a Human object
human_obj = Human()

上面的代码创建了一个Human类的实例, 我们可以通过使用type函数或__class属性查看它的类是什么

1
2
3
4
# human_obj is of type Human
type(human_obj) # Output: <class '__main__.Human'>

human_obj.__class__ # Output: <class '__main__.Human'>

可以看到两种方法输出的结果一致.

在python中, 函数也是一个对象, 我们可以使用相同的方法来验证

1
2
3
4
5
6
# Check the type of the function
def simple_function():
pass

type(simple_function) # Output: <class 'function'>
simple_function.__class__ # Output: <class 'function'>

可以看出, simple_function是源于function类的一个对象.

在python中, 类也算作是对象(元类)

举一个例子, Human类对于自己而言是对象, 对的, 你没有听错, 任何类都是一个类(元类), 能够被创建和实例化.

让我们探究Human类的类

1
2
3
4
5
class Human:
pass

type(Human) # Output: <class 'type'>
Human.__class__ # Output: <class 'type'>

可以看得出来, Human类甚至是在python中的所有类都是type类的对象. 不同于type函数, 这里的type是一个类(元类). type类, 用于创建所有类的类在python中称为元类(Metaclass), 让我们进一步了解一下.

Metaclass in Python

元类是一种用于实例化类的类, 即元类是类的类.

在最开始, 我们查看了变量a和变量b的类分别为int和float. 在当int和float作为类时, 它们被类的类或称为元类的类所创建.

1
2
3
4
5
type(int)    # Output: <class 'type'>
type(float) # Output: <class 'type'>

# Even type of object class is - type
type(object) # Output: <class 'type'>

type类是int和float类的元类. type类是内建object类或元类, 进而继承于object类的子类的元类都是type类. 当type类自身也是一个类时, 那么type类的元类是哪个呢? 答案则是自己就是元类.

1
type(type) # Output: <class 'type'>

在日常编程中, 元类很少被提及, 也很少被广泛使用. 我深入这个话题是因为元类在创建对象的过程中扮演着重要的角色.

在下面的篇幅中, 这两个重要的观点覆盖下文.

  1. 在python中, 所有的类都是type类的对象, 这个type类叫做元类
  2. 在python中, 对于所有类, 默认情况下, 都继承于object基类

The object instantiation process in Python

现在我们对元类和对象有了一个基础的认识, 现在我们开始深入了解对象的创建和实例化的过程. 考虑下述Human类.

1
2
3
4
5
6
7
8
9
10
11
12
class Human:
def __init__(self, first_name, last_name):
self.first_name = first_name
self.last_name = last_name

human_obj = Human("Virat", "Kohli")

isinstance(human_obj, Human) # Output: True

# As object is the base class for all the class hence
# isinstance(human_obj, object) is True
isinstance(human_obj, object) # Output: True

可以看得出, 输出结果一致, 可以论证上述观点. 若我们想在深入一点, 我们可以很自然地提出一些问题:

  1. 每次定义Human类, 我们并不会从__init__方法返回任何东西, 那么当调用Human类的时候它是怎么返回Human对象的.
  2. 我们知道__init__方法用于初始化对象的, 但__init__方法是如何获取self实参的.

在这一节中, 我们带着两个问题探讨其中的答案.

在python中, 对象的创建分为两个步骤, 在第一步中, python创建一个对象, 下一步是初始化这个对象. 大多情况下, 我们只关注第二步. python使用__new__方法和__init__分别完成第一步和第二步.

如果这个类没有定义有这两个方法, 它们会继承object基类的. 即当Human类没有定义__new__方法时, 在实例化对象的过程中, python会直接调用object的__new__方法. 在初始化过程中, 调用Human类的__init__方法(如果定义有). 接下来, 我们进一步探讨这些方法的相关细节.

The__new__method

__new__方法在实例化对象的过程中第一个被调用的方法. 它是object类中是一个静态方法, 接受cls实参或类的引用作为第一个参数. 余下的参数(“Virat”和”Kohli”)则是在调用这个类Human("Virat", "Kohli")时传递进来的. __new__方法用于创建一个类型为cls的实例(通过调用父类为该对象分配内存). 返回这个cls的实例.

通常情况下, 这个方法不会进行任何初始化, 初始化的工作是交给__init__方法来完成. 然而, 当你覆写(override)__new__方法时, 你可以在该方法返回之前直接初始化对象或在有需要的时候修改它.

__new__方法签名如下:

1
2
3
4
# cls - is the mandatory argument. Object returned by the __new__ method is of type cls
@staticmethod
def __new__(cls[,...]):
pass

当然, 我们可以通过覆写__new__方法进而修改对象创建的过程的.

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 Human:
def __new__(cls, first_name=None):
# cls = Human. cls is the class using which the object will be created.
# Created object will be of type cls.
# We must call the object class' __new__ to allocate memory
obj = super().__new__(cls) # This is equivalent to object.__new__(cls)

# Modify the object created
if first_name:
obj.name = first_name
else:
obj.name = "Virat"

print(type(obj)) # Prints: <__main__.Human object at 0x103665668>
# return the object
return obj

# Create an object
# __init__ method of `object` class will be called.
virat = Human()

print(virat.name) # Output: Virat

sachin = Human("Sachin")
print(sachin.name) # Output: Sachin

在上面的例子中, 我们覆写了__new__方法, 它接受的第一个参数是自身的类.

在python中, __new__方法是一个特例, 尽管它是一个静态方法, 但覆写它时, 我们并不需要加上staticmethod装饰器.

在__new__方法中, 我们先调用父类的__new__方法super().__new__(cls). 父类的__new__方法会创造并返回之前传递cls的对象. 在这里, 我们传递的cls是Human类. 父类的__new__方法返回的是Human类的实例.

__new__方法是用来创造对象和分配内存给对象用的. 在重写(overrideen)__new__方法是, 方法体的第一行必须是调用父类的__new__方法.

Human类的__new__方法修改了来自父类提供的类,并加入了新的属性. 最后, Human类的父类创建完成(即__new__方法执行完毕), Human类的对象也拥有了新属性. 我们成功修改了创建Human对象实例过程.

现在让我们考虑另一个例子, 在这个例子中, 我们创建一个Animal类和覆写__new__方法, 当我们调用Animal()时, 依次调用从Animal类到object类的__new__方法, 不同是的, 在调用object类的__new__方法时, 传递的参数并不是Animal自己的类, 而是Huamn类. 最后得到的对象是Human的对象而不是Animal的对象

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
class Animal:
def __new__(cls):
# Passing Human class reference instead of Animal class reference
obj = super().__new__(Human) # This is equivalent to object.__new__(Human)

print(f"Type of obj: {type(obj)}") # Prints: Type of obj: <class '__main__.Human'>

# return the object
return obj

# Create an object
cat = Animal()
# Output:
# Type of obj: <class '__main__.Human'>

type(cat) # Output: <class '__main__.Human'>

The__init__method

__init__方法在第二步对象实例化过程被调用, 它的第一个参数是一个对象或从__new__方法中返回的实例. 剩余的参数则是在调用这个类(Human(“Virat”, “Kohli”))所包含的参数传入进去. 这些参数用于初始化对象. __init__方法不允许返回任何值. 如果你尝试使用__init__方法来返回任何值, 则会抛出一个异常, 就像这样:

“””
class Human:
def init(self, first_name):
self.first_name = first_name
return self

human_obj = Human(‘Virat’)

Output: TypeError: init() should return None, not ‘Human’

“””

考虑以下简单的例子, 思考__new__和__init__方法的不同之处.

“””
class Human:
def init(self, first_name):
self.first_name = first_name
return self

human_obj = Human(‘Virat’)

Output: TypeError: init() should return None, not ‘Human’

Consider a simple example to understand both the new and init method.

class Human:
def new(cls, *args, **kwargs):
# Here, the new method of the object class must be called to create
# and allocate the memory to the object
print(“Inside new method”)
print(f”args arguments {args}”)
print(f”kwargs arguments {kwargs}”)

    # The code below calls the __new__ method of the object's class.
    # Object class' __new__ method allocates a memory
    # for the instance and returns that instance
    human_obj = super(Human, cls).__new__(cls)

    print(f"human_obj instance - {human_obj}")
    return human_obj

# As we have overridden the __init__ method, the __init__ method of the object class will not be called
def __init__(self, first_name, last_name):
    print("Inside __init__ method")
    # self = human_obj returned from the __new__ method

    self.first_name = first_name
    self.last_name = last_name

    print(f"human_obj instance inside __init__ {self}: {self.first_name}, {self.last_name}")

human_obj = Human(“Virat”, “Kohli”)

Output

Inside new method

args arguments (‘Virat’, ‘Kohli’)

kwargs arguments {}

human_obj instance - <__main__.Human object at 0x103376630>

Inside init method

human_obj instance inside init <__main__.Human object at 0x103376630>: Virat, Kohli

“””

在上述代码中, 我们覆写了__new__和__init__方法. 当__new__方法执行完毕时, python会调用__init__方法来初始化对象. 因为对象的创建在第一步, 对象的初始化在第二步, 所以__new__方法永远是在__init__方法执行完毕后才执行

init__和__new__方法在python中称为魔术方法(magic methods), 魔术方法的名字前缀和后缀都带有双下划线(). 魔术方法会被python隐式调用. 你并不需要显示调用它们. 例如, __new__和__init__方法被隐式调用.

接下来来介绍一下更加神奇的魔术方法__call__

The call mehtod

__call__方法是一种魔术方法, 可以使得对象变得可调用(callable), 可调用对象(Callable objects)是一种可调用的对象. 例如, functions就是可调用对象, 通过一对括号像函数一样实现调用 .

为了更好理解, 请看下面的例子:

1
2
3
4
5
6
7
8
def print_function():
print("I am a callable object")

# print_function is callable as it can be called using round parentheses
print_function()

# Output
# I am a callable object

让我们尝试调用整形(integer)对象. 然而, 整形对象是不可调用的, 调用它会抛出一个异常.

1
2
3
4
a = 10

# As the integer object is not callable, calling `a` using round parentheses will raise an exception.
a() # Output: TypeError: 'int' object is not callable

callable()

callable函数可以判断这个对象是否可调用. 它接受一个对象作为一个参数, 如果这个对象是可调用的就返回True.

1
2
3
4
5
6
7
# Functions are callable
callable(print_function)
# Output: True

# Interger object is not callable
callable(a)
# Output: False

现在让我们看看类是不是也是也是可调用的

1
2
callable(Human)
# Output: True

显然, 类也是可调用的, 它应该是可调用的, 你不相信?, 当我们调用类的时候, 返回的是就是这个类的实例, 现在我们可以探究对象是否源于类的调用.

1
2
3
4
5
6
7
8
9
human_obj = Human("Virat", "Kohli")

callable(human_obj) # Output: False

# Let's try calling the human_obj
human_obj()

# As human_obj is not callable it raises an exception
# Output: TypeError: 'Human' object is not callable

然而, human_obj不可调用, 而Human类是可调用的

为了使得任何对象可调用, python提供了__call__方法, 不过这个方法需要自行实现. 例如, 为了使得human_obj对象可调用, 在Human类中实现__call__方法. 当Human类实现了__call__方法, 所有基于Human类的对象都能够给像函数一样调用.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
class Human:
def __init__(self, first_name, last_name):
print("I am inside __init__ method")
self.first_name = first_name
self.last_name = last_name

def __call__(cls):
print("I am inside __call__ method")

human_obj = Human("Virat", "Kohli")
# Output: I am inside __init__ method

# Both human_obj() and human_obj.__call__() are equaivalent
human_obj()
# Output: I am inside __call__ method

human_obj.__call__()
# Output: I am inside __call__ method

callable(human_obj)
# Output: True

在python中, 类能够称为可调用对象, 这是因为它的元类(type)存在call方法.

当我们调用Huamn()时, 本质就是调用type类的call方法

现在带着对__call__方法的理解, 带着以下问题去探究答案.

  1. 谁调用__new__和__int__方法
  2. 谁把self对象传递给__inti__方法
  3. 当__int__方法被调用时, __int__方法不能返回任何东西, 那么在调用类时又如何返回这个类的对象.(换言之, 在Huamn()时, 是怎么返回human_obj对象的?)

请看下面代码

1
2
3
4
5
6
7
class Human:
def __init__(self, first_name, last_name):
self.first_name = first_name
self.last_name = last_name


human_obj = Human("Virat", "Kohli")

我们知道当我们调用这个类(Human(“Virat”, “Kohli”))时, type类的__call__方法会被调用. 然而, 在type类中, __call__方法都实现了什么内容? 当我们谈及到CPython, 关于type类的__call__方法的定义在c代码中, 我们可以简单的进行转换, 得到这个类似的代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
# type's __call__ method which gets called when Human class is called i.e. Human()
def __call__(cls, *args, **kwargs):
# cls = Human class
# args = ["Virat", "Kohli"]
# Calling __new__ method of the Human class, as __new__ method is not defined
# on Human, __new__ method of the object class is called
human_obj = cls.__new__(*args, **kwargs)

# After __new__ method returns the object, __init__ method will only be called if
# 1. human_obj is not None
# 2. human_obj is an instance of class Human
# 3. __init__ method is defined on the Human class
if human_obj is not None and isinstance(human_obj, cls) and hasattr(human_obj, '__init__'):
# As __init__ is called on human_obj, self will be equal to human_obj in __init__ method
human_obj.init(*args, **kwargs)

return human_obj

让我们解读以上代码, 当我们实例化Huamn(“Virat”, “kohli”)时, python会先调用type类中的__call__方法, 对于该方法的实现大致如上, 从上面可以看得出, type类的__call__方法接受一个Human类作为一个参数(cls就是Human类), 而剩余的参数则是我们实例化Human时所传递的参数, 在__call__方法中, 如果Human类定义有__new__方法, 则调用这个. 否则从它的父类找到__new__方法并调用. __new__方法最终会返回一个human_obj, 之后就是调用Human类中的__init__方法来初始化该对象.

![](https://www.honeybadger.io/images/blog/posts/python-instantiation-metaclass/object-instantiation-and-creation.png?1692840667 https://www.honeybadger.io/images/blog/posts/python-instantiation-metaclass/object-instantiation-and-creation.png?1692840667)

至此, 在python中, 创建和初始化一个对象执行了以下步骤.

  1. 调用Human类–Huamn(); 本质就是调用type类的__call__方法(type.call(Human, “Virat”, “Kohli”))
  2. type.__call__方法先调用Human类的__new__方法, 如果Human类没有定义有, 就调用object类的.
  3. __new__方法返回Human类的对象
  4. 之后, type.__call__方法接下来调用Human类的__int__方法. human_obj类会作为self传入Human类的__int__方法第一个参数.
  5. __init__方法会实例化这个对象. 实例化完成不会返回任何东西
  6. 在最后, type.__call__返回human_obj对象

根据type.__call__的定义, 每当我们创建一个新的对象, __new__方法都会被调用, __init__的调用依赖于__new__方法的返回值. 如果__new__返回一个Human类的对象或Human类的子类, 那么__inti__方法也会被调用.

下面可以通过几个例子来加深理解

Case1: 如果__new__方法返回一个Human类的对象, __int__方法会被调用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
class Human:
def __new__(cls, *args, **kwargs):
print(f"Creating the object with cls: {cls} and args: {args}")
obj = super().__new__(cls)
print(f"Object created with obj: {obj} and type: {type(obj)}")
return obj

def __init__(self, first_name, last_name):
print(f"Started: __init__ method of Human class with self: {self}")
self.first_name = first_name
self.last_name = last_name
print(f"Ended: __init__ method of Human class")

human_obj = Human("Virat", "Kohli")
Output:

# Creating the object with cls: <class '__main__.Human'> and args: ('Virat', 'Kohli')
# Object created with obj: <__main__.Human object at 0x102f6a4e0> and type: <class '__main__.Human'>
# Started: __init__ method of Human class with self: <__main__.Human object at 0x102f6a400>
# Ended: __init__ method of Human class with self: <__main__.Human object at 0x102f6a400>

Case 2: 如果__new__方法没有返回任何值, __inti__不会被调用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
class Human:
def __new__(cls, *args, **kwargs):
print(f"Creating the object with cls: {cls} and args: {args}")
obj = super().__new__(cls)
print(f"Object created with obj: {obj} and type: {type(obj)}")
print("Not returning object from __new__ method, hence __init__ method will not be called")

def __init__(self, first_name, last_name):
print(f"Started: __init__ method of Human class with self: {self}")
self.first_name = first_name
self.last_name = last_name
print(f"Ended: __init__ method of Human class")

human_obj = Human("Virat", "Kohli")
Output:

# Creating the object with cls: <class '__main__.Human'> and args: ('Virat', 'Kohli')
# Object created with obj: <__main__.Human object at 0x102f6a5c0> and type: <class '__main__.Human'>
# Not returning object from __new__ method, hence __init__ method will not be called

在上面的代码中, Human类的__new__方法会被调用. 然而, Human类的对象被创建,(分配内存给这个对象), 但并没有返回这个对象, __init__方法不会被调用. 最后, human_ob变量并没有拥有这个对象的引用.

1
print(human_obj). # Output: None

Case3: __new__方法返回一个整形对象

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
class Human:
def __new__(cls, *args, **kwargs):
print(f"Creating the object with cls: {cls} and args: {args}")
obj = super().__new__(cls)
print(f"Object created with obj: {obj} and type: {type(obj)}")
print("Not returning object from __new__ method, hence __init__ method will not be called")
return 10

def __init__(self, first_name, last_name):
print(f"Started: __init__ method of Human class with self: {self}")
self.first_name = first_name
self.last_name = last_name
print(f"Ended: __init__ method of Human class")

human_obj = Human("Virat", "Kohli")

在上面的代码中, Human类中的__new__方法被调用,创建了一个Human类的对象, 然而, 在__new__方法中返回的不是Human类的对象, 而是一个整形10, 显然, __inti__方法也不会被调用.

在某些场景上, 我们希望能够在__new__方法中初始化对象并不返回这个对象的实例, 我们可以这个做

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
class Human:
def __new__(cls, *args, **kwargs):
print(f"Creating the object with cls: {cls} and args: {args}")
obj = super().__new__(cls)
print(f"Object created with obj: {obj} and type: {type(obj)}")
print("Not returning object from __new__ method, hence __init__ method will not be called")
obj.__init__(*args, **kwargs)
return 10

def __init__(self, first_name, last_name):
print(f"Started: __init__ method of Human class with self: {self}")
self.first_name = first_name
self.last_name = last_name
print(f"Ended: __init__ method of Human class")

human_obj = Human("Virat", "Kohli")
Output:

# Creating the object with cls: <class '__main__.Human'> and args: ('Virat', 'Kohli')
# Object created with obj: <__main__.Human object at 0x102f6a860> and type: <class '__main__.Human'>
# Not returning object from __new__ method, hence __init__ method will not be called
# Started: __init__ method of Human class with self: <__main__.Human object at 0x102f6a860>
# Ended: __init__ method of Human class

得到的结果为

1
2
print(human_obj)
# Output: 10