dziedziczenie warunkowe

0

Witam wszystkich

Problem polega na tym, że mamy w pewnym module C.py klasę C która dziedziczy z klasy X.
Sęk w tym, że klasa X importowana z innego modułu tutaj ABX.py powinna w zależności od parametrów inicjalizacyjnych klasy C
niejako przekazać do klasy C nie samą siebie ale inną klasę. I tak gdy parametr typ przekazywany do klasy C jest "A"
to klasa pośrednicząca X powinna transformować się w klasę A, a gdy parametr jest "B" to klasa X powinna transformować się w B.
Ważne jest tutaj aby instancja klasy C w zależności od przekazanego parametru, który decyduje jaka klasa będzie dziedziczona
zachowała wszystkie swoje właściwości czyli właściwości klasy C jaki właściwości klasy z której będzie dziedziczyć warunkowo czyli klasy A lub B.
A klasa X ma się po prostu rozpłynąć kończąc swoje zadanie :)

Reasumując nie wiem jak napisać kod klasy X aby spełnione były warunki na końcu modułu C.py

To co jest zamieszczone poniżej to tylko nieudana próba osiągnięcia tego.
Chyba że macie jakiś inny pomysł.

## moduł ABX.py

# możemy modyfikować dowolnie kod klasy A,B,X, jak i ten moduł.
class A(object): pass
class B(object): pass

class X(object):
    def __init__(self, typ):
        if   typ == "A":
            # Tutaj przykładowy kod, który w miejsce klasy X wstawia klase A
            self.__class__ = A
        elif typ == "B":
            # Tutaj przykładowy kod, który w miejsce klasy X wstawia klase B
            self.__class__ = B
## moduł C.py

from ABC import X

# Tej klasy nie wolno ruszać, ani zasadnizco tego modułu
class C(X):pass

c = C("A")                  # mają być spełnione poniższe warunki
print isinstance(c, C) #True instancja musi dziedziczyć po klasie C
print isinstance(c, X) #False
print isinstance(c, A) #True instancja musi dziedziczyć w tym wypadku również po klasie A
print isinstance(c, B) #False

c = C("B")
print isinstance(c, C) #True instancja musi dziedziczyć po klasie C
print isinstance(c, X) #False
print isinstance(c, A) #False
print isinstance(c, B) #True instancja musi dziedziczyć w tym wypadku również po klasie B
2

Jak klasa ma się "transformować" w inną, nie wiem po co to Robisz, ale nie lepiej użyć funkcji, która, w zależności od parametru, prozaicznie zwraca obiekt danej klasy?

0
lion137 napisał(a):

Jak klasa ma się "transformować" w inną, nie wiem po co to Robisz, ale nie lepiej użyć funkcji, która, w zależności od parametru, prozaicznie zwraca obiekt danej klasy?

Mój kod powoduje transformację, tylko problem polega na tym, że

c = C("A")                 
print isinstance(c, C) # tutaj otrzymujemy False 
print isinstance(c, A) # a tutaj otrzymujemy True 

czyli tracimy właściwości klasy C
a zmienna c staje się instancją ale tylko klasy A

a mnie nie o to chodzi, ale o to aby kod

c = C("A")                 

powodował, że klasa C dziedziczy z A podczas inicjalizacji

Poco to robię?
Po prostu mam taki praktyczny problem. To co tutaj podaję jest tylko tego uproszczeniem.
Nie mogę ruszyć pewnego modułu tutaj reprezentowanego przykładowo przez C.py.
Dlatego nie mogę do tego wykorzystać funkcji

0

Możesz takiego potworka wyczarować:

class A:
	pass

class B:
	pass

class X:
	pass

class XA(X, A):
	pass

class XB(X, B):
	pass


class Magic:
	def __new__(cls, arg):
		if arg == 'A':
			return XA()
		else:
			return XB()


a = Magic('A')
b = Magic('B')

print(isinstance(a, A))  # True
print(isinstance(a, X))  # True
print(isinstance(b, B))  # True
print(isinstance(b, X))  # True

ale nie polecam ;)

0
iksde napisał(a):

Możesz takiego potworka wyczarować:

class A:
	pass

class B:
	pass

class X:
	pass

class XA(X, A):
	pass

class XB(X, B):
	pass


class Magic:
	def __new__(cls, arg):
		if arg == 'A':
			return XA()
		else:
			return XB()


a = Magic('A')
b = Magic('B')

print(isinstance(a, A))  # True
print(isinstance(a, X))  # True
print(isinstance(b, B))  # True
print(isinstance(b, X))  # True

ale nie polecam ;)

Dzięki ale to nie pasuje do mojego problemu. Ponieważ musiał bym mieć możliwość dowolnie modyfikować kod.

W moim, niemogącym ulec zmianie module C.py jest import ABX.py.
Należy napisać kod do tego ABX.py tak, aby uruchamiając C.py były spełnione założenia.

c = C("A")                  # mają być spełnione poniższe warunki
print isinstance(c, C) #True instancja musi dziedziczyć po klasie C
print isinstance(c, X) #False
print isinstance(c, A) #True instancja musi dziedziczyć w tym wypadku również po klasie A
print isinstance(c, B) #False

c = C("B")
print isinstance(c, C) #True instancja musi dziedziczyć po klasie C
print isinstance(c, X) #False
print isinstance(c, A) #False
print isinstance(c, B) #True instancja musi dziedziczyć w tym wypadku również po klasie B

Taka mała errata

#w module C.py jest    
from ABC import X 
#a powinno być
from ABX import X, A, B
1

Doszedłem do czegoś takiego

# abx.py

import inspect

_classes = {}

def register(class_):
    _classes[class_.__name__] = class_
    return class_

@register
class A(object): pass

@register
class B(object): pass

class X(object):
    def __init__(self, arg):
        self.__class__ = type(arg, (self.__class__, _classes[arg]), {})
# C.py

from ABX import X, A, B

# C.py
class C(X): pass

c = C("A")
assert isinstance(c, C)
assert isinstance(c, A)
assert not isinstance(c, B)
c = C("B")
assert isinstance(c, C)
assert not isinstance(c, A)
assert isinstance(c, B)

I działa, ale tego warunku assert not isinstance(c, X) nie jestem w stanie spełnić i sprawić żeby X "zniknęło", nie kojarzę też żadnego sposobu na to by to zrobić.

Są to w ogóle okropne hacki, cokolwiek chcesz robić, robisz to źle.

0

Co za magia! Chyba tej nocy nie zdążę zakumać, jak to w ogóle może działać, bo mi się już oczy kleją :)
Ale to już wygląda na to o co chodzi. Pewnie dałoby się zrobić prościej i czytelniej.
Ja już prawie mam swój prostszy pomysł, ale jeszcze się nim nie pochwalę, bo mogę spalić.
W sumie te trzy warunki, które są spełnione, to mi w zupełności wystarczają, a ten assert not isinstance(c, X)
to nie musi być spełniony, o ile nie zaburzy prawidłowego korzystania z klasy C, przysłaniając jej metody czy właściwości.
A wydaje się, że nie zaburzy.

0

Działa to tak, że type pozwala na dynamiczne tworzenie klas:

class Base(object):
    pass

Nazwa = type('Nazwa', (Base,), {})

Jest równoważne

class Base(object):
    pass

class Nazwa(Base):
    pass

Zatem w momencie tworzenia klasy podmieniam jej atrybut __class__ na dynamicznie stworzoną klasę. Uwaga - ponieważ za każdym razem tworzę nowy typ klasy, prowadzi to do dziwnych efektów ubocznych pokroju type(C("A")) == type(C("A")) zwraca False (to akurat można by naprawić trzymając cache typów) - dlatego właśnie takich hacków się nie robi, może cię to uderzyć mocno tam, gdzie się nie będziesz spodziewał.

Dekorator register służy tylko po to, by zebrać dekorowane klasy A, B do słownika _modules, żebym mógł je podać na liście rodziców nowego typu zamiast stosować jakąś ifologię if arg == 'A'.

EDIT

Wspomniana wersja z cache typów, dla której type(C("A")) == type(C("A")) daje True.

import inspect

_classes = {}
_types = {}

def register(class_):
    _classes[class_.__name__] = class_
    return class_

@register
class A(object): pass

@register
class B(object): pass

class X(object):
    def __init__(self, arg):
        type_ = _types.setdefault(arg, type(arg, (self.__class__, _classes[arg]), {}))
        self.__class__ = type_

Jakieś inne nieoczywiste efekty uboczne pewnie też tu są xD

0

No to zaryzykuje rozwiązanie, które wydaje mi się najprostsze.
Przyśniło mi się dzisiejszej nocy :)
Poniżej dla jeszcze większej czytelności zamieszczam jedno modułowy kod ale oddający problem.

# _*_ coding: utf-8  _*_
class A(object): pass
class B(object): pass

class X(object):
    def __new__(Cls, typ):
        if   typ == "A":
            Cls.__bases__= (X,A,)
        elif typ == "B":
            Cls.__bases__= (X,B,)
        return super(X, Cls).__new__(Cls)

# poniżej nie ruszamy kodu !!! a powyżej możemy
class C(X):pass

c = C("A")
assert isinstance(c, C) #instancja dziedziczy po klasie C
assert isinstance(c, A) #instancja dziedziczy także po klasie A
assert not isinstance(c, B) #instancja nie dziedziczy po B

c = C("B")
assert isinstance(c, C) #instancja dziedziczy po klasie C
assert not isinstance(c, A) #instancja nie dziedziczy po A
assert isinstance(c, B) #instancja dziedziczy również po klasie B

print "To działa !!!"

Proszę napiszcie co o tym sądzicie i czym to grozi :O
Jakby ktoś miał jeszcze jakieś fajne pomysły to jestem ich ciekaw.

0

Niestety mój kod tylko pozornie działa dobrze. W rzeczywistości nie nadaje się do wykorzystania.

# _*_ coding: utf-8  _*_
class A(object): pass
class B(object): pass

class X(object):
    def __new__(Cls, typ):
        if   typ == "A":
            Cls.__bases__= (X,A,)
        elif typ == "B":
            Cls.__bases__= (X,B,)
        return super(X, Cls).__new__(Cls)

# poniżej nie ruszamy kodu !!! a powyżej możemy
class C(X):pass


a = C("A")
assert isinstance(a, A) #instancja teraz dziedziczy A
assert not isinstance(a, B) #instancja nie dziedziczy po B

b = C("B")
assert not isinstance(b, A) #instancja nie dziedziczy po A
assert isinstance(b, B) #instancja dziedziczy po B

#teraz sprawdzamy czy "a" dalej jest instancją A
assert not isinstance(a, A) #i niestety nie jest 
assert isinstance(a, B) #ponieważ teraz jest instancją B
0

W związku z trudnościami w realizacji mojej idei warunkowego dziedziczenia klas zostałem zmuszony do szukania innego sposobu.
Ten sposób okazał się trywialny poprzez zastosowanie warunkowej agregacji klas a nie dziedziczenia.
Plus tego rozwiązania to to, że żadnych niespodzianek tutaj nie będzie.

# _*_ coding: utf-8  _*_
class A(object): pass
class B(object): pass

class X(object):
    def __init__(self, typ):
        if typ == "A":
            self.x = A()
        elif typ == "B":
            self.x = B()

# poniżej nie ruszamy kodu !!! a powyżej możemy
class C(X): pass

a = C("A")
print a.x
b = C("B")
print b.x

Jednakże nie odpuszczam tematu idei warunkowego dziedziczenia klas za pośrednictwem pewnej klasy pośredniczącej, u mnie X.
Mam kolejne rozwiązanie, które przedstawię w następnym poście a które nie zawiera wad z poprzedniego.

0

Nie wiem, czy dobrze zrozumiałem co chcesz osiągnąć, ale...

class A:
  pass

class B:
  pass

class C:
  pass

def build_class(*bases):
  class X(*bases):
    pass
  return X

... tak, chyba jesteś w stanie zrealizować to co chcesz zrealizować definiując sobie taką klaskę w funkcji...

>>> x_a = build_class(A)
>>> x_a.mro()
[<class '__main__.build_class.<locals>.X'>, <class '__main__.A'>, <class 'object'>]
>>> x_ab = build_class(A, B)
>>> x_ab.mro()
[<class '__main__.build_class.<locals>.X'>, <class '__main__.A'>, <class '__main__.B'>, <class 'object'>]
>>> x = build_class()
>>> x.mro()
[<class '__main__.build_class.<locals>.X'>, <class 'object'>]

... i chyba nie muszę mówić, że to jest paskudne hackowanie i nie powinieneś tego tak robić :D

0

Chcemy zaingerować w pewien moduł, do którego nie mamy dostępu, bo jest to np. dll, pyd. Jedyny dostęp do niego jest z zewnątrz poprzez podmienienie modułu, który on importuje. A importuje klasę przykładowo u mnie X. I ta klasa ma stawać się różną klasą A lub B w zależności od inicjalizacji klasy u mnie C w pliku pyd, która go importuje. Inną klasą w sensie dziedziczenia metod czy pól. Nie ma możliwości wykorzystać tu funkcji, jedyny pośrednik to klasa importowana i później dziedziczona poza naszą kontrolą. Mam nadzieję że jasno się wyrażam.

I co chcesz przez to osiągnąć? Po co jest to potrzebne z perspektywy z punktu tej dziedziczącej klasy poza waszą kontrolą? Chcesz, żeby konkretny obiekt klasy dynamicznie w trakcie wykonania zmieniał swój typ? Czy żeby w momencie tworzenia instancji powstawały obiekty różnych typów?

Na jakim etapie da się określić, jakiego typu instancja jest potrzebna? Skoro chcesz podmieniać typy bez wiedzy klasy korzystającej z nich, to domyślam się, że ten "docelowy" typ jest określany raz na zawsze, najpóźniej przy uruchomieniu programu. Skoro tak, to czy nie sensowniej byłoby wykorzystać warunkowy import żeby zaciągnąć klasę, która ma być odziedziczona?

if is_condition_met():
    from src.A import A as X
else:
    from src.B import B as X


class Y(X):
    pass

Nie jest to może najpiękniejsze rozwiązanie, ale wciąż wydaje mi się lepsze niż gmeranie w wewnętrznych property klasy przy tworzeniu instancji żeby dynamicznie zmienić jej typ. Jest też moim zdaniem bardziej przejrzyste niż pisanie jakichś turbo-generatorów klas jak ten który pokazałem w poprzednim poście.

Poza tym jeśli dobrze pamiętam to np. niektóre biblioteki GIS do Pythona pod dynamicznie importują jedną z dostępnych w systemie bibliotek w zależności od tego, która jest zainstalowana (m.in. libgeos).

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