Home > Software engineering >  Problem with a function of a class of an external file: 'NoneType' object has no attribute
Problem with a function of a class of an external file: 'NoneType' object has no attribute

Time:04-13

I have a window that prints the news content in a textbox. You must first click on the "View Titles" button, then select the title, and finally click on the "View Content" button. It works correctly, without any problems.

In this question, I have placed the window code in a class of an external file (page1.py). The file will be opened in the main file (main.py). If I click on the "View Titles" button it works fine, but if I click on the "View Content" button I get an error in the function def content:

    item = df.iloc[selection[-1]]
AttributeError: 'NoneType' object has no attribute 'iloc'

I would like that when I click on the "View content" button, the content of the news is printed in the textobox. As I said above, the code when it was in a simple file with only one window ... it worked perfectly, while now if put in an external class I have problems.

I am new to Python. Can you show me the code in the answer? Otherwise I may not understand, thanks and sorry

Main.py

import tkinter as tk
from tkinter import ttk
from PIL import ImageTk
from page1 import Page1

root = tk.Tk()
root.geometry('480x320')

topbar = tk.Frame(root, bg='#e10a0a', height=43)
topbar.pack(fill='x')

style = ttk.Style()
style.theme_use('default') # select a theme that allows configuration of ttk.Notebook
# put the tabs at the left with white background
style.configure('TNotebook', tabposition='wn', background='white', tabmargins=0)
# configure tab with white background initially, yellow background when selected
style.configure('TNotebook.Tab', background='white', width=10, focuscolor='yellow', borderwidth=0)
style.map('TNotebook.Tab', background=[('selected', 'yellow')])

nb = ttk.Notebook(root)
nb.pack(fill='both', expand=1)

page1 = Page1(nb)

nb.add(page1, text='aaaaa', compound='left')

root.mainloop()

page1.py

import tkinter as tk
from tkinter import ttk
from tkinter import *
from tkinter import ttk
import tkinter as tk
import tkinter.font as tkFont
from tkinter import ttk

import tkinter as tk   # PEP8: `import *` is not preferred
from tkinter import ttk
from tkinter.scrolledtext import ScrolledText 
import requests
import requests_cache 
from bs4 import BeautifulSoup
import pandas as pd
import re
import json
from dateutil import parser
import datetime
import locale



class Page1(tk.Frame):
    def __init__(self, master, **kw):
        super().__init__(master, **kw)



        def get_data_for(place):
            headers = {
                'User-Agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_10_1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/39.0.2171.95 Safari/537.36'
            }

            results = []

            response = requests.get(f'https://www.tuttomercatoweb.com/{place}/', headers=headers)
            print('url:', response.url)
            print('status:', response.status_code)
            #print('html:', response.text[:1000])

            soup = BeautifulSoup(response.content, 'html.parser')

            #Cover
            cover_news = soup.find('div', {'class':'box pp'})
            link = cover_news.find('a', href=True)['href']
            coverNewsResponse = requests.get(link, headers=headers)
            coverSoup = BeautifulSoup(coverNewsResponse.content, 'html.parser')
            jsonStr = str(coverSoup.find('script'))
            jsonStr = re.search('({.*})', jsonStr).group(1)
            jsonData = json.loads(jsonStr)
            
            timePublished = parser.parse(jsonData['datePublished']).strftime("%H:%M")
            datePublished = parser.parse(jsonData['datePublished']).strftime("%Y-%m-%d")
            title = jsonData['headline']
            news = f" {timePublished} {place.upper()}, {title} (TMW)"
            results.append( [datePublished, timePublished, place, title, news, link] )

            # Side panel
            side_news = soup.find_all('div', {'class':'box small'})
            for each in side_news:
                link = each.find('a', href=True)['href']
                sideNewsResponse = requests.get(link, headers=headers)
                sideSoup = BeautifulSoup(sideNewsResponse.content, 'html.parser')
                jsonStr = str(sideSoup.find('script'))
                jsonStr = re.search('({.*})', jsonStr).group(1)
                jsonData = json.loads(jsonStr)
                
                timePublished = parser.parse(jsonData['datePublished']).strftime("%H:%M")
                datePublished = parser.parse(jsonData['datePublished']).strftime("%Y-%m-%d")
                title = jsonData['headline']
                news = f" {timePublished} {place.upper()}, {title} (TMW)"
                results.append( [datePublished, timePublished, place, title, news, link] )
                
                

            news = soup.find_all('div', attrs={"class": "tcc-list-news"})

            for each in news:
                for div in each.find_all("div"):
                    timePublished  = div.find('span', attrs={'class': 'hh serif'}).text
                    datePublished = div.find_previous('div', {'class':'tcc-border upper date'})
                    
                    if datePublished:
                        if datePublished.text in ['Ieri']:
                            yesterday = datetime.datetime.today() - datetime.timedelta(days = 1)
                            datePublished = yesterday.strftime("%Y-%m-%d")
                        else:
                            locale.setlocale(locale.LC_ALL, '') #locale.setlocale(locale.LC_ALL, 'it')
                            currentYear = datetime.datetime.today().strftime("%Y")
                            dateStr = datePublished.text
                            dateStr = datetime.datetime.strptime(dateStr   ' '   currentYear, '%A %d %B %Y')
                            datePublished = dateStr.strftime("%Y-%m-%d")
                    else:
                        datePublished = datetime.datetime.today().strftime("%Y-%m-%d")
                    
                    title = " ".join(span.text for span in div.select("a > span"))
                    news = f" {timePublished} {place.upper()}, {title} (TMW)"
                    link  = div.find('a')['href']
                    results.append( [datePublished, timePublished, place, title, news, link] )

            return results

        def all_titles():
            global df

            allnews = []  # local variable

            for place in ['atalanta',  'bologna']:
                print('search:', place)
                results = get_data_for(place)
                print('found:', len(results))
                allnews  = results
                text_download.insert('end', f"search: {place}\nfound: {len(results)}\n")

            df = pd.DataFrame(allnews, columns=['date', 'time', 'place', 'title', 'news', 'link'])
            df = df.sort_values(by=['date', 'time', 'place', 'title'], ascending=[False, False, True, True])
            df = df.drop_duplicates(subset=['date', 'time', 'place', 'title'])
            df = df.reset_index(drop=True)

            listbox_title.delete('0', 'end')

            for index, row in df.iterrows():
                listbox_title.insert('end', row['news'])

        def content(event=None):   # `command=` executes without `event`, but `bind` executes with `event` - so it needs default value
            # tuple
            selection = listbox_title.curselection()
            print('selection:', selection)

            if selection:

                item = df.iloc[selection[-1]]
                #print('item:', item)

                url = item['link']
                #print('url:', url)

                headers = {
                    'User-Agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_10_1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/39.0.2171.95 Safari/537.36'
                }

                # keep page in database `SQLite` 
                # https://github.com/reclosedev/requests-cache
                # https://sqlite.org/index.html
                session = requests_cache.CachedSession('titles')
                response = session.get(url, headers=headers)
                #response = requests.get(url, headers=headers)
                soup = BeautifulSoup(response.content, 'html.parser')

                content_download = "\n".join(item.get_text() for item in soup.select("div.text.mbottom"))

                text_download.delete('1.0', 'end') # remove previous content)
                text_download.insert('end', content_download)

        # --- main ---

        df = None

       # window = tk.Tk()
       # window.geometry("800x800")

        frame_title = tk.Frame(self)
        frame_title.pack(fill='both', expand=True, pady=5, padx=5)

        listbox_title = tk.Listbox(frame_title, selectbackground="#960000", selectforeground="white", bg="white")
        listbox_title.pack(side='left', fill='both', expand=True)

        scrollbar_title = tk.Scrollbar(frame_title)
        scrollbar_title.pack(side='left', fill='y')

        scrollbar_title['command'] = listbox_title.yview
        listbox_title.config(yscrollcommand=scrollbar_title.set)

        listbox_title.bind('<Double-Button-1>', content)  # it executes `content(event)`

        # ----

        text_download = ScrolledText(self, bg="white")
        text_download.pack(fill='both', expand=True, pady=0, padx=5)

        # ----

        buttons_frame = tk.Frame(self)
        buttons_frame.pack(fill='x')

        button1 = tk.Button(buttons_frame, text="View Titles", command=all_titles)  # don't use `[]` to execute functions
        button1.pack(side='left', pady=5, padx=5)

        button2 = tk.Button(buttons_frame, text="View Content", command=content)   # don't use `[]` to execute functions
        button2.pack(side='left', pady=5, padx=(0,5))

CodePudding user response:

The simple way to fix the issue is to change global df to nonlocal df inside all_titles() function.

However, I would suggest to use instance variable instead by changing all df to self.df:

class Page1:
    def __init__(self, master, **kw):
        ...
        def all_titles():
            ...
            # changed df to self.df
            self.df = pd.DataFrame(allnews, columns=['date', 'time', 'place', 'title', 'news', 'link'])
            self.df = self.df.sort_values(by=['date', 'time', 'place', 'title'], ascending=[False, False, True, True])
            self.df = self.df.drop_duplicates(subset=['date', 'time', 'place', 'title'])
            self.df = self.df.reset_index(drop=True)

            listbox_title.delete('0', 'end')

            for index, row in self.df.iterrows():
                listbox_title.insert('end', row['news'])

        def content(event=None):
            ...
            if selection:
                item = self.df.iloc[selection[-1]]
                ...
            ...

        self.df = None
        ...

CodePudding user response:

I think you've declared df to be a global variable in one of the functions nested under your __init__ method...then later on in your main section, which actually runs in the __init__, you set df to None. You'll want to be much more careful about making variables global and think a bit more about function scope; do those functions you're defining need to be within the scope of __init__, or can they have a different scope?

  • Related