0

I'm not sure why I'm seeing this error message: AttributeError: 'generator' object has no attribute 'replace' (on line: modified_file = hex_read_file.replace(batch_to_amend_final, batch_amendment_final).

import binascii, os, re, time

os.chdir(...)
files_to_amend = os.listdir(...)
joiner = "00"

# Allow user to input the text to be replaced, and with what
while True:
    batch_to_amend3 = input("\n\nWhat number would you like to amend? \n\n >>> ")
    batch_amendment3 = input("\n\nWhat is the new number? \n\n >>> ")
    batch_to_amend2 = batch_to_amend3.encode()
    batch_to_amend = joiner.encode().join(binascii.hexlify(bytes((i,))) for i in batch_to_amend2)
    batch_amendment2 = batch_amendment3.encode()
    batch_amendment = joiner.encode().join(binascii.hexlify(bytes((i,))) for i in batch_amendment2)

# Function to translate label files
def lbl_translate(files_to_amend):
    with open(files_to_amend, 'rb') as read_file:
        read_file2 = read_file.read()
        hex_read_file = (binascii.hexlify(bytes((i,))) for i in read_file2)
        print(hex_read_file)
        modified_file = hex_read_file.replace(batch_to_amend, batch_amendment)
        with open(files_to_amend, 'wb') as write_file:
            write_file.write(modified_file)
            write_file.close()
            print("Amended: " + files_to_amend)

# Calling function to modify labels        
for label in files_to_amend:
    lbl_translate(label)
jonrsharpe
  • 115,751
  • 26
  • 228
  • 437
Jack
  • 3
  • 6

1 Answers1

1

hex_read_file is a generator comprehension (note the round brackets around the statement) defined here:

hex_read_file = (binascii.hexlify(bytes((i,))) for i in read_file2)

As many already pointed out in the comments, comprehesions don't have a replace method as strings have, so you have two possibilities, depending on your specific use-case:

  1. Turn the comprehension in a bytestring and call replace on that (considering how you use write_file.write(modified_file) afterwards, this is the option that would work with that directly):
hex_read_file = bytes(binascii.hexlify(bytes((int(i),))) for i in read_file2) # note: I added th eadditional int() call to fix the issue mentioned in the comments
  1. Filter and replace directly in the comprehension (and modify how you write out the result):
def lbl_translate(files_to_amend, replacement_map):
    with open(files_to_amend, 'rb') as read_file:
        read_file2 = read_file.read()
        hex_read_file = ( replacement_map.get(binascii.hexlify(bytes((int(i),))), binascii.hexlify(bytes((int(i),)))) for i in read_file2) # see Note below
        with open(files_to_amend, 'wb') as write_file:
            for b in hex_read_file:
                write_file.write(b)
        print("Amended: " + files_to_amend)

where replacement_map is a dict that you fill in with the batch_to_amend as key and the batch_amendment value (you can speficy multiple amendments too and it will work just the same). The call would then be:

for label in files_to_amend:
    lbl_translate(label,{batch_to_amend:batch_amendment})

NOTE: Using standard python dicts, because of how comprehensions work, you need to call binascii.hexlify(bytes((int(i),))) twice here. A better option uses collections.defaultdict

A better option would use defaultdict, if they were implemented in a sensible way (see here for more context on why I say that). defaltdicts expect a lambda with no parameters generating the value for unknown keys, instead you need to create your own subclass of dict and implement the __missing__ method to obtain the desired behaviour:

hex_read_file = ( replacement_map[binascii.hexlify(bytes((int(i),)))] for i in read_file2) # replacement_map is a collections.defaultdict

and you define replacement_map as:

class dict_with_key_as_default(dict): # find a better name for the type
    def __missing__(self, key):
        '''if a value is not in the dictionary, return the key value instead.'''
        return key

replacement_map = dict_with_key_as_default() 
replacement_map[batch_to_amend] = batch_amendment
for label in files_to_amend:
    lbl_translate(label, replacement_map)

(class dict_with_key_as_default taken from this answer and renamed for clarity)


Edit note: As mentioned in the comments, the OP has an error in the comprehension where they call hexlify() on some binary string instead of integer values. The solution adds a cast to int for the bytes where relevant, but it's far from the best solution to this problem. Since the OP's intent is not clear, I left it as close to the original as possible, but an alternative solution should be used instead.

GPhilo
  • 18,519
  • 9
  • 63
  • 89
  • Thanks so much for this - but the second solution won't work in my particular case as I don't know the "batch_amendment" numbers in advance, it needs to be entered in at the time. And your first solution seems to have solved my initial issue, but is now returning "TypeError: 'bytes' object cannot be interpreted as an integer". Any thoughts on how I can get around that one? – Jack Nov 23 '21 at 07:19
  • You can always build the dict with the results from user input, that's what I'm doing in the code as well (I reuse the variables you fill with it in your code), so you don't need to know the numbers in advance :) As per the other issue, where exactly do you get the error? – GPhilo Nov 23 '21 at 08:16
  • Ok, so I've been messing around with both suggestions, but I can't get it working... Your second solution is returning the following: hex_read_file = ( replacement_map[binascii.hexlify(bytes((i,)))] for i in read_file2) # replacement_map is a collections.defaultdict TypeError: () missing 1 required positional argument: 'x' And the first solution is returning this: hex_read_file = bytes(binascii.hexlify(bytes((i,))) for i in read_file2) TypeError: 'bytes' object cannot be interpreted as an integer Any thoughts? – Jack Nov 25 '21 at 12:07
  • I do have a few: firstly, my answer with the `defaultdict` is plain wrong (and I'm surprised no one commented that yet), I'll amend it in a minute. About the second TypeError, the source is from your original code: you read the content of the file in binary mode, so you already get bytes out of it. You then have `bytes((i,))` in the comprehension, which won't work if `i` i already a bytes object. What's the purpose of putting `i` in a tuple before passing it into `hexlify`? It woul work with `bytes((int(i),))` but I assume there is a better way to achieve your desired result. – GPhilo Nov 25 '21 at 12:21
  • @Jack I updated the answer using `defaultdict` and the other one as well, both should work for you now – GPhilo Nov 25 '21 at 12:31
  • 1st solution is now returning (on the hex_read_file = " line) : "TypeError: 'bytes' object cannot be interpreted as an integer". / And the 2nd solution is returning (on the "hex_read_file = ... " line) : "SyntaxError Generator expression must be parenthesized" / I appreciate all your help thus far, and I don't know if there's something else affecting the code you have provided me with, but it just isn't working as of yet... And I'm going to be honest and say I did just steal the "bytes(i, ))" bit from elsewhere on stackoverflow... – Jack Nov 26 '21 at 12:33
  • Can you copy here the full line where you get the error in both cases? (sorround them with ` characters so they're fomatted as code in the comment) – GPhilo Nov 26 '21 at 12:41
  • 1st solution: `hex_read_file = bytes(binascii.hexlify(bytes((int(i),))) for i in read_file2) TypeError: 'bytes' object cannot be interpreted as an integer` / 2nd solution: Error comes up as "SyntaxError. Generator expression must be parenthesized" on line `hex_read_file = ( replacement_map.get(binascii.hexlify(bytes((int(i),))), binascii.hexlify(bytes((int(i),))) for i in read_file2)` – Jack Dec 06 '21 at 10:55
  • @Jack the second one has some parenthesis mismatch, it should be: `( replacement_map.get(binascii.hexlify(bytes((int(i),))), binascii.hexlify(bytes((int(i),)))) for i in read_file2 )` – GPhilo Dec 06 '21 at 11:36
  • Thanks, that seems to have solved that issue! But now whenever I run the program the file doubles in size? I really can't work out why it's doing this @GPhilo – Jack Dec 08 '21 at 09:30