什么是元类?它们有什么用途?
type(obj)
函数返回对象的类型。
type()
函数对于一个类来说,它的元类就是其 metaclass。
要使用元类:
class Foo(object):
__metaclass__ = MyMetaClass
type
是它自己的元类。一个类的类是元类--一个类的主体是传递给用于构造类的元类的参数。
在这里,你可以阅读有关如何使用元类来自定义类构造的内容。
type
实际上是一个 元类
—— 一种创建其他类的类。
大多数 元类
都是 type
的子类。这个 元类
将新建的类作为其第一个参数来接收,并提供了访问类对象及其详细信息的途径,如下所述:
>>> class MetaClass(type):
... def __init__(cls, name, bases, attrs):
... print ('class name: %s' %name )
... print ('Defining class %s' %cls)
... print('Bases %s: ' %bases)
... print('Attributes')
... for (name, value) in attrs.items():
... print ('%s :%r' %(name, value))
...
>>> class NewClass(object, metaclass=MetaClass):
... get_choch='dairy'
...
class name: NewClass
Bases <class 'object'>:
Defining class <class 'NewClass'>
get_choch :'dairy'
__module__ :'builtins'
__qualname__ :'NewClass'
注意:
请注意,该类在任何时候都没有被实例化;仅仅是创建该类的简单行为就会触发元类
的执行。
Python类本身就是对象,就像实例一样,它们属于它们的元类。
默认元类在您将类定义为以下内容时应用:
class foo:
...
定义元类时,需要继承type并覆盖以下魔法方法来插入自己的逻辑。
class somemeta(type):
__new__(mcs, name, bases, clsdict):
"""
mcs: is the base metaclass, in this case type.
name: name of the new class, as provided by the user.
bases: tuple of base classes
clsdict: a dictionary containing all methods and attributes defined on class
you must return a class object by invoking the __new__ constructor on the base metaclass.
ie:
return type.__call__(mcs, name, bases, clsdict).
in the following case:
class foo(baseclass):
__metaclass__ = somemeta
an_attr = 12
def bar(self):
...
@classmethod
def foo(cls):
...
arguments would be : ( somemeta, "foo", (baseclass, baseofbase,..., object), {"an_attr":12, "bar": <function>, "foo": <bound class method>}
you can modify any of these values before passing on to type
"""
return type.__call__(mcs, name, bases, clsdict)
def __init__(self, name, bases, clsdict):
"""
called after type has been created. unlike in standard classes, __init__ method cannot modify the instance (cls) - and should be used for class validaton.
"""
pass
def __prepare__():
"""
returns a dict or something that can be used as a namespace.
the type will then attach methods and attributes from class definition to it.
call order :
somemeta.__new__ -> type.__new__ -> type.__init__ -> somemeta.__init__
"""
return dict()
def mymethod(cls):
""" works like a classmethod, but for class objects. Also, my method will not be visible to instances of cls.
"""
pass
无论如何,这两个钩子是最常用的。元类编程非常强大,上述并不是所有元类编程用途的详尽列表。
type()函数可以返回一个对象的类型或创建一个新类型,
例如,我们可以使用type()函数创建一个Hi类,并且不需要使用这种方式:class Hi(object):
def func(self, name='mike'):
print('Hi, %s.' % name)
Hi = type('Hi', (object,), dict(hi=func))
h = Hi()
h.hi()
Hi, mike.
type(Hi)
type
type(h)
__main__.Hi
除了使用type()动态创建类之外,您还可以控制类的创建行为并使用元类。
根据Python对象模型,类是对象,因此类必须是另一个特定类的实例。 默认情况下,Python类是type类的实例。也就是说,type是大多数内置类和用户定义类的元类。
class ListMetaclass(type):
def __new__(cls, name, bases, attrs):
attrs['add'] = lambda self, value: self.append(value)
return type.__new__(cls, name, bases, attrs)
class CustomList(list, metaclass=ListMetaclass):
pass
lst = CustomList()
lst.add('custom_list_1')
lst.add('custom_list_2')
lst
['custom_list_1', 'custom_list_2']
当我们在元类中传递关键字参数时,魔法将会生效,它告诉Python解释器通过ListMetaclass来创建CustomList。在new()中,此时我们可以修改类定义,例如添加一个新的方法,然后返回修改后的定义。
除了已经发布的答案之外,我可以说一个元类(metaclass)
定义了一个类的行为方式。所以,您可以显式地设置您的元类。每当Python获取关键字 class
时,它就开始搜索元类
。如果没有找到-则使用默认的元类类型来创建该类的对象。使用__metaclass__
属性,您可以设置您的类的元类
:
class MyClass:
__metaclass__ = type
# write here other method
# write here one more method
print(MyClass.__metaclass__)
它将会生成如下输出:
class 'type'
当然,你可以创建自己的metaclass
来定义使用你的类创建的任何类的行为。
要做到这一点,必须继承您的默认metaclass
类型类,因为这是主要的metaclass
:
class MyMetaClass(type):
__metaclass__ = type
# you can write here any behaviour you want
class MyTestClass:
__metaclass__ = MyMetaClass
Obj = MyTestClass()
print(Obj.__metaclass__)
print(MyMetaClass.__metaclass__)
输出结果将会是:
class '__main__.MyMetaClass'
class 'type'
__init_subclass__(cls, **kwargs)
,以替换元类的许多常见用例。当定义的类的子类被创建时,它会被调用。请参阅Python文档。以下是它的另一个用法示例:
metaclass
来更改其实例(即类)的功能。class MetaMemberControl(type):
__slots__ = ()
@classmethod
def __prepare__(mcs, f_cls_name, f_cls_parents, # f_cls means: future class
meta_args=None, meta_options=None): # meta_args and meta_options is not necessarily needed, just so you know.
f_cls_attr = dict()
if not "do something or if you want to define your cool stuff of dict...":
return dict(make_your_special_dict=None)
else:
return f_cls_attr
def __new__(mcs, f_cls_name, f_cls_parents, f_cls_attr,
meta_args=None, meta_options=None):
original_getattr = f_cls_attr.get('__getattribute__')
original_setattr = f_cls_attr.get('__setattr__')
def init_getattr(self, item):
if not item.startswith('_'): # you can set break points at here
alias_name = '_' + item
if alias_name in f_cls_attr['__slots__']:
item = alias_name
if original_getattr is not None:
return original_getattr(self, item)
else:
return super(eval(f_cls_name), self).__getattribute__(item)
def init_setattr(self, key, value):
if not key.startswith('_') and ('_' + key) in f_cls_attr['__slots__']:
raise AttributeError(f"you can't modify private members:_{key}")
if original_setattr is not None:
original_setattr(self, key, value)
else:
super(eval(f_cls_name), self).__setattr__(key, value)
f_cls_attr['__getattribute__'] = init_getattr
f_cls_attr['__setattr__'] = init_setattr
cls = super().__new__(mcs, f_cls_name, f_cls_parents, f_cls_attr)
return cls
class Human(metaclass=MetaMemberControl):
__slots__ = ('_age', '_name')
def __init__(self, name, age):
self._name = name
self._age = age
def __getattribute__(self, item):
"""
is just for IDE recognize.
"""
return super().__getattribute__(item)
""" with MetaMemberControl then you don't have to write as following
@property
def name(self):
return self._name
@property
def age(self):
return self._age
"""
def test_demo():
human = Human('Carson', 27)
# human.age = 18 # you can't modify private members:_age <-- this is defined by yourself.
# human.k = 18 # 'Human' object has no attribute 'k' <-- system error.
age1 = human._age # It's OK, although the IDE will show some warnings. (Access to a protected member _age of a class)
age2 = human.age # It's OK! see below:
"""
if you do not define `__getattribute__` at the class of Human,
the IDE will show you: Unresolved attribute reference 'age' for class 'Human'
but it's ok on running since the MetaMemberControl will help you.
"""
if __name__ == '__main__':
test_demo()
元类
非常强大,它可以做很多事情(比如猴子魔法),但是要小心这些只能由你知道。
顶部答案是正确的。
但是读者可能会在这里搜索有关类似命名的内部类的答案。它们存在于流行的库中,例如Django
和WTForms
。
正如DavidW在本回答下面的评论中指出的那样,这些都是 特定于库 的功能,不能与具有类似名称的不相关的 Python语言特性混淆。
相反,这些是类字典中的命名空间。它们使用内部类构造,以增加可读性。
在这个示例特殊字段中, abstract
与Author模型的字段明显分开。
from django.db import models
class Author(models.Model):
name = models.CharField(max_length=50)
email = models.EmailField()
class Meta:
abstract = True
另一个例子来自于WTForms
的文档:
from wtforms.form import Form
from wtforms.csrf.session import SessionCSRF
from wtforms.fields import StringField
class MyBaseForm(Form):
class Meta:
csrf = True
csrf_class = SessionCSRF
name = StringField("name")
这个语法在 Python 编程语言中并没有特殊的处理。在这里,Meta
不是一个关键字,也不会触发元类行为。相反,像 Django
和 WTForms
这样的第三方库代码会读取某些类的构造函数和其他地方的此属性。
声明的存在会修改具有这些声明的类的行为。例如,WTForms
会读取 self.Meta.csrf
来确定表单是否需要一个 csrf
字段。
在面向对象编程中,元类是其实例为类的一种类。正如普通类定义了某些对象的行为一样,元类定义了某些类及其实例的行为。
元类这个术语简单地说就是用于创建类的东西。换句话说,它是类的类。元类用于创建类,就像对象是类的实例一样,类是元类的实例。在Python中,类也被认为是对象。
Meta
的嵌套类具有特殊含义。问题涉及到一个与之类似名称的不相关的Python语言特性。 - DavidW