DictPropery
The role of this class is to turn the method that returns a dictionary-like object in the class into a read_only controllable attribute (descriptor).
class Bottle(object):
@DictProperty('environ', 'bottle.request.query', read_only=True)
def query(self):
""" The :attr:`query_string` parsed into a :class:`FormsDict`. These
values are sometimes called "URL arguments" or "GET parameters", but
not to be confused with "URL wildcards" as they are provided by the
:class:`Router`. """
get = self.environ['bottle.get'] = FormsDict()
pairs = _parse_qsl(self.environ.get('QUERY_STRING', ''))
for key, value in pairs:
get[key] = value
return get
GET = query
class DictProperty(object):
""" Property that maps to a key in a local dict-like attribute. """
def __init__(self, attr, key=None, read_only=False): # attr, key, read_only接收装饰器的三个位置参数
self.attr, self.key, self.read_only = attr, key, read_only
def __call__(self, func): # func接收类方法request.query
functools.update_wrapper(self, func, updated=[])
self.getter, self.key = func, self.key or func.__name__
return self
def __get__(self, obj, cls): # 当访问request.GET的时候,就会调用该方法, obj为当前对象,cls为当前类
if obj is None: return self # obj为None说明被装饰的方法作为类变量来访问(Bottle.query),返回描述符自身
key, storage = self.key, getattr(obj, self.attr)
if key not in storage: storage[key] = self.getter(obj) # 如果bottle.request.query不在storage也就是不在request.environ中的时候,在request.environ中添加'bottle.request.query':request.query(self), 即reqeuest.query(self)的返回值:GET参数的字典.
return storage[key]
def __set__(self, obj, value): # 当request.GET被赋值时,调用__set__
if self.read_only: raise AttributeError("Read-Only property.") # raise read only
getattr(obj, self.attr)[self.key] = value # 在request.environ字典中添加一个'bottle.request.query':value。
def __delete__(self, obj): # 当该类方法被装饰的方法别删除是调用
if self.read_only: raise AttributeError("Read-Only property.")
del getattr(obj, self.attr)[self.key] # 从request.environ字典中删除bottle.request.query
The DictProperty class is a decorator as well as a descriptor. It can be seen that it is actually operating, the environ dictionary of its managed instance request, when the instance of this descriptor is accessed for the first time, the result of request.query(self) is put into the environ dictionary. When you access request.GET multiple times later, you can get the result directly in environ without repeating the calculation.
Bottle
class Bottle(object):
def __call__(self, environ, start_response): # 因为bottle__call__方法就是wsgi的协议函数,所以Bottle()实例就作为了wsgi服务器的appliction函数
""" Each instance of :class:'Bottle' is a WSGI application. """
return self.wsgi(environ, start_response)
def __enter__(self): # return
""" Use this application as default for all module-level shortcuts. """
default_app.push(self)
return self
def __exit__(self, exc_type, exc_value, traceback):
default_app.pop()
bottle implements the context manager protocol, so you can write like this, the endpoint '/hello' in the context manager will not be affected by the endpoint of the same name outside the with block. Of course these two endpoints are not on a Bottle instance either. . . .
@route('/hello')
def greet(name):
return HTTPResponse('hello2')
with Bottle() as b_app:
@b_app.route('/hello')
def hello():
return HTTPResponse('hello')
run(host='localhost', port='8888', debug=True, reloader=True)
MultiDict
This class implements a dictionary whose value is stored in a list. When storing repeated key values, the values will be stored in the form of a list. By default, the last value in the list is returned.
class MultiDict(DictMixin):
""" This dict stores multiple values per key, but behaves exactly like a
normal dict in that it returns only the newest value for any given key.
There are special methods available to access the full list of values.
"""
def __init__(self, *a, **k):
self.dict = dict((k, [v]) for (k, v) in dict(*a, **k).items())
def __delitem__(self, key):
del self.dict[key]
def __getitem__(self, key):
return self.dict[key][-1]
def __setitem__(self, key, value):
self.append(key, value)
def keys(self):
return self.dict.keys()
def get(self, key, default=None, index=-1, type=None):
try:
val = self.dict[key][index]
return type(val) if type else val
except Exception:
pass
return default
def append(self, key, value):
""" Add a new value to the list of values for this key. """
self.dict.setdefault(key, []).append(value)
def replace(self, key, value):
""" Replace the list of values with a single value. """
self.dict[key] = [value]
def getall(self, key):
""" Return a (possibly empty) list of values for a key. """
return self.dict.get(key) or []
#: Aliases for WTForms to mimic other multi-dict APIs (Django)
getone = get
getlist = getall
You can examine the behavior of the update and pop methods of the MultiDict generated object:
In [69]: dm = MultiDict()
In [70]: dm['a'] = 1
In [71]: dm['a'] = 2
In [72]: dm.getlist('a')
Out[72]: [1, 2]
In [73]: dm.update({'a':3})
In [74]: dm.getlist('a')
Out[74]: [1, 2, 3]
In [75]: dm.pop('a')
Out[75]: 3
In [76]: dm['a']
---------------------------------------------------------------------------
KeyError Traceback (most recent call last)
<ipython-input-76-54e5ceebc4de> in <module>()
----> 1 dm['a']
e:\py3env\lib\site-packages\bottle-0.13.dev0-py3.6.egg\bottle.py in __getitem__(self, key)
2093
2094 def __getitem__(self, key):
-> 2095 return self.dict[key][-1]
2096
2097 def __setitem__(self, key, value):
KeyError: 'a'
Why does MultiDict not rewrite the update method? When the update method is used to update the key'a', the value will be added to the value list by default. Because the update method is implemented by operating dict[key]. That is, the __getitem__, __setitem__, __delitem__ methods are used. As long as the three under methods are re-customized, update has the current behavior. You can locate an update implementation in the MutableMapping class.
The FormsDict class extends MultiDict, so has similar behavior.
The HeaderDict class also inherits MulticDict, but rewrites __setitem__, __getitem__ and other methods, the value is still stored in the form of a list but there can only be one item.
In [85]: hd = HeaderDict()
In [86]: hd['a']='b'
In [87]: hd['a']='c'
In [89]: hd.getall('a')
Out[89]: ['c']
The key of HeaderDict is processed by str.title when accessing, so the key is case-insensitive, and the '-' and '_' in the key are also processed equivalently, that is, request.headers.get( 'CONTENT-LENGTH') and request.headers.get('content_length') are equivalent.