Czy lepiej jest wstępnie inicjować atrybuty w klasie, czy dodawać je po drodze?

11

Przykro mi, jeśli jest to całkowicie absurdalne pytanie, ale jestem ciekawy, jakie są najlepsze praktyki i nie mogę znaleźć dobrej odpowiedzi w Google.

W Pythonie zwykle używam pustej klasy jako super-catchallowego kontenera struktury danych (coś w rodzaju pliku JSON) i dodając atrybuty po drodze:

class DataObj:
    "Catch-all data object"
    def __init__(self):
        pass

def processData(inputs):
    data = DataObj()
    data.a = 1
    data.b = "sym"
    data.c = [2,5,2,1]

Daje mi to ogromną elastyczność, ponieważ obiekt kontenerowy może zasadniczo przechowywać wszystko. Więc jeśli pojawią się nowe wymagania, po prostu dodam go jako kolejny atrybut do obiektu DataObj (który przekazuję w moim kodzie).

Jednak ostatnio (programistom FP) wywarło na mnie wrażenie, że jest to okropna praktyka, ponieważ bardzo utrudnia czytanie kodu. Należy przejść przez cały kod, aby dowiedzieć się, jakie atrybuty ma DataObj.

Pytanie : Jak mogę to przepisać, aby uzyskać większą łatwość konserwacji bez utraty elastyczności?

Czy są jakieś pomysły z programowania funkcjonalnego, które mogę zastosować?

Szukam najlepszych praktyk.

Uwaga : jednym z pomysłów jest wstępne zainicjowanie klasy za pomocą wszystkich atrybutów, które można napotkać, np

class DataObj:
    "Catch-all data object"
    def __init__(self):
        data.a = 0
        data.b = ""
        data.c = []

def processData(inputs):
    data = DataObj()
    data.a = 1
    data.b = "sym"
    data.c = [2,5,2,1]

Czy to naprawdę dobry pomysł? Co jeśli nie wiem, jakie są moje atrybuty a priori?

Gilead
źródło
Twoje struktury danych są tak zmienne, że wydaje się, że martwisz się o ich łatwość konserwacji. W swoim wolnym czasie ™ spróbuj przeczytać ten artykuł o niezmiennych modelach danych . Może to całkowicie zmienić sposób, w jaki rozumujesz dane.
9000
@ 9000 Artykuł taki jak ten przekonuje już przekonanych. Wydawało mi się, że jest to raczej poradnik niż dlaczego (lista osób, które tak naprawdę nie są przekonujące, chyba że czujesz, że masz te specyficzne potrzeby). Dla mnie to nie przekonuje kogoś, kto aktualizuje faktury w VB, że konieczność ciągłego tworzenia nowych kopii ich faktury ma sens (dodawanie płatności, nowy obiekt faktury; dodawanie części, nowy obiekt faktury).
Paul,

Odpowiedzi:

10

Jak mogę to przepisać, aby uzyskać większą łatwość konserwacji bez utraty elastyczności?

Ty nie. Elastyczność jest właśnie przyczyną problemu. Jeśli dowolny kod w dowolnym miejscu może zmienić atrybuty obiektu, łatwość konserwacji jest już podzielona na części. Idealnie, każda klasa ma zestaw atrybutów, które są ustawione w kamieniu po __init__i takie same dla każdego wystąpienia. Nie zawsze jest to możliwe lub rozsądne, ale powinno tak być zawsze, gdy nie masz naprawdę dobrych powodów, aby tego uniknąć.

jednym z pomysłów jest wstępne zainicjowanie klasy za pomocą wszystkich atrybutów, które oczekuje się napotkać

To nie jest dobry pomysł. Jasne, wtedy atrybut istnieje, ale może mieć fałszywą wartość, a nawet prawidłową, która zakrywa kod nieprzypisujący wartości (lub błędnie napisany). AttributeErrorjest przerażające, ale błędne wyniki są gorsze. Ogólnie wartości domyślne są w porządku, ale aby wybrać rozsądną wartość domyślną (i zdecydować, co jest wymagane), musisz wiedzieć, do czego służy obiekt.

Co jeśli nie wiem, jakie są moje atrybuty a priori?

W takim razie jesteś wkręcony i powinieneś użyć dykta lub listy zamiast twardych nazw atrybutów. Ale rozumiem, że miałeś na myśli „... w czasie, gdy piszę klasę kontenera”. Zatem odpowiedź brzmi: „Możesz edytować pliki w trybie lockstep, duh”. Potrzebujesz nowego atrybutu? Dodaj atrybut fregowania do klasy kontenera. Jest więcej kodu używającego tej klasy i nie potrzebuje on tego atrybutu? Rozważ podzielenie rzeczy na dwie osobne klasy (użyj miksów, aby pozostać SUCHYM), więc włącz opcjonalnie, jeśli ma to sens.

Jeśli boisz się pisać powtarzające się klasy kontenerów: Zastosuj metaprogramowanie rozważnie lub użyj, collections.namedtuplejeśli nie musisz mutować członków po utworzeniu (Twoi znajomi z FP byliby zadowoleni).


źródło
7

Zawsze możesz skorzystać z klasy Buncha Alexa Martellego . W Twoim przypadku:

class DataObj:
    "Catch-all data object"
    def __init__(self, **kwds):
        self.__dict__.update(kwds)

def processData(inputs):
    data = DataObj(a=1, b="sym", c=[2,5,2,1])

W ten sposób czytelnik przynajmniej zrozumie, że dane są po prostu głupim magazynem danych i od razu widać, które wartości są przechowywane pod inną nazwą, ponieważ wszystko dzieje się w jednym wierszu.

I tak, robienie rzeczy w ten sposób jest czasem dobrym pomysłem.

pillmuncher
źródło
1

Prawdopodobnie skorzystałbym z drugiego podejścia, prawdopodobnie Nonedo wskazania nieprawidłowych danych. Prawdą jest, że trudno jest odczytać / zachować, jeśli dodasz atrybuty później. Jednak więcej informacji na temat celu tej klasy / obiektu dałoby wgląd w to, dlaczego pierwszy pomysł jest złym projektem: gdzie miałbyś kiedykolwiek całkowicie pustą klasę bez metod lub danych domyślnych? Dlaczego nie miałbyś wiedzieć, jakie atrybuty ma klasa?

Jest możliwe , że processDatamoże być lepiej jak metoda ( process_datapodążać Pythona nazewnictwa), ponieważ działa na klasy. Biorąc pod uwagę przykład, wygląda na to, że może być lepsza jako struktura danych (gdzie dictmoże wystarczyć).

Biorąc pod uwagę prawdziwy przykład, możesz rozważyć przesłanie pytania do CodeReview , gdzie mogą one pomóc w przebudowaniu kodu.

Casey Kuball
źródło