mirror of
https://github.com/kennethreitz/python-guide.git
synced 2026-06-05 23:00:18 +00:00
@@ -32,6 +32,7 @@ This part of the guide focuses on best practices for writing Python code.
|
||||
writing/reading
|
||||
writing/documentation
|
||||
writing/tests
|
||||
writing/logging
|
||||
writing/gotchas
|
||||
writing/license
|
||||
|
||||
@@ -59,6 +60,7 @@ different scenarios.
|
||||
scenarios/scientific
|
||||
scenarios/imaging
|
||||
scenarios/xml
|
||||
scenarios/json
|
||||
scenarios/crypto
|
||||
|
||||
|
||||
|
||||
@@ -56,7 +56,7 @@ who choose to create a command-line interface because it is quick and simple.
|
||||
Cliff
|
||||
------
|
||||
|
||||
`Cliff <https://cliff.readthedocs.org/en/latest>`_ is a framework for
|
||||
`Cliff <http://docs.openstack.org/developer/cliff/>`_ is a framework for
|
||||
building command-line programs. It uses setuptools entry points to provide
|
||||
subcommands, output formatters, and other extensions. The framework is meant
|
||||
to be used to create multi-level commands such as subversion and git, where
|
||||
|
||||
@@ -0,0 +1,48 @@
|
||||
JSON
|
||||
====
|
||||
|
||||
The `json <https://docs.python.org/2/library/json.html>`_ library can parse JSON from strings or files. When parsing, the library converts the JSON into a Python dictionary or list. It can also parse Python dictionaries or lists into JSON strings.
|
||||
|
||||
Parsing JSON
|
||||
------------
|
||||
|
||||
Take the following string containing JSON data:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
json_string = '{"first_name": "Guido", "last_name":"Rossum"}'
|
||||
|
||||
It can be parsed like this:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
import json
|
||||
parsed_json = json.loads(json_string)
|
||||
|
||||
and can now be used as a normal dictionary:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
print(parsed_json['first_name'])
|
||||
"Guido"
|
||||
|
||||
You can also convert a the following to JSON:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
d = {
|
||||
'first_name': 'Guido',
|
||||
'second_name': 'Rossum',
|
||||
'titles': ['BDFL', 'Developer'],
|
||||
}
|
||||
|
||||
print(json.dumps(d))
|
||||
'{"first_name": "Guido", "last_name": "Rossum", "titles": ["BDFL", "Developer"]}'
|
||||
|
||||
|
||||
simplejson
|
||||
----------
|
||||
|
||||
`simplejson <https://simplejson.readthedocs.org/en/latest/>`_ is the externally maintained development version of the json library.
|
||||
|
||||
simplejson mimics the json standard library, it is available so that developers that use an older version of python can use the latest features available in the json lib.
|
||||
@@ -0,0 +1,203 @@
|
||||
Logging
|
||||
=======
|
||||
|
||||
The :mod:`logging` module has been a part of Python's Standard Library since
|
||||
version 2.3. It is succinctly described in :pep:`282`. The documentation
|
||||
is notoriously hard to read, except for the `basic logging tutorial`_.
|
||||
|
||||
Logging serves two purposes:
|
||||
|
||||
- **Diagnostic logging** records events related to the application's
|
||||
operation. If a user calls in to report an error, for example, the logs
|
||||
can be searched for context.
|
||||
- **Audit logging** records events for business analysis. A user's
|
||||
transactions can be extracted and combined with other user details for
|
||||
reports or to optimize a business goal.
|
||||
|
||||
|
||||
... or Print?
|
||||
-------------
|
||||
|
||||
The only time that ``print`` is a better option than logging is when
|
||||
the goal is to display a help statement for a command line application.
|
||||
Other reasons why logging is better than ``print``:
|
||||
|
||||
- The `log record`_, which is created with every logging event, contains
|
||||
readily available diagnostic information such as the file name,
|
||||
full path, function, and line number of the logging event.
|
||||
- Events logged in included modules are automatically accessible via the
|
||||
root logger
|
||||
to your application's logging stream, unless you filter them out.
|
||||
- Logging can be selectively silenced by using the method
|
||||
:meth:`logging.Logger.setLevel` or disabled by setting the attribute
|
||||
:attr:`logging.Logger.disabled` to ``True``.
|
||||
|
||||
|
||||
Logging in a Library
|
||||
--------------------
|
||||
|
||||
Notes for `configuring logging for a library`_ are in the
|
||||
`logging tutorial`_. Because the *user*, not the library, should
|
||||
dictate what happens when a logging event occurs, one admonition bears
|
||||
repeating:
|
||||
|
||||
.. note::
|
||||
It is strongly advised that you do not add any handlers other than
|
||||
NullHandler to your library’s loggers.
|
||||
|
||||
|
||||
Best practice when instantiating loggers in a library is to only create them
|
||||
using the ``__name__`` global variable: the :mod:`logging` module creates a
|
||||
hierarchy of loggers using dot notation, so using ``__name__`` ensures
|
||||
no name collisions.
|
||||
|
||||
Here is an example of best practice from the `requests source`_ -- place
|
||||
this in your ``__init__.py``
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
# Set default logging handler to avoid "No handler found" warnings.
|
||||
import logging
|
||||
try: # Python 2.7+
|
||||
from logging import NullHandler
|
||||
except ImportError:
|
||||
class NullHandler(logging.Handler):
|
||||
def emit(self, record):
|
||||
pass
|
||||
|
||||
logging.getLogger(__name__).addHandler(NullHandler())
|
||||
|
||||
|
||||
|
||||
Logging in an Application
|
||||
-------------------------
|
||||
|
||||
The `twelve factor app <http://12factor.net>`_, an authoritative reference
|
||||
for good practice in application development, contains a section on
|
||||
`logging best practice <http://12factor.net/logs>`_. It emphatically
|
||||
advocates for treating log events as an event stream, and for
|
||||
sending that event stream to standard output to be handled by the
|
||||
application environment.
|
||||
|
||||
|
||||
There are at least three ways to configure a logger:
|
||||
|
||||
- Using an INI-formatted file:
|
||||
- **Pro**: possible to update configuration while running
|
||||
using the function :func:`logging.config.listen` to listen
|
||||
on a socket.
|
||||
- **Con**: less control (*e.g.* custom subclassed filters or loggers)
|
||||
than possible when configuring a logger in code.
|
||||
- Using a dictionary or a JSON-formatted file:
|
||||
- **Pro**: in addition to updating while running, it is possible to
|
||||
load from a file using the :mod:`json` module, in the standard
|
||||
library since Python 2.6.
|
||||
- **Con**: less control than when configuring a logger in code.
|
||||
- Using code:
|
||||
- **Pro**: complete control over the configuration.
|
||||
- **Con**: modifications require a change to source code.
|
||||
|
||||
|
||||
Example Configuration via an INI File
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
Let us say the file is named ``logging_config.ini``.
|
||||
More details for the file format are in the `logging configuration`_
|
||||
section of the `logging tutorial`_.
|
||||
|
||||
.. code-block:: ini
|
||||
|
||||
[loggers]
|
||||
keys=root
|
||||
|
||||
[handlers]
|
||||
keys=stream_handler
|
||||
|
||||
[formatters]
|
||||
keys=formatter
|
||||
|
||||
[logger_root]
|
||||
level=DEBUG
|
||||
handlers=stream_handler
|
||||
|
||||
[handler_stream_handler]
|
||||
class=StreamHandler
|
||||
level=DEBUG
|
||||
formatter=formatter
|
||||
args=(sys.stderr,)
|
||||
|
||||
[formatter_formatter]
|
||||
format=%(asctime)s %(name)-12s %(levelname)-8s %(message)s
|
||||
|
||||
|
||||
Then use :meth:`logging.config.fileConfig` in the code:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
import logging
|
||||
from logging.config import fileConfig
|
||||
|
||||
fileConfig('logging_config.ini')
|
||||
logger = logging.getLogger()
|
||||
logger.debug('often makes a very good meal of %s', 'visiting tourists')
|
||||
|
||||
|
||||
Example Configuration via a Dictionary
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
As of Python 2.7, you can use a dictionary with configuration details.
|
||||
:pep:`319` contains a list of the mandatory and optional elements in
|
||||
the configuration dictionary.
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
import logging
|
||||
from logging.config import dictConfig
|
||||
|
||||
logging_config = dict(
|
||||
version = 1,
|
||||
formatters = {
|
||||
'f': {'format':
|
||||
'%(asctime)s %(name)-12s %(levelname)-8s %(message)s'}
|
||||
},
|
||||
handlers = {
|
||||
'h': {'class': 'logging.StreamHandler',
|
||||
'formatter': 'f',
|
||||
'level': logging.DEBUG}
|
||||
},
|
||||
loggers = {
|
||||
root : {'handlers': ['h'],
|
||||
'level': logging.DEBUG}
|
||||
}
|
||||
)
|
||||
|
||||
dictConfig(logging_config)
|
||||
|
||||
logger = logging.getLogger()
|
||||
logger.debug('often makes a very good meal of %s', 'visiting tourists')
|
||||
|
||||
|
||||
Example Configuration Directly in Code
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
import logging
|
||||
|
||||
logger = logging.getLogger()
|
||||
handler = logging.StreamHandler()
|
||||
formatter = logging.Formatter(
|
||||
'%(asctime)s %(name)-12s %(levelname)-8s %(message)s')
|
||||
handler.setFormatter(formatter)
|
||||
logger.addHandler(handler)
|
||||
logger.setLevel(logging.DEBUG)
|
||||
|
||||
logger.debug('often makes a very good meal of %s', 'visiting tourists')
|
||||
|
||||
|
||||
.. _basic logging tutorial: http://docs.python.org/howto/logging.html#logging-basic-tutorial
|
||||
.. _logging configuration: https://docs.python.org/howto/logging.html#configuring-logging
|
||||
.. _logging tutorial: http://docs.python.org/howto/logging.html
|
||||
.. _configuring logging for a library: https://docs.python.org/howto/logging.html#configuring-logging-for-a-library
|
||||
.. _log record: https://docs.python.org/library/logging.html#logrecord-attributes
|
||||
.. _requests source: https://github.com/kennethreitz/requests
|
||||
@@ -58,7 +58,7 @@ Some general rules of testing:
|
||||
good set of tests, you or other maintainers will rely largely on the
|
||||
testing suite to fix the problem or modify a given behavior. Therefore
|
||||
the testing code will be read as much as or even more than the running
|
||||
code. A unit test whose purpose is unclear is not very helpful is this
|
||||
code. A unit test whose purpose is unclear is not very helpful in this
|
||||
case.
|
||||
|
||||
- Another use of the testing code is as an introduction to new developers. When
|
||||
|
||||
Reference in New Issue
Block a user