Stay Hungry,Stay Foolish!

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))

 

posted @ 2022-04-29 23:58  lightsong  阅读(281)  评论(0编辑  收藏  举报
Life Is Short, We Need Ship To Travel