TypeError: video_stream() missing 1 required positional argument: 'self'

Написал интерфейс для программы используя для него отдельный класс. Пытался в отдельный фрейм (место для видео на интерфейсе) вывести видео. Нашел код, который выводит видео в Tkinter, но при добавлении его в класс не видел переменной в методе def. Добавил self и выдал ошибку. Вот код, который нашел:

from Tkinter import *
from PIL import ImageTk, Image
import cv2


root = Tk()
# Create a frame
app = Frame(root, bg="white")
app.grid()
# Create a label in the frame
lmain = Label(app)
lmain.grid()

# Capture from camera
cap = cv2.VideoCapture(0)

# function for video streaming
def video_stream():
    _, frame = cap.read()
    cv2image = cv2.cvtColor(frame, cv2.COLOR_BGR2RGBA)
    img = Image.fromarray(cv2image)
    imgtk = ImageTk.PhotoImage(image=img)
    lmain.imgtk = imgtk
    lmain.configure(image=imgtk)
    lmain.after(1, video_stream) 

video_stream()
root.mainloop()
    

При переводе в класс:

from tkinter import *
from PIL import ImageTk, Image
import cv2

class GUT:

    root = Tk()
    # Create a frame
    app = Frame(root, bg="white")
    app.grid()
    # Create a label in the frame
    lmain = Label(app)
    lmain.grid()

    # Capture from camera
    cap = cv2.VideoCapture("8-bit trip.mp4")

    # function for video streaming
    def video_stream(self):

        _, frame = self.cap.read()
        cv2image = cv2.cvtColor(frame, cv2.COLOR_BGR2RGBA)
        img = Image.fromarray(cv2image)
        imgtk = ImageTk.PhotoImage(image=img)
        self.lmain.imgtk = imgtk
        self.lmain.configure(image=imgtk)
        self.lmain.after(1, self.video_stream)

    video_stream()

    root.mainloop()

(self добавил т.к. не видел переменные) Как исправить ошибку или есть альтернативный вариант для привязки видео к отдельному фрейму в интерфейсе.


Ответы (1 шт):

Автор решения: insolor
  1. Сначала нужно ознакомиться с теорией - как должен выглядеть класс в Python, и как он работает, попробовать написать несколько классов без tkinter.
  2. После этого нужно подумать - нужен ли вам класс вообще? Часто это совершенно излишне. Если у вас на весь код одна функция, и вы зачем-то этот код хотите обернуть в класс, то вы что-то делаете не так.
  3. Дополнительно ко второму пункту: не нужно бездумно весь код собирать в один класс, иначе у вас получится классический "божественный объект". Это не ООП, это карго культ ООП. Не нужно думать, что просто добавление в ваш код классов сделает ваш код лучше.
  4. В целом, ООП - это один из инструментов структурирования и уменьшения сложности кода. Если вы просто обернете весь код в один класс - это никак не уменьшит сложность кода (скорее увеличит).

К прочтению: Перестаньте писать классы (Джэк Дидрих)


Если уж сильно хочется обернуть код в класс, то код верхнего уровня (код вне функций, кроме импортов) нужно перенести в метод __init__ класса, тогда этот код будет выполняться при создании объекта. Поля, которые должны быть доступны из методов класса нужно записывать через self. Методы также должны вызываться от self (см. вызов метода video_stream в примере ниже). Само по себе такое оборачивание бессмысленно, см. объяснение в начале.

Грубый пример такого "оборачивания":

class GUT:
    def __init__(self):
        root = Tk()
        # Create a frame
        app = Frame(root, bg="white")
        app.grid()
        # Create a label in the frame
        self.lmain = Label(app)
        self.lmain.grid()

        # Capture from camera
        self.cap = cv2.VideoCapture("8-bit trip.mp4")
        
        self.video_stream()

        root.mainloop()

    # function for video streaming
    def video_stream(self):
        _, frame = self.cap.read()
        cv2image = cv2.cvtColor(frame, cv2.COLOR_BGR2RGBA)
        img = Image.fromarray(cv2image)
        imgtk = ImageTk.PhotoImage(image=img)
        self.lmain.imgtk = imgtk
        self.lmain.configure(image=imgtk)
        self.lmain.after(1, self.video_stream)


if __name__ == '__main__':
    GUT()

Для tkinter есть более-менее общепринятый подход - для окна или фрейма (если фрейм работает более-менее независимо других частей окна) делать свой класс, при этом класс наследуется от класса окна (Tk или Toplevel) или фрейма.

Вариант с наследованием от Tk:

class GUT(Tk):
    def __init__(self):
        super().__init__()
        # Create a frame
        app = Frame(self, bg="white")
        app.grid()
        # Create a label in the frame
        self.lmain = Label(app)
        self.lmain.grid()

        # Capture from camera
        self.cap = cv2.VideoCapture("8-bit trip.mp4")
        
        self.video_stream()

    # function for video streaming
    def video_stream(self):

        _, frame = self.cap.read()
        cv2image = cv2.cvtColor(frame, cv2.COLOR_BGR2RGBA)
        img = Image.fromarray(cv2image)
        imgtk = ImageTk.PhotoImage(image=img)
        self.lmain.imgtk = imgtk
        self.lmain.configure(image=imgtk)
        self.lmain.after(1, self.video_stream)


if __name__ == '__main__':
    GUT().mainloop()

Вариант с наследованием от Frame:

class GUT(Frame):
    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)

        # Create a label in the frame
        self.lmain = Label(self)
        self.lmain.grid()

        # Capture from camera
        self.cap = cv2.VideoCapture("8-bit trip.mp4")
        
        self.video_stream()

    # function for video streaming
    def video_stream(self):

        _, frame = self.cap.read()
        cv2image = cv2.cvtColor(frame, cv2.COLOR_BGR2RGBA)
        img = Image.fromarray(cv2image)
        imgtk = ImageTk.PhotoImage(image=img)
        self.lmain.imgtk = imgtk
        self.lmain.configure(image=imgtk)
        self.lmain.after(1, self.video_stream)


if __name__ == '__main__':
    root = Tk()
    app = GUT(root, bg="white")
    app.grid()
    root.mainloop()

Также можно пробовать реализовать нечто вроде MVC - тогда у вас отдельный класс (view) должен отвечать только за создание графического интерфейса (части графического интерфейса) и взаимодействие с пользователем, отдельный (model) - за хранение состояния приложения или части приложения, отдельный (controller) - за выполнение какой-то полезной работы, например того же получения данных с камеры. Это имеет смысл делать для сложных многооконных/многофреймовых приложений. Для простого однооконного приложения такое разделение скорее всего будет излишне, и возможно приведет к усложнению кода.

→ Ссылка