Sortowanie listy obiektów w Pythonie po atrybutach

0

No właśnie .. mam listę obiektów, którą chcę posortować ze względu na dany atrybut (w klasie może być ich więcej, na razie dałem tylko 1). Jak to ładnie zrobić? Napisałem sobie bubblesorta, ale nie podoba mi się sposób wyświetlania wyników ... A poza tym, to dosyć żmudne rozwiązanie - nie ma czegoś w stylu "przeciążenia" sorta, jak np. w C++?

Oto kod:

from random import randint

class klasa :

    __wartosc = 0

    def __init__(self, w) :
        self.__wartosc = w

    def pokaz(self) :
        print(self.__wartosc)

def swap(numList,i) :
        temp = numList[i]
        numList[i] = numList[i+1]
        numList[i+1] = temp

def bubbleSort(numList, n) :
    for i in range(0,n-1) :
        for i in range(0,n-1) :
            if numList[i]._klasa__wartosc > numList[i+1]._klasa__wartosc :
                swap(numList,i)
    return numList


tmp = []
for i in range(0,5) :
    tmp.append(klasa(randint(0,100)))

for i in range(0,5) :
    print(tmp[i].pokaz())

tmp = bubbleSort(tmp, len(tmp));

print("--")

for i in range(0,5) :
    print(tmp[i].pokaz())

I output:

>>> 
69
None
45
None
0
None
0
None
29
None
--
0
None
0
None
29
None
45
None
69
None
>>> 

Używam Pythona 3.2 .. i co oznaczają te wyświetlane "None", jak się ich pozbyć?

0
Kamill napisał(a)

Co oznaczają te wyświetlane "None", jak się ich pozbyć?

To że próbujesz printować printa?

def pokaz(self) :
   print(self.__wartosc)    

for i in range(0,5) :
   print(tmp[i].pokaz())
0

Ożesz .. jaki wstyd :P głupi błąd :P ko a co z drugą częścią pytania - da się jakoś inaczej posortować listę obiektów wg atrybutu?

0

Dobra, sam znalazłem, może się komuś przyda:

from random import randint
import operator

class klasa :

    __wartosc = 0

    def __init__(self, w) :
        self.__wartosc = w

    def pokaz(self) :
        print(self.__wartosc)

tmp = []
for i in range(0,5) :
    tmp.append(klasa(randint(0,100)))

for i in range(0,5) :
    tmp[i].pokaz()

tmp[:] = sorted(tmp, key=operator.attrgetter('_klasa__wartosc'))

print("----------------------------------------------------------------------")

for i in range(0,5) :
    tmp[i].pokaz()

0

Chyba trzeba sięgnąć po taki stary, zapomniany skrót jak RTFM. W "przyjaznej" dokumentacji wszystko jest, w tym kilka metod sortowania.

class klasa :
 
    __wartosc = 0
 
    def __init__(self, w) :
        self.__wartosc = w
 
    def pokaz(self) :
        print(self.__wartosc)

__wartosc jest zmienną klasy, nie instancji. Pola klasy definiujesz na poziomie klasy, tak jak to właśie zrobiłeś. Zmienne instancji tworzysz albo wyłacznie odwołaniami do self albo poprzed pole __slots__ (na razie o slotach zapomnij). Prawidłowo powinno być:

class Klasa(object):
    def __init__(self, w) :
        self.wartosc = w

Dziedziczenie z object jest w Pythonie 3.x domyślne, ale pisanie wprost jest w dobrym tonie, przynajmniej póki linia 2.x jest dominująca. W tym języku nie ma prawdziwie prywatnych pól. Klasy nazywaj z wielkiej litery. Swap realizuj poprzez równoległe przypisanie, bez zmiennych pomocniczych - x, y = y, x. Nie rób metod wypisujących obiekt, zaimplementuj metodę __repr__ zwracającą jego tekstową reprezentację i ewentualnie __str__ odpowiedzialną za konwersję na string.

range(0, x) jest mniej czytelną formą range(x), obie formy są równoważne, druga jest swego rodzaju idiomem w Pythonie.

Aby obiekty były porównywalne powinny implementować metodę __cmp__, chociaż na potrzeby standardowych sortowań można dostarczyć funkcję klucza, zwracającą wartość, po której obiekty będą sortowane.

import random, operator


class Sth(object):
    def __init__(self, x=None, y=None, z=None):
        def valOrRand(n):
            return n if n is not None else random.randint(0, 100)
            
        self.x, self.y, self.z = valOrRand(x), valOrRand(y), valOrRand(z)

    def __cmp__(self, other):
        return self.x - other.x 

    def __repr__(self):
        return 'Sth({0}, {1}, {2})'.format(self.x, self.y, self.z)

        
if __name__ == '__main__':
    tbl = [ Sth() for n in range(5) ]
    print('base:\t{0}'.format(tbl))

    # 1
    tbl.sort()
    print('by x:\t{0}'.format(tbl))

    # 2
    tbl = list(sorted(tbl, key=lambda s: s.y))
    print('by y:\t{0}'.format(tbl))

    # 3
    tbl.sort(key=operator.attrgetter('z'))
    print('by z:\t{0}'.format(tbl))

Kod nie jest idealny, ma natomiast pokazać kilka idiomów typowych dla Pythona. Konstruktor Sth posiada domyślne argumenty ustawione na None, wszystko co nie jest None (is not None) jest akceptowane jako podany argument, dla tego konkretnego przypadku wykonywana jest akcja domyślna - losowanie. Jest to zrealizowane przy użyciu funkcji zdefiniowanej wewnątrz metody, Python pozwala na takie zagnieżdżanie. Zmienna tbl tworzona jest przy użyciu list comprehension. Pierwsze sortowanie (#1) wykonywane jest stabilnie, w miejscu, poprzez poprzez metodę sort listy, z użyciem zdefiniowanej w Sth metody __cmp__. Następne wykorzystuje ogólną funkcję sortującą, która przyjmuje dowolny iterowalny obiekt i zwraca iterator na posortowane wartości, dlatego też jest on przed przypisaniem konwertowany na listę. Jako klucz używane jest lambda-wyrażenie zwracające pole y obiektu. W ostatnim wypadku ponownie sortujemy w miejscu, tym razem z obiektem typu attrgetter z modułu operator, który zwraca podane w konstruktorze pole z obiektu podanego do metody __call__(czyli jego wywołania).

Całość kompatybilna z linią 2.x od 2.6 (metoda format) i całą linią 3.x.

0

Jeeej dzięki ! ^^ Zaraz zabieram się za analizę tego, co napisałeś:)

0

Poprawiłem wg Twoich wskazówek. A powiedz mi jeszcze, jak zrobić coś takiego: soruję, ale nie za pomocą atrybutu, tylko za pomocą funkcji wywoływanej dla obiektu ... jak to zrobić?

Bo takie coś daje błędy:

from random import randint
import operator
 
class Klasa(object):

    def __init__(self, wartosc = None) :
        self.__wartosc = wartosc

    def __repr__(self) :
        print(self.__wartosc)

    def __str__(self) :
        return str(self.__wartosc)

    def funkcja(self) :
        return randint(0,6)

lista = []
for i in range(5) :
    lista.append(Klasa(randint(0,100)))

lista.sort(key = operator.attrgetter('_Klasa_funkcja()'))

    
0
Kamill napisał(a)
from random import randint
import operator
 
class Klasa(object):

    def __init__(self, wartosc = None) :
        self.__wartosc = wartosc

    def __repr__(self) :
        print(self.__wartosc)

    def __str__(self) :
        return str(self.__wartosc)

    def funkcja(self) :
        return randint(0,6)

lista = []
for i in range(5) :
    lista.append(Klasa(randint(0,100)))

lista.sort(key = operator.attrgetter('_Klasa_funkcja()'))

Nie stosuj "pseudoprywatnych" pól bo prefiks __ w praktyce nie po to powstał, jak widzisz całą "prywatność" łatwo obejść. Tutaj panuje konwencja "zaczyna się od _ - nie ruszać, zależny wewnętrznie od obiektu", tyle. None w konstruktorze ma sens jeżeli jakoś je obsługujesz, używasz do określenia, że użytkownik nic wartościowego nie podstawił za argument domyślny. __repr__ ma zwracać string, nie wywoływać efekty uboczne, jeżeli napiszesz print(Sth()) to na utworzonym obiekcie zostanie wywołana metoda __repr__ i potem wynik zostanie wypisany przez print. Korzystaj z list comprehension, na analogicznej składni opierają się generatory, to jedne z najważniejszych i najpotężniejszych konstrukcji w Pythonie, poza oferowanymi możliwościami skracają znacząco kod. Staraj się nie importować symboli bezpośrednio do swojego modułu, zaśmiecasz sobie przestrzeń globalną, dodatkowo inne moduły importujące Twój otrzymają je "gratisowo", nawet o tym nie wiedząc jeżeli zrobią from XXX import *.

attrgetter nie odstawia magii, to po prostu opakowane wywołanie funkcji getattr, getattr(obiekt, 'podana nazwa pola'). Python ma fantastyczną dokumentację, gdybyś z niej skorzystał to zobaczyłbyś, że w użytym wcześniej module operator siedzie metoda methodcaller... która tutaj się nie przyda. Sortowanie oczekuje funkcji jednoargumentowej, która przyjmie obiekt, w tym celu można po prostu użyć metody nie przypiętej jeszcze do obiektu, znajdującej się w klasie:

import random
 
class Klasa(object):
    def __init__(self, wartosc) :
        self.wartosc = wartosc

    def __repr__(self) :
        return 'Klasa({0})'.format(self.wartosc)

    def funkcja(self) :
        return random.randint(0, 6)

lista = [ Klasa(random.randint(0, 100)) for n in range(5) ]
lista.sort(key=Klasa.funkcja)
print(lista)

Chociaz nie widzę sensu sortowania po wartości losowej. Co do formatowania to obowiązuje dokument PEP-8, z małym zastrzeżeniem, mixedCase jest coraz popularniejszy, zapewne przez wpływy Haskella także od kilku lat już za nie_używanie_tych_zajmujących_miejsce_i_wolnych_w_pisaniu_podkreśleń nikt raczej nie rozpala stosu.

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