Wywołanie metody z klasy wybranej na podstawie zmiennej

0

Witam,

Wykonuję metodę z klasy wybranej na podstawie wartości zmiennej (symulacja w main.py)

Jest to oczywiście początkowy szablon, więc każda z klas będzie miała dodatkowo swoje własne metody czy atrybuty.
Oczywiście zanim na poważnie przejdę do dalszego pisania to wolałbym się troszkę bardziej podszkolić ale zalążek już stworzyłem.
Może ocenicie? :)

Zastanawia mnie :

  • Jak to napisać bez używania globals()?
  • Jak można to ulepszyć?
  • Czy widzicie tu coś wyjątkowo złego?

Python : 3.7.4

main.py

#!/usr/bin/env python3
# -*- coding: utf-8 -*-

import sys
import os

sys.path.insert(1, os.path.dirname(os.path.abspath(__file__)))
from template import Template


if __name__ == "__main__":
    print(Template("Base"))
    print(Template("One"))
    print(Template("Two"))

template.py

#!/usr/bin/env python3
# -*- coding: utf-8 -*-

from base import Base
from one  import One
from two  import Two

class Template(object):
    __slots__ = ('class_name',)

    def __init__(self, class_name: str) -> None:
        self.class_name = class_name

    def __str__(self) -> str:
        return globals()[self.class_name]().get_template()

base.py

#!/usr/bin/env python3
# -*- coding: utf-8 -*-

class Base(object):
    __slots__ = ('template',)

    def __init__(self) -> None:
        self.template = "Template Base class"

    def set_template(self, template: str) -> None:
        self.template = template

    def get_template(self) -> str:
        return self.template

one.py

#!/usr/bin/env python3
# -*- coding: utf-8 -*-


from base import Base


class One(Base):
    __slots__ = ('template',)

    def __init__(self) -> None:
        self.template = "Template One class"

    def set_template(self, template: str) -> None:
        self.template = template

    def get_template(self) -> str:
        return self.template

two.py

#!/usr/bin/env python3
# -*- coding: utf-8 -*-


from base import Base


class Two(Base):
    __slots__ = ('template',)

    def __init__(self) -> None:
        self.template = "Template Two class"
        pass

    def set_template(self, template: str) -> None:
        self.template = template

    def get_template(self) -> str:
        return self.template

1

Czemu taki design, żeby w __str__ używać globals(), to hack klasyczny, nie dało by się dziedziczenia zastosować? Masz __str__, spoko, ale potrzebne jest jeszcze __repr__, żeby Python też widział Twój obiekt.

2

@lion137 Jakie dziedziczenie w takim wypadku? Chyba złego słowa omylnie użyłeś, na cholerę ci selektor w klasie którą chcesz wybrać, podejście nie dość że niepoprawne, to w dodatku ma tyle sensu co pisanie skryptów w Javie xD.
Chyba że ja coś źle rozumiem i jakoś inaczej chciałbyś to zrobić, ale najlepszym wyjściem tutaj to połączenie __new__ i 'pythonowego switch casea' który ma czas dostępu O(1) i nie zapycha zbędnym overloadem oraz zmiennymi naszej klasy wyjściowej.

Minimalny przykład 'Switch case':

class A:
    pass

class B:
    pass

if __name__ == "__main__":
    switch_case = {"A": A, "B": B}
    a = switch_case.get("A")
    b = switch_case.get("B")

Jeśli chcesz przekazywać argumenty, lub mieć argumenty domyślne brane z jakichś zmiennych w momencie inicjalizacji słownika, lub realnie kryjącą się wartość w danym momencie (obie wersje są możliwe). To możesz przejrzeć sobie moje posty na forum, wklejałem to już dobre 5-6 razy jak się bawić z lambdą w różnego typu domknięcia, głównie przy tkinterze :)

Całego switch_case'a możesz zamknąć w metodzie swojego Template oczywiście. Jak pisałem, to jest przykład minimalistyczny :D

@Edit:
Skoro już na drugim forum szerzej odpisałem na pytanie, jeszcze wrzucę dwa selektory które tam napisałem:

Wybranie klasy:

class Selector:
	switch = {"A": A, "B": B}
	def __new__(self, arg):
		return self.switch.get(arg)
	
class A:
	def __init__(self, val):
		self.x = val
class B:
	x = 5
		
if __name__ == "__main__":
	selected = Selector("A")
	selected = selected(3)
	print(selected)
	print(selected.x)
	selected = Selector("B")
	print(selected)
	print(selected.x)

Wybranie + inicjalizacja:

class Selector:
	switch = {"A": A, "B": B}
	def __new__(self, select, **kwargs):
		return self.switch.get(select)(**kwargs)
	
class A:
	def __init__(self, **kwargs):
		self.x = kwargs.get("val")
class B:
	x = 5
	def __init__(self, **kwargs):
		pass
		
if __name__ == "__main__":
	selected = Selector("A", val=3)
	print(selected)
	print(selected.x)
	selected = Selector("B")
	print(selected)
	print(selected.x)

Ogólnie to są przykłady jak ominąć globals, z tego momentu już powinieneś dać radę to rozszerzyć sobie odpowiednio by ci wywoływał metody itd. To chyba najbardziej eleganckie wyjście. (Bo wywołanie print(Selector("A")) zwróci ci __str__ z klasy A :D )

0

Co do posta na 2 forach... Guaz... przyłapałeś mnie... ;-(
Jednakże zależało mi na większej opinii osób, bo napisać coś dobrze a napisać coś prawidłowo to nie zawsze to samo.
Do tego jeśli napisane jest źle i nieprawidłowo, to nie zawsze ważne czy działa... ;-(
PS: Czasami lepiej napisać coś źle i nieprawidłowo niż nie napisać wcale. Ponoć zawsze można to poprawić :)

Bardzo dziękuję za pomoc i poświęcony czas :)
Jutro posiedzę nad tym dłużej bo dzisiaj dopiero wróciłem.
Przykład bardzo ciekawy - jednakże coś u mnie nie chciał zaskoczyć - po małych poprawkach i zmianach zadziałał:

class A:
    def __init__(self, **kwargs):
        self.x = kwargs.get("val")


class B:
    def __init__(self, **kwargs):
        self.x = kwargs.get("val")


class Selector:
    switch = {"A": A, "B": B}

    def __new__(self, select, **kwargs):
        return self.switch.get(select)(**kwargs)


if __name__ == "__main__":

    selected = Selector("A", val="Class A")
    print(selected.x)

    selected = Selector("B", val="Class B")
    print(selected.x)

Zastanawia mnie jeszcze jedno - czy ten switch na pewno jest potrzebny - czy zamiast tego słownika nie da się tego zrobić bez niego.
Jutro nad tym posiedzę :-)

0
class A:
    def __init__(self, **kwargs):
        self.x = kwargs.get("val")

class B:
    def __init__(self, **kwargs):
        self.x = kwargs.get("val")

class Selector:
    switch = {"A": A, "B": B}

    def __new__(self, select, **kwargs):
        return self.switch.get(select)(**kwargs)

    def get_some_string(self):
        return "Some string"

if __name__ == "__main__":

    selected = Selector("A", val="Class A")
    print(selected.x)
    print(selected.get_some_string()) # jak dostać się do takich metod?

    selected = Selector("B", val="Class B")
    print(selected.x)

To takie jeszcze jedno pytanie - wydaje mi się (może niesłusznie ale jeszcze mi się nie udało tego zrobić) ale w takim rozwiązaniu nie jestem w stanie do klasy Selector dodać jakichś metod które chciałbym następnie wykorzystać.

Ogólnie klasa selector ma mieć dodatkowe metody i atrybuty a w tym rozwiązaniu za bardzo tego nie jestem w stanie zrobić (albo raczej jeszcze nie wiem jak jeśli to możliwe).

0

Musisz dodac/zwracac lambde (funkcje anonimowa)

Edit: wydaje mi sie ze "wskaznik" do pelnoprawnej (named) funkcji tez mozesz zwrocic bo wszystko jest obiektem. Tylko zwracasz funkcje a nie jej wywolanie!

0

A co chcesz osiągnąć? W sensie w jakim celu chcesz coś takiego, jakie to ma mieć zastosowanie

0

Osiągnąć? Potrzebuję różnych klas które będą miały funkcje wspólne (ale nie działąły identycznie) i funkcje własne (tu można uznać za te klasy - A i B z przykładu @Guaz'a lub klasy One i Two z początku mojego postu). Klasy te oczywiście mają też mieć możliwość wykonywać metody rodzica (stąd na początku robiłem dziedziczenie ale nie napisałem o tym tu a powinienem).

W klasie rodzica będą także metody które nie będą uruchamiane przez dzieci (chociaż mogłyby za pomocą super) aler będą związane z innymi rzeczami niedotyczącymi klas dzieci.

Na razie czytam o różnicach między new a init (nie chodzi mi o ilość tworzonych instancji) bo zdziwiło mnie, że nie mogłem się dostać do metody klasy Separator.
Jak nie uda mi się jakoś tego ogarnąć to chwilowo wrócę do swojego rozwiązania bo potrzebuję na szybko to póki co zrobić aby działało a potem będę poprawiał.

0

Jak na moje to to jest kombinowane. Przy tych trywialnych przykładach wydaje się to zbędne, może jakbyś pokazał realne zastosowanie to znaleźlibyśmy inne rozwiązanie?

0

Zwracając w __new__ obiekt, przy tworzeniu tego obiektu zawsze przypiszesz obiekt który wybierzesz, po Selektorze nie będzie tam śladu.
Natomiast odwołując się do tego co piszesz teraz, to jest nam potrzebne zupełnie inne podejście... Chyba że Selektor będzie obiektem zawierającym wyłącznie statyczne pola i metody, a jego próba przypisania zwróci nam odpowiedni obiekt.

static przykład:

#(...)
class Selector:
    switch = {"A": A, "B": B}

    def __new__(self, select, **kwargs):
        return self.switch.get(select)(**kwargs)

    @staticmethod
    def get_some_string(self):
        return "Some string"

if __name__ == "__main__":
    print(Selector.get_some_string())

I stąd zapewne twoje problemy w zrozumieniu przykładu :).
Ponieważ gdy przypiszesz select = Selector("A") to w tym miejscu w select, nie istnieje Selector, tylko twoja klasa A, przed nadpisanie metody __new__ która jest wywoływana przed initem, i zamiast domyślnie zwracać siebie self zwraca ci wybrany obiekt, chyba że nastąpi KeyError jeśli takiej opcji w słowniku nie będzie.

Defakto, z dziedziczeniem nie zrobiłeś błędu w swoim pierwotnym przykładzie skoro chcesz stworzyć klasę po której inne będą dziedziczyć metody itd.

0

@Guaz - no właśnie problem w tym, że nie opisałem co dokładnie chce osiągnąć.
Co do tego, że nie osiągnę metod tym sposobem co podałeś - tu się zgadzam i zauważyłem to już :(

Co do tego do czego mi to:

  • Programik ma za zadanie sprawdzić status urządzenia na podstawie jego nazwy.
  • Na podstawie nazwy urządzenia ma być wybrana konkretna klasa (One, Two, itd) przez klasę Template (Selektor)
  • Klasy One i Two mają podobne oraz różne od siebie metody/argumenty
  • Klasa Base ma metody wspólne dla wszystkich klas modułów i są one identyczne (np wyłączenie lub włączenie zasilania urządzenia)

Ogólnie mój pierwszy przykład rozwiązuje takie problemy:

  • mogę wywołać metodę z danej podklasy używając tylko jej nazwy
  • mogę użyć metod z głównej klasy
  • mogę używać atrybutów klasy głównej i podklas

Czyli ogólnie z moim pierwszym przykładem mogę zrobić wszystko co potrzebuję ale z użyciem globals()
Na razie jeszcze kombinuję (nawet trochę za bardzo) z połączeniem obu sposobów w jeden aby pozbyć się globals().
Jak skończę to wrzucę tu co wykombinowałem.

0

@Guaz: Rzeczywiście, ale jak widać wtedy, tak jak i on sam, nie wiedziałem o co chodzi:)

0

Czemu w takim razie po prostu nie skorzystasz z dziedziczenia? A jak pozbyć się globals to przecież wiesz (padła już odpowiedź, że wystarczy użyć switcha (dicta)).

1 użytkowników online, w tym zalogowanych: 0, gości: 1