Working with collections¶
qdict
usage examples¶
Using dot notation to access dictionary elements¶
Qdict allows reading and writing dictionary elements as if they were attributes.
>>> from qtils import *
>>> d = qdict(hello = "world")
>>> d.hello
'world'
>>> d.answer = 42
>>> d['answer']
42
>>> d
{'hello': 'world', 'answer': 42}
Dealing with keyword argument dictionaries¶
Accessing
>>> from qtils import *
>>> def foo(**kwargs): pass
Working with complex data from API endpoints in JSON/YAML¶
Data from an API endpoint can be marshalled into a qdict
object tree, allowing convenient access
to objects within the response.
Let’s load up some memes from imgflip using and endpoint, and list what we got. (The response shown in the example below was altered for readibility reasons)
>>> import json
>>> from qtils import *
# Loading up memes from https://api.imgflip.com/get_memes
# with requests to api_response variable with something like:
# api_response = requests.get(https://api.imgflip.com/get_memes).text
# The api_response will look something like this:
>>> api_response = """
... {
... "success":true,
... "memes":[
... {
... "name":"Distracted Boyfriend",
... "url":"https://i.imgflip.com/1ur9b0.jpg"
... },
... {
... "name":"Drake Hotline Bling",
... "url":"https://i.imgflip.com/30b1gx.jpg"
... },
... {
... "name":"Two Buttons",
... "url":"https://i.imgflip.com/1g8my4.jpg"
... }
... ]
... }
... """
# Loading the data into a qdict tree.
>>> api_data = qdict.convert(json.loads(api_response))
# The data from the API can be used as any other python object
>>> if api_data.success:
... for meme in api_data.memes:
... print( "{:<30}{}".format( meme.name, meme.url ) )
Distracted Boyfriend https://i.imgflip.com/1ur9b0.jpg
Drake Hotline Bling https://i.imgflip.com/30b1gx.jpg
Two Buttons https://i.imgflip.com/1g8my4.jpg
Caveats¶
Because qdict
uses the attribute access to allow access to items, the access to actual
attributes becomes quite tricky and can result in unexpected behaviour.
This problem is not encoutered during normal use. However, if a new class is to be created to inhert from qdict, unexpected things will happen.
Consider the following example:
# We create a new class based on qdict and declare a class attribute
# called `a`, a public instance attribute called `b` and a private instance attribute
# called `_c`
>>> class MyDict(qdict):
... a = 'initial value'
... def __init__(self, a, b, c):
... # we attempt to overwrite the class attribute to make it an instance attribute
... self.a = a
... # we attempt to store data in self as an instance attribute
... self.b = b
... # we attempt to store data in a private instance variable
... self._c = c
...
>>> md = MyDict('foo', 'bar', 42)
# all 'attributes' became values in our dictionary
>>> md
{'a': 'foo', 'b': 'bar', '_c': 42}
# but when we try to read `a` it returns the class attribute
>>> md.a
'initial value'
# when we set 'a' as an instance attribute
>>> md.a = 'apple'
# but we have changed the value in the dictionary
>>> md
{'a': 'apple', 'b': 'bar', '_c': 42}
>>>
# but accessing it still returns the class attribute
>>> md.a
'initial value'
# reading 'a' with the dictionary API returns the value
>>> md['a']
'apple'
# `b` seems to be working, but it is NOT an attribute
>>> md.b
'bar'
# it is a value in the dictionary
>>> md['b']
'bar'
# when we try to set 'b'
>>> md.b = 'cat'
# we are changing the value in the dictionary
>>> md['b']
'cat'
# Same thing applies to `_c`, it seems to be working,
>>> md._c
42
# still, it is a value in the dictionary
>>> md['_c']
42
To handle a scenario like this we have the __qdict_allow_attributes__
attribute. When it’s set
to true, it will allow access to class attributes and private instance attributes (name starting with an
underscore). Please note that there is a performance penalyt to this, because every write will have to
check if the name exists either as a class attribute or within the instances __dict__
. This means two
dictionary lookups for every attribute write.
# We are using the same class as in the previous example.
>>> class MyDict(qdict):
... __qdict_allow_attributes__ = True
... a = None
... def __init__(self, a, b, c):
... self.a = a
... self.b = b
... self._c = c
...
>>> md = MyDict('foo', 'bar', 42)
# Only `b` ended up in the dictionary, because it's not private.
>>> md
{'b': 'bar'}
# reading `a` correctly returns value
>>> md.a
'foo'
# when we set 'a' as an instance attribute
>>> md.a = 'apple'
# we actually set an instance attribute
>>> md
{'b': 'bar'}
# accessing correctly returns the value we set
>>> md.a
'apple'
# reading 'a' with the dictionary API raises KeyError
>>> md['a']
Traceback (most recent call last):
...
KeyError: 'a'
# `b` seems to be working, but it's still NOT an attribute
>>> md.b
'bar'
# it is a value in the dictionary
>>> md['b']
'bar'
# `_c` works as expected
>>> md._c
42
# and it is also NOT in the dictionary
>>> md['_c']
Traceback (most recent call last):
...
KeyError: '_c'
qlist
usage examples¶
Marking objects as public in a module¶
When importing *
from a module, python will import all names which are not private (starting
with an _
underscore). This behaviour can be controlled by explicitly defining
what needs to be exported using the __all__ magic variable. This
variable must be a list of strings. Each string must match an existing object’s name in the module.
By convention, the __all__
keyword must sit in the beginning of the module. It is sometimes quite
cumbersome to keep it sync with the names of the classes and functions in the module that are
defined later. This is especially true during early development stages.
Wouldn’t it be nice if there was a way to simply mark objects which we want to export form a module?
The qlist class has a qlist.register()
method which can be used as a decorator on functions
and classes. It will add the object’s __name__
to itself, and return the object unchanged.
Consider the following example:
>>> from qtils import qlist
>>> __all__ = qlist()
>>> @__all__.register
... class Foo(object):
... pass
>>> # Some class we don't want in the convenience API
>>> class ANotPrivateButRarelyUsedClass(): pass
>>> @__all__.register
... def bar():
... pass
>>> print(__all__)
['Foo', 'bar']
ObjectDict
usage examples¶
The main function of ObjectDict is
Registering a function with ObjectDict.register()
decorator:
Registering a class with ObjectDict.register()
decorator:
>>> my_dir = ObjectDict()
>>> @my_dir.register
... def foo(self): pass
>>> my_dir['foo']
<function foo at ...>
>>> @my_dir.register
... class Bar(object): pass
>>> my_dir['Bar']
<class '__main__.Bar'>
QEnum
usage examples¶
An enhanced QEnum
that can return it’s possible values