HiveBrain v1.2.0
Get Started
← Back to all entries
patternpythonMinor

Python Barcode Generator

Submitted by: @import:stackexchange-codereview··
0
Viewed 0 times
barcodepythongenerator

Problem

I was asked to provide some example code for a job interview and didn't have anything to offer, so wrote the following function.

A barcode generator may seem a bit basic, but there's some logic behind the choice:

  • It's something I recently needed, so not entirely contrived.



  • It's simple enough to do in one function, so it can be reviewed easily.



  • It's complex enough that a new programmer would make a mess of it.



  • It's visual. You can render the result in a browser.



  • It's useful, so I could make a little gist out of it.



The barcode is in the Interleaved 2 of 5 Format. It's a simple format, and there's a Wikipedia article that covers it well.

Update: A new version of this code has been posted here. The new code includes many of the suggestions made below.

``
def render(digits):

'''This function converts its input, a string of decimal digits, into a
barcode, using the interleaved 2 of 5 format. The input string must not
contain an odd number of digits. The output is an SVG string.

import barcode
svg = barcode.render('0123456789')

Strings are used to avoid problems with insignificant zeros, and allow
for iterating over the bits in a byte [for zipping].

# Wikipedia, ITF Format: http://en.wikipedia.org/wiki/Interleaved_2_of_5
'''

# Encode: Convert the digits into an interleaved string of bits, with a
# start code of four narrow bars [four zeros].

bits = '0000'
bytes = {
'0': '00110', '5': '10100',
'1': '10001', '6': '01100',
'2': '01001', '7': '00011',
'3': '11000', '8': '10010',
'4': '00101', '9': '01010'
}

for i in range(0, len(digits), 2):

# get the next two digits and convert them to bytes
black, white = bytes[digits[i]], bytes[digits[i+1]]

# zip and concat to
bits` 5 pairs of bits from the 2 bytes
for black, white in zip(black, white): bits += black + white

# Render: Convert t

Solution

The code generally looks pretty good, but I think there are some things you could fix up here.

Don't forget the stop code

You have the start code of 0000 but you've forgotten the stop code which is 100. When you calculate, you could just tack it onto the end with bits += '100'

Use SVG `s

Since the purpose is to create an SVG barcode, it make sense to tighten the resulting SVG. You may already know how to use a
in SVG and it definitely makes things a little easier to understand here. The way your code is currently structures, it creates four different styles of which are all combinations of narrow/wide and black/white. You could predefine each of those shapes and then simply intantiate them within the body of the svg code. That would make the definition for svg and bar like this:

svg = '''

'''
bar = ''


Using it would then be

svg += bar.format(bit, pos, style)


An improvement would be to create both bars and spaces like this:



Then your loop could look then like this:

for i, bit in enumerate(bits):
    width = int(bit) * 2 + 2
    svg += bar.format('bs'[i%2], bit, pos)
    pos += width


Although that may look verbose, it actually is over 2k shorter for a 12 digit barcode.

Think carefully about data representation

Your code currently translates digits into a series of '1' and '0' characters and then translates again into SVG rectangles. Why not eliminate a step? Your code could just as easily translate them in a single operation.

Use list comprehensions instead of
for loops

The use of list comprehensions is almost always faster than a
for loop in Python, so we use them when we can. It also tends to make the code shorter. So for example, we could change your loop to calculate the string of bar code bits to calculate all the black bits and then all the white bits like this:

black = "".join([bytes[i] for i in digits[0::2]])
white = "".join([bytes[i] for i in digits[1::2]])
# shuffle them together
databits = "".join("".join(i) for i in zip(black,white))
# create the full bar code string with start and stop
bits = "".join(['0000',databits,'100'])


Doing this with
join also saves time. Appending strings with += is very slow in Python.

Prefer
xrange to range

When you use
range, a whole list object is created, using memory. With xrange, a generator is created instead populated which can save memory. For any reasonably sized bar code this won't make much difference here, but it's good practice for Python 2.7. In Python 3, xrange doesn't exist and range creates a generator, so keep that in mind if you change versions.

Don't draw more than you have to

You're drawing one
` for every bar or space, but it's really not necessary. Instead, you could create one larger rectangle that's the "background" space color and then only draw the black bars.

Code Snippets

svg = '''<svg height="50"><defs>
<g id="b0"><rect x="0" y="0" width="2" height="50"/></g>
<g id="b1"><rect x="0" y="0" width="4" height="50"/></g>
</defs>'''
bar = '<use xlink:href="#b{0}" x="{1}" y="0" {2}/>'
svg += bar.format(bit, pos, style)
<g id="b0"><rect x="0" y="0" width="2" height="50" style="fill:rgb(0,0,0)"/></g>
<g id="b1"><rect x="0" y="0" width="4" height="50" style="fill:rgb(0,0,0)"/></g>
<g id="s0"><rect x="0" y="0" width="2" height="50" style="fill:rgb(255,255,255)"/></g>
<g id="s1"><rect x="0" y="0" width="4" height="50" style="fill:rgb(255,255,255)"/></g>
for i, bit in enumerate(bits):
    width = int(bit) * 2 + 2
    svg += bar.format('bs'[i%2], bit, pos)
    pos += width
black = "".join([bytes[i] for i in digits[0::2]])
white = "".join([bytes[i] for i in digits[1::2]])
# shuffle them together
databits = "".join("".join(i) for i in zip(black,white))
# create the full bar code string with start and stop
bits = "".join(['0000',databits,'100'])

Context

StackExchange Code Review Q#49409, answer score: 5

Revisions (0)

No revisions yet.