5
\$\begingroup\$

I'm writing a Python script to randomly generate C functions. This is my first time using classes in Python and I'm basically doing my best to pretend I'm using Java.

I'm looking for a review of my OOP, Python style, and in general whether or not I'm doing things in the "Pythonic" way, whatever that means.

import random
import sys
import string

# writes text to a stored outputString
# fetch the outputString later for outputting
class Output:
    def __init__(self):
        self.clear()

    def clear(self):
        self.outputString = ''
        self.__lineStart = True
        self.lineCount = 0
        self.nestingLevel = 0           

    def __write(self, str):
        self.outputString += str    

    def line(self):
        self.__write('\n')
        self.__lineStart = True
        self.lineCount += 1

    def output(self, str):
        # start each line with tabs equal to nesting level      
        if(self.__lineStart == True):
            for q in range(0, self.nestingLevel):
                self.__write('\t')
            self.__lineStart = False

        self.__write(str)

    def incNesting(self):
        self.nestingLevel += 1

    def decNesting(self):
        self.nestingLevel -= 1

class Variables:
    def __init__(self, varCount):
        if varCount > 26:
            raise Exception('Don\'t you think you\'re being a bit too aggressive?')
        self.varCount = varCount
        lowercase = list(string.ascii_lowercase)
        self.variables = lowercase[0:varCount]

    def Rand(self, count = 1):
        if count > self.varCount:
            raise Exception('Variables.Rand requested more variables than available')
        elif count == 1:
            return random.choice(self.variables)
        elif count > 1:
            return random.sample(self.variables, count)
        else:
            raise Exception('Variables.Rand invalid argument')

    def InitStatement(self, out):
        out.output('int ')
        vars = []
        for var in self.variables:
            vars.append('%s = %i' % (var, random.randint(-0xFFFFFFF, 0xFFFFFFF)))
        out.output(', '.join(vars))
        out.output(';')
        out.line()      

class Action:
    def __init__(self, variables, output):
        self.variables = variables
        self.out = output
        self.labelCount = 0

class ControlSelector:
    @staticmethod
    def RandomControl(output, variables):
        controlType = random.choice(ControlType.implementedTypes)
        return ControlType.Make(controlType, output, variables)

class ControlType:
    STATEMENT = 1
    CONDITIONAL = 2
    GOTO = 3
    CALLGEN = 4
    CALLREAL = 5
    CALLAPI = 6
    STOP = 7
    allTypes = [STATEMENT, CONDITIONAL, GOTO, CALLGEN, CALLREAL, CALLAPI, STOP]
    implementedTypes = [STATEMENT, CONDITIONAL, STOP]

    @staticmethod
    def Make(controlType, output, variables): # factory pattern .. I think
        if(controlType ==  ControlType.STATEMENT):
            return StatementControl(output, variables)
        elif(controlType ==  ControlType.CONDITIONAL):
            return ConditionalControl(output, variables)
        elif(controlType ==  ControlType.STOP):
            return StopControl(output, variables)
        else:
            raise Exception('ControlType.Make bad argument')

    def __init__(self, output, variables):
        self.out = output
        self.variables = variables

    def Exec(self):
        raise NotImplementedError("")

    def PutCode(self):  
        control = ControlSelector.RandomControl(self.out, self.variables)

        control.Exec()

class PutCodeControl(ControlType):
    def Exec(self):
        self.PutCode()

class StatementControl(ControlType):
    def PutAssignment(self):
        dest, src = self.variables.Rand(2)
        self.out.output(src + ' = ' + dest + ';')
        self.out.line()     

    def Exec(self):
        self.PutAssignment()
        self.PutCode()

class ConditionalControl(ControlType):
    def PutConditional(self):
        first, second = self.variables.Rand(2)
        self.out.output('if(')
        self.out.output(first + ' == ' + second)
        self.out.output('){')
        self.out.line()     

    def PutCloseBrace(self):
        self.out.output('}')
        self.out.line()

    def PutIf(self):
        self.PutConditional()
        self.out.incNesting()
        self.PutCode()
        self.PutCloseBrace()
        self.out.decNesting()   

    def Exec(self):
        self.PutIf()
        self.PutCode()

class StopControl(ControlType):
    def Exec(self):
        return

class Function:
    totalFuncCounter = 0

    def __init__(self):
        self.funcNum = Function.totalFuncCounter
        Function.totalFuncCounter += 1

        self.variables = Variables(8)
        self.out = Output()
        self.name = 'RandFunc' + str(self.funcNum)

        self.action = Action(self.variables, self.out)

    def __FunctionHeader(self):
        self.out.output('int ' + self.name + '()')
        self.out.line()
        self.out.output('{')
        self.out.line()

    def __FunctionFooter(self):
        retval = self.variables.Rand()
        self.out.output('return ' + retval + ';')
        self.out.line()
        self.out.output('}')
        self.out.line()     

    def __Generate(self):       
        # int functionname()
        # {
        self.__FunctionHeader()

        # int a = 1, b = 2, etc
        self.variables.InitStatement(self.out)

        # [function body]
        PutCodeControl(self.out, self.variables).Exec()

        # return a;
        self.__FunctionFooter()

    def Generate(self, minLines = None):
        if minLines == None:
            minLines = 1 # < 1 will cause errors

        while self.out.lineCount < minLines:
            self.out.clear()
            self.__Generate()

    def OutputString(self):
        return self.out.outputString

def PrintLongFunction():
    func = Function()
    func.Generate(minLines = 50)
    sys.stdout.write(func.OutputString())

if __name__ == "__main__":
    PrintLongFunction()

Sample output is available here.

\$\endgroup\$
2
\$\begingroup\$

Welcome to python :)

pep8

If you want to go "pythonic" you may want to read pep8. It is the common coding guideline in the world of python.

Python Version

I assume you are writing python3 (your code works in 2.x and 3.x). If you are targeting 2.x you should add explicit inheritance from object. (keyword: new-style classes)

Doc-Strings

Doc-strings (the python equivalent to java-doc comments) are strings inside the block you comment:

class Output:
    """writes text to a stored outputString
       fetch the outputString later for outputting"""

underscores

def __write(self, str):
    self.outputString += str

In your use case there is no reason to prefix functions with __. It is not just a convention but does have a meaning for the interpreter as well.

shadowing built-in

Don't use str as variable name. There is a built-in type called str.

ControlSelector.RandomControl

If you want a function you make a function - not a method. There is no reason for having the class ControlSelector.

def random_control(output, variables):
    controlType = random.choice(ControlType.implementedTypes)
    return ControlType.Make(controlType, output, variables)

minLines == None

The line

if minLines == None:

was suggested to be replaced with

if not minLines:

be aware that this changes the behavior in case of Generate(minLines=0) since if not 0 is True while 0 == None evaluates to False. If you want to check for None use is:

if minLine is None:

The __main__ pattern

if __name__ == "__main__":
    PrintLongFunction()

Awesome. Using the __name__ == "__main__" pattern is something you should get used to. Great start.

\$\endgroup\$
2
\$\begingroup\$

In class Output:

def output(self, str):
    # start each line with tabs equal to nesting level      
    if(self.__lineStart == True):
        for q in range(0, self.nestingLevel):
            self.__write('\t')
        self.__lineStart = False

    self.__write(str)

can be rewritten as

def output(self, str):
    if self.__lineStart:
        self.__write('\t' * self.nestingLevel)
        self.__lineStart = False

    self.__write(str)

You thus remove the useless parenthesis of if condition and make the code shorter and cleaner.

You also can rewrite

Exception('Don\'t you think you\'re being a bit too aggressive?') 

as

Exception("Don't you think you're being a bit too aggressive?")

Some list comprehension :

vars = []
for var in self.variables:
  vars.append('%s = %i' % (var, random.randint(-0xFFFFFFF, 0xFFFFFFF)))

is equivalent to

vars = ['%s = %i' % (var, random.randint(-0xFFFFFFF, 0xFFFFFFF))
          for var in self.variables]

Replace

if minLines == None:

with

if not minLines:
\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.