Python语言之类与元类的深度挖掘 II
Vivian 2018-06-06 来源 : 阅读 610 评论 0

摘要:上一篇解决了Python语言通过调用类对象生成实例对象过程中可能遇到的命名空间相关的一些问题,这次我们向上回溯一层,看看类对象本身是如何产生的。希望大家学习Python语言有所帮助。

上一篇解决了Python语言通过调用类对象生成实例对象过程中可能遇到的命名空间相关的一些问题,这次我们向上回溯一层,看看类对象本身是如何产生的。希望大家学习Python语言有所帮助。

  我们知道 type() 方法可以查看一个对象的类型,或者说判断这个对象是由那个类产生的:

print(type(12))
 
print(type('python'))
 
class A:
 
pass
 
print(type(A))

   

  通过这段代码可以看出,类对象 A 是由type() 产生的,也就是说 type 也可以用来产生新的对象,而且产生的是类对象,因此它是所有类对象的类:

print(type.__doc__)
 
type(object_or_name, bases, dict)
 
type(object) -> the object's type
 
type(name, bases, dict) -> a new type

  class 定义类的语法实际上转化为 type(name, bases, dict),其中 name 参数为类的名字,bases 为继承父类的元组,dict 为类的属性和方法:

class A:
  pass
# 实际上等于
B = type('A', (), {})
 
print(A.__name__ == B.__name__)
True

  理论上说这就是元类的意义,但从实际的角度出发显然使用 class 语法更方便、合理,而元类的实际意义则是通过继承 type 类来构造一个新的元类,并进行特定的操作以产生具有特定行为的类对象。这样看来它的本质与普通的类对象没有差异,只不过继承的是 type 类。

  在生成实例时是通过调用 __init__ 方法进行初始化的,而实际上在此之前会先调用 __new__ 方法用于创建实例,再通过 __init__ 初始化,就好像 __new__ 负责声明变量,而 __init__ 负责对声明的变量进行初始化一样。这里有一个规则是 __new__(cls,) 的返回值必须是 cls 参数的实例,否则 __init__ 将不会触发,例如在 enum.Enum 的定义中,由于枚举类型是单例模式,因此在定义 __new__ 的时候没有返回其实例,也就不会进行初始化:

class Enum:
 
  def __new__(cls, value):
 
  print(cls, value)
 
  return value
 
  def __init__(self):
 
  print("Will not be called!")
 
  e = Enum(1)
 
   <class '__main__.Enum'> 1

  通常情况下自己定义 __new__ 需要通过调用父类的 __new__ 方法创建一个 cls 的实例,同样在定义元类的时候则是调用上面提到的 type 的用法(因为元类继承自 type):

class <d>Meta</d>Enum(type):
 
  def __new__(<d>meta</d>class, name, base, attrs):
 
  print("<d>Meta</d>class: {}\nName: {}\nParents: {}\nAttributes: {}".format(<d>meta</d>class, name, base, attrs))
 
  return super().__new__(<d>meta</d>class, name, base, attrs)
 
  class Enum(<d>meta</d>class=<d>Meta</d>Enum):
 
  # Python 2.7 中定义元类的方法是使用 __<d>meta</d>class__ 变量
 
  # [PEP 3115](https://www.python.org/dev/peps/pep-3115/)
 
  # 将 Python 3.0 以后语法改为 class Cls(<d>meta</d>class=<d>Meta</d>)
 
  test = 0
 
  <d>Meta</d>class:
 
  Name: Enum
 
  Parents: ()
 
  Attributes: {'__qualname__': 'Enum', '__module__': '__main__', 'test': 0}
 
  此时我们再来看 Enum 的类,已经不再是 type 而是其元类 <d>Meta</d>Enum:
 
  type(Enum)
 
  __main__.<d>Meta</d>Enum

  除了 __new__ 方法之外,PEP 3115 还定义了 __prepare__ 属性,用于设定初始化的命名空间(即 type 的第 3 个参数),还是以 enum.Enum 为例,我们需要限制枚举类型中属性名称不得重复使用,则可以通过元类限制类的行为:

  # 定义新的字典类,在赋值新的 dict[k] = v 时

  # 检查 k 是否重复

class _EnumDict(dict):
 
 def __init__(self):
 
 super().__init__()
 
 self.members = []
 
 def __setitem__(self, k, v):
 
 if k in self.members:
 
 raise TypeError("Attempted to reuse key: '{}'".format(k))
 
 else:
 
 self.members.append(k)
 
 super().__setitem__(k, v)
 
 class <d>Meta</d>Enum(type):
 
 @classmethod
 
 def __prepare__(<d>meta</d>class, cls, bases):
 
 return _EnumDict()
 
 def __new__(<d>meta</d>class, name, base, attrs):
 
 return super().__new__(<d>meta</d>class, name, base, attrs)
 
 class Enum(<d>meta</d>class=<d>Meta</d>Enum):
 
 pass
 
 class Color(Enum):
 
 try:
 
 red = 1
 
 red = 2
 
 except TypeError:# 这里没有使用 as err: 的原因是
 
 print("TypeError catched")
 
 TypeError catched

  Python 中一切皆为对象,所有的对象都是某一类的实例,或是某一元类的实例,type 是自己的元类也是自己的实例。     本文由职坐标整理并发布,了解更多内容,请关注职坐标编程语言Python频道!


本文由 @Vivian 发布于职坐标。未经许可,禁止转载。
喜欢 | 1 不喜欢 | 0
看完这篇文章有何感觉?已经有1人表态,100%的人喜欢 快给朋友分享吧~
评论(0)
后参与评论

您输入的评论内容中包含违禁敏感词

我知道了

助您圆梦职场 匹配合适岗位
验证码手机号,获得海同独家IT培训资料
选择就业方向:
人工智能物联网
大数据开发/分析
人工智能Python
Java全栈开发
WEB前端+H5

请输入正确的手机号码

请输入正确的验证码

获取验证码

您今天的短信下发次数太多了,明天再试试吧!

提交

我们会在第一时间安排职业规划师联系您!

您也可以联系我们的职业规划师咨询:

小职老师的微信号:z_zhizuobiao
小职老师的微信号:z_zhizuobiao

版权所有 职坐标-一站式IT培训就业服务领导者 沪ICP备13042190号-4
上海海同信息科技有限公司 Copyright ©2015 www.zhizuobiao.com,All Rights Reserved.
 沪公网安备 31011502005948号    

©2015 www.zhizuobiao.com All Rights Reserved

208小时内训课程