Python中类型和对象之间的关系

9

我正在阅读包含以下内容的 博客:

因为在 Python 中一切都是对象,一切都是类的实例,甚至类本身也是。那么,类型就是用来实例化类的类。所以请记住:对象是每个对象的基础,类型是每种类型的类。听起来很困惑?这不是你的错,不要担心。但是,只为了给你致命一击,Python 就是建立在这上面的。

>>> type(object)
<class 'type'>
>>> type.__bases__
(<class 'object'>,)

我不太理解这个。有没有人可以用另一种方式来解释这个关系以使其更清晰?


2
可能值得阅读这个问题及其相关答案:https://dev59.com/WnVD5IYBdhLWcg3wE3Ro - mgilson
1
如果忽略“type”对象,那么“isinstance(type, type) => True”就很容易理解了,“type”是唯一的奇怪对象。 - Tadhg McDonald-Jensen
4个回答

5

type(x)x.__class__的结果基本相同:

for obj in (object,type,1,str):
    assert type(obj) is obj.__class__

print("type(obj) and obj.__class__ gave the same results in all test cases")

__bases__ 表示一个类继承自哪些基类:

class parent:
    pass

class child(parent,int):
    pass

print(child.__bases__) # prints (<class '__main__.parent'>, <class 'int'>)

然而,如果您询问关于objecttype之间奇怪的关系:

   # are `object` and `type` both instances of the `object` class?
>>> isinstance(object, object) and isinstance(type, object)
True
   # are they also instances of the `type` class?
>>> isinstance(object, type) and isinstance(type, type)
True
   # `type` is a subclass of `object` and not the other way around (this makes sense)
>>> [issubclass(type, object), issubclass(object, type)]
[True, False]

这更像是一个鸡生蛋的问题:哪个先出现了?

答案是 PyObject,它在 C 中被定义。

objecttype 可用于 Python 解释器之前,它们的底层机制已在 C 中定义,并且在定义后重写实例检查(类似于抽象类,请参见 PEP 3119)。

你可以将其视为以下 Python 实现:

#this wouldn't be available in python
class superTYPE(type):
    def __instancecheck__(cls,inst):
        if inst ==TYPE:
            return True
        else:
            return NotImplemented #for this demo
    
class TYPE(type,metaclass=superTYPE):
    def __instancecheck__(cls,inst):
        if inst in (OBJECT,TYPE):
            return True
        else:
            return NotImplemented #for this demo

class OBJECT(metaclass=TYPE):
    pass

# these all pass
assert isinstance(TYPE,OBJECT)
assert isinstance(OBJECT,TYPE)
assert isinstance(TYPE,TYPE)
assert isinstance(OBJECT,OBJECT)

实际上,更好的表达方式可能是:

#this isn't available in python
class superTYPE(type):
    def __instancecheck__(cls,inst):
        if inst in (TYPE,OBJECT):
            return True
        else:
            return NotImplemented #for this demo
    
class OBJECT(metaclass=superTYPE):
    pass


class TYPE(OBJECT):
    pass

但是如果你想确切地了解它的工作原理,你需要查看用C语言编写的源代码。


2
感谢您抽出时间回答这个问题。 - user1592380

1

简而言之-可能不行。但我试过了。

这真的很奇怪,感觉就像无穷尽的乌龟一样。我以前其实没有深入研究过这个领域,虽然听起来很有趣也很强大。这个解释很令人困惑,那个页面上的其他信息也是如此,但我感觉自己有些启示了。无论我能否清晰地解释这一点,我不确定,但我会试着去做。

首先让我们看看这些乌龟:

>>> isinstance(type, object)
True
>>> isinstance(object, type)
True

等等,什么?

typeobject的实例时,object怎么会是type的实例呢?这感觉就像在说:

class Parrot: pass

ex = Parrot()

isinstance(ex, Parrot)
isinstance(Parrot, ex)

这两次应该都是True。但显然并不是这样的。就像Tadhg McDonald-Jensen指出的那样。

>>> isinstance(type, type)
True

这应该向您表明,幕后有一些神奇的事情正在发生。所以在这一点上,让我们完全忘记Python(我知道,为什么我们要做这样可怕的事情呢?)
总的来说,所有计算机程序都是1和0 (更准确地说,它们只是一堆逻辑门和电子,在>〜2.5v和〜<2.5v,但0和1足够好)。无论你是用汇编语言、实际机器码、Python、C#、Java、Perl还是其他什么语言编写的,它们都只是一堆比特。
如果你编写了一个类定义,那么这个类只是一些比特。该类的一个实例只是更多的比特。而编程语言、编译器和解释器只是更多的比特。
对于Python来说,是“python”解释器赋予了意义,这些比特是我们的Python程序。有趣的是,我们通常认为的大部分Python实际上是用Python编写的(虽然大部分是C,对于我们CPython人来说,Jython是Java等)。

现在我们来谈谈我们所说的类型对象。正如文章所指出的那样,它们有点特别。因此,我们知道我们可以创建一个类,然后该类就是一个对象:

>>> class Confusion: pass
...
>>> isinstance(Confusion, object)

如果你仔细想一下,这是有道理的——你可能已经创建了类级别的变量:
>>> class Counter:
...  count = 0
...  def __init__(self):
...   Counter.count += 1
...   print(self.count)
...
>>> Counter()
1
<__main__.Counter object at 0x7fa03fca4518>
>>> Counter()
2
<__main__.Counter object at 0x7fa03fca4470>
>>> Counter()
3
<__main__.Counter object at 0x7fa03fca4518>
>>> Counter()
4
<__main__.Counter object at 0x7fa03fca4470>
>>> Counter.count
4
>>> Counter.__repr__(Counter)
'<type object at 0x1199738>'

但正如最后一个例子所示(并在帖子中提到),类声明,即使用 class SomeClass: pass 得到的声明,实际上是另一个类的 实例。特别地,它是 type 类的一个实例。而那个 实例(我们称之为类)在被调用时将产生一个自身的 实例

>>> Counter.__call__()
5
<__main__.Counter object at 0x7fa03fca4518>

那么这一切与typeobject之间的关系有什么关联呢?

嗯,某处python创建了一系列位于object的位和一系列位于type的位,并将它们以一种特定的方式连接在一起。

>>> type.__bases__
(<class 'object'>,)
>>> object.__bases__
()

因为我目前不想查看源代码,所以我猜测首先创建了type,然后从该类型生成object,并将type.__bases__设置为(class 'object')。通过在typeobject之间创建这种循环关系,它给人的印象是一直都是乌龟,但实际上最后两只乌龟只是站在彼此身上。

我认为没有比文章描述更好的解释这里正在发生的事情 - 至少在传统的OOP is-a/has-a思维方式中,并不是那种类型的事情。就像在二维空间中绘制三维图形一样,你会遇到问题。

这只是两组具有一些位于其中的位,恰好是彼此地址的位。


在你确信这是严格的 C 级别魔法和位操作之前,你可能需要查看 PEP 3119 - Tadhg McDonald-Jensen
抱歉,我的意思不是要声称这是C级别的魔法(事实上,我甚至提到“我们通常认为是Python的很多东西实际上都是用Python编写的”),也不是说比特和字节是C的领域。那只是愚蠢的想法。显然,我的关于鸡蛋/鸡的排序理论是错误的。我的猜测需要更多的教育 :-\ - Wayne Werner
抱歉,你没有表达出来,我一直认为这是 C 语言的魔法,直到我发现了抽象类和 PEP 3119,然后我才能够在 Python 中复制循环行为。实际创建的方式完全可能是相反的,我也没有查看过源代码。 :) - Tadhg McDonald-Jensen

1

关系

type()object()之间的关系密不可分。

它们都是彼此的实例:

>>> isinstance(object, type)
True
>>> isinstance(object, object)
True
>>> isinstance(type, type)
True
>>> isinstance(type, object)
True

关于子类化,类型是一种对象,但反之则不成立:

>>> issubclass(object, type)
False
>>> issubclass(type, object)
True

力学

我认为object()提供了所有对象的基本功能:

>>> dir(object)
['__class__', '__delattr__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', '__getstate__', '__gt__', '__hash__', '__init__', '__init_subclass__', '__le__', '__lt__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__']

type()__class__之间的唯一联系是__class__设置为type();否则,后者根本不会被使用:

>>> object.__class__
<class 'type'>

然后type()继承自object(),但覆盖了一些关键的创建类的方法:

for methname in dir(type):
    if 'type' in repr(getattr(type, methname, '')):
        print(methname)

        
    __call__
    __class__
    __delattr__
    __dict__
    __dir__
    __doc__
    __getattribute__
    __init__
    __init_subclass__
    __instancecheck__
    __mro__
    __name__
    __new__
    __or__
    __prepare__
    __qualname__
    __repr__
    __ror__
    __setattr__
    __sizeof__
    __subclasscheck__
    __subclasses__
    __subclasshook__
    mro

使用这些方法和属性,type现在可以创建类。类并不是特别的。它们只是object的一个实例,具有适合制作子类和实例的方法。

0

你可以用以下两句话总结所有神秘的术语和关系:

  1. 每个类都定义了作用于其实例的方法,类本身的行为在type类中定义。

  2. 所有对象共享的通用行为 - 通常被视为内置的东西 - 都由object定义,因此所有对象都共享这些行为(除非子类覆盖它:)

本答案的其余部分将尝试深入探讨一些在Python中有用的内容示例,以小块地解决描述的每个部分。

在Python中,一切都是一个object

实际上,这意味着在所有Python对象中标准的操作将在object中定义,例如内存中的属性查找获取对象大小。具体而言(但不太有用),它还意味着:

  • 语句isinstance(x, object)对于任何可能的值x都会始终返回True

  • 这也意味着任何被定义的类都会被认为是object的子类。所以对于任何类xissubclass(x, object)始终返回True

我想简要说明一下Python如何将东西转换为字符串,这些内容来自文档

object.__repr__(self)

当调用内置函数repr()时,会调用此方法来计算对象的“官方”字符串表示形式。如果可能的话,这应该看起来像一个有效的Python表达式。[...] 如果一个类定义了__repr__()但没有定义__str__(),那么在需要实例的“非正式”字符串表示时也会使用__repr__()

通常用于调试,因此表示形式应具有信息丰富性和明确性。

object.__str__(self)

[...] 用于计算对象的“非正式”或可打印的字符串表示形式。[...]

内置类型object定义的默认实现调用object.__repr__()

对于 Python 初学者来说,了解 strrepr 的存在非常有用:当你打印数据时,它使用 str,而当你在命令行上评估东西时,它使用 repr,这在字符串中最为明显:

>>> x = "123"
>>> print(x) # prints string as is which since it contains digits is misleading
123
>>> x # shows "official" representation of x with quotes to indicate it is a string
'123'
>>> print(repr(x)) # from a script you can get this behaviour by calling repr()
'123'

这与我们关于object的对话有关,因为关于"内置类型object定义的默认实现[for __str__]调用了object.__repr__()。"这句话的意思是,如果我们定义了一个__repr__方法,那么当我们打印对象时也会得到它。

class Test:
    def __init__(self, x):
        self.x = x
    def __repr__(self):
        return "Test(x={})".format(repr(self.x))

x = Test("hi") # make a new object
print(x) # prints Test(x='hi')
# and the process it goes through is this chain:
assert (   str(x)           # converting to a string
        == Test.__str__(x)  # is the same as calling __str__ on the class
        == object.__str__(x)# which falls back to the method defined in superclass
        == repr(x)          # which calls repr(x) (according to those docs)
        == Test.__repr__(x) # which calls the __repr__ method on the class
        )

在大多数情况下,我们不太关心这么多细节 - 我们只关心当我们打印数据时,Python能够表现得合理。唯一真正重要的部分是默认的合理行为是在object内定义的!

type是实例化类的类

type是每种类型的类

所以,就像 int定义了1+3应该做什么或者 str定义了字符串的方法一样, type定义了特定于类型对象的行为。例如,调用类对象(如int("34"))将创建该类的新实例 - 创建新对象的这种行为是在 type.__call__方法中定义的。为了完整起见,我们有技术上的含义:

  • 我们可以说 "1 是一个 int",在代码中翻译为 isinstance(1,int) == True。同样地,我们可以说 "int 是一个 type",翻译为 isinstance(int, type) == True

  • 所有的类都被认为是 type 的实例。因此,isinstance(x, type) 对于所有的类都是真的。当我说类时,我指的是像 int, str, bool 或由 class 关键字 创建的变量。

一切皆为对象,甚至是类。
这意味着所有对象的标准行为也适用于类对象。因此,编写 str.join 将通过与其他对象相同的属性查找查找 join 方法,这是有意义的。(现在我只是访问属性而不是调用方法)
作为更具体的示例,我们可以看一下str.join,它是一种熟悉的数据类型(字符串)上任意选择的方法,以及 type.mro,它是 type 对象上的一种方法。
>>> x = "hello"
>>> str.join # unbound method of strings
<method 'join' of 'str' objects>
>>> x.join #bound method of x
<built-in method join of str object at 0x109bf23b0>
>>> hex(id(x)) # the memory address of x as seen above
'0x109bf23b0'

>>> type.mro #unbound method
<method 'mro' of 'type' objects>
>>> int.mro #mro method bound to int
<built-in method mro of type object at 0x106afeca0>
>>> hex(id(int)) # address of int object as seen above
'0x106afeca0'
>>> int.mro() #mro stands for Method Resolution Order, is related to __bases__
[<class 'int'>, <class 'object'>]

所以,在这一点上,我建议您回去重新阅读我在本答案开头所做的2个声明,希望您更自信地相信它。

P.S. 以同样的方式,您可以创建一个str的子类来创建具有不同行为的特殊字符串对象,您还可以创建type的子类来创建具有不同行为的特殊类。这被称为元类(类对象的类),使用元类的实际应用通常是抽象的。(意在言外)


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接