• dominate 标签层级嵌套写法分析


    dominate

    https://github.com/Knio/dominate

    domnate是一款强大的python领域的html生成库。

    Dominate is a Python library for creating and manipulating HTML documents using an elegant DOM API. It allows you to write HTML pages in pure Python very concisely, which eliminate the need to learn another template language, and to take advantage of the more powerful features of Python.

    其中dom的采用声明式的写法, 仅仅借用with语句。

    这种写法很是有趣。

    import dominate
    from dominate.tags import *
    
    doc = dominate.document(title='Dominate your HTML')
    
    with doc.head:
        link(rel='stylesheet', href='style.css')
        script(type='text/javascript', src='script.js')
    
    with doc:
        with div(id='header').add(ol()):
            for i in ['home', 'about', 'contact']:
                li(a(i.title(), href='/%s.html' % i))
    
        with div():
            attr(cls='body')
            p('Lorem ipsum..')
    
    print(doc)

    https://www.mianshigee.com/project/Knio-dominate

    DOM API support

    对于dom元素的上下级别的嵌套关系, 其实其API是支持的。

    分析其源码, 其提供了add API,专门用于添加儿子节点。

    https://github.com/Knio/dominate/blob/master/dominate/dom_tag.py#L192

      def add(self, *args):
        '''
        Add new child tags.
        '''
        for obj in args:
          if isinstance(obj, numbers.Number):
            # Convert to string so we fall into next if block
            obj = str(obj)
    
          if isinstance(obj, basestring):
            obj = escape(obj)
            self.children.append(obj)
    
          elif isinstance(obj, dom_tag):
            stack = dom_tag._with_contexts.get(_get_thread_context())
            if stack:
              stack[-1].used.add(obj)
            self.children.append(obj)
            obj.parent = self
            obj.setdocument(self.document)
    
          elif isinstance(obj, dict):
            for attr, value in obj.items():
              self.set_attribute(*dom_tag.clean_pair(attr, value))
    
          elif hasattr(obj, '__iter__'):
            for subobj in obj:
              self.add(subobj)
    
          else:  # wtf is it?
            raise ValueError('%r not a tag or string.' % obj)
    
        if len(args) == 1:
          return args[0]
    
        return args

    使用这种接口, 会写出很多意大利豆芽菜式样的语句, 例如:

    parent.add(td())

    parent.add(td())

    parent.add(td())

    实际上是浪费开发者精力。

    那么样例中with语句的层级写法是如何实现的呢?

    让我们先看下with语句的含义。

    with 目的

    https://www.geeksforgeeks.org/with-statement-in-python/

    例如访问文件,

    不使用with, 写法比较繁琐, 需要开发者主动关闭文件。

    # file handling
      
    # 1) without using with statement
    file = open('file_path', 'w')
    file.write('hello world !')
    file.close()
      
    # 2) without using with statement
    file = open('file_path', 'w')
    try:
        file.write('hello world')
    finally:
        file.close()

    使用with, 简洁明了,不用考虑关闭文件,因为 file本身提供了关闭功能,但是要和with配合使用。

    # using with statement
    with open('file_path', 'w') as file:
        file.write('hello world !')

    with底层实现

    https://www.geeksforgeeks.org/with-statement-in-python/

    with之所以能做一些善后工作, 是因为 with 后面跟随的对象, 本身实现了 两个元方法。

    with语句内部的子语句,可以人为运行在with构造的环境中, 内部语句只管运行, 不用管环境的清理工作。

    To use with statement in user defined objects you only need to add the methods __enter__() and __exit__() in the object methods. Consider the following example for further clarification.

    # a simple file writer object
      
    class MessageWriter(object):
        def __init__(self, file_name):
            self.file_name = file_name
          
        def __enter__(self):
            self.file = open(self.file_name, 'w')
            return self.file
      
        def __exit__(self):
            self.file.close()
      
    # using with statement with MessageWriter
      
    with MessageWriter('my_file.txt') as xfile:
        xfile.write('hello world')

    支持with的内置对象有 lock socket subprocess telnets

    The with statement is popularly used with file streams, as shown above and with Locks, sockets, subprocesses and telnets etc.

    运行逻辑

    https://www.geeksforgeeks.org/context-manager-in-python/?ref=lbp

    # Python program creating a
    # context manager
    
    class ContextManager():
        def __init__(self):
            print('init method called')
            
        def __enter__(self):
            print('enter method called')
            return self
        
        def __exit__(self, exc_type, exc_value, exc_traceback):
            print('exit method called')
    
    
    with ContextManager() as manager:
        print('with statement block')

    有趣的执行过程。

    In this case a ContextManager object is created. This is assigned to the variable after the as keyword i.e manager. On running the above program, the following get executed in sequence:

    • __init__()
    • __enter__()
    • statement body (code inside the with block)
    • __exit__()[the parameters in this method are used to manage exceptions]

    文件管理器实现代码

    # Python program showing
    # file management using
    # context manager
    
    class FileManager():
        def __init__(self, filename, mode):
            self.filename = filename
            self.mode = mode
            self.file = None
            
        def __enter__(self):
            self.file = open(self.filename, self.mode)
            return self.file
        
        def __exit__(self, exc_type, exc_value, exc_traceback):
            self.file.close()
    
    # loading a file
    with FileManager('test.txt', 'w') as f:
        f.write('Test')
    
    print(f.closed)

    数据库连接实现代码

    # Python program shows the
    # connection management
    # for MongoDB
    
    from pymongo import MongoClient
    
    class MongoDBConnectionManager():
        def __init__(self, hostname, port):
            self.hostname = hostname
            self.port = port
            self.connection = None
    
        def __enter__(self):
            self.connection = MongoClient(self.hostname, self.port)
            return self
    
        def __exit__(self, exc_type, exc_value, exc_traceback):
            self.connection.close()
    
    # connecting with a localhost
    with MongoDBConnectionManager('localhost', '27017') as mongo:
        collection = mongo.connection.SampleDb.test
        data = collection.find({'_id': 1})
        print(data.get('name'))

    contextmanager

    https://www.geeksforgeeks.org/with-statement-in-python/

    from contextlib import contextmanager
    
    class MessageWriter(object):
        def __init__(self, filename):
            self.file_name = filename
    
        @contextmanager
        def open_file(self):
            try:
                file = open(self.file_name, 'w')
                yield file
            finally:
                file.close()
    
    # usage
    message_writer = MessageWriter('hello.txt')
    with message_writer.open_file() as my_file:
        my_file.write('hello world')

    https://stackoverflow.com/questions/3012488/what-is-the-python-with-statement-designed-for

    from contextlib import contextmanager
    import os
    
    @contextmanager
    def working_directory(path):
        current_dir = os.getcwd()
        os.chdir(path)
        try:
            yield
        finally:
            os.chdir(current_dir)
    
    with working_directory("data/stuff"):
        # do something within data/stuff
    # here I am back again in the original working directory

    https://www.bogotobogo.com/python/Multithread/python_multithreading_Using_Locks_with_statement_Context_Manager.php

    lock对象本身也实现了 __enter__ 和 __exit__ 方法, 是可以直接配合with使用的,不用开发者考虑锁的获取和释放。

    with的内部的语句块,相当于纯正的 临界代码。

    import threading
    import logging
    
    logging.basicConfig(level=logging.DEBUG,
                        format='(%(threadName)-10s) %(message)s',)
    
    def worker_with(lock):
        with lock:
            logging.debug('Lock acquired via with')
            
    def worker_not_with(lock):
        lock.acquire()
        try:
            logging.debug('Lock acquired directly')
        finally:
            lock.release()
    
    if __name__ == '__main__':
        lock = threading.Lock()
        w = threading.Thread(target=worker_with, args=(lock,))
        nw = threading.Thread(target=worker_not_with, args=(lock,))
    
        w.start()
        nw.start()

    dominate and with

    对于dominate中的每个dom元素, 都拥有 __enter__ 和 __exit__ 方法

    enter方法负责 在线程环境的 stack 顶部建立一个 frame对象, 用于存储 儿子的 dom。

    exit方法负责 将线程环境的 stack 顶部的 frame中的 dom对象去除, 添加到本dom的儿子容器中。

    https://github.com/Knio/dominate/blob/master/dominate/dom_tag.py#L123

      def __enter__(self):
        stack = dom_tag._with_contexts[_get_thread_context()]
        stack.append(dom_tag.frame(self, [], set()))
        return self
    
    
      def __exit__(self, type, value, traceback):
        thread_id = _get_thread_context()
        stack = dom_tag._with_contexts[thread_id]
        frame = stack.pop()
        for item in frame.items:
          if item in frame.used: continue
          self.add(item)
        if not stack:
          del dom_tag._with_contexts[thread_id]

    但是儿子dom是怎么挂载上去的?

    https://github.com/Knio/dominate/blob/master/dominate/dom_tag.py#L108

    我们发现在 __init__ 函数中, 最后有一句 _add_to_ctx 调用,

    这个函数 就是把儿子dom节点,添加到栈顶部 frame 的items中。

    设计还是比较精妙。

      def __init__(self, *args, **kwargs):
        '''
        Creates a new tag. Child tags should be passed as arguments and attributes
        should be passed as keyword arguments.
        There is a non-rendering attribute which controls how the tag renders:
        * `__inline` - Boolean value. If True renders all children tags on the same
                       line.
        '''
    
        self.attributes = {}
        self.children   = []
        self.parent     = None
        self.document   = None
    
        # Does not insert newlines on all children if True (recursive attribute)
        self.is_inline = kwargs.pop('__inline', self.is_inline)
        self.is_pretty = kwargs.pop('__pretty', self.is_pretty)
    
        #Add child elements
        if args:
          self.add(*args)
    
        for attr, value in kwargs.items():
          self.set_attribute(*type(self).clean_pair(attr, value))
    
        self._ctx = None
        self._add_to_ctx()
    
    
      # context manager
      frame = namedtuple('frame', ['tag', 'items', 'used'])
      # stack of frames
      _with_contexts = defaultdict(list)
    
      def _add_to_ctx(self):
        stack = dom_tag._with_contexts.get(_get_thread_context())
        if stack:
          self._ctx = stack[-1]
          stack[-1].items.append(self)

    dominate and style

    https://stackoverflow.com/questions/53992303/add-style-element-to-an-html-using-python-dominate-library

    dom对象的生成, 可以带有 atrribute, 实现style属性的赋值, 给dom添加样式。

    也可以通过在head中, 添加style对象。

    import dominate
    from dominate.tags import link, script, style
    
    doc = dominate.document(title='Dominate your HTML')
    
    with doc.head:
        link(rel='stylesheet', href='style.css')
        script(type='text/javascript', src='script.js')
        style("""\
             body {
                 background-color: #F9F8F1;
                 color: #2C232A;
                 font-family: sans-serif;
                 font-size: 2.6em;
                 margin: 3em 1em;
             }
    
         """)
    
    print(doc.render(pretty=True))
  • 相关阅读:
    ajax原理和XmlHttpRequest对象
    在vue项目中 如何定义全局变量 全局函数
    杂乱知识 -- 记录工作或学习中遇到的一些点
    JavaScript中的数组遍历forEach()与map()方法以及兼容写法
    cookie的存取删
    微信小程序中公用内容
    mysql 多实例
    yum 安装和卸载
    rpm 安装卸载
    git blame
  • 原文地址:https://www.cnblogs.com/lightsong/p/16209176.html
Copyright © 2020-2023  润新知