2013-09-21 06:21:49 +08:00
|
|
|
============================
|
|
|
|
Performance and optimization
|
|
|
|
============================
|
|
|
|
|
|
|
|
This document provides an overview of techniques and tools that can help get
|
|
|
|
your Django code running more efficiently - faster, and using fewer system
|
|
|
|
resources.
|
|
|
|
|
|
|
|
Introduction
|
|
|
|
============
|
|
|
|
|
|
|
|
Generally one's first concern is to write code that *works*, whose logic
|
|
|
|
functions as required to produce the expected output. Sometimes, however, this
|
|
|
|
will not be enough to make the code work as *efficiently* as one would like.
|
|
|
|
|
|
|
|
In this case, what's needed is something - and in practice, often a collection
|
|
|
|
of things - to improve the code's performance without, or only minimally,
|
|
|
|
affecting its behavior.
|
|
|
|
|
|
|
|
General approaches
|
|
|
|
==================
|
|
|
|
|
|
|
|
What are you optimizing *for*?
|
|
|
|
------------------------------
|
|
|
|
|
|
|
|
It's important to have a clear idea what you mean by 'performance'. There is
|
|
|
|
not just one metric of it.
|
|
|
|
|
|
|
|
Improved speed might be the most obvious aim for a program, but sometimes other
|
|
|
|
performance improvements might be sought, such as lower memory consumption or
|
|
|
|
fewer demands on the database or network.
|
|
|
|
|
|
|
|
Improvements in one area will often bring about improved performance in
|
|
|
|
another, but not always; sometimes one can even be at the expense of another.
|
|
|
|
For example, an improvement in a program's speed might cause it to use more
|
|
|
|
memory. Even worse, it can be self-defeating - if the speed improvement is so
|
|
|
|
memory-hungry that the system starts to run out of memory, you'll have done
|
|
|
|
more harm than good.
|
|
|
|
|
|
|
|
There are other trade-offs to bear in mind. Your own time is a valuable
|
|
|
|
resource, more precious than CPU time. Some improvements might be too difficult
|
|
|
|
to be worth implementing, or might affect the portability or maintainability of
|
|
|
|
the code. Not all performance improvements are worth the effort.
|
|
|
|
|
|
|
|
So, you need to know what performance improvements you are aiming for, and you
|
|
|
|
also need to know that you have a good reason for aiming in that direction -
|
|
|
|
and for that you need:
|
|
|
|
|
|
|
|
Performance benchmarking
|
|
|
|
------------------------
|
|
|
|
|
|
|
|
It's no good just guessing or assuming where the inefficiencies lie in your
|
|
|
|
code.
|
|
|
|
|
|
|
|
Django tools
|
2016-01-03 18:56:22 +08:00
|
|
|
~~~~~~~~~~~~
|
2013-09-21 06:21:49 +08:00
|
|
|
|
|
|
|
`django-debug-toolbar
|
2017-05-20 23:51:21 +08:00
|
|
|
<https://github.com/jazzband/django-debug-toolbar/>`_ is a very handy tool that
|
|
|
|
provides insights into what your code is doing and how much time it spends
|
|
|
|
doing it. In particular it can show you all the SQL queries your page is
|
|
|
|
generating, and how long each one has taken.
|
2013-09-21 06:21:49 +08:00
|
|
|
|
|
|
|
Third-party panels are also available for the toolbar, that can (for example)
|
|
|
|
report on cache performance and template rendering times.
|
|
|
|
|
|
|
|
Third-party services
|
2016-01-03 18:56:22 +08:00
|
|
|
~~~~~~~~~~~~~~~~~~~~
|
2013-09-21 06:21:49 +08:00
|
|
|
|
2014-02-15 20:00:56 +08:00
|
|
|
There are a number of free services that will analyze and report on the
|
2013-09-21 06:21:49 +08:00
|
|
|
performance of your site's pages from the perspective of a remote HTTP client,
|
|
|
|
in effect simulating the experience of an actual user.
|
|
|
|
|
|
|
|
These can't report on the internals of your code, but can provide a useful
|
|
|
|
insight into your site's overall performance, including aspects that can't be
|
|
|
|
adequately measured from within Django environment. Examples include:
|
|
|
|
|
2014-12-20 01:07:03 +08:00
|
|
|
* `Yahoo's Yslow <http://yslow.org/>`_
|
2013-09-21 06:21:49 +08:00
|
|
|
* `Google PageSpeed <https://developers.google.com/speed/pagespeed/>`_
|
|
|
|
|
|
|
|
There are also several paid-for services that perform a similar analysis,
|
|
|
|
including some that are Django-aware and can integrate with your codebase to
|
|
|
|
profile its performance far more comprehensively.
|
|
|
|
|
|
|
|
Get things right from the start
|
|
|
|
-------------------------------
|
|
|
|
|
|
|
|
Some work in optimization involves tackling performance shortcomings, but some
|
2019-06-17 22:54:55 +08:00
|
|
|
of the work can be built in to what you'd do anyway, as part of the good
|
2013-09-21 06:21:49 +08:00
|
|
|
practices you should adopt even before you start thinking about improving
|
|
|
|
performance.
|
|
|
|
|
|
|
|
In this respect Python is an excellent language to work with, because solutions
|
|
|
|
that look elegant and feel right usually are the best performing ones. As with
|
|
|
|
most skills, learning what "looks right" takes practice, but one of the most
|
|
|
|
useful guidelines is:
|
|
|
|
|
|
|
|
Work at the appropriate level
|
2016-01-03 18:56:22 +08:00
|
|
|
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
2013-09-21 06:21:49 +08:00
|
|
|
|
|
|
|
Django offers many different ways of approaching things, but just because it's
|
|
|
|
possible to do something in a certain way doesn't mean that it's the most
|
|
|
|
appropriate way to do it. For example, you might find that you could calculate
|
|
|
|
the same thing - the number of items in a collection, perhaps - in a
|
|
|
|
``QuerySet``, in Python, or in a template.
|
|
|
|
|
|
|
|
However, it will almost always be faster to do this work at lower rather than
|
|
|
|
higher levels. At higher levels the system has to deal with objects through
|
|
|
|
multiple levels of abstraction and layers of machinery.
|
|
|
|
|
|
|
|
That is, the database can typically do things faster than Python can, which can
|
|
|
|
do them faster than the template language can::
|
|
|
|
|
|
|
|
# QuerySet operation on the database
|
|
|
|
# fast, because that's what databases are good at
|
|
|
|
my_bicycles.count()
|
|
|
|
|
|
|
|
# counting Python objects
|
|
|
|
# slower, because it requires a database query anyway, and processing
|
|
|
|
# of the Python objects
|
|
|
|
len(my_bicycles)
|
|
|
|
|
|
|
|
# Django template filter
|
|
|
|
# slower still, because it will have to count them in Python anyway,
|
|
|
|
# and because of template language overheads
|
|
|
|
{{ my_bicycles|length }}
|
|
|
|
|
|
|
|
Generally speaking, the most appropriate level for the job is the lowest-level
|
|
|
|
one that it is comfortable to code for.
|
|
|
|
|
|
|
|
.. note::
|
|
|
|
|
|
|
|
The example above is merely illustrative.
|
|
|
|
|
|
|
|
Firstly, in a real-life case you need to consider what is happening before
|
|
|
|
and after your count to work out what's an optimal way of doing it *in that
|
|
|
|
particular context*. The database optimization documents describes :ref:`a
|
|
|
|
case where counting in the template would be better
|
|
|
|
<overuse_of_count_and_exists>`.
|
|
|
|
|
|
|
|
Secondly, there are other options to consider: in a real-life case, ``{{
|
|
|
|
my_bicycles.count }}``, which invokes the ``QuerySet`` ``count()`` method
|
|
|
|
directly from the template, might be the most appropriate choice.
|
|
|
|
|
|
|
|
Caching
|
|
|
|
=======
|
|
|
|
|
|
|
|
Often it is expensive (that is, resource-hungry and slow) to compute a value,
|
|
|
|
so there can be huge benefit in saving the value to a quickly accessible cache,
|
|
|
|
ready for the next time it's required.
|
|
|
|
|
|
|
|
It's a sufficiently significant and powerful technique that Django includes a
|
2013-09-21 16:07:10 +08:00
|
|
|
comprehensive caching framework, as well as other smaller pieces of caching
|
|
|
|
functionality.
|
2013-09-21 06:21:49 +08:00
|
|
|
|
|
|
|
:doc:`The caching framework </topics/cache>`
|
|
|
|
--------------------------------------------
|
|
|
|
|
|
|
|
Django's :doc:`caching framework </topics/cache>` offers very significant
|
|
|
|
opportunities for performance gains, by saving dynamic content so that it
|
|
|
|
doesn't need to be calculated for each request.
|
|
|
|
|
|
|
|
For convenience, Django offers different levels of cache granularity: you can
|
|
|
|
cache the output of specific views, or only the pieces that are difficult to
|
|
|
|
produce, or even an entire site.
|
|
|
|
|
|
|
|
Implementing caching should not be regarded as an alternative to improving code
|
|
|
|
that's performing poorly because it has been written badly. It's one of the
|
|
|
|
final steps towards producing well-performing code, not a shortcut.
|
|
|
|
|
|
|
|
:class:`~django.utils.functional.cached_property`
|
2013-09-21 16:07:10 +08:00
|
|
|
-------------------------------------------------
|
2013-09-21 06:21:49 +08:00
|
|
|
|
2016-03-14 02:47:58 +08:00
|
|
|
It's common to have to call a class instance's method more than once. If
|
2013-09-21 06:21:49 +08:00
|
|
|
that function is expensive, then doing so can be wasteful.
|
|
|
|
|
2015-11-11 02:27:46 +08:00
|
|
|
Using the :class:`~django.utils.functional.cached_property` decorator saves the
|
|
|
|
value returned by a property; the next time the function is called on that
|
|
|
|
instance, it will return the saved value rather than re-computing it. Note that
|
|
|
|
this only works on methods that take ``self`` as their only argument and that
|
|
|
|
it changes the method to a property.
|
2013-09-21 06:21:49 +08:00
|
|
|
|
2013-09-21 16:07:10 +08:00
|
|
|
Certain Django components also have their own caching functionality; these are
|
|
|
|
discussed below in the sections related to those components.
|
2013-09-21 06:21:49 +08:00
|
|
|
|
|
|
|
Understanding laziness
|
|
|
|
======================
|
|
|
|
|
|
|
|
*Laziness* is a strategy complementary to caching. Caching avoids
|
|
|
|
recomputation by saving results; laziness delays computation until it's
|
|
|
|
actually required.
|
|
|
|
|
|
|
|
Laziness allows us to refer to things before they are instantiated, or even
|
|
|
|
before it's possible to instantiate them. This has numerous uses.
|
|
|
|
|
|
|
|
For example, :ref:`lazy translation <lazy-translations>` can be used before the
|
|
|
|
target language is even known, because it doesn't take place until the
|
|
|
|
translated string is actually required, such as in a rendered template.
|
|
|
|
|
|
|
|
Laziness is also a way to save effort by trying to avoid work in the first
|
|
|
|
place. That is, one aspect of laziness is not doing anything until it has to be
|
|
|
|
done, because it may not turn out to be necessary after all. Laziness can
|
|
|
|
therefore have performance implications, and the more expensive the work
|
|
|
|
concerned, the more there is to gain through laziness.
|
|
|
|
|
|
|
|
Python provides a number of tools for lazy evaluation, particularly through the
|
|
|
|
:py:term:`generator` and :py:term:`generator expression` constructs. It's worth
|
|
|
|
reading up on laziness in Python to discover opportunities for making use of
|
|
|
|
lazy patterns in your code.
|
|
|
|
|
|
|
|
Laziness in Django
|
|
|
|
------------------
|
|
|
|
|
|
|
|
Django is itself quite lazy. A good example of this can be found in the
|
|
|
|
evaluation of ``QuerySets``. :ref:`QuerySets are lazy <querysets-are-lazy>`.
|
|
|
|
Thus a ``QuerySet`` can be created, passed around and combined with other
|
|
|
|
``QuerySets``, without actually incurring any trips to the database to fetch
|
|
|
|
the items it describes. What gets passed around is the ``QuerySet`` object, not
|
|
|
|
the collection of items that - eventually - will be required from the database.
|
|
|
|
|
|
|
|
On the other hand, :ref:`certain operations will force the evaluation of a
|
|
|
|
QuerySet <when-querysets-are-evaluated>`. Avoiding the premature evaluation of
|
|
|
|
a ``QuerySet`` can save making an expensive and unnecessary trip to the
|
|
|
|
database.
|
|
|
|
|
2015-11-07 21:30:20 +08:00
|
|
|
Django also offers a :meth:`~django.utils.functional.keep_lazy` decorator.
|
2013-09-21 06:21:49 +08:00
|
|
|
This allows a function that has been called with a lazy argument to behave
|
|
|
|
lazily itself, only being evaluated when it needs to be. Thus the lazy argument
|
|
|
|
- which could be an expensive one - will not be called upon for evaluation
|
|
|
|
until it's strictly required.
|
|
|
|
|
|
|
|
Databases
|
|
|
|
=========
|
|
|
|
|
2016-01-25 05:26:11 +08:00
|
|
|
Database optimization
|
|
|
|
---------------------
|
2013-09-21 06:21:49 +08:00
|
|
|
|
2017-07-18 20:24:49 +08:00
|
|
|
Django's database layer provides various ways to help developers get the best
|
2013-09-21 06:21:49 +08:00
|
|
|
performance from their databases. The :doc:`database optimization documentation
|
|
|
|
</topics/db/optimization>` gathers together links to the relevant
|
|
|
|
documentation and adds various tips that outline the steps to take when
|
|
|
|
attempting to optimize your database usage.
|
|
|
|
|
|
|
|
Other database-related tips
|
|
|
|
---------------------------
|
|
|
|
|
|
|
|
Enabling :ref:`persistent-database-connections` can speed up connections to the
|
|
|
|
database accounts for a significant part of the request processing time.
|
|
|
|
|
|
|
|
This helps a lot on virtualized hosts with limited network performance, for example.
|
|
|
|
|
|
|
|
HTTP performance
|
|
|
|
================
|
|
|
|
|
|
|
|
Middleware
|
|
|
|
----------
|
|
|
|
|
|
|
|
Django comes with a few helpful pieces of :doc:`middleware </ref/middleware>`
|
|
|
|
that can help optimize your site's performance. They include:
|
|
|
|
|
|
|
|
:class:`~django.middleware.http.ConditionalGetMiddleware`
|
2016-01-03 18:56:22 +08:00
|
|
|
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
2013-09-21 06:21:49 +08:00
|
|
|
|
|
|
|
Adds support for modern browsers to conditionally GET responses based on the
|
2016-04-03 18:15:10 +08:00
|
|
|
``ETag`` and ``Last-Modified`` headers. It also calculates and sets an ETag if
|
|
|
|
needed.
|
2013-09-21 06:21:49 +08:00
|
|
|
|
|
|
|
:class:`~django.middleware.gzip.GZipMiddleware`
|
2016-01-03 18:56:22 +08:00
|
|
|
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
2013-09-21 06:21:49 +08:00
|
|
|
|
|
|
|
Compresses responses for all modern browsers, saving bandwidth and transfer
|
|
|
|
time. Note that GZipMiddleware is currently considered a security risk, and is
|
|
|
|
vulnerable to attacks that nullify the protection provided by TLS/SSL. See the
|
|
|
|
warning in :class:`~django.middleware.gzip.GZipMiddleware` for more information.
|
|
|
|
|
2013-09-23 06:48:43 +08:00
|
|
|
Sessions
|
|
|
|
--------
|
|
|
|
|
2015-10-08 07:35:18 +08:00
|
|
|
Using cached sessions
|
2016-01-03 18:56:22 +08:00
|
|
|
~~~~~~~~~~~~~~~~~~~~~
|
2013-09-23 06:48:43 +08:00
|
|
|
|
|
|
|
:ref:`Using cached sessions <cached-sessions-backend>` may be a way to increase
|
|
|
|
performance by eliminating the need to load session data from a slower storage
|
|
|
|
source like the database and instead storing frequently used session data in
|
|
|
|
memory.
|
|
|
|
|
2013-09-21 16:07:10 +08:00
|
|
|
Static files
|
|
|
|
------------
|
|
|
|
|
2014-03-01 00:44:03 +08:00
|
|
|
Static files, which by definition are not dynamic, make an excellent target for
|
2013-09-21 16:07:10 +08:00
|
|
|
optimization gains.
|
|
|
|
|
2018-10-27 22:30:28 +08:00
|
|
|
:class:`~django.contrib.staticfiles.storage.ManifestStaticFilesStorage`
|
|
|
|
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
2013-09-21 16:07:10 +08:00
|
|
|
|
|
|
|
By taking advantage of web browsers' caching abilities, you can
|
|
|
|
eliminate network hits entirely for a given file after the initial download.
|
|
|
|
|
2018-10-27 22:30:28 +08:00
|
|
|
:class:`~django.contrib.staticfiles.storage.ManifestStaticFilesStorage` appends a
|
2013-09-21 16:07:10 +08:00
|
|
|
content-dependent tag to the filenames of :doc:`static files
|
|
|
|
</ref/contrib/staticfiles>` to make it safe for browsers to cache them
|
|
|
|
long-term without missing future changes - when a file changes, so will the
|
|
|
|
tag, so browsers will reload the asset automatically.
|
|
|
|
|
|
|
|
"Minification"
|
2016-01-03 18:56:22 +08:00
|
|
|
~~~~~~~~~~~~~~
|
2013-09-21 06:21:49 +08:00
|
|
|
|
2013-09-21 16:07:10 +08:00
|
|
|
Several third-party Django tools and packages provide the ability to "minify"
|
2014-02-23 01:30:28 +08:00
|
|
|
HTML, CSS, and JavaScript. They remove unnecessary whitespace, newlines, and
|
2013-09-21 16:07:10 +08:00
|
|
|
comments, and shorten variable names, and thus reduce the size of the documents
|
|
|
|
that your site publishes.
|
2013-09-21 06:21:49 +08:00
|
|
|
|
|
|
|
Template performance
|
|
|
|
====================
|
|
|
|
|
|
|
|
Note that:
|
|
|
|
|
|
|
|
* using ``{% block %}`` is faster than using ``{% include %}``
|
|
|
|
* heavily-fragmented templates, assembled from many small pieces, can affect
|
|
|
|
performance
|
|
|
|
|
|
|
|
The cached template loader
|
|
|
|
--------------------------
|
|
|
|
|
|
|
|
Enabling the :class:`cached template loader
|
|
|
|
<django.template.loaders.cached.Loader>` often improves performance
|
|
|
|
drastically, as it avoids compiling each template every time it needs to be
|
|
|
|
rendered.
|
|
|
|
|
|
|
|
Using different versions of available software
|
|
|
|
==============================================
|
|
|
|
|
|
|
|
It can sometimes be worth checking whether different and better-performing
|
|
|
|
versions of the software that you're using are available.
|
|
|
|
|
2013-09-21 16:07:10 +08:00
|
|
|
These techniques are targeted at more advanced users who want to push the
|
|
|
|
boundaries of performance of an already well-optimized Django site.
|
|
|
|
|
|
|
|
However, they are not magic solutions to performance problems, and they're
|
|
|
|
unlikely to bring better than marginal gains to sites that don't already do the
|
|
|
|
more basic things the right way.
|
2013-09-21 06:21:49 +08:00
|
|
|
|
|
|
|
.. note::
|
|
|
|
|
|
|
|
It's worth repeating: **reaching for alternatives to software you're
|
2013-09-21 16:07:10 +08:00
|
|
|
already using is never the first answer to performance problems**. When
|
|
|
|
you reach this level of optimization, you need a formal benchmarking
|
|
|
|
solution.
|
2013-09-21 06:21:49 +08:00
|
|
|
|
|
|
|
Newer is often - but not always - better
|
|
|
|
----------------------------------------
|
|
|
|
|
|
|
|
It's fairly rare for a new release of well-maintained software to be less
|
|
|
|
efficient, but the maintainers can't anticipate every possible use-case - so
|
|
|
|
while being aware that newer versions are likely to perform better, don't
|
2019-06-17 22:54:55 +08:00
|
|
|
assume that they always will.
|
2013-09-21 06:21:49 +08:00
|
|
|
|
|
|
|
This is true of Django itself. Successive releases have offered a number of
|
|
|
|
improvements across the system, but you should still check the real-world
|
|
|
|
performance of your application, because in some cases you may find that
|
|
|
|
changes mean it performs worse rather than better.
|
|
|
|
|
|
|
|
Newer versions of Python, and also of Python packages, will often perform
|
|
|
|
better too - but measure, rather than assume.
|
|
|
|
|
|
|
|
.. note::
|
|
|
|
|
|
|
|
Unless you've encountered an unusual performance problem in a particular
|
|
|
|
version, you'll generally find better features, reliability, and security
|
|
|
|
in a new release and that these benefits are far more significant than any
|
|
|
|
performance you might win or lose.
|
|
|
|
|
|
|
|
Alternatives to Django's template language
|
|
|
|
------------------------------------------
|
|
|
|
|
|
|
|
For nearly all cases, Django's built-in template language is perfectly
|
|
|
|
adequate. However, if the bottlenecks in your Django project seem to lie in the
|
|
|
|
template system and you have exhausted other opportunities to remedy this, a
|
|
|
|
third-party alternative may be the answer.
|
|
|
|
|
|
|
|
`Jinja2 <http://jinja.pocoo.org/docs/>`_ can offer performance improvements,
|
|
|
|
particularly when it comes to speed.
|
|
|
|
|
|
|
|
Alternative template systems vary in the extent to which they share Django's
|
|
|
|
templating language.
|
|
|
|
|
|
|
|
.. note::
|
|
|
|
|
|
|
|
*If* you experience performance issues in templates, the first thing to do
|
|
|
|
is to understand exactly why. Using an alternative template system may
|
|
|
|
prove faster, but the same gains may also be available without going to
|
|
|
|
that trouble - for example, expensive processing and logic in your
|
|
|
|
templates could be done more efficiently in your views.
|
|
|
|
|
|
|
|
Alternative software implementations
|
|
|
|
------------------------------------
|
|
|
|
|
2013-09-21 16:07:10 +08:00
|
|
|
It may be worth checking whether Python software you're using has been
|
2013-09-21 06:21:49 +08:00
|
|
|
provided in a different implementation that can execute the same code faster.
|
|
|
|
|
2013-09-21 16:07:10 +08:00
|
|
|
However: most performance problems in well-written Django sites aren't at the
|
|
|
|
Python execution level, but rather in inefficient database querying, caching,
|
|
|
|
and templates. If you're relying on poorly-written Python code, your
|
|
|
|
performance problems are unlikely to be solved by having it execute faster.
|
|
|
|
|
|
|
|
Using an alternative implementation may introduce compatibility, deployment,
|
|
|
|
portability, or maintenance issues. It goes without saying that before adopting
|
|
|
|
a non-standard implementation you should ensure it provides sufficient
|
|
|
|
performance gains for your application to outweigh the potential risks.
|
|
|
|
|
|
|
|
With these caveats in mind, you should be aware of:
|
|
|
|
|
2018-01-07 21:28:41 +08:00
|
|
|
`PyPy <https://pypy.org/>`_
|
|
|
|
~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
2013-09-21 16:07:10 +08:00
|
|
|
|
2018-01-07 21:28:41 +08:00
|
|
|
`PyPy <https://pypy.org/>`_ is an implementation of Python in Python itself
|
|
|
|
(the 'standard' Python implementation is in C). PyPy can offer substantial
|
2013-09-21 16:07:10 +08:00
|
|
|
performance gains, typically for heavyweight applications.
|
|
|
|
|
|
|
|
A key aim of the PyPy project is `compatibility
|
2018-01-07 21:28:41 +08:00
|
|
|
<https://pypy.org/compat.html>`_ with existing Python APIs and libraries.
|
2013-09-21 16:07:10 +08:00
|
|
|
Django is compatible, but you will need to check the compatibility of other
|
|
|
|
libraries you rely on.
|
|
|
|
|
|
|
|
C implementations of Python libraries
|
2016-01-03 18:56:22 +08:00
|
|
|
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
2013-09-21 06:21:49 +08:00
|
|
|
|
2013-09-21 16:07:10 +08:00
|
|
|
Some Python libraries are also implemented in C, and can be much faster. They
|
2014-03-01 00:44:03 +08:00
|
|
|
aim to offer the same APIs. Note that compatibility issues and behavior
|
2013-09-21 16:07:10 +08:00
|
|
|
differences are not unknown (and not always immediately evident).
|