[Tutor] Pythonify this code!
Dave Angel
davea at ieee.org
Mon Jul 13 13:21:51 CEST 2009
Muhammad Ali wrote:
> Hi,
>
> This is my first attempt at python. I tried my hand at steganography for
> fun. The code includes separate, combine, encode, decode functions. The
> separate function takes a number and it's base and returns a list containing
> each digit of the number in a list, vice versa for combine. The encode
> function encodes each char's ascii value into each pixel's RGB values at the
> least significant end. The decode function does the opposite. e.g.,
>
> a = 97, so if we have at a pixel r, g, b (230, 107, 155) it will become
> (230, 109, 157) when it's encoded.
>
> Since I am new and don't speak the python idiom yet, I would like the
> experts to pythonify the following code as much as possible for both python
> 2.6 and 3.0. Any other suggestion to improve the quality would also be
> highly appreciated
>
> Thanks a lot!
>
>
> #The code starts here:
>
> def separate(num, base):
> li = []
> while num / base > 0:
> li.insert(0, num % base)
> num = num / base
>
> li.insert(0,num)
> return li
>
> def combine(tup, base):
> num = 0
> mul = pow(base, len(tup) - 1)
> for i in tup:
> num = num + i * mul
> mul = mul / base
>
> return num
>
>
> #Will make changes to the original image and not to a copy! You have been
> warned!
> import Image
>
> def encode(img, text):
> x = 0
> y = 0
> height, width = img.size
>
> text = text + "~~"
>
> if len(text) > height * width:
> return false
>
> pix = img.load()
>
> for c in text:
> li = separate(ord(c), 10)
>
> if(len(li) == 1):
> li.insert(0, 0)
> li.insert(0, 0)
> elif(len(li) == 2):
> li.insert(0, 0)
>
> r, g, b = pix[x, y]
>
> r = r - (r % 10) + li[0]
> if (r > 255):
> r = r - 10
>
> g = g - (g % 10) + li[1]
> if (g > 255):
> g = g - 10
>
> b = b - (b % 10) + li[2]
> if (b > 255):
> b = b - 10
>
> pix[x,y] = (r,g,b)
>
> if y == width - 1:
> y = 0
> x = x + 1
> else:
> y = y + 1
>
> img.save(img.filename)
>
> def decode(img):
> x = 0
> y = 0
> text = ""
> c = ""
> height, width = img.size
> pix = img.load()
> while 1:
> r, g, b = pix[x, y]
> if (c == '~') and chr(combine([r % 10, g % 10, b % 10], 10)) == '~':
> return text[:len(text) - 1]
>
> c = chr(combineUnits([r % 10, g % 10, b % 10], 10))
> text = text + c
>
> if y == width - 1:
> y = 0
> x = x + 1
> else:
> y = y + 1
>
>
Pretty good for your first Python code. Clearly you have some
experience in other language(s). Comments follow, no particular order.
Import should be at begin of module, unless it's just plain not
possible. Functions should have doc-strings, explaining what they do,
and what the arguments look like. For example, first two functions
wouldn't work with floats, only ints. There also should be comments
stating what ranges of values are legal. For example, num is a positive
integer, and base is an integer between one and 250 or so.
separate() could be simplified and optimized, some depending on the
range of expected parameters, but below we'll see that it should be
replaced entirely. So don't optimize prematurely.
separate() and combine() both naturally want to do the digits in the
oppposite order, and nothing else cares. But again, it's premature
optimization.
Looking at encode(), we see that the only call to separate() is with 10
as a base. And it immediately takes the nicely formatted number, and
pads it to 3 digits. Sounds like a standard formatting operation to me,
so why not just use the format method with a 3 for width? Then loop
over the string, or even use a list comprehension on it.
encode() refers to }false", but the built-in value is called "False"
encode() uses repetitive code to combine two tuple/lists. Ideal for a
separate function, perhaps using zip(). Then we could replace 14 lines
with:
pix[x, y] =pixel(pix[x, y], li)
in encode(), row/column incrementing could be simplified, and common
case sped up:
y += 1
if y == width: y, x = 0, x+1
Incidentally, aren't you processing the pixels in column-major order?
Doesn't matter much for tiny images, but for symmetric designs, it's
usually better to keep memory accesses localized, to reduce swapping. I
could be all wet here, though, just going on memory.
decode() refers to combineUnits() in one of its two calls, but it's
actually called combine(). Further, there's no need to call it twice.
Better would be to have two "c" variables, maybe calling one of them 'lastc'
lastc = c
c = chr(combineUnits([r % 10, g % 10, b % 10], 10))
if (c == '~') and lstc == '~':
return text[:len(text) - 1]
In decode(), while 1 is usually spelled: while True
Hope these help.
DaveA
More information about the Tutor
mailing list