python元类,
工作已经三年多了,python开发也进行了3年之久,也从一个小小开发者,转换成面试官(依然觉得自己很low,还需要继续努力学习)。 但每次问到别人python metaclass时,别人的回答几乎没有人让我满意的,无外乎千篇一律的 metaclass 多用在orm上。
我去,元类和orm有什么关系啊,就是网上抄来抄去,也许当年有一位牛人做了如此的解读后,让无数的不假思索者找到救命稻草一般。
python 元类只要你想用,你就可以用它。 我们都知道元类是生成类的类,一个类指定了metaclass,就意味着在定义class时,解释器运行此处后 即可检查此处的metaclass,通过你的metaclass来生成你的类对象,你的metaclass里面定义的
__new__函数,此时将作用到你的类中。 那么你可以做很多事情,就像是给你自己的类装上了装饰器一般,预装属性,预装函数。
我们自定义metaclass 即自定义了类的初始化过程。 比如我们想自定义一个 restful API, 或者我们想通过一个接口根据用户传递的不同参数响应不同的方法。
我们当然可以,通过传递的参数, 然后通过python系统函数 getattr 来完成此类工作。
import APIClass
from django.http import JsonResponse
def APIviews(request):
request_data = request.POST.get("data", {})
api_func = request_data.get("api_func", None)
func = getattr(APIClass, api_func)
return func(request)
class APIClass:
def get_comment_list(self, request):
return JsonResponse({"comment_list":[]})
def get_comment_detail(self, request):
return JsonResponse({"comment_detail":[]})
但如果我们想做很多复杂的操作呢,比如restful api,通常的框架某一个handler下的 create,对应post请求,read 对应get请求,如若我们更多的需求呢,难道我们依然在read函数里面根据参数的不同做if else 判断吗?
这时候直接一个getattr无法做到了,我们可以使用元类来实现它。 我们直接上maas的api源码,并做一些修改,这里给大家看看大佬们其实也是这么实现的,并没有大家想想的那么高大上,那么高不可攀。
class OperationsHandlerType(type):
"""除了curd 外的 我们使用属性将 新类的方法存储在定义的属性里,这样所有继承此metaclass的类
都会将自己的方法与相关属性关联起来
"""
callmap = { 'GET': 'read', 'POST': 'create',
'PUT': 'update', 'DELETE': 'delete' }
def __new__(metaclass, name, bases, namespace):
cls = super(type, metaclass).__new__(
metaclass, name, bases, namespace)
# Create a signature:function mapping for CRUD operations.
crud = {
(http_method, None): getattr(cls, method)
for http_method, method in list(OperationsHandlerType.callmap.items())
if getattr(cls, method, None) is not None
}
# Create a signature:function mapping for non-CRUD operations.
operations = {
attribute.export: attribute
for attribute in list(vars(cls).values())
if getattr(attribute, "export", None) is not None
}
# Create the exports mapping.
exports = {}
for base in bases:
for key, value in vars(base).items():
export = getattr(value, "export", None)
if export is not None:
new_func = getattr(cls, key, None)
if new_func is not None:
exports[export] = new_func
# Export custom operations.
exports.update(operations)
methods_exported = {method for http_method, method in exports}
for http_method, method in OperationsResource.crudmap.items():
if method in methods_exported:
raise AssertionError(
"A CRUD operation (%s/%s) has been registered as an "
"operation on %s." % (http_method, method, name))
# Export CRUD methods.
exports.update(crud)
# Update the class.
cls.exports = exports
cls.allowed_methods = frozenset(
http_method for http_method, name in exports)
# Flags used later.
has_fields = cls.fields is not BaseHandler.fields
has_resource_uri = hasattr(cls, "resource_uri")
is_internal_only = cls.__module__ in {__name__, "metadataserver.api"}
if has_fields and has_resource_uri:
_, uri_params, *_ = cls.resource_uri()
missing = set(uri_params).difference(cls.fields)
if len(missing) != 0:
raise OperationsHandlerMisconfigured(
"{handler.__module__}.{handler.__name__} does not render "
"all fields required to construct a self-referential URI. "
"Fields missing: {missing}.".format(
handler=cls, missing=" ".join(sorted(missing))))
if (not has_resource_uri and not is_internal_only and
not cls.is_anonymous):
log.warn(
"{handler.__module__}.{handler.__name__} does not have "
"`resource_uri`. This means it may be omitted from generated "
"documentation. Please investigate.", handler=cls)
return cls
我们通过设置exports属性,将 cls 定义的函数,在初始化时,将函数引用存储在exports中,这样我们就可以使用 exports 来操作响应的对象方法了。
我们定义装饰器
def operation(idempotent, exported_as=None):
"""Decorator to make a method available on the API.
:param idempotent: If this operation is idempotent. Idempotent operations
are made available via HTTP GET, non-idempotent operations via HTTP
POST.
:param exported_as: Optional operation name; defaults to the name of the
exported method.
"""
method = "GET" if idempotent else "POST"
def _decorator(func):
if exported_as is None:
func.export = method, func.__name__
else:
func.export = method, exported_as
return func
return _decorator
这装饰器的作用就是在定义 handler类时,api接口的函数(非 create read update delete)进行装饰,这样就给函数 赋予了 export,在__new__方法中即可进行。
在设定一个 mixin类,此类正是像我们一开始简单的设计api那里一样,不过现在更灵活,更强大。
class OperationsHandlerMixin:
"""Handler mixin for operations dispatch.
This enabled dispatch to custom functions that piggyback on HTTP methods
that ordinarily, in Piston, are used for CRUD operations.
This must be used in cooperation with :class:`OperationsResource` and
:class:`OperationsHandlerType`.
"""
# CSRF protection is on by default. Only pure 0-legged oauth API requests
# don't go through the CSRF machinery (see
# middleware.CSRFHelperMiddleware).
# This is a field used by piston to decide whether or not CSRF protection
# should be performed.
csrf_exempt = False
# Populated by OperationsHandlerType.
exports = None
# Specified by subclasses.
anonymous = None
def dispatch(self, request, *args, **kwargs):
op = request.GET.get("op") or request.POST.get("op")
signature = request.method.upper(), op
function = self.exports.get(signature)
if function is None:
raise MAASAPIBadRequest(
"Unrecognised signature: method=%s op=%s" % signature)
else:
return function(self, request, *args, **kwargs)
那么现在就可以定义我们的handler类的
class OperationsHandler(
OperationsHandlerMixin,
metaclass=OperationsHandlerType):
def create(self, request):
pass
@operation(idempotent=False)
def list_all_data(self, request):
pass
以上我们就可以实现一个自定义的restful, 其实元类没那么可怕,同时还很可爱。每当我们查看django源码,piston源码,maas源码,cloud-init、openstack等优秀框架时,总被别人优秀设计叹为观止,但如果我们认真阅读,认真学习,深度思考,我们也能从中学到一些实战的经验,可以用在我们日后的工作中。
从小小的知识点开始,深度才有远度,希望大家今后在面试、在工作中,别条件反射一般说到metaclass 便是orm,他两完全没有任何关系,你可以从你自己设计向面试官表达你对知识理解,那样的你一定是做过实现的,做过实战的,一定比那些网上看看教程、看看别人嚼碎的知识要更吸引人的多。
我很low,所以才要更努力学习。加油加油!