详谈python中的元类(深入浅出之类的类)

元类
关于元类

实例对象是由类创建的,但是类是什么创建的呢?类是元类创建的。

在大多数编程语言中,类就是一组用来描述如何生成一个对象的代码段。
但是。python中的类,远不止如此。类同样也是一种对象。只要使用关键字class。python解释器在执行的时候就会创建一个对象。

在下面的代码中,将在内存中创建一个对象,名字为ObjectTest.这个对象(类)自身拥有创建对象(类的实例)的能力。

class ObjectTest(object):
    pass
    
print(ObjectTest)  # <class '__main__.ObjectTest'>

这就是为什么它是一个类的原因。但是它的本质是一个对象。
对象的话,就可以对它做如下操作了。

  1. 将它赋值给一个变量。
  2. 拷贝它。
  3. 给它增加属性。
  4. 将它作为函数参数进行传递。
# 将它作为函数参数传递。
def echo(o):
    print(o)

echo(ObjectTest)  # <class '__main__.ObjectTest'>

# 为它添加属性, hasattr()进行判断类是否拥有该属性。
print(hasattr(ObjectTest, "new_attr"))  # False
ObjectTest.new_attr = 'good'
print(hasattr(ObjectTest, 'new_attr'))  # True

# 将它赋值给一个变量。

test = ObjectTest
print(test)  # <class '__main__.ObjectTest'>

可以动态的创建类
因为类也是对象,你可以在运行时动态的创建它们,

def dynamic_test(name):
    if name == 'A':
        class A(object):
            pass

        return A
    else:
        class B(object):
            pass

        return B


Myclass = dynamic_test('A')

print(Myclass)  # <class '__main__.dynamic_test.<locals>.A'>
print(Myclass())  # <__main__.dynamic_test.<locals>.A object at 0x000001D2A74C0370>

内置函数type()
print(type(123))
print(type("123"))
print(type(ObjectTest))
print(type(ObjectTest()))

输出:
<class ‘int’>
<class ‘str’>
<class ‘type’>
<class ‘_main_.ObjectTest’>

在这里type也能动态的创建类。type可以接受一个类的描述作为参数,然后返回一个类。
(同一个函数拥有两种完全不同的用法是很逗的事情,但是在python中是为了保持向后兼容性。)
type也能这样工作:
type(类名,父类的元组(针对基继承的情况,可以为空), 包含属性的字典()名称和值)。

使用type创建类
# 类的创建方式
# class MyClassTest(object):
#     pass

# 使用type创建
MyClassTest = type('MyClassTest', (), {})
print(MyClassTest)
print(MyClassTest())
# 在这里我们使用MyClassTest作为类名,并且也可以把它当做一个变量作为类的引用,类和变量是不同的。

# class C(object):
#     instance_name = True

C = type('C', (), {'instance_name': True})
print(C.instance_name)
c = C()
print(c.instance_name)

# 这就是普通类的使用方式,这和普通类没什么区别的。


# class D(C):
#     pass

# 继承的写法
D = type("D", (C,), {})
print(D.instance_name)


# 类增加方法,定义一个有着恰当签名的函数并将其作为属性赋值就行了。

def test_a(self):
    print(self.a)


E = type("E", (C,), {'a': 'a'})
print(hasattr(C, 'a'))
print(hasattr(E, 'a'))
print("sssssssssssss")
print(hasattr(E, 'instance_name'))

这就充分体现了在python中,类也是对象,你可以动态的创建类。这就是当你在使用关键字class时python在幕后做的事情,而且还通过元类来实现的。

关键点

元类就是用来创建类的。你创建类就是为了创建类的实例对象,但是在Python中类也是对象,这就像是套娃了,元类就是用来创建这些类(对象)的。因为元类就是类的类。
type可以这样做:
A = type(‘A’, (), {})
这是因为函数type()实际上就是一个元类,type就是python在背后用来创建所有类的元类。现在你知道为什么type()全部采用小写的形式而不是Type?我查阅了一些资料,都说这是为了和str保持一致性,str是用来创建字符串对象的类,而int是用来创建整数对象的类。type就是创建类对象的类。你可以使用__class__属性来查看这个。python中所有一切都是对象(这就是人们常说的python中一切皆对象。),这包括整数,字符串,函数以及类,这一切的一切,它们全都是对象。而且都是从一个类创建而来。

而且对于任何一个__class__的__class__属性就是<type ‘type’>例:

print(a.__class__.__class__)
print(b.__class__.__class__)
print(c.__class__.__class__)
print(D.__class__.__class__)
d = D()
print(d.__class__.__class__)

结果都是:<type ‘type’>

因为元类就是创建类这种对象的类。可以把元类称之为“类工厂”(并非工厂类),type就是python的内建元类,你也可以自己创建自己的元类。

创建元类
class A:
    pass


class B(A):
    pass

如果A中有_metaclass__这个属性,python会在内存中通过_metaclass__创建一个名字为A的类对象(类对象,记住了),如果python没有找到__metaclass__,它将会继续在A(父类)中寻找中寻找__metaclass__ 属性,并尝试做和前面同样的操作。如果Python在任何父类中都找不到__metaclass__,它就会在模块层次中去寻找 metaclass,并尝试做同样的操作。如果还是找不到__metaclass__,Python就会用内置的type来创建这个类对象。
你可以在__metaclass__中放置些什么代码呢?答案就是:可以创建一个类的东西。那么什么可以用来创建一个类呢?type,或者任何使用到type或者子类化type的东东都可以。

自定义元类

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

# 元类会自动将你通常传给type的参数作为自己的参数传入
def new_attr(new_class_name, new_class_parents, new_class_attr):
    """
    返回一个类对象,将属性都转换为大写形式
    :param new_class_name:
    :param new_class_parents:
    :param new_class_attr:
    :return:
    """
    # 筛选出所有不以__开头的属性。
    attrs = ((name, value) for name, value in new_class_attr.items() if not name.startswith('__'))
    # 将筛选出来的转换为大写形式
    upper_attr = dict((name.upper(), value) for name, value in attrs)
    # type来做类对象的创建
    return type(new_class_name, new_class_parents, upper_attr)


class TestUpper(object, metaclass=new_attr):
    # 也可以只在这里面定义__metaclass__,这样就只会作用于这个类中。

    attr_lower = 'low b'


print('s' * 60)
print(hasattr(TestUpper, 'attr_lower'))
print(hasattr(TestUpper, 'ATTR_LOWER'))

用一个真正的class来当做元类,type实际上是一个类,就像str,int一样。所以你可以从type继承:

class NewUpperClass(type):
    # __new__ 是在__init__之前被调用的特殊方法
    # __new__是用来创建对象并返回值的方法
    # 而__init__只是用来将传入的参数初始化给对象
    # 你很少用到__new__,除非你希望能够控制对象的创建
    # 这里,创建的对象是类,我们希望能够自定义它,所以我们这里改写__new__
    # 如果你希望的话,你也可以在__init__中做些事情
    # 还有一些高级的用法会涉及到改写__call__特殊方法,但是我们这里不用
    def __new__(upper_metaclass, new_class_name, new_class_parents, new_class_attr):
        attr = ((name, value) for name, value in new_class_attr.items() if not name.startswith('__'))
        upper_attr = dict((name.upper(), value) for name, value in attr)
        # 复用type.__new__方法
        # 这就是基本的OOP编程,没什么魔法
        return type(new_class_name, new_class_parents, upper_attr)


class Test(object, metaclass=NewUpperClass):
    bar = 'test'


print(hasattr(Test, 'bar'))  # False
print(hasattr(Test, 'BAR'))  # True

上面的那种写法不够OOP,而且直接调用了type方法,没有改写父类的__new__方法,改进一下下:

# OOP写法
class UpperAttrMetaclass(type):
    def __new__(upperattr_metaclass, future_class_name, future_class_parents, future_class_attr):
        attrs = ((name, value) for name, value in future_class_attr.items() if not name.startswith('__'))
        uppercase_attr = dict((name.upper(), value) for name, value in attrs)

        # 复用type.__new__方法
        # 这就是基本的OOP编程,没什么魔法
        return type.__new__(upperattr_metaclass, future_class_name, future_class_parents, uppercase_attr)


class Test(object, metaclass=UpperAttrMetaclass):
    abc = 'asd'


print(hasattr(Test, 'abc'))
print(hasattr(Test, 'ABC'))

你可能已经注意到了有个额外的参数upper_metaclass,类方法的第一个参数总是表示当前的实例,就像在普 通的类方法中的self参数一样。self一样,所有的参数都有它们的传统名称。因此,真实的元类应该是像这样的:

class UpperAttrMetaclass(type):
    def __new__(cls, name, bases, dct):
        attrs = ((name, value) for name, value in dct.items() if not name.startswith('__'))
        uppercase_attr = dict((name.upper(), value) for name, value in attrs)
        return type.__new__(cls, name, bases, uppercase_attr)

如果使用super方法的话,我们还可以使它变得更清晰一些,这会缓解继承(是的,你可以拥有元类,从元类继承,从type继承)

class UpperAttrMetaclass(type):
    def __new__(cls, name, bases, dct):
        attrs = ((name, value) for name, value in dct.items() if not name.startswith('__'))
        uppercase_attr = dict((name.upper(), value) for name, value in attrs)
        return super(UpperAttrMetaclass, cls).__new__(cls, name, bases, uppercase_attr)

通常会使用元类去做一些晦涩的事情,依赖于自省,控制继承等等。确实,用元类来搞些“黑暗魔法” 是特别有用的,因而会搞出些复杂的东西来。但就元类本身而言,它们其实是很简单的:

  • 拦截类的创建
  • 修改类
  • 返回修改之后的类
为什么使用metaclass类而不是函数

由于__metaclass__可以接受任何可调用的对象,那为何还要使用类呢,因为很显然使用类会更加复杂啊?

  • 意图会更加清晰。当你读到UpperAttrMetaclass(type)时,你知道接下来要发生什么。
  • 你可以使用OOP编程。元类可以从元类中继承而来,改写父类的方法。元类甚至还可以使用元类
  • 可以把代码组织的更好。当你使用元类的时候肯定不会是像我上面举的这种简单场景,通常都是针对比较复杂的问题。将多个方法归总到一个类中会很有帮助,也会使得代码更容易阅读。
  • 你可以使用__new__, __init__以及__call__这样的特殊方法。它们能帮你处理不同的任务。就算通常你可以把所有的东西都在__new__里处理掉,有些人还是觉得用__init__更舒服些。
为什么使用元类

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

元类的主要用途是创建API。一个典型的例子是Django ORM。它允许你像这样定义:

class Person(models.Model):
    name = models.CharField(max_length=30)
    age = models.IntegerField()

guy  = Person(name='bob', age='35')
print guy.age

这并不会返回一个IntegerField对象,而是会返回一个int,甚至可以直接从数据库中取出数据。这是有可能的,因为 models.Model定义了__metaclass__, 并且使用了一些魔法能够将你刚刚定义的简单的Person类转变成对数据库的一个复杂hook。Django框架将这些看起来很复杂的东西通过暴露出一个 简单的使用元类的API将其化简,通过这个API重新创建代码,在背后完成真正的工作。

Python中的一切都是对象,它们要么是类的实例,要么是元类的实例,除了type。type实际上是它自己的元类,在纯Python环境中这可 不是你能够做到的,这是通过在实现层面耍一些小手段做到的。其次,元类是很复杂的。对于非常简单的类,你可能不希望通过使用元类来对类做修改。你可以通过 其他两种技术来修改类:

  • Monkey patching

  • class decorators

当你需要动态修改类时,99%的时间里你最好使用上面这两种技术。当然了,其实在99%的时间里你根本就不需要动态修改类

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值