Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with
or
.
Download ZIP

Loading…

Make brian ready for python 3 #26

Merged
merged 24 commits into from

2 participants

@mstimberg
Owner

This branch tracks changes necessary to support Python 3 (using 2to3, i.e. with a Python 2 codebase)

mstimberg added some commits
@mstimberg mstimberg Enable testing in Python 3 3354823
@mstimberg mstimberg Convert checks for isNumericType and isSequenceType into something
Python3-friendly.
fa588a0
@mstimberg mstimberg Really check the test for number types and test it a bit more thoroughly
for different kinds of numpy scalars
684d42f
@mstimberg mstimberg Make itertools usage python3-ready. c85d7e9
@mstimberg mstimberg Make sure to run the built brian2 package, not the source one (important
for Python 3)
c031801
@mstimberg mstimberg Correct some print statements, do not insist on sympy 0.7.1 anymore d74027f
@mstimberg mstimberg Make Dimension object hashable e3600db
@mstimberg mstimberg Make ipython check work for python 3 b603510
@mstimberg mstimberg Make sure the logger module encodes strings in a way that works with
Python 2 and 3
71d9ee5
@mstimberg mstimberg Do not run nosetests from the source directory to make sure it uses the
installed version of brian
e4b2ff8
@mstimberg mstimberg Adapt doctests for python3-style print statements 9ff1282
@mstimberg mstimberg Fail gracefully when weave is not available (it does not work for Python
3)
fae52ff
@mstimberg mstimberg Do not implement __eq__ and __lt__ for clocks a1cce02
@mstimberg mstimberg Use operator.truediv instead of operator.div 0615753
@mstimberg mstimberg Make sure to pass strings (not Expression objects) to sympy fa1de0c
@mstimberg mstimberg Don't print the type of objects as part of doctests (format changes from
Python 2 to 3)
e419338
@mstimberg mstimberg Use the IGNORE_EXCEPTION_DETAIL directive for doctest that use a
non-builtin exception type (the Python3 output contains the module name
in this case while the Python2 output doesn't)
fb04c7e
@mstimberg mstimberg Ignore errors when generating the xml report 731c47a
@mstimberg mstimberg Use the correct .coveragerc file for testing 99b1872
@mstimberg mstimberg Remove unused import 2dec509
@mstimberg mstimberg Install different numpy versions for Python 2 and 3 3556d5c
@thesamovar thesamovar was assigned
@mstimberg
Owner

The testsuite now passes under python 2.6, 2.7, 3.2, and 3.3! Using 2to3 as part of the setup file and fixing the small issues in the code was actually easier than expected -- what took the most time was convincing jenkins and travis to play well with it (it's important to use the brian2 package preprocessed by 2to3, not the code in the source directory) and sort the dependencies out. One example for the dependency issues is that numpy 1.6.2 does not work with python 3, while numpy 1.7.0 does not work with python 2... numpy 1.7.1 should be out soon (there's already a RC) and fix this problem, though... I'll add some notes to the developer docs about common issues that one should keep in mind, but otherwise it's good to merge.

One downside is that scipy.weave is not available for python3 and people generally seem to recommend switching to Cython, anyway. Maybe it's worth adding Cython as a codegen target before a release so we can claim that we have "true" code generation (i.e. not Python code) even for python3. But it's not a big deal either, python3 compatibility is more a nice-to-have thing for the future, currently our focus is still on python2.

@thesamovar
Owner

Agreed on Cython as a codegen target. I wonder if it might not turn out to be extremely straightforward to do it, actually. Will add an issue for that now.

GitHub is saying 'Merge with caution' - is there a known error? Should I merge?

@mstimberg
Owner

Ah, that's annoying: sympy is struggling with the python 2->3 transition, they did not integrate 2to3 nicely into setup.py so there are two different tarballs for python2 and python3. For some reason pip selected the wrong one in the last test. I now explicitly specified the URL of the tarball for python3 as a workaround, hopefully that should work. Maybe wait for the test run to complete (which may take an hour or so as it has to build numpy from scratch...) before merging.

@mstimberg
Owner

It seems to have worked -- and the test was surprisingly fast! So I'd say it's ready to merge.

@thesamovar thesamovar merged commit 6c3a160 into master
@thesamovar thesamovar deleted the python3_support branch
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Commits on Mar 26, 2013
  1. @mstimberg
Commits on Mar 28, 2013
  1. @mstimberg

    Enable testing in Python 3

    mstimberg authored
  2. @mstimberg
  3. @mstimberg

    Really check the test for number types and test it a bit more thoroughly

    mstimberg authored
    for different kinds of numpy scalars
  4. @mstimberg
  5. @mstimberg
  6. @mstimberg
  7. @mstimberg
  8. @mstimberg
  9. @mstimberg
  10. @mstimberg
  11. @mstimberg
  12. @mstimberg
  13. @mstimberg
  14. @mstimberg
  15. @mstimberg
  16. @mstimberg
  17. @mstimberg

    Use the IGNORE_EXCEPTION_DETAIL directive for doctest that use a

    mstimberg authored
    non-builtin exception type (the Python3 output contains the module name
    in this case while the Python2 output doesn't)
  18. @mstimberg
  19. @mstimberg
  20. @mstimberg

    Remove unused import

    mstimberg authored
  21. @mstimberg
Commits on Mar 29, 2013
  1. @mstimberg
  2. @mstimberg
This page is out of date. Refresh to see the latest.
View
15 .travis.yml
@@ -2,16 +2,23 @@ language: python
python:
- "2.6"
- "2.7"
+ - "3.2"
+ - "3.3"
# install build dependencies
before_install:
- sudo apt-get update -qq
- sudo apt-get build-dep -qq python-scipy python-sympy python-pyparsing
# command to install dependencies
install:
- - "pip install numpy==1.6.2"
- - "pip install scipy sympy pyparsing==1.5.7 sphinx ipython --use-mirrors"
+ # install different versions of numpy for python 2 and 3
+ - "if [ ${TRAVIS_PYTHON_VERSION:0:1} == '2' ]; then pip install numpy==1.6.2 --use-mirrors; else pip install numpy --use-mirrors; fi"
+ - "pip install scipy sphinx ipython --use-mirrors"
+ # Make sure to use the python 3 version for sympy
+ - "if [ ${TRAVIS_PYTHON_VERSION:0:1} == '2' ]; then pip install sympy --use-mirrors; else pip install http://sympy.googlecode.com/files/sympy-0.7.2-py3.2.tar.gz; fi"
+ # install different pyparsing versions for python 2 and 3
+ - "if [ ${TRAVIS_PYTHON_VERSION:0:1} == '2' ]; then pip install pyparsing==1.5.7 --use-mirrors; else pip install pyparsing --use-mirrors; fi"
- "pip install . --use-mirrors"
-# command to run tests
-script: nosetests --with-doctest brian2
+# command to run tests (make sure to not run it from the source directory)
+script: "cd ~;nosetests --with-doctest brian2"
notifications:
email: false
View
8 brian2/__init__.py
@@ -13,22 +13,22 @@
try:
import numpy
except ImportError as ex:
- print >>sys.stderr, 'Importing numpy failed:', ex
+ sys.stderr.write('Importing numpy failed: %s\n' % ex)
missing.append('numpy')
try:
import scipy
except ImportError as ex:
- print >>sys.stderr, 'Importing scipy failed:', ex
+ sys.stderr.write('Importing scipy failed: %s\n' % ex)
missing.append('scipy')
try:
import sympy
except ImportError as ex:
- print >>sys.stderr, 'Importing sympy failed:', ex
+ sys.stderr.write('Importing sympy failed: %s\n' % ex)
missing.append('sympy')
try:
import pyparsing
except ImportError as ex:
- print >>sys.stderr, 'Importing pyparsing failed:', ex
+ sys.stderr.write('Importing pyparsing failed: %s\n' % ex)
missing.append('pyparsing')
if len(missing):
View
14 brian2/codegen/languages/cpp.py
@@ -1,20 +1,24 @@
'''
TODO: restrict keyword optimisations
'''
-import itertools
+import re
from sympy.printing.ccode import CCodePrinter
import numpy
-from scipy import weave
-import re
from brian2.utils.stringtools import deindent
from brian2.utils.parsing import parse_to_sympy
-
from brian2.codegen.functions.base import Function
-from .base import Language, CodeObject
+from brian2.utils.logger import get_logger
+from .base import Language, CodeObject
+logger = get_logger(__name__)
+try:
+ from scipy import weave
+except ImportError as ex:
+ logger.warn('Importing scipy.weave failed: %s' % ex)
+ weave = None
__all__ = ['CPPLanguage', 'CPPCodeObject',
'c_data_type',
View
18 brian2/core/clocks.py
@@ -45,10 +45,10 @@ class Clock(Nameable):
@check_units(dt=second, t=second)
def __init__(self, dt=None, name=None):
- Nameable.__init__(self, name)
self._dt_spec = dt
self.i = 0 #: The time step of the simulation as an integer.
self.i_end = 0 #: The time step the simulation will end as an integer
+ Nameable.__init__(self, name)
logger.debug("Created clock {self.name} with dt={self._dt_spec}".format(self=self))
def reinit(self):
@@ -153,21 +153,5 @@ def running(self):
return self.i<self.i_end
epsilon = 1e-14
-
- def __lt__(self, other):
- selft = self.t_
- othert = other.t_
- if selft==othert or abs(selft-othert)<=self.epsilon*abs(selft):
- return False
- return selft<othert
-
- def __eq__(self, other):
- selft = self.t_
- othert = other.t_
- if selft==othert or abs(selft-othert)<=self.epsilon*abs(selft):
- return True
- else:
- return False
-
defaultclock = Clock(name='defaultclock')
View
7 brian2/core/network.py
@@ -3,6 +3,7 @@
from brian2.utils.logger import get_logger
from brian2.core.names import Nameable
from brian2.core.base import BrianObject
+from brian2.core.clocks import Clock
from brian2.units.fundamentalunits import check_units
from brian2.units.allunits import second
from brian2.core.preferences import brian_prefs
@@ -236,8 +237,10 @@ def prepare(self):
self._prepared = True
def _nextclocks(self):
- minclock = min(self._clocks)
- curclocks = set(clock for clock in self._clocks if clock==minclock)
+ minclock = min(self._clocks, key=lambda c: c.t_)
+ curclocks = set(clock for clock in self._clocks if
+ (clock.t_ == minclock.t_ or
+ abs(clock.t_ - minclock.t_)<Clock.epsilon))
return minclock, curclocks
@check_units(duration=second, report_period=second)
View
6 brian2/core/operations.py
@@ -63,7 +63,7 @@ def network_operation(*args, **kwds):
>>> from brian2 import *
>>> @network_operation
... def f():
- ... print 'something'
+ ... print('something')
...
>>> net = Network(f)
@@ -71,7 +71,7 @@ def network_operation(*args, **kwds):
>>> @network_operation
... def f(t):
- ... print 'The time is', t
+ ... print('The time is', t)
...
>>> net = Network(f)
@@ -80,7 +80,7 @@ def network_operation(*args, **kwds):
>>> myclock = Clock(dt=0.5*ms)
>>> @network_operation(when=(myclock, 'start', 0))
... def f():
- ... print 'This will happen at the start of each timestep.'
+ ... print('This will happen at the start of each timestep.')
...
>>> net = Network(f)
View
10 brian2/memory/dynamicarray.py
@@ -60,11 +60,11 @@ class DynamicArray(object):
>>> x.resize((4, 4))
>>> x[:] += 1
>>> x.data[:] = x.data**2
- >>> print x.data
- [[16 16 16 4]
- [16 16 16 4]
- [ 9 9 9 4]
- [ 1 1 1 1]]
+ >>> x.data
+ array([[16, 16, 16, 4],
+ [16, 16, 16, 4],
+ [ 9, 9, 9, 4],
+ [ 1, 1, 1, 1]])
Notes
-----
View
8 brian2/stateupdaters/integration.py
@@ -61,11 +61,11 @@ def split_expression(expr):
Examples
--------
- >>> print split_expression('dt * f(x, t)')
+ >>> split_expression('dt * f(x, t)')
(dt*f(x, t), None)
- >>> print split_expression('dt * f(x, t) + dW * g(x, t)')
+ >>> split_expression('dt * f(x, t) + dW * g(x, t)')
(dt*f(x, t), dW*g(x, t))
- >>> print split_expression('1/(2*dt**.5)*(g_support - g(x, t))*(dW**2)')
+ >>> split_expression('1/(2*dt**.5)*(g_support - g(x, t))*(dW**2)')
(0, dW**2*dt**(-0.5)*g_support/2 - dW**2*dt**(-0.5)*g(x, t)/2)
'''
@@ -255,7 +255,7 @@ def replace_func(x, t, expr, temp_vars):
replacements (see `_generate_RHS`).
'''
try:
- s_expr = parse_to_sympy(expr, local_dict=symbols)
+ s_expr = parse_to_sympy(unicode(expr), local_dict=symbols)
except SympifyError as ex:
raise ValueError('Error parsing the expression "%s": %s' %
(expr, str(ex)))
View
9 brian2/tests/test_functions.py
@@ -11,8 +11,15 @@ def test_math_functions():
'''
test_array = np.array([-1, -0.5, 0, 0.5, 1])
- with catch_logs() as _: # Let's suppress warnings about illegal values
+ # We can only test C++ if weave is availabe
+ try:
+ import scipy.weave
languages = [PythonLanguage(), CPPLanguage()]
+ except ImportError:
+ # Can't test C++
+ languages = [PythonLanguage()]
+
+ with catch_logs() as _: # Let's suppress warnings about illegal values
for lang in languages:
# Functions with a single argument
View
13 brian2/tests/test_units.py
@@ -1,7 +1,6 @@
import itertools
import warnings
import pickle
-from exceptions import RuntimeWarning
import numpy as np
from numpy.testing import assert_raises, assert_equal
@@ -14,6 +13,7 @@
Unit,
have_same_dimensions,
get_dimensions,
+ is_scalar_type,
DimensionMismatchError,
check_units,
in_unit,
@@ -99,6 +99,17 @@ def test_get_dimensions():
assert_equal(dims.get_dimension('length'), 0)
assert get_dimensions(5) is DIMENSIONLESS
+ assert get_dimensions(5.0) is DIMENSIONLESS
+ assert get_dimensions(np.array(5, dtype=np.int)) is DIMENSIONLESS
+ assert get_dimensions(np.array(5.0)) is DIMENSIONLESS
+ assert get_dimensions(np.float32(5.0)) is DIMENSIONLESS
+ assert get_dimensions(np.float64(5.0)) is DIMENSIONLESS
+ assert is_scalar_type(5)
+ assert is_scalar_type(5.0)
+ assert is_scalar_type(np.array(5, dtype=np.int))
+ assert is_scalar_type(np.array(5.0))
+ assert is_scalar_type(np.float32(5.0))
+ assert is_scalar_type(np.float64(5.0))
assert_raises(TypeError, lambda: get_dimensions('a string'))
# wrong number of indices
View
15 brian2/tests/test_utils.py
@@ -4,20 +4,25 @@ def test_environment():
'''
Test information about the environment we are running under.
'''
- import __builtin__
+ try:
+ # Python 2
+ import __builtin__ as builtins
+ except ImportError:
+ # Python 3
+ import builtins
- if hasattr(__builtin__, '__IPYTHON__'):
+ if hasattr(builtins, '__IPYTHON__'):
testing_under_ipython = True
- del __builtin__.__IPYTHON__
+ del builtins.__IPYTHON__
else:
testing_under_ipython = False
assert not running_from_ipython()
- __builtin__.__IPYTHON__ = True
+ builtins.__IPYTHON__ = True
assert running_from_ipython()
if not testing_under_ipython:
- del __builtin__.__IPYTHON__
+ del builtins.__IPYTHON__
if __name__ == '__main__':
View
65 brian2/units/fundamentalunits.py
@@ -15,10 +15,11 @@
"""
from __future__ import division
+import numbers
+import collections
from warnings import warn
-from operator import isNumberType, isSequenceType
import operator
-from itertools import izip
+import itertools
import numpy as np
@@ -348,11 +349,11 @@ def __str__(self):
# wrong sort of input
def __mul__(self, value):
return get_or_create_dimension([x + y for x, y in
- izip(self._dims, value._dims)])
+ itertools.izip(self._dims, value._dims)])
def __div__(self, value):
return get_or_create_dimension([x - y for x, y in
- izip(self._dims, value._dims)])
+ itertools.izip(self._dims, value._dims)])
def __truediv__(self, value):
return self.__div__(value)
@@ -382,6 +383,9 @@ def __eq__(self, value):
def __ne__(self, value):
return not self.__eq__(value)
+ def __hash__(self):
+ return hash(self._dims)
+
#### MAKE DIMENSION PICKABLE ####
def __getstate__(self):
return self._dims
@@ -433,7 +437,7 @@ def get_or_create_dimension(*args, **kwds):
e.g. length, metre, and m all refer to the same thing here.
"""
if len(args):
- if isSequenceType(args[0]) and len(args[0]) == 7:
+ if isinstance(args[0], collections.Sequence) and len(args[0]) == 7:
# initialisation by list
dims = args[0]
else:
@@ -506,7 +510,10 @@ def is_scalar_type(obj):
``True`` if `obj` is a scalar that can be interpreted as a
dimensionless `Quantity`.
"""
- return isNumberType(obj) and not isSequenceType(obj)
+ if isinstance(obj, np.number) or isinstance(obj, np.ndarray):
+ return np.isscalar(obj) or np.ndim(obj) == 0
+ else:
+ return isinstance(obj, numbers.Number)
def get_dimensions(obj):
@@ -527,8 +534,9 @@ def get_dimensions(obj):
dim: `Dimension`
The dimensions of the `obj`.
"""
- if isNumberType(obj) and not isinstance(obj, Quantity):
- return DIMENSIONLESS
+ if (isinstance(obj, numbers.Number) or isinstance(obj, np.number) or
+ isinstance(obj, np.ndarray) and not isinstance(obj, Quantity)):
+ return DIMENSIONLESS
try:
return obj.dimensions
except AttributeError:
@@ -612,6 +620,7 @@ def in_unit(x, u, precision=None):
>>> in_unit(10 * mV, ohm * amp)
'0.01 ohm A'
>>> in_unit(10 * nS, ohm) # doctest: +NORMALIZE_WHITESPACE
+ ... # doctest: +IGNORE_EXCEPTION_DETAIL
Traceback (most recent call last):
...
DimensionMismatchError: Non-matching unit for method "in_unit",
@@ -741,21 +750,21 @@ class Quantity(np.ndarray, object):
>>> from brian2 import *
>>> I = 3 * amp # I is a Quantity object
>>> R = 2 * ohm # same for R
- >>> print I * R
- 6.0 V
- >>> print (I * R).in_unit(mvolt)
- 6000.0 mV
- >>> print (I * R) / mvolt
- 6000.0
- >>> X = I + R
+ >>> I * R
+ 6.0 * volt
+ >>> (I * R).in_unit(mvolt)
+ '6000.0 mV'
+ >>> (I * R) / mvolt
+ Quantity(6000.0)
+ >>> X = I + R # doctest: +IGNORE_EXCEPTION_DETAIL
Traceback (most recent call last):
...
DimensionMismatchError: Addition, dimensions were (A) (m^2 kg s^-3 A^-2)
>>> Is = np.array([1, 2, 3]) * amp
- >>> print Is * R
- [ 2. 4. 6.] V
- >>> print np.asarray(Is * R) # gets rid of units
- [ 2. 4. 6.]
+ >>> Is * R
+ array([ 2., 4., 6.]) * volt
+ >>> np.asarray(Is * R) # gets rid of units
+ array([ 2., 4., 6.])
See also
--------
@@ -1221,26 +1230,26 @@ def __imul__(self, other):
inplace=True)
def __div__(self, other):
- return self._binary_operation(other, operator.div, operator.div)
+ return self._binary_operation(other, operator.truediv, operator.truediv)
def __truediv__(self, other):
return self.__div__(other)
def __rdiv__(self, other):
# division with swapped arguments
- rdiv = lambda a, b: operator.div(b, a)
+ rdiv = lambda a, b: operator.truediv(b, a)
return self._binary_operation(other, rdiv, rdiv)
def __rtruediv__(self, other):
return self.__rdiv__(other)
def __idiv__(self, other):
- return self._binary_operation(other, np.ndarray.__idiv__, operator.div,
- inplace=True)
+ return self._binary_operation(other, np.ndarray.__itruediv__,
+ operator.truediv, inplace=True)
def __itruediv__(self, other):
return self._binary_operation(other, np.ndarray.__itruediv__,
- operator.div, inplace=True)
+ operator.truediv, inplace=True)
def __mod__(self, other):
return self._binary_operation(other, operator.mod,
@@ -1527,8 +1536,8 @@ class Unit(Quantity):
You can then do
- >>> print (1*Nm).in_unit(Nm)
- 1.0 N m
+ >>> (1*Nm).in_unit(Nm)
+ '1.0 N m'
which returns ``"1 N m"`` because the `Unit` class generates a new
display name of ``"N m"`` from the display names ``"N"`` and ``"m"`` for
@@ -1647,7 +1656,7 @@ def create(dim, name="", dispname="", scalefactor="", **keywords):
for k in keywords:
scale[_di[k]] = keywords[k]
v = 1.0
- for s, i in izip(scale, dim._dims):
+ for s, i in itertools.izip(scale, dim._dims):
if i:
v *= _siprefixes[s] ** i
u = Unit(v * _siprefixes[scalefactor], dim=dim, scale=tuple(scale))
@@ -1980,7 +1989,7 @@ def check_units(**au):
explicitly named in the definition of the function. For example, the code
above checks that the variable wibble should be a length, so writing
- >>> getvoltage(1*amp, 1*ohm, wibble=1)
+ >>> getvoltage(1*amp, 1*ohm, wibble=1) # doctest: +IGNORE_EXCEPTION_DETAIL
Traceback (most recent call last):
...
DimensionMismatchError: Function "getvoltage" variable "wibble" has wrong dimensions, dimensions were (1) (m)
View
4 brian2/utils/environment.py
@@ -2,7 +2,7 @@
Utility functions to get information about the environment Brian is running in.
'''
-import __builtin__
+import __builtin__ as builtins
def running_from_ipython():
'''
@@ -13,5 +13,5 @@ def running_from_ipython():
ipython : bool
Whether running under ipython or not.
'''
- return getattr(__builtin__, '__IPYTHON__', False)
+ return getattr(builtins, '__IPYTHON__', False)
View
21 brian2/utils/logger.py
@@ -1,7 +1,6 @@
'''
Brian's logging module.
'''
-
import atexit
import logging
import os
@@ -26,6 +25,10 @@
# Initial setup
#===============================================================================
+def _encode(text):
+ ''' Small helper function to encode unicode strings as UTF-8. '''
+ return text.encode('UTF-8')
+
# get the root logger
logger = logging.getLogger('')
logger.setLevel(logging.DEBUG)
@@ -33,10 +36,10 @@
# Log to a file
try:
# Temporary filename used for logging
- TMP_LOG = tempfile.NamedTemporaryFile(prefix='brian_debug_', suffix='.log',
- delete=False)
+ TMP_LOG = tempfile.NamedTemporaryFile(prefix='brian_debug_',
+ suffix='.log', delete=False)
TMP_LOG = TMP_LOG.name
- FILE_HANDLER = logging.FileHandler(TMP_LOG, mode='w+b')
+ FILE_HANDLER = logging.FileHandler(TMP_LOG, mode='wt')
FILE_HANDLER.setLevel(logging.DEBUG)
FILE_HANDLER.setFormatter(logging.Formatter('%(asctime)s %(levelname)-8s %(name)s: %(message)s'))
logger.addHandler(FILE_HANDLER)
@@ -53,11 +56,13 @@
delete=False)
with tmp_file:
# Timestamp
- tmp_file.write('# %s\n' % time.asctime())
+ tmp_file.write(_encode(u'# %s\n' % time.asctime()))
# Command line arguments
- tmp_file.write('# Run as: %s\n\n' % (' '.join(sys.argv)))
+ tmp_file.write(_encode(u'# Run as: %s\n\n' % (' '.join(sys.argv))))
# The actual script file
- with open(os.path.abspath(sys.argv[0])) as script_file:
+ # TODO: We are copying the script file as it is, this might clash
+ # with the encoding we used for the comments added above
+ with open(os.path.abspath(sys.argv[0]), 'rb') as script_file:
shutil.copyfileobj(script_file, tmp_file)
TMP_SCRIPT = tmp_file.name
except IOError as ex:
@@ -426,7 +431,7 @@ class catch_logs(object):
WARNING brian2.logtest: An uncaught warning
>>> with catch_logs() as l:
... logger.warn('a caught warning')
- ... print 'l contains:', l
+ ... print('l contains: %s' % l)
...
l contains: [('WARNING', 'brian2.logtest', 'a caught warning')]
View
30 brian2/utils/stringtools.py
@@ -26,19 +26,19 @@ def indent(text, numtabs=1, spacespertab=4, tab=None):
--------
>>> multiline = """def f(x):
... return x*x"""
- >>> print multiline
+ >>> print(multiline)
def f(x):
return x*x
- >>> print indent(multiline)
+ >>> print(indent(multiline))
def f(x):
return x*x
- >>> print indent(multiline, numtabs=2)
+ >>> print(indent(multiline, numtabs=2))
def f(x):
return x*x
- >>> print indent(multiline, spacespertab=2)
+ >>> print(indent(multiline, spacespertab=2))
def f(x):
return x*x
- >>> print indent(multiline, tab='####')
+ >>> print(indent(multiline, tab='####'))
####def f(x):
#### return x*x
'''
@@ -66,16 +66,16 @@ def deindent(text, numtabs=None, spacespertab=4, docstring=False):
>>> multiline = """ def f(x):
... return x**2"""
- >>> print multiline
+ >>> print(multiline)
def f(x):
return x**2
- >>> print deindent(multiline)
+ >>> print(deindent(multiline))
def f(x):
return x**2
- >>> print deindent(multiline, docstring=True)
+ >>> print(deindent(multiline, docstring=True))
def f(x):
return x**2
- >>> print deindent(multiline, numtabs=1, spacespertab=2)
+ >>> print(deindent(multiline, numtabs=1, spacespertab=2))
def f(x):
return x**2
@@ -83,10 +83,10 @@ def f(x):
>>> docstring = """First docstring line.
... This line determines the indentation."""
- >>> print docstring
+ >>> print(docstring)
First docstring line.
This line determines the indentation.
- >>> print deindent(docstring, docstring=True)
+ >>> print(deindent(docstring, docstring=True))
First docstring line.
This line determines the indentation.
'''
@@ -125,7 +125,7 @@ def word_substitute(expr, substitutions):
--------
>>> expr = 'a*_b+c5+8+f(A)'
- >>> print word_substitute(expr, {'a':'banana', 'f':'func'})
+ >>> print(word_substitute(expr, {'a':'banana', 'f':'func'}))
banana*_b+c5+8+func(A)
'''
for var, replace_var in substitutions.iteritems():
@@ -143,9 +143,7 @@ def get_identifiers(expr):
>>> expr = 'a*_b+c5+8+f(A)'
>>> ids = get_identifiers(expr)
- >>> print type(ids)
- <type 'set'>
- >>> print sorted(list(ids))
+ >>> print(sorted(list(ids)))
['A', '_b', 'a', 'c5', 'f']
'''
return set(re.findall(r'\b[A-Za-z_][A-Za-z0-9_]*\b', expr))
@@ -160,7 +158,7 @@ def strip_empty_lines(s):
>>> multiline = """A string with
...
... an empty line."""
- >>> print strip_empty_lines(multiline)
+ >>> print(strip_empty_lines(multiline))
A string with
an empty line.
'''
View
36 dev/jenkins/test_brian2.sh
@@ -3,33 +3,47 @@ source /home/jenkins/.jenkins/virtual_envs/$PythonVersion/$packages/bin/activate
pip install --upgrade -I nose coverage || :
# Make sure pyparsing and ipython (used for pretty printing) are installed
-pip install pyparsing
+if [ ${PythonVersion:0:1} == '2' ]; then
+ pip install pyparsing==1.5.7
+ else
+ pip install pyparsing --upgrade
+fi
pip install ipython
# Make sure we have sphinx (for testing the sphinxext)
pip install sphinx
echo "Using newest available package versions"
-pip install --upgrade numpy
+
+# Use numpy 1.6.2 for Python 2 for now
+if [ ${PythonVersion:0:1} == '2' ]; then
+ pip install numpy==1.6.2
+ else
+ pip install numpy --upgrade
+fi
+
pip install --upgrade scipy
-pip install sympy==0.7.1
+pip install --upgrade sympy
pip install --upgrade matplotlib
# Print the version numbers for the dependencies
-python -c "import numpy; print 'numpy version: ', numpy.__version__"
-python -c "import scipy; print 'scipy version: ', scipy.__version__"
-python -c "import sympy; print 'sympy version: ', sympy.__version__"
-python -c "import matplotlib; print 'matplotlib version: ', matplotlib.__version__"
+python -c "import numpy; print('numpy version: ' + numpy.__version__)"
+python -c "import scipy; print('scipy version: ' + scipy.__version__)"
+python -c "import sympy; print('sympy version: ' + sympy.__version__)"
+python -c "import matplotlib; print('matplotlib version: ' + matplotlib.__version__)"
# Build Brian2
python setup.py build --build-lib=build/lib
-export PYTHONPATH=$(pwd)/build/lib:$PYTHONPATH
+
+# Move to the build directory to make sure it is used for testing
+# (important for Python 3)
+cd build/lib
# delete remaining compiled code from previous runs
echo deleting '~/.python*_compiled' if it exists
rm -r ~/.python*_compiled || :
# Run unit tests and record coverage but do not fail the build if anything goes wrong here
-coverage erase --rcfile=.coveragerc || :
-coverage run --rcfile=.coveragerc ~/.jenkins/virtual_envs/$PythonVersion/$packages/bin/nosetests --with-xunit --logging-clear-handlers --verbose --with-doctest brian2 || :
-coverage xml --rcfile=.coveragerc || :
+coverage erase --rcfile=../../.coveragerc || :
+coverage run --rcfile=../../.coveragerc ~/.jenkins/virtual_envs/$PythonVersion/$packages/bin/nosetests --with-xunit --logging-clear-handlers --verbose --with-doctest brian2 || :
+coverage xml -i --rcfile=../../.coveragerc || :
View
55 docs_sphinx/developer/guidelines/style.rst
@@ -1,6 +1,7 @@
Coding conventions
==================
-
+General recommendations
+-----------------------
Syntax is chosen as much as possible from the user point of view,
to reflect the concepts as directly as possible. Ideally, a Brian script
should be readable by someone who doesn't know Python or Brian, although this
@@ -50,20 +51,50 @@ for our code. This in particular includes the following conventions:
to specify what is being made available with a wildcard import. As an
exception from this rule, the main ``brian2/__init__.py`` may use wildcard
imports.
-
-Brian2 is no longer supporting Python versions 2.5 and older. Therefore, a
-couple of backwards-incompatible idioms can be used that will also ease a future
-transition towards Python 3. The `Porting to Python 3 <http://python3porting.com/>`__
-book is available online and has a couple of recommendations. For example:
+
+Python 2 vs. Python 3
+---------------------
+Brian is written in Python 2 but runs on Python 3 using the
+`2to3 <http://docs.python.org/2/library/2to3.html>`__ conversion tool (which is
+automatically applied if Brian is installed using the standard
+``python setup.py install`` mechanism). To make this possible without too much
+effort, Brian no longer supports Python 2.5 and can therefore make use of a
+couple of forward-compatible (but backward-incompatible) idioms introduced in
+Python 2.6. The `Porting to Python 3 <http://python3porting.com/>`__
+book is available online and has a lot of information on these topics. Here are
+some things to keep in mind when developing Brian:
* If you are working with integers and using division, consider using ``//``
for flooring division (default behaviour for ``/`` in python 2) and switch the
behaviour of ``/`` to floating point division by using
``from __future__ import division`` .
-* instead of ``except Exception, ex:`` use ``except Exception as ex:`` (note that plain ``except:`` should never be used!)
-* If you write code dealing with strings, possibly reading and writing them to
- files, make sure you make the distinction between bytes and unicode (see
- `"separate binary data and strings" <http://python3porting.com/preparing.html#separate-binary-data-and-strings>`__ )
+* If importing modules from the standard library (which have changed quite a
+ bit from Python 2 to Python 3), only use simple import statements like
+ ``import itertools`` instead of ``from itertools import izip`` -- *2to3* is
+ otherwise unable to make the correct conversion.
+* If you are using the ``print`` statement (which should only occur in tests,
+ in particular doctests -- always use the :doc:`logging` framework if you want
+ to present messages to the user otherwise), try "cheating" and use the
+ functional style in Python 2, i.e. write ``print('some text')`` instead of
+ ``print 'some text'``. More complicated print statements should be avoided,
+ e.g instead of ``print >>sys.stderr, 'Error message`` use
+ ``sys.stderr.write('Error message\n')`` (or, again, use logging).
+* Exception stacktraces look a bit different in Python 2 and 3: For non-standard
+ exceptions, Python 2 only prints the Exception class name (e.g.
+ ``DimensionMismatchError``) whereas Python 3 prints the name including the
+ module name (e.g. ``brian2.units.fundamentalunits.DimensionMismatchError``).
+ This will make doctests fail that match the exception message. In this case,
+ write the doctest in the style of Python 2 but add the doctest directive
+ ``#doctest: +IGNORE_EXCEPTION_DETAIL`` to the statement leading to the
+ exception. This unfortunately has the side effect of also ignoring the
+ text of the exception, but it will still fail for an incorrect exception type.
+* If you write code reading and writing strings to files, make sure you make
+ the distinction between bytes and unicode (see `"separate binary data and strings" <http://python3porting.com/preparing.html#separate-binary-data-and-strings>`__ )
+ In general, strings within Brian are unicode strings and only converted to
+ bytes when reading from or writing to a file (or something like a network
+ stream, for example).
* If you are sorting lists or dictionaries, have a look at
- `"when sorting, use key instead of cmp" <http://python3porting.com/preparing.html#when-sorting-use-key-instead-of-cmp>`__ )
-
+ `"when sorting, use key instead of cmp" <http://python3porting.com/preparing.html#when-sorting-use-key-instead-of-cmp>`__
+* Make sure to define a ``__hash__`` function for objects that define an
+ ``__eq__`` function (and to define it consistently). Python 3 is more strict
+ about this, an object with ``__eq__`` but without ``__hash__`` is unhashable.
View
37 setup.py
@@ -2,18 +2,34 @@
'''
Preliminary Brian2 setup script
'''
+import sys
from distutils.core import setup
+try:
+ from distutils.command.build_py import build_py_2to3 as build_py
+except ImportError:
+ from distutils.command.build_py import build_py
-from brian2.core.preferences import brian_prefs
+from distutils.command.install_data import install_data
-try:
- with open('./brian2/default_preferences', 'wt') as f:
- defaults = brian_prefs.defaults_as_file
- f.write(defaults)
-except IOError as ex:
- raise IOError(('Could not write the default preferences to a file: %s' %
- str(ex)))
+class install_preferences(install_data):
+ def run(self):
+ # Make sure we load the brian2 packages from the installation
+ # directory, not from the source directory (important for Python 3)
+ sys.path.insert(0, self.install_purelib)
+ from brian2.core.preferences import brian_prefs
+
+ try:
+ with open('./brian2/default_preferences', 'wt') as f:
+ defaults = brian_prefs.defaults_as_file
+ f.write(defaults)
+ except IOError as ex:
+ raise IOError(('Could not write the default preferences to a '
+ 'file: %s' % str(ex)))
+
+ # Now, run the original install_data command which copies the
+ # generated file to the appropriate place
+ install_data.run(self)
setup(name='Brian2',
version='2.0dev',
@@ -35,6 +51,7 @@
requires=['numpy(>=1.4.1)',
'scipy(>=0.7.0)',
'sympy(>=0.7.1)'
- ],
+ ],
+ cmdclass = {'build_py': build_py,
+ 'install_data': install_preferences}
)
-
Something went wrong with that request. Please try again.