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