Python type,object和metaclass

Python面向对象

  • Everything is an object

Python比起java是更加纯粹的面向对象语言。

对象(object)是什么?
  • 每个对象是一条规则,是一些实体的体现

    • 标识名(给两个名字可以区别他们是否是一个对象)
    • 值(如很多属性)
    • 类型(一定都有)
    • 超类或基类(不一定都有)

    对象名并不是对象的一部分

因此,变量、函数、类实例、类的类型等等都是对象,以类的类型举例:
对象(类)自身拥有创建对象(类实例)的能力,但是,它的本质仍然是一个对象,于是乎你可以对它做如下的操作:

  1. 你可以将它赋值给一个变量
  2. 你可以拷贝它
  3. 你可以为它增加属性
  4. 你可以将它作为函数参数进行传递

为了更深入理解对象是什么,我们来讲对象具象化: 假设地球上,所有东西都是从一些模板中演化而来的:

  • 实例对象:要地球定位一个对象,那么首先要知道其位置(标识名);其次你要知道这个对象是什么,是从那块模板出现的呢(类型);最后也要知道他具体的指标,这就是值(属性);因为是从模板出现的,他并没有父母或者孩子

  • 类的类型:假设地球上的东西都是从某些模板中演化成功的。那么,首先你要知道模板的位置(标识名);然后这个模板也许也是从一个模板出现的(超类或基类);最后一个模板可能是一部分用于生成每一个实例,一部分用于宏观调控(值)

也许,你觉得到此为止了,但是并没有,Python中模板也不是天生就有的,他的来源就是类型。不过,我们先停下来,仔细看看对象(object)的关系

对象的关系(is a)
  • 继承关系(关键字:subclass of, superclass of and superclass-subclass),可用__bases__查看
  • 类型实例关系(关键字:instance of, type of, type-instance and class-instance),表现为某个类型的实例化;使用它的__class__属性可以查看,或者使用type()函数查看
type and object

回到前面的问题,模板(Object)也不是天生就有的,而是演化而来。在Python中,模板的类型即为Type,模板也是实例的Type。

<type 'type'> <type 'object'> 是python系统中的两个基本对象

  1. Python有两种object,Type Or Non-type;后者的实例化无任何意义,例如数字2、字符串”123”等;检查方法是看其是否<type 'type'>的实例
  2. class,type这两术语完全一致( Python >= 2.3)
  3. Non-type是实例,但实例不只是Non-type,还可能是Type,type也是另一种type的实例;
>>> object
<type 'object'>
>>> type
<type 'type'>
>>> object.__class__
<type 'type'>
>>> object.__bases__
()
>>> type.__bases__
(<type 'object'>,)
>>> type.__class__ # type的类型是自己
<type 'type'>

由上,得出的结论为:

  1. object是父子关系的顶端,所有的数据类型的父类都是它
  2. type是类型实例关系的顶端,所有对象都是它的实例的

这样奇怪的就是

  • <type 'object'><type 'type'>的实例,而<type 'type'> 是object的实例
  • <type 'type'> 是自己的实例

第二个问题可以理解为Type是一切的起源,而Type自己本身也需要一个类型,所以Type是自己的实例。

为什么Type也是Object?

很多运行时体系(不一定是针对某种语言)都提供在运行时获取类型信息的功能,那么获取出来的是什么东西呢?获取出来的是一个描述类型信息的对象。那么所有描述类型信息的对象,都是“类型”这个类型的实例。这个type类型不是类型本身,而是用于描述类型的对象实例,类型本身是一种抽象的信息,type类的实例对象是它具体的信息载体。

Type与Object之间先后关系是什么?

同样地:在Java的对象模型中:

  • 所有的类都是Class类的实例,Object是类,那么Object也是Class类的一个实例。
  • 所有的类都最终继承自Object类,Class是类,那么Class也继承自Object。

这就像是先有鸡还是先有蛋的问题

解决

  1. 简短答案:“鸡・蛋”问题通常都是通过一种叫“自举”(bootstrap)的过程来解决的。

  2. 其实“鸡蛋问题”的根本矛盾就在于假定了“鸡”或“蛋”的其中一个要先进入“完全可用”的状态。而许多现实中被简化为“鸡蛋问题”的情况实际可以在“混沌”中把“鸡”和“蛋”都初始化好,而不存在先后问题;在它们初始化的过程中,两者都不处于“完全可用”状态,而完成初始化后它们就同时都进入了可用状态。

    打个比方,番茄炒蛋。并不是要先把番茄完全炒好,然后把鸡蛋完全炒好,然后把它们混起来;而是先炒番茄炒到半熟,再炒鸡蛋炒到半熟,然后把两个半熟的部分混在一起同时炒熟。

  3. 在问题中,第1个假设是错的:java.lang.Object是一个Java类,但并不是java.lang.Class的一个实例。后者只是一个用于描述Java类与接口的、用于支持反射操作的类型。这点上Java跟其它一些更纯粹的面向对象语言(例如Python和Ruby)不同。
    而第2个假设是对的:java.lang.Class是java.lang.Object的派生类,前者继承自后者。

  4. 虽然第1个假设不对,但“鸡蛋问题”仍然存在:在一个已经启动完毕、可以使用的Java对象系统里,必须要有一个java.lang.Class实例对应java.lang.Object这个类;而java.lang.Class是java.lang.Object的派生类,按“一般思维”前者应该要在后者完成初始化之后才可以初始化…

事实是:这些相互依赖的核心类型完全可以在“混沌”中一口气都初始化好,然后对象系统的状态才叫做完成了“bootstrap”,后面就可以按照Java对象系统的一般规则去运行。JVM、JavaScript、Python、Ruby等的运行时都有这样的bootstrap过程。

在“混沌”(boostrap过程)里

  1. JVM可以为对象系统中最重要的一些核心类型先分配好内存空间,让它们进入[已分配空间]但[尚未完全初始化]状态。此时这些对象虽然已经分配了空间,但因为状态还不完整所以尚不可使用。
  2. 然后,通过这些分配好的空间把这些核心类型之间的引用关系串好。到此为止所有动作都由JVM完成,尚未执行任何Java字节码。
  3. 然后这些核心类型就进入了[完全初始化]状态,对象系统就可以开始自我运行下去,也就是可以开始执行Java字节码来进一步完成Java系统的初始化了。

以上摘自知乎:先有Class还是先有Object?

Python中的对象

在python里,对象又可以分为三种

  • Type(metaclasses) : type对象及其子类
  • TypeObject(classes) : object对象及其子类
  • Instance : 通过实例化class object得来的对象

以下图作解释,其中:

  1. 实线为父子关系,箭头指向为基类
  2. 虚线为实例关系,箭头指向为类型

python Object

那么,可以清晰地看出三类的关系,需要注意的是,在第一列和第二列中可以有复杂的继承关系,并不一定像图中那么简单。

为什么同时需要type,object
  • 如果type和object只保留一个,那么一定是object。只有object 时,和大部分静态语言的类型架构类似,如java 。
  • 这样的架构将让python 失去一种很重要的动态特性–动态创建类型。本来,类(第二列的同学)在Python里面是一个对象(typeobject),对象是可以在运行时动态修改的,所以我们能在你定义一个类之后去修改他的行为或属性!
  • 拿掉第一列后,第二列变成了纯类型,写成怎样的,运行时行为就怎样,无法实现动态创建和修改类的特性。

为什么拿掉第一列后,就无法动态创建和修改类?
为了回答这个问题,我们要需要仔细聊聊第一类。

metaclass

动态语言和静态语言最大的不同,就是函数和类的定义,不是编译时定义的,而是运行时动态创建的。

  • type()函数可以查看一个类型或变量的类型
  • class的定义是运行时动态创建的,而创建class的方法就是使用type()函数。
  • type()函数既可以返回一个对象的类型,又可以创建出新的类型
type(类名, 父类的元组(针对继承的情况,可以为空),包含属性的字典(名称和值))

# e.g
>>> class MyShinyClass(object):
...       pass

>>> MyShinyClass = type('MyShinyClass', (), {}) # returns a class object
>>> print(MyShinyClass)
<class '__main__.MyShinyClass'>
>>> print(MyShinyClass()) # create an instance with the class
<__main__.MyShinyClass object at 0x8997cec>

__metaclass__属性

你可以在写一个类的时候为其添加__metaclass__属性。

class Foo(object):
    __metaclass__ = something…

如果你这么做了,Python就会用元类来创建类Foo。小心点,这里面有些技巧。你首先写下class Foo(object),但是类对象Foo还没有在内存中创建。Python会在类的定义中寻找__metaclass__属性,如果找到了,Python就会用它来创建类Foo,如果没有找到,就会用内建的type来创建这个类。把下面这段话反复读几次。当你写如下代码时 :

class Foo(Bar):
    pass

Python做了如下的操作:

  1. Foo中有__metaclass__这个属性吗?如果是,Python会在内存中通过__metaclass__创建一个名字为Foo的类对象)
  2. 如果Python没有找到__metaclass__,它会继续在Bar(父类)中寻找__metaclass__属性,并尝试做和前面同样的操作
  3. 如果Python在任何父类中都找不到__metaclass__,它就会在模块层次中去寻找__metaclass__,并尝试做同样的操作
  4. 如果还是找不到__metaclass__,Python就会用内置的type来创建这个类对象。

现在的问题就是,你可以在__metaclass__中放置些什么代码呢?
答案就是:可以创建一个类的东西。
那么什么可以用来创建一个类呢?
type,或者任何使用到type或者子类化type的东东都可以。

自定义元类

元类的主要目的就是为了当创建类时能够自动地改变类。通常,你会为API做这样的事情,你希望可以创建符合当前上下文的类。假想一个很傻的例子,你决定在你的模块里所有的类的属性都应该是大写形式。有好几种方法可以办到,但其中一种就是通过在模块级别设定metaclass。采用这种方法,这个模块中的所有类都会通过这个元类来创建,我们只需要告诉元类把所有的属性都改成大写形式就万事大吉了。

以下code来自stackoverflow

# 元类会自动将你通常传给‘type’的参数作为自己的参数传入
def upper_attr(future_class_name, future_class_parents, future_class_attr):
  """
    Return a class object, with the list of its attribute turned
    into uppercase.
  """

  # 将除以__开头的属性均换为大写
  uppercase_attr = {}
  for name, val in future_class_attr.items():
      if not name.startswith('__'):
          uppercase_attr[name.upper()] = val
      else:
          uppercase_attr[name] = val

  # type创建
  return type(future_class_name, future_class_parents, uppercase_attr)

__metaclass__ = upper_attr # 影响模块中的所有类

class Foo():
  # 我们也可以只在这里定义__metaclass__,这样就只会作用于这个类中
  bar = 'bip'

print(hasattr(Foo, 'bar'))
# Out: False
print(hasattr(Foo, 'BAR'))
# Out: True

f = Foo()
print(f.BAR)
# Out: 'bip'

我们来使用一个真正的类作为元类

class UpperAttrMetaclass(type):
    # __new__在__init__之前被调用,用来创建对象并返回
    # 而__init__只是用来将传入的参数初始化给对象
    # 你很少用到__new__,除非你希望能够控制对象的创建
    # 这里,创建的对象是类,我们希望能够自定义它,所以我们这里改写__new__
    # 如果你希望的话,你也可以在__init__中做些事情
    # 还有一些高级的用法会涉及到改写__call__特殊方法,但是我们这里不用

    def __new__(upperattr_metaclass, future_class_name,
                future_class_parents, future_class_attr):

        uppercase_attr = {}
        for name, val in future_class_attr.items():
            if not name.startswith('__'):
                uppercase_attr[name.upper()] = val
            else:
                uppercase_attr[name] = val

        return type(future_class_name, future_class_parents, uppercase_attr)

这并不是OOP方法,我们直接调用了type,而没有使用覆盖new

class UpperAttrMetaclass(type):

    def __new__(upperattr_metaclass, future_class_name,
                future_class_parents, future_class_attr):

        uppercase_attr = {}
        for name, val in future_class_attr.items():
            if not name.startswith('__'):
                uppercase_attr[name.upper()] = val
            else:
                uppercase_attr[name] = val

        # reuse the type.__new__ method
        # this is basic OOP, nothing magic in there
        return type.__new__(upperattr_metaclass, future_class_name,
                            future_class_parents, uppercase_attr)

第一个参数upperattr_metaclass并不奇怪。类方法的第一个参数总是表示当前的实例,就像在普通的类方法中的self参数一样。当然了,为了清晰起见,这里的名字我起的比较长。但是就像self一样,所有的参数都有它们的传统名称。因此,在真实的产品代码中一个元类应该是像这样的

class UpperAttrMetaclass(type):

    def __new__(cls, clsname, bases, dct):

        uppercase_attr = {}
        for name, val in dct.items():
            if not name.startswith('__'):
                uppercase_attr[name.upper()] = val
            else:
                uppercase_attr[name] = val

        return type.__new__(cls, clsname, bases, uppercase_attr)
        # 如果使用super
        # return super(UpperAttrMetaclass, cls).__new__(cls, clsname, bases, uppercase_attr)

metaclass与动态创建和修改类

由上述可见,metaclass用于

  1. 拦截类的创建
  2. 修改类
  3. 返回修改之后的类

由此可以回答之前所述的问题,“为什么拿掉第一列后,就无法动态创建和修改类?”因为拿掉第一列,类的定义就是死的,无法更改,自然无法动态创建和修改。

继承的问题:还记得那三类对象吗?多重继承会怎么样?如:

  • 如果一个类是否能继承自多个元类?如果可以,它的类型是什么?
  • 能否同时继承元类和第二类?如果可以,那么继承得到的是元类还是第二类?集成的父类可以是不同的类型吗?

此问题并不常见,而且很少人会这么用?请自行尝试。

最后的问题:究竟为什么要使用元类?

一般来说,你根本就用不上它:

“元类就是深度的魔法,99%的用户应该根本不必为此操心。如果你想搞清楚究竟是否需要用到元类,那么你就不需要它。那些实际用到元类的人都非常清楚地知道他们需要做什么,而且根本不需要解释为什么要用元类。” —— Python界的领袖 Tim Peters

我们来考虑什么时候能用它,metaclass用于修改类的创建。为什么不能用为对象增加方法或者是继承来做到而非要用这么麻烦的元类呢?
元类的主要用途是创建API。一个典型的例子是ORM。

我的理解是:
在这种情况下,类的定义较接近于一般的实现,而我们想得到的结果是相对而言具体的。这样我们可以动态修改类来得到一个想要的结果。那为什么不单独写一个方法呢?
运用封装所以使用简单,这也正是为什么使用元类的原因。

最后的具体化例子:

  1. 本来模板都归一个共同的type来管理,type来为他们创建,其工程也中规中矩,没有什么特别的地方。也正因为如此,所以感觉没有type的存在。但是,有时候具体到一个实例上来说,就发生了改变。
  2. 假如一个将军,他手下有一群士兵。而每个士兵来自于一个模板,模板里有什么呢?都是一些很正常的关于人素质的属性,如身高、体重、文化程度、健康程度等。但是,将军并不需要这些东西,他需要的是和打仗相关的东西,如一个士兵战斗力是多少、使用武器是否熟练、能否使用先进的工具等。所以如果将军自己通过原来的素质属性去转化,就比较麻烦。那么为什么不在成为士兵的那一刻自动就将原来的素质转化为战斗时的素质呢?
  3. 士兵因情况需要动态发生改变,所以需要新的type。之所以不重新写个方法返回新的战斗士兵对象,是因为将军需要的只是战斗素质,原来的性质根本不关心,因此改变创建时的情况使得整体对于将军而言更加简单。

参考资料

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值