Saya mencoba untuk membebani metode __add__ pada instance Nametuple dan saya mengalami sedikit masalah.

Input parameter ke dalam Nametuple saya dihasilkan secara dinamis. Empat parameter selalu sama dan dalam urutan yang sama, tetapi sisanya dapat berupa apa saja dan dalam jumlah berapa pun. Jadi saya harus dapat secara dinamis mendefinisikan pabrik kelas Nametuple saya. Dan setelah saya membuat beberapa instance, saya ingin dapat menambahkannya bersama ke dalam instance bernama Tuple baru, dengan semua parameter unik bersama-sama. Tetapi saya mengalami masalah saat membebani metode __add__ dengan benar. Tampaknya tidak berhasil.

Jadi misalnya, jika saya memiliki 3 instance Nametuple

e = Row(a=1, b=2, c=3, d=4)
m = Row(a=1, b=2, c=3, d=4, param1='a', param2='b')
t = Row(a=1, b=2, c=3, d=4, param3='val', param4=10)

Saya ingin dapat menambahkannya seperti e + m + t yang mengembalikan

Row(a=1, b=2, c=3, d=4, param1='a', param2='b', param3='val', param4=10)

Ini kode saya saat ini

class Row(object):
    ''' Creates a new namedtuple object '''
    __slots__ = ()

    def __new__(cls, *args, **kwargs):
        ''' make a new Row instance '''
        default = namedtuple('Row', 'a, b, c, d')
        newcols = set(args) - set(default._fields)
        finalfields = default._fields + tuple(newcols) if newcols else default._fields
        return namedtuple('Row', finalfields)

    def __add__(self, other):
        ''' This is the new add '''
        self_dict = self._asdict()
        other_dict = other._asdict()
        self_dict.update(other_dict)
        new_fields = tuple(self_dict.keys())
        new_row = namedtuple('Row', new_fields)
        return new_row(**self_dict)

Dengan ini, saya dapat membuat tupel bernama baru dengan benar secara dinamis, dan membuat instance-nya

e = Row()
m = Row(*['a', 'b', 'c', 'd', 'param1', 'param2'])

e._fields
('a', 'b', 'c', 'd')
m._fields
('a', 'b', 'c', 'd', 'param1', 'param2')

e2 = e(1, 2, 3, 4)
m2 = m(1, 2, 3, 4, 'a', 'b')

e2
Row(a=1, b=2, c=3, d=4)
type(e2)
__main__.Row

m2
Row(a=1, b=2, c=3, d=4, param1='a', param2='b')

Tetapi ketika saya menambahkannya, __add__ saya yang kelebihan beban tidak pernah dipanggil dan sepertinya saya hanya mendapatkan objek Tuple biasa kembali

w = e2 + m2
print(w)
(1, 2, 3, 4, 1, 2, 3, 4, 'a', 'b')
type(w)
tuple

Metode __add__ saya tampaknya tidak aktif pada objek instance saya.

Row.__add__?
Signature: Row.__add__(self, other)
Docstring: This is the new add
File:      <ipython-input-535-817d9f528ae7>
Type:      instancemethod

e.__add__?
Type:        wrapper_descriptor
String form: <slot wrapper '__add__' of 'tuple' objects>
Docstring:   x.__add__(y) <==> x+y

e2.__add__?
Type:        method-wrapper
String form: <method-wrapper '__add__' of Row object at 0x122614050>
Docstring:   x.__add__(y) <==> x+y

Apa yang saya lakukan salah? Saya juga mencoba mensubklasifikasikan namedtuple('Row', ...), seperti yang ditunjukkan dalam dokumen https://docs.python.org/2/library/collections.html#collections.namedtuple, tapi saya tidak bisa membuatnya bekerja. Saya tidak bisa membuatnya secara dinamis mengubah parameter bernama.

Inilah kegagalan itu

BaseRow = namedtuple('BaseRow', 'a, b, c, d')

class Row(BaseRow):
    __slots__ = ()

    def __new__(cls, *args, **kwargs):
        new_fields = set(kwargs.keys()) - set(cls._fields)
        cls._fields += tuple(new_fields)
        obj = super(Row, cls).__new__(cls, *args, **kwargs)
        return obj

e = Row(a=1, b=2, c=3, d=4, param1='a')
TypeError: __new__() got an unexpected keyword argument 'param1'
1
havok2063 18 Agustus 2017, 06:43

2 jawaban

Jawaban Terbaik

Terima kasih atas tanggapannya. Saya agak terpaksa menggunakan Namedtuples karena saya berurusan dengan hasil yang dikembalikan oleh SQLAlchemy, yang mengembalikan hal-hal sebagai KeyedTuples, yang merupakan versi mereka dari Namedtuple. Jadi saya harus menggunakan namedtuple agar fungsi umum saya dapat bekerja dengan keduanya. Saya yakin ini mematahkan seluruh etos tupel.

Untuk anak cucu, inilah cara saya menyelesaikannya. Karena Nametuple sebenarnya hanya sebuah fungsi yang menghasilkan kelas, saya cukup menulis fungsi saya sendiri, yang secara dinamis akan menghasilkan objek NameTuple baru, dengan cara yang sama, dan membebani metode __add__ ke setiap kelas yang dihasilkan.

def mytuple(name, params=None, **kwargs):

    # check the params input
    if params and isinstance(params, six.string_types):
        params = params.split(',') if ',' in params else [params]
        params = [p.strip() for p in params]

    # create default namedtuple and find new columns
    default = namedtuple(name, 'a, b, c, d')
    newcols = [col for col in params if col not in default._fields] if params else None
    finalfields = default._fields + tuple(newcols) if newcols else default._fields
    nt = namedtuple(name, finalfields, **kwargs)

    def new_add(self, other):
        ''' Overloaded add to combine tuples without duplicates '''    
        self_dict = self._asdict()
        other_dict = other._asdict()
        self_dict.update(other_dict)

        new_fields = tuple(self_dict.keys())
        new_row = mytuple(self.__class__.__name__, new_fields)
        return new_row(**self_dict)

    # append new properties and overloaded methods
    nt.__add__ = new_add
    return nt

Ini digunakan seperti itu

# create first version
nt = mytuple('Row', 'a, b, c, d')
e = nt(1,2,3,4)
e
Row(a=1, b=2, c=3, d=4)

# create second version
nt = mytuple('Row', 'a, b, c, d, param1, param2')
m = nt(1,2,3,4,'a','b')
m
Row(a=1, b=2, c=3, d=4, param1='a', param2='b')

# create third version
nt = mytuple('Row', 'a, b, c, d, param3, param4')
s = nt(1,2,3,4,'stuff',10.2345)
s
Row(a=1, b=2, c=3, d=4, param3='stuff', param4=10.2345)

# add them together
d = e + m + s
d
Row(a=1, b=2, c=3, d=4, param1='a', param2='b', param3='stuff', param4=10.2345)
0
havok2063 20 Agustus 2017, 00:39

Metode __add__ yang Anda tetapkan adalah metode yang hanya dapat diakses oleh instance tipe kelas Row.

Saat Anda mengganti metode __new__ dari kelas Row, Anda mengembalikan objek bertipe namedtuple(...), bukan Row. Oleh karena itu, manipulasi lebih lanjut dari objek tersebut tidak akan memiliki akses ke metode __add__ Anda karena objek tersebut bukan Row, melainkan namedtuple().

Seperti yang disebutkan oleh @ user2357112, sepertinya Anda mempersulit diri sendiri dan mungkin lebih baik menggunakan kamus. Jika Anda memerlukan tipe yang tidak dapat diubah dan dapat di-hash untuk setiap Baris Anda sehingga Anda dapat membuat set dan menggunakannya sebagai kunci kamus, konversikan kamus Anda ke tupel bernama tepat sebelum menggunakannya dengan cara itu.

1
drootang 18 Agustus 2017, 04:26