# Censor the middle of words of length N from a text file

LENGTHS_TO_CENSOR = {4, 5}
CENSOR_CHAR       = '*'
CENSOR_EXT        = "-censored"

def replace_inner(word, char):
if len(word) < 3:
return word
return word[0] + char * len(word[1:-1]) + word[-1]

def create_censor_file(filename):
output_file = open(filename + CENSOR_EXT, "w+")
with open(filename) as source:
for line in source:
idx = 0

while idx < len(line):

# If the character isn't a letter, write it to the output file.
if not line[idx].isalpha():
output_file.write(line[idx])
idx += 1

else:
word = ""

while idx < len(line) and line[idx].isalpha():
word += line[idx]
idx += 1

if len(word) in LENGTHS_TO_CENSOR:
word = replace_inner(word, CENSOR_CHAR)

output_file.write(word)

output_file.close()

def main():
filename = input("File to be censored: ")
create_censor_file(filename)

if __name__ == "__main__":
main()


I was assigned a task to censor words that are length n in a file. This file can potentially contain punctuation and numbers.

I originally tackled the problem by splitting the line into a list of words (using .split(' ')) and checking the length to determine if the program should censor the word or not. This failed for inputs such as:

does not work.for.this.input

or.this

The output file must be exactly like the input but with words of length in LENGTHS_TO_CENSOR censored with CENSOR_CHAR.

I decided to abandon trying to make it Pythonic and ended up with this result. I want to know if there is a way to take this method and make it more Pythonic.

1. create_censor_file should really be called create_censored_file.
2. I'd rename source to source_file for consistency and clarity.
3. You should use with for both files.
4. Why not use just w instead of w+?
5. This is probably one of the few things that regexes are actually useful for. You can just use re.sub(r'(?<=\b\w)\w{' + ','.join(map(lambda x: str(x-2), LENGTHS_TO_CENSOR)) + '}(?=\w\b)', lambda match: CENCOR_CHAR * len(match.group(0)), source.read())

A couple other things:

1. Good job with the main function and if __name__ == '__main__' check!
2. I have not yet tested this code.

Result:

LENGTHS_TO_CENSOR = {4, 5}
CENSOR_CHAR       = '*'
CENSOR_EXT        = "-censored"

def create_censor_file(filename):
with open(filename + CENSOR_EXT, "w") as output_file, open(filename) as source_file:
output_file.write(
re.sub(
r'(?<=\b\w)\w{'
+ ','.join(map(lambda x: str(x-2), LENGTHS_TO_CENSOR))
+ '}(?=\w\b)',
lambda match: CENSOR_CHAR * len(match.group(0)),

• This is good! One small addidtion you can do multiple with statements on one line saving a line of indentation. with open(a) as a, open(b) as b – Ludisposed Sep 14 '18 at 7:25