4
\$\begingroup\$

I'm kind of new to Python, and I'm trying to improve (especially) my object oriented programming, and getting to know Python syntax better.

I'd love to know if there is something to improve, (especially) in the matter of object oriented, or making the code more Pythonic (Syntactic sugar).

import turtle

RIGHT = 'r'
LEFT = 'l'


class Dragon_Curve(turtle.Turtle):
    def __init__(self, iterations, length):
        super(Dragon_Curve, self).__init__()
        self.iterations = iterations
        self.length = length
        self.hideturtle()
        self.speed(50)
        self.sequence = self.create_seq("", iterations)
        self.draw_sequence(self.sequence)

    def draw_sequence(self, dragon_sequence):
        for i in dragon_sequence:
            if i == RIGHT:
                self.right(90)
                self.forward(length)
            elif i == LEFT:
                self.left(90)
                self.forward(length)

    #  Create the dragon's path, depending on the number of iterations.
    def create_seq(self, given_seq, num_iterations):
        self.seq = ""
        if num_iterations == 0:
            print("the dragon's path is {}".format(given_seq))
            return given_seq
        else:
            self.seq += given_seq
            self.seq += 'r'
            self.seq += self.opposite(given_seq)[::-1]
        return self.create_seq(self.seq, num_iterations - 1)

    # flip the given sequence.
    # If the argument is 'rrl', it will return 'llr'    
    def opposite(self, str):
        seq = ""
        for i in str:
            if i == 'r':
                seq += 'l'
            elif i == 'l':
                seq += 'r'
        return seq

if __name__ == '__main__':
    iterations = int(input("Enter number of iterations - "))
    length = int(input("Enter length size (recommended 5) - "))
    dragon = Dragon_Curve(iterations, length)
    turtle.Screen().title("The Dragon Curve")
    turtle.done()
\$\endgroup\$
2
\$\begingroup\$

Here are some suggestions and remarks, in no particular order:

  • i is not a great name for a variable in most cases, and especially not when it's not short for "index", as is the case in your draw_sequence function. I would suggest naming it direction.
  • The official Python PEP8 style guide states that class names should be CamelCased, so your class should be named DragonCurve, without the underscore.
  • There's no point in making self.seq an instance/member variable, since it's only used by the create_seq function and its value is reset on each call. It can safely be turned into a local variable.
  • Stick to using the LEFT/RIGHT constants instead of 'l'/'r'.
  • One small improvement that you could make is replacing your empty strings "" with a constant named EMPTY_SEQUENCE. This would completely hide the fact that sequences are represented as strings from the reader, and would make it easy for you to change the representation if you would ever want to.
  • Function names should be verbs, so opposite is not a good name for a function. Perhaps something like "flip" or "invert" would be more suitable.
  • The parameter named str will shadow the built-in function str. This is generally not a good idea, so maybe it should be named seq or sequence, since that is actually the abstraction that the function operates on.
\$\endgroup\$
1
\$\begingroup\$

The code is good, I have some minor improvements to remove some repetition and excessive verbosity:

Same code in both branches

You can simplify draw_sequence to:

def draw_sequence(self, dragon_sequence):
    for i in dragon_sequence:
        if i == RIGHT:
            self.right(90)
        elif i == LEFT:
            self.left(90)
        self.forward(length)

Avoiding to repeat self.forward(length) twice.

You could also avoid repeating 90 but it may become hard to read so this is at reader's discretion:

def draw_sequence(self, dragon_sequence):
    for i in dragon_sequence:
        turn = self.right if i == RIGHT else self.left(90)
        turn(90)
        self.forward(length)

opposite: ''.join and generator comprehension

The oppositefunction (that should be namedflip` as rreillo remarks) is very long even if it has a very simple purpose, so you should shorten it using a generator comprehension:

def opposite(self, sequence):
    return ''.join(RIGHT if LEFT else LEFT for char in sequence)

Now the logic of the function is much clearer without the repetition of the seq temporary variable.

The method should become a @staticmethod because self is not used.

Standard Test/Documentation Practice

You provided documentation and an example for your function over its name, but the standard is to write it below in triple quotes like this:

def flip(self, sequence):
    """
    Flips each item in the sequence.

    >>> Dragon_Curve.flip("rrl")
    "llr"
    >>> Dragon_Curve.flip("llr")
    "rrl"
    """
    return ''.join(RIGHT if LEFT else LEFT for char in sequence)

(I added a second test to show that applying this function twice gets back to the start)

This way you can get help when you call >>> help(Dragon_Curve.flip) and you can test automatically with doctest.testmod()

Terminal friendly

In Unix programs are usually written to be terminal friendly, so you could consider using the argparse library to take inputs from the command line instead of from the prompt, also you could avoid printing the path as on successful execution usually nothing is printed.

\$\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.