Efectuați o copie () a unui atribut de clasă dacă o singură dacă o subclasă suprascrie o metodă

Încerc să realizez următoarele:

class Data(object):
    def __init__(self, data):
        self.data = data
        self.valid = False
        #Analyze and validate data
        self.preprocess_data()
        self.validate_data()

    def preprocess_data():
        pass

    def validate_data():
        #process data

class MyData(Data):
    def __init__():
        super(MyData,self).__init__(data)

    def preprocess_data(self):
        #preprocess it

When a subclass executes the overriden preprocess_data method, I want to automatically perform the following operation: self.data = self.data.copy()

Cum se poate face acest lucru (dacă este cazul)? M-am gândit la decorarea preprocess , dar nu cred că metodele suprasolicitate care sunt decorate în clasa de bază moștenesc "decorarea"

2

4 răspunsuri

class Data(object):
    def __init__(self, data):
        self.data = data
        self.valid = False
        #Analyze and validate data
        self._preprocess_data()
        self.validate_data()
    def _preprocess_data(self):
        if self.preprocess_data.im_func != Data.preprocess_data.im_func:
            self.data = self.data.copy()
        return self.preprocess_data()

Aceasta testează dacă funcția din spatele self.preprocess_data </​​code> este Data.preprocess_data </​​code>; dacă nu vă copiază datele. Desigur, acest lucru necesită să solicitați _preprocess_data </​​code> în clasa dvs., astfel încât codul suplimentar să fie executat efectiv.

2
adăugat
Succinct și își face treaba. Mulțumesc @ThiefMaster.
adăugat autor sebastian, sursa

Voi vota pentru modul de a face lucrurile lui @ ThiefMaster. Dacă doriți să vă complicați viața, puteți utiliza metaclase cu decoratori :

from functools import wraps

class PreprocessMetaclass(type): 
    def __new__(cls, name, bases, dct):

        try:
            if Data in bases and "preprocess_data" in dct:
                f = dct["preprocess_data"]
                @wraps(f)
                def preprocess_data(self, *args, **kwargs):
                    self.data = self.data.copy()
                    return f(self, *args, **kwargs)

                attrs = dct.copy()
                attrs["preprocess_data"] = preprocess_data
        except NameError as e:
            # This is if Data itself is being created, just leave it as it is.
            # Raises an NameError, because Data does not exist yet in "if Data in bases"
            attrs = dct

        return super(PreprocessMetaclass, cls).__new__(cls, name, bases, attrs)


class Data(object):
    # This here works if the subclasses don't specify
    # their own metaclass that isn't a subclass of the above.
    __metaclass__ = PreprocessMetaclass

    def __init__(self, data):
        self.data = data
        self.valid = False
        #Analyze and validate data
        self.preprocess_data()
        self.validate_data()

    def preprocess_data(self):
        self.data["newkey"] = 3

    def validate_data(self):
        print "This is the result: self.data =", self.data

class MyData(Data):
    def __init__(self, data):
        super(MyData,self).__init__(data)

    def preprocess_data(self):
        """The docs of the subclass"""
        self.data["newkey"] = 4

if __name__ == "__main__":
    dct1, dct2 = {"data": 1}, {"mydata": 2}

    print "Create Data"
    d = Data(dct1)

    print "Create MyData"
    md = MyData(dct2)

    print "The original dict of Data (changed):", dct1
    print "The original dict of MyData (unchanged):", dct2

    # If you do that, you will see that the docstring is still there,
    # but the arguments are no longer the same.
    # If that is needed (you don't have arguments, but the subclass might have)
    # then just enter the same arguments as the subclass's function
    # help(MyData)

PS It's the first time I ever needed to use metaclasses, but here it's the perfect scenario. You need to override a function definition before the class is created (before __init__). Now, you might not need it, but more generally, you might be forced to use that.

Un exemplu și mai simplu:

if not (self.preprocess_data is Data.preprocess_data):
    self.data = self.data.copy()
self.preprocess_data()
1
adăugat
jadkik94, mulțumesc pentru abordarea metaclas + decorator. Pentru moment, voi opta pentru simplitate și va adopta soluția @ ThiefMaster. Este întotdeauna bine să știți alte alternative. Mulțumiri!!! De asemenea, îmi place ultima sugestie.
adăugat autor sebastian, sursa
Ești binevenit, sper că o vei folosi la un moment dat: personal, este prima dată când am găsit metaclase utile. Puteți lua în considerare utilizarea acestuia dacă devine mult mai complexă.
adăugat autor jadkik94, sursa

Acest lucru poate fi rezolvat în mod trivial cu un decorator și o metaclașă simplă. Acest lucru este puțin cam nebun, dar face exact ceea ce ați cerut.

import functools

class DataMeta(type):
    def __new__(cls, name, bases, dictn):
        fn = dictn.get('preprocess_data')
        if fn:
            if getattr(fn, '_original', False) is False:
                @functools.wraps(fn)
                def wrapper(self, *args, **kwargs):
                    self.data = self.data.copy()
                    return fn(self, *args, **kwargs)
                dictn['preprocess_data'] = wrapper
        return type.__new__(cls, name, bases, dictn)

def base_method(fn):
    fn._original = True
    return fn

class Data(object):
    __metaclass__ = DataMeta

    def __init__(self, data):
        self.data = data
        self.valid = False
        #Analyze and validate data
        self.preprocess_data()
        self.validate_data()

    @base_method
    def preprocess_data(self):
        print "Original preprocess_data called"

    def validate_data(self):
        pass

class MyData(Data):
    def __init__(self, data):
        super(MyData, self).__init__(data)

    def preprocess_data(self):
        print "Overridden preprocess_data called"

class MyData1(Data):
    def __init__(self, data):
        super(MyData1, self).__init__(data)

class Dummy(object):
    def copy(self):
        print 'Copying data'

md = MyData(Dummy()) # Prints 'Copying data'
md1 = MyData1(Dummy()) # Doesn't print it, since MyData1 doesn't override preprocess_data
1
adăugat
Jeethu, mulțumesc pentru soluția ta. Apreciez asta.
adăugat autor sebastian, sursa

Ce zici simplu:

class Data(object):

    # Part of user API
    def preprocess_data():
        self.data = self.data.copy()
        self.preprocess_data_impl()

    # Part of internal API    
    def preprocess_data_impl():
        pass

class MyData(Data):

    def preprocess_data_impl():
        # Do stuff

Știu că nu este atât de plin de farmec ca și utilizarea unui decorator, dar face foarte ușor să urmezi ceea ce se întâmplă de fapt.

0
adăugat
Hi @aix, Dacă vă uitați la __ init__ din clasa de bază Data, implement un șablon pentru procesul de validare (mai întâi preprocesați, apoi validați.) Intenția mea este că toate subclasele apel acest cod . Dacă subclasele implementează preprocess_data </​​code>, atunci vreau să self.data.copy() . În caz contrar, nu doriți să o copiați.
adăugat autor sebastian, sursa
Python România
Python România
100 participanți

Comunitatea pasionaților de Python din România.