AttributeError: 'tuple' object has no attribute 'append' [duplicate] - python

This question already has answers here:
How to change values in a tuple?
(17 answers)
Closed last month.
Can anyone help me with this code?
Jobs = ()
openFile = open('Jobs.txt')
x = 1
while x != 0:
Stuff = openFile.readline(x)
if Stuff != '':
Jobs.append(Stuff)
else:
x = 0
This code throws:
AttributeError: 'tuple' object has no attribute 'append'
I'm using Python 3.6.

In the line:
Jobs = ()
you create a tuple. A tuple is immutable and has no methods to add, remove or alter elements. You probably wanted to create a list (lists have an .append-method). To create a list use the square brackets instead of round ones:
Jobs = []
or use the list-"constructor":
Jobs = list()
However some suggestions for your code:
opening a file requires that you close it again. Otherwise Python will keep the file handle as long as it is running. To make it easier there is a context manager for this:
with open('Jobs.txt') as openFile:
x = 1
while x != 0:
Stuff = openFile.readline(x)
if Stuff != '':
Jobs.append(Stuff)
else:
x = 0
As soon as the context manager finishes the file will be closed automatically, even if an exception occurs.
It's used very rarely but iter accepts two arguments. If you give it two arguments, then it will call the first each iteration and stop as soon as the second argument is encountered. That seems like a perfect fit here:
with open('Jobs.txt') as openFile:
for Stuff in iter(openFile.readline, ''):
Jobs.append(Stuff)
I'm not sure if that's actually working like expected because openFile.readline keeps trailing newline characters (\n) so if you want to stop at the first empty line you need for Stuff in iter(openFile.readline, '\n'). (Could also be a windows thingy on my computer, ignore this if you don't have problems!)
This can also be done in two lines, without creating the Jobs before you start the loop:
with open('Jobs.txt') as openFile:
# you could also use "tuple" instead of "list" here.
Jobs = list(iter(openFile.readline, ''))
Besides iter with two arguments you could also use itertools.takewhile:
import itertools
with open('Jobs.txt') as openFile:
Jobs = list(itertools.takewhile(lambda x: x != '', openFile))
The lambda is a bit slow, if you need it faster you could also use ''.__ne__ or bool (the latter one works because an empty string is considered False):
import itertools
with open('Jobs.txt') as openFile:
Jobs = list(itertools.takewhile(''.__ne__, openFile))

The Jobs object you created is a tuple, which is immutable. Therefore, you cannot "append" anything to it.
Try
Jobs = []
instead, in which you create a list object.

I had also experienced the same problem. But I found the solution. For me this worked.
Problem:
w=[]
x=[],
y=[],
z=[]
for i in range(4):
w.append(i) # doesn't throw error
print(w)
This did not give error for w because I had initialized w to w = [] without comma(,) and it treated as list but when I applied the same for x it gave the error because I have initialized it as x = [], with comma here and it treated as tuple.
for i in range(4):
y.append(i) # throws error AttributeError: 'tuple' object has no attribute 'append'
print(y)
This solved for me and I have tried this in python3 in pycharm.

The curved brackets are not the correct bracket for creating lists.
Use the square brackets for lists.

Related

"AttributeError: 'generator' object has no attribute 'replace' "

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)
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:
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
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.

Call many python functions from a module by looping through a list of function names and making them variables

I have three similar functions in tld_list.py. I am working out of mainBase.py file.
I am trying to create a variable string which will call the appropriate function by looping through the list of all functions. My code reads from a list of function names, iterates through the list and running the function on each iteration. Each function returns 10 pieces of information from separate websites
I have tried 2 variations annotated as Option A and Option B below
# This is mainBase.py
import tld_list # I use this in conjunction with Option A
from tld_list import * # I use this with Option B
functionList = ["functionA", "functionB", "functionC"]
tldIterator = 0
while tldIterator < len(functionList):
# This will determine which function is called first
# In the first case, the function is functionA
currentFunction = str(functionList[tldIterator])
Option A
currentFunction = "tld_list." + currentFunction
websiteName = currentFunction(x, y)
print(websiteName[1]
print(websiteName[2]
...
print(websiteName[10]
Option B
websiteName = currentFunction(x, y)
print(websiteName[1]
print(websiteName[2]
...
print(websiteName[10]
Even though it is not seen, I continue to loop through the iteration by ending each loop with tldIterator += 1
Both options fail for the same reason stating TypeError: 'str' object is not callable
I am wondering what I am doing wrong, or if it is even possible to call a function in a loop with a variable
You have the function names but what you really want are the function objects bound to those names in tld_list. Since function names are attributes of the module, getattr does the job. Also, it seems like list iteration rather than keeping track of your own tldIterator index would suffice.
import tld_list
function_names = ["functionA", "functionB", "functionC"]
functions = [getattr(tld_list, name) for name in function_names]
for fctn in functions:
website_name = fctn(x,y)
You can create a dictionary to provide a name to function conversion:
def funcA(...): pass
def funcB(...): pass
def funcC(...): pass
func_find = {"Huey": funcA, "Dewey": funcB, "Louie": FuncC}
Then you can call them, e.g.
result = func_find["Huey"](...)
You should avoid this type of code. Try using if's, or references instead. But you can try:
websiteName = exec('{}(x, y)'.format(currentFunction))

Why python function receiving empty list?

I have a function of for saving three lists so it takes those lists as arguments and then writes each of them line by line to a text file.
rotor1, rotor1_pos, rotor2, rotor2_pos, rotor3, rotor3_pos = [x for x in range(26)], 0, [x for x in range(26)], 0, [x for x in range(26)], 0
def save_rotors(rpos1=rotor1_pos,rpos2=rotor2_pos,rpos3=rotor3_pos,r1=rotor1[:],r2=rotor2[:],r3=rotor3[:]):
print(r1,r2,r3)
with open('rotors.txt.',mode='w') as f:
f.write(str(rpos1)+'\n')
for num in r1:
f.write(str(num)+'\n')
f.write(str(rpos2)+'\n')
for num in r2:
f.write(str(num)+'\n')
f.write(str(rpos3)+'\n')
for num in r3:
f.write(str(num)+'\n')
But when I run
print(rotor1,rotor2,rotor3)
save_rotors()
I find that my lists are correctly populated but inside of save_rotors I have empty lists? As shown above I immediately printing out the r1,r2,r3 arguments so nothing else is happening to the lists, they are only being passed as arguments. My function reads the correct values from the integer valued variables but not the list. My text file ends up looking like
0
[]
0
[]
0
[]
What is going on here?
You've used a Mutable Default Argument Which is defining the lists when the function is defined.
You should instead pass the arguments in by calling the function:
def save_rotors(pos1 ,rpos2, rpos3, r1, r2, r3):
...
save_rotors(rotor1_pos, rotor2_pos, rotor3_pos, rotor1, rotor2, rotor3)
EDIT:
After a discussion in chat, we discovered that there may be some non-visible characters in the original source file that were causing the interpreter some grief. Copying and Pasting the code as it appears in the question seems to work just fine. I will leave the original discussion of mutable default arguments here, however since I still feel it is relevant.

Openpyxl python3 project where TypeError: argument of type 'NoneType' is not iterable and adding an exception doesn't seem to work

I'm having the 'TypeError: argument of type 'NoneType' is not iterable and adding an exception doesn't seem to work' error that I've seen elsewhere on here and have found from other answers that it's due to the loop returning a None value but adding a None exception doesn't work and I can't see how else to do this.
I'm using openpyxl with Python3 and have subjects in column A that are updated through another project so could have 0 to multiple lines (I unmerge them all at the start of the script). I need to search all of the rows for each project in column J for dates to copy across to another column, and as column 2 is blank for rows that have been updated I use a for loop with a if statement to add blank rows to an array for later deletion.
All rows that aren't blank, I use the else statement to count in tempList how many blank rows are in that project and then for loop to set myStringTest as the column J value so I can search for the text I want - if I find it I want to copy a slice of the text (a date) across to another column - I just have print('test') here at the moment.
for i in range(2, sheet.max_row + 1):
if sheet.cell(row=i, column=1).value == None:
print('This row has nothing in it')
arrayForDeletion2.append(i)
else:
tempList = 1
x = i
while sheet.cell(row=x+1, column=1).value == None:
tempList += 1
x += 1
if tempList >30:
break
print(i)
print(tempList)
for y in range(i, i+tempList):
myStringTest = (sheet.cell(row=i, column=10).value)
if 'Original' in myStringTest and not None:
print('test')
i += 1
>>>Traceback (most recent call last):
File "main.py", line 113, in <module>
if 'Original' in myStringTest and not None:
TypeError: argument of type 'NoneType' is not iterable
So A2 may have 6 update rows, with J4, J5 and J7 each having a string saying 'Original target date: XX/XX/XX' - I'm looking to recognise that in the if statement and if so to copy out just the date to another column. If no string, do nothing.
That's probably not very clear sorry but I'm fairly new to python and have looked around a lot for an answer but can't work this out. Really grateful for any help. Thanks.
If sheet.cell(r,c).value returns None, you are testing 'Original' in None, effecively <str> in <NoneType>. Since an implicit cast (of <NoneType> to <str>) is not available, python throws an error. You've tried to catch this using and None, but this code is not evaluated how you expected it to. Try the below, you'll get the same error:
p = None
if 'test' in p:
print(p)
Moving the None check to a separate statement before the in should solve the issue. Python raises the error, so your application can execute fallback code that corrects unintended behaviour or notifies the user about faulty input. If you want to catch and handle it locally, use a try/except block:
p = None
try:
if 'lol' in p:
print('p')
else:
print('none')
except TypeError:
print('Code to be executed if p is None goes here.')
Incidentally, the below prints p, because not None always evaluates to True. You will need an explicit myStringTest is not None, in which the is keyword triggers the comparison.
p = None
if not None:
print('p')
else:
print('none')
Lastly, a recommendation. Semantically, x == None tests whether x is equivalent to the None object.
But there is only one None object, which is the None object. So the test you would like to execute uses the is keyword: x is None, which checks whether x and None refer to the same object.
How does this matter? Well, part of it is style. That leads to discussions like these. But part of it is functionality. This comments on readability, this remarks on a cool edge case: != | == use the __eq__() method of a class and you do not know how it is implemented. If I want to know whether x and y refer to the same object, is guarantees proper behaviour.

'NoneType' object has no attribute '__getitem__' 4

from collections import deque
def muladd(f1,f2_inv):
global ans
for j in f1:
ans = f1[j]*f2_inv[j]
f1 = [2,3,7,5,4,0,0,0,0,0,0,0,0]
f2 = [0,0,0,0,8,7,6,5,9,0,0,0,0]
conv = [0,0,0,0,0,0,0,0,0]
f2_inv = f2.reverse()
for i in conv:
conv[i]= muladd(f1,f2_inv)
print conv[i]
f1.rotate(1)
​
I am not able to run the code.
When I run this code I get the error:
'NoneType' object has no attribute '__getitem__'
reverse(), like many methods that operate on lists, does not return anything, but modifies the list in-place. So f2 is None. You should use the standalone reversed() function.
f2_inv = list(reversed(f1))
or slicing:
f2_inv = f1[::-1]
(Note, there are other issues with this code, such as the fact that i is always 0 in your loop because you iterate over a list of only 0s.)
In your loop
for i in conv:
conv[i]= muladd(f1,f2_inv)
print conv[i]
f1.rotate(1)
i will be an object (it loops over all the objects of conv, so i is an object instead of an index number). This way you cannot use conv[i]. To be able to use i as an index, use:
for i in range(len(conv)):
conv[i]= muladd(f1,f2_inv)
print conv[i]
f1.rotate(1)
(this is not the cause of your error, but will cause problems if yours is fixed, as conv[i] would always be conv[0], because i is 0 instead of the current index number)

Categories

Resources