Spot any errors? let me know, but Unleash your pedant politely please.

Tuesday, 15 July 2014

PEP 20 > PEP 8

While trying to become a better Pythoneer, I'm struggling a little with PEP 8. I'm isolated, so I kind of don't care, but at some point I may have to learn to play well with others, so I should. I got a reminder of PEP 8 during a brief attempt to use PyCharm. I've only very briefly used an IDE with any success. That was with Eclipse and Java. That was only with help from people and online tutorials. PyCharm pointed out the myriad ways I was breaking PEP 8. It didn't tell me the ways I was conforming to PEP 20. Some code from PEP 8:
class Rectangle(Blob):

    def __init__(self, width, height,
                 color='black', emphasis=None, highlight=0):
        if (width == 0 and height == 0 and
                color == 'red' and emphasis == 'strong' or
                highlight > 100):
            raise ValueError("sorry, you lose")
        if width == 0 and height == 0 and (color == 'red' or
                                           emphasis is None):
            raise ValueError("I don't think so -- values are %s, %s" %
                             (width, height))
        Blob.__init__(self, width, height,
                      color, emphasis, highlight)
 
And this is how I'd probably write it:
class Rectangle(Blob):

    def __init__(self,
                 width,
                 height,
                 color     = 'black',
                 emphasis  = None,
                 highlight = 0):
                 
        if  0 == width == height \
        and color == 'red'       \
        and emphasis == 'strong' \
        or  highlight > 100:
            raise ValueError("sorry, you lose")

        if  0 == width == height \
        and (color == 'red' or emphasis is None):
            raise ValueError("I don't think so -- values are %s, %s" %
                             (width, height))

        Blob.__init__(self      = self,
                      width     = width,
                      height    = height,
                      color     = color,
                      emphasis  = emphasis,
                      highlight = highlight)
From PEP 20, my intention is to hit the following:
Beautiful is better than ugly. Explicit is better than implicit. Readability counts.
And from PEP 8, I'm playing the trump card:
When in doubt, use your best judgment.
Some preferences, like extra spaces here and there, are obviously subjective. Objectively, my experience of writing and maintaining code in Ada and Pascal tells me to always be explicit about using named actual parameters. It's probably even more important in a dynamically typed language. If Python didn't allow me to do this, I'd likely have rejected the language. That is has this feature in common with Ada and the indented scope of Occam (No braces, Yay!) made it a no-brainer for me. The main problem would appear to be that it's longer. Less code fits on-screen at once. I've never found that to be a problem even when I was editing in eve on VMS from a VT220. This wouldn't bother me much, even though I think it's less readable:
        Blob.__init__(self=self,
                      width=width,
                      height=height,
                      color=color,
                      emphasis=emphasis,
                      highlight=highlight)
But this drives me insane:
        Blob.__init__(self,
                      height,
                      width,
                      color,
                      emphasis,
                      highlight)
If you don't understand why, the deliberate mistake should be enough to persuade you that in the majority of cases, you really want to be be explicit about those parameter names. I'd only an the exception for functions that only take one parameter. Look, this is still fine even though the order deviated from the formal parameter list:
        Blob.__init__(self      = self,
                      height    = height,
                      width     = width,
                      color     = color,
                      emphasis  = emphasis,
                      highlight = highlight)
And this is impossible to do sober unless it's deliberate. It's also easy to spot in a review or when debugging:
        Blob.__init__(self      = self,
                      width     = height,
                      height    = width,
                      color     = color,
                      emphasis  = emphasis,
                      highlight = highlight)

Wednesday, 9 July 2014

Idiotic Python

One of the problems of learning a programming language is that sometimes things one has written even relatively recently look idiotic.

Sometimes this is due to some bad advice on teh internets. Or perhaps misunderstanding an explanation.

I'd consider that I'm getting reasonably proficient in Python these days. I've had no formal teaching though, and I'm working in isolation, so I don't get feedback from colleagues or a mentor. I'm a lurker at the edge of the Python community.

I'm trying to improve.  I bought Writing Idiomatic Python by Jeff Knupp.  I read through it, skimming for things both do already and don't do/never knew about.  Things I already do were pat-on-the-back affirming, and I think confirmed I'm essentially on the right track.  The rest is a learning opportunity.

When there's a lull at work, which isn't often, I open the book, and pick something to check against in my current project. Yesterday I looked at Avoid comparing directly to True, False, or None and started searching for ==True==False==None; !=True; !=False; !=Nonethinking there'd be a few to fix.  I found far more than I though would be there.  Most disappointingly, quite a few !=False and ==True.

I fixed a lot of this.  Sometimes it seems less readable, so not in all places yet.

A significant penny dropped though, even though I've read this section a few times and nodded to myself as though I understood.  It's the use of is not None to check whether optional parameters have been set.  Sadly, my code is riddled with well meaning but potentially harmful attempts to check optional parameters.

Another penny is teetering on the edge.  In trying to go from harmful to idiomatic, I've made a significant number of minor changes. Some of these alter the order of an if statement to remove negation from the condition:
if not weekend:
    work()
else:
    relax()
if weekend:
    relax()
else:
    work()

None of the changes are difficult, but there's risk of human error here which will be very difficult to find without adequate unit tests.  I don't have unit tests.  I've certainly tested as I've developed, but those tests were transient, not part of a test suite.

In a quick search on Python unit testing, I stumbled across a nice little intro from Jeff Knupp (again).

Sigh. I fear that retro-fitting unit tests is going to expose some unpleasant coupling.


Saturday, 5 July 2014

Football is the World Game

I'm not an Association Football fan. I much prefer Rugby football.

I used to play for fun/exercise in my 20s. Not to any standard, but enough to be able to appreciate the game a little.  I watch Match of The Day sometimes, and usually get sucked in to The World Cup, because the standard is usually pretty high.

In the Internet age, this means that every four years, I get exposed to Americans belittling soccer.

http://daringfireball.net/thetalkshow/2014/06/16/ep-085

The real reason for this is indoctrination in a sport other than football.  It's simply cultural bias. It's why I prefer Rugby, why Texans prefer Gridiron, why Canadians prefer Hockey, or the Swiss prefer Tennis.

It's important to understand that football is the world game.  I suspect it's popular for two reasons. The rules are really pretty simple, so it can be grasped easily. The only equipment needed is a field, or a street or a car park; a football, volleyball, tennis ball, tin can, or even a stone. And, of course, jumpers for goalposts.  It's a game all almost all children, all over the world can play for free.  In unsupervised children's games, the only vaguely complex rule, offside, is discarded in favour of simply ridiculing goal-hangers.  Offside is enforced by peer pressure.

A common theme in disparaging football is that the one thing that separates us from animals is our hands, and hands aren't allowed.  I find this puzzling. The main thing that separates us from animals is our brains.  Other animals have hands, can use tools, are bipedal etc.  None can understand and play football.

I'd add something about baseball fielders needing catchers' mitts, which seems laughable to someone who's played cricket, but that would just be wallowing in the ignorance of my own cultural bias.

I switched from Markdown Pro to Macdown

The main reason: Unlike MarkDown Pro,  syntax highlighting is built in to MacDown .

MacDown is also free, although I'm perfectly happy to pay for good software I find useful.

The markup for code in MacDown is very simple:



This is a lazy stop-gap. I ought to be generating documentation from embedded docstrings automatically …but that's for another day.

Thursday, 1 May 2014

Dynamically adding instance methods.

I've been playing with some code today. It works, but may well be too clever for its own good. It may be slightly insane. There may be much better ways to do this. I'd written a little class (SingleCommandSFTP) to make some other code a little shorter and neater. With one command, it was no problem, but when it expanded to three, the duplication of calls to __open and __close irked a little, so I added __run_command. The pragmatic thing to do was to stop. But unfortunately, I was having fun, and wondered about dynamically adding instance methods to call __run_command for all the available commands. I ended up with this:
class SingleCommandSFTP(object):
    """
    SingleCommandSFTP is a simple abstraction of the the Paramiko
    SFTP client.   

    http://www.lag.net/paramiko/docs/paramiko.SFTPClient-class.html
    
    e.g.
        sftp_client = SingleCommandSFTP(host     = "hostname",
                                        username = "username",
                                        password = "password")

        sftp_client.remove(path = '/mnt/a/b/file.txt')
    
    It is intended mainly for single operations as the
    connection is opened and closed for each command.
    For multiple commands it will be inefficient.
    """
    
    def __init__(self,
                 host,
                 username,
                 password,
                 port = 22):

        self.host     = host
        self.username = username
        self.password = password
        self.port     = port
        
        
    def __open(self):
        self.transport = paramiko.Transport((self.host,
                                             self.port))
        self.transport.connect(username = self.username,
                               password = self.password) 
        self.sftp_client = paramiko.SFTPClient.from_transport(self.transport)

        
    def __close(self):
        self.sftp_client.close()
        self.transport.close()
        
    def __run_command(self,
                      command,
                      **params):
        
        """
        Call to a paramiko.SFTPClient.'command' instance method.
        """

        log.info('sftp_client.{command}({parameters})'.format(command    = command,
                                                  parameters = params))
        self.__open()
        method = getattr(self.sftp_client,command)
        method(**params)
        self.__close()



    # instance methods using __run_command for all instance methods of SFTPClient
    # are added once, dynamically, below. 
    # SFTPClient Reference can be found at http://www.lag.net/paramiko/docs/paramiko.SFTPClient-class.html
    

def instance_method_code_string(object,attr):
    argspec = inspect.getargspec(getattr(object,attr))
    parameters = [arg for arg in argspec.args]
    if argspec.defaults:
        for index in range(len(argspec.defaults)):
           try:
               parameters[-1-index]+='="' + argspec.defaults[-1-index] + '"'
           except:
               parameters[-1-index]+='=%s' %argspec.defaults[-1-index]
    if argspec.varargs:
        parameters.append['*'+argspec.varargs]
    if argspec.keywords:
        parameters.append['*'+argspec.keywords]

    code_string = "def {name}({parameters}):\n".format(name       = attr,
                                                       parameters = ','.join(parameters))
    code_string +="    self._SingleCommandSFTP__run_command(command='{name}',{parameters}"\
                  .format(name       = attr,
                          parameters = ','.join('{p}={p}'.format(p=p) for p in argspec.args[1:]))

    if argspec.varargs:
        code_string +='*'+argspec.varargs
    if argspec.keywords:
        code_string +='**'+argspec.keywords

    code_string +=")\n"
    return code_string
    
def add_SFTPClient_equivalent_instance_methods_to_SingleCommandSFTP():
    
    instance_method_names = [attr for attr in dir(paramiko.SFTPClient) if getattr(paramiko.SFTPClient,attr).__class__==paramiko.SFTPClient.__init__.__class__ and attr[0]!='_']
    
    for instance_method_name in instance_method_names:
        code_string = instance_method_code_string(object=paramiko.SFTPClient, attr=instance_method_name)
        exec(code_string)
        setattr(SingleCommandSFTP,instance_method_name, eval(instance_method_name))


if 'instance_methods_added' not in dir():
    instance_methods_added = True       
    add_SFTPClient_equivalent_instance_methods_to_SingleCommandSFTP()
    

getattr, setattr, eval, exec, getargspec. I won't have bloody clue what this does in two weeks' time!

Saturday, 5 April 2014

#RedirectToAppStoreAdsSuck

#RedirectToAppStoreAdsSuck

I'm getting a bit fed up with ads in my browser redirecting me to the App Store. So I decided to install the apps (if they are free), immediately remove them, and leave a negative review.

I suggest we all do this if we have a spare minute. The danger is that the apps climb the charts, but I found the process to be cathartic.

Feel free to cut and paste the text below into the review.


"I Installed and immediately deleted this app without opening it in order to leave this negative review to complain about being redirected to the App Store from an ad in a web page.

Seriously, this is an intensely intrusive, terrible way to try to get me to try your app.

Please stop!

#RedirectToAppStoreAdsSuck"

Thursday, 19 December 2013

Markdown Pro and Syntax Highlighting

I've been writing some documentation for some code in markdown using Markdown Pro, but the code looked a little boring:

def func(blah):
    pass

I've used Alex Gorbatchev's Syntax Highlighter on Blogger, so I figured I could use it for these pages too.

def func(blah):
    pass

This worked quite well, and I wrote a script (thinking that I was being clever) to add the syntax highlighter code to all the HTML files in a folder hierarchy.  The only problem was that the syntax highlighting code isn't shown in the Markdown HTML view, XML inside pre tags was blank.

A couple of days ago, I started tweaking the Markdown Pro generated css to make the tables a bit nicer and colours of headings stand out a little more.  Last night, as I was dropping off to sleep, it occurred to me that I may be able to add the highlighter css and javascript to a Markdown Pro template too.  Just tried it, and it works. The template has to be re-applied to refresh after changes in order to force the javascript to run. Export to HTML is still good. It's necessary to refresh in order to save the highlighted code to PDF.


Note that the 'template' isn't the usual simple template, but all of the exported css that Markdown Pro would generate on exporting to HTML, (with <script> tags).

(The 'template' file is here if you're curious)