python元類--求別再說orm了

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,所以纔要更努力學習。加油加油!

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章