Wywołanie metody z klasy wybranej na podstawie zmiennej

Odpowiedz Nowy wątek
2019-08-02 23:42
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

Linux [Debian], Arduino, Raspberry Pi, ESP8266, C++, Python.
edytowany 1x, ostatnio: Setesh, 2019-08-03 00:24
Testing... Testing... - Setesh 2019-08-04 00:22

Pozostało 580 znaków

2019-08-03 09:00
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.


Pozostało 580 znaków

2019-08-03 12:13

@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 )


Linux Mint
Arduino / Python 3.5.2
edytowany 7x, ostatnio: Guaz, 2019-08-03 13:10

Pozostało 580 znaków

2019-08-04 00:10
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ę :-)


Linux [Debian], Arduino, Raspberry Pi, ESP8266, C++, Python.
edytowany 3x, ostatnio: Setesh, 2019-08-04 00:30
Zakładając podanie stringa, oczekując odebrania czegoś z konkretnej klasy... Switch nie jest potrzebny, możesz zrobić niepythonową kolumnę if/else dla wielu klas co jest jedynym sensownym zamiennikiem. Do głowy mi przychodzi jeszcze jeden ale bezsensowny i odradzany - mianowicie: Eval oraz Exec, pozwala wykonać string jakby był fragmentem kodu. Jeśli coś w przykładzie jest niejasne, to pisz :D - Guaz 2019-08-04 00:19
Jedyne co będzie to przekazywanie wspólnych danych do klasy która po ich obróbce zwróci inne informacje. Całość oparta na stringach (przekazywanie danych między klasami - metody to inna sprawa) więc rocket science tu nie będzie. Raczej podstawy. Ewentualny brak słownika to odrębna bajka - bardziej ciekawostka. - Setesh 2019-08-04 00:26
No to globals już sam wypisałeś, chociaż odradzane bo coś może ci przysłonić klasę w którymś momencie jakbyś zrobił błąd. Pozostałe dwie które ja osobiście widzę na ten moment, to w sumie wypisałem w komentarzu. A słownika nie ma się co bać ponieważ każda klasa w pythonie to głównie zręcznie rozszerzony słownik z własnymi metodami :). I jest to struktura danych która w wielu językach programowania jest nieodłączna: w Javie i C++ jako Map. I z ciekawości, co ci nie działało przed modyfikacją? Bo klasa B z założenia miała nic nie robić poza posiadaniem statycznego x :) - Guaz 2019-08-04 00:38
@Guaz trochę Cię poniosło z tym niepythonowym if/else chainem. - Mózg 2019-08-04 12:02
@Guaz - Z tym twoim przykładem najpierw wywalało, że klasa A nie jest zdefiniowana. Przeniosłem selektor za klasy A i B. Zaczęło śmigać bez błędów. Potem małe zmiany aby to uprościć i wyszło co wkleiłem. - Setesh 2019-08-04 12:19
@Mózg: Zakładam większą skalę tego Templatea. Załóżmy 20 opcji, czytelniejszy i szybszy będzie dostęp do słownika czy if/else na 40+ linii z copy/paste? Jak dla mnie wielkie kolumny if/else gdy da się je ominąć poprzez słownikowy wybór są zawsze lepsze. Defakto, trzymając się tego by kod w pythonie był prosty do zrozumienia, tak, osobiście uważam że dłuższe kolumny if/else nie są pythonowe. @Setesh faktycznie, przeoczyłem to po przeniesieniu definicji switch ze środka new aby nie budować go za każdym razem i jest budowany jako static content w momencie deklaracji klasy. - Guaz 2019-08-04 12:32

Pozostało 580 znaków

2019-08-04 19:00
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).


Linux [Debian], Arduino, Raspberry Pi, ESP8266, C++, Python.

Pozostało 580 znaków

2019-08-04 19:04
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!


01010100 01110101 01110100 01100001 01101010 00100000 01101110 01101001 01100101 00100000 01101101 01100001 00100000 01101110 01101001 01100011 00100000 01100011 01101001 01100101 01101011 01100001 01110111 01100101 01100111 01101111 00101110 00100000 01001001 01100011 00100000 01110011 01110100 01101111 01101110 01110100 00101110
edytowany 4x, ostatnio: stivens, 2019-08-04 19:11

Pozostało 580 znaków

2019-08-04 19:29
0

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

Pozostało 580 znaków

2019-08-05 00:39
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ł.


Linux [Debian], Arduino, Raspberry Pi, ESP8266, C++, Python.

Pozostało 580 znaków

2019-08-05 00:53
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?

Pozostało 580 znaków

2019-08-05 03:16
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.


Linux Mint
Arduino / Python 3.5.2
edytowany 2x, ostatnio: Guaz, 2019-08-05 03:18
To by było na tyle z Twojego "prostego do zrozumienia kodu." - Mózg 2019-08-05 13:01

Pozostało 580 znaków

2019-08-05 22:30
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.


Linux [Debian], Arduino, Raspberry Pi, ESP8266, C++, Python.

Pozostało 580 znaków

Odpowiedz
Liczba odpowiedzi na stronę

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