Сценарии использования `A .__ new __ (B)`, т.е. «использование одного класса для создания другого» в python - PullRequest
0 голосов
/ 06 августа 2020

Я беру

A.__new__(B)

для обозначения «использовать метод создания A объекта для создания (неинициализированного) B объекта».

В каких случаях Я могу / должен использовать такие способности?

Подробнее

Вопрос о когда можно использовать some_cls.__new__(some_other_cls) возможности

Но поскольку респонденты утверждают, что само использование __new__, вероятно, вводится в заблуждение, я покажу ситуацию, когда я (возможно, заблуждающийся) считаю, что использование __new__ может быть хорошей идеей.

Фактический контекст таков. из json -сериализации моделей scikit-learn таким образом, что граничное устройство может десериализовать и запустить целевой метод (например, «прогнозировать»). Здесь есть два важных аспекта: (1) scikit-learn классы оценки '__init__ принимают параметры, касающиеся подбора модели, но атрибуты, необходимые для запуска метода predict, динамически создаются в методе fit, и (2) предпочтительна облегченная сериализованная модель, а на периферийном устройстве, вероятно, выполняется облегченная функция (не исходный метод scikit-learn.

Я надеюсь, что приведенный ниже код достаточно точно иллюстрирует эту ситуацию:

class Greeter:
    def __init__(self, greet):
        self.greet = greet
        
    def fit(self, this):
        self.greet_this_ = self.greet + ' ' + this
        
    def __call__(self):
        if not hasattr(self, 'greet_this_'):
            raise ValueError("Greeter not fit")
        return self.greet_this_

obj = Greeter("hello")
obj.fit('world')
assert obj() == 'hello world'

Вот код сериализации:

import json

def serialize(obj, needed_attrs):
    return json.dumps({a: getattr(obj, a) for a in needed_attrs})

class MethodLess: ...

def deserialize(obj_json, cls=MethodLess):
    obj = cls.__new__(cls)  # make the (unintialized) instance
    # Note: couldn't do obj = cls() because cls might need some inputs
    
    for k, v in json.loads(obj_json).items():  # fill it with it's attributes
        setattr(obj, k, v)

    return obj

obj_json = serialize(obj, ['greet_this_'])
deserialized_obj = deserialize(obj_json, cls=Greeter)
assert deserialized_obj() == 'hello world' == obj()

Но нам даже не нужен исходный класс, только метод, который нам нужно вызвать.

class MinimalGreeter:
    def __call__(self):
        if not hasattr(self, 'greet_this_'):
            raise ValueError("Greeter not fit")
        return self.greet_this_
    
obj_json = serialize(obj, ['greet_this_'])
deserialized_obj = deserialize(obj_json, cls=MinimalGreeter)
assert deserialized_obj() == 'hello world' == obj()

Эквивалентно, в функциональном форма ...

def call(self):
    if not hasattr(self, 'greet_this_'):
        raise ValueError("Greeter not fit")
    return self.greet_this_
    
obj_json = serialize(obj, ['greet_this_'])
deserialized_obj = deserialize(obj_json, cls=MethodLess)
assert call(deserialized_obj) == 'hello world' == call(obj) == obj()

Ответы [ 2 ]

0 голосов
/ 06 августа 2020

Я бы сохранил детали того, как вы (де) сериализуете объект в самом классе. Затем десериализация может декодировать большой двоичный объект JSON и извлечь значения, необходимые для передачи в __init__. Например:

import json


class Greeter:
    def __init__(self, greet):
        self.greet = greet
        
    def fit(self, this):
        self.greet_this_ = self.greet + ' ' + this
        
    def __call__(self):
        if not hasattr(self, 'greet_this_'):
            raise ValueError("Greeter not fit")
        return self.greet_this_

    def to_json(self):
        return json.dumps({'greet': self.greet, 'greet_this_': self.greet_this_)

    @classmethod
    def from_json(cls, obj_json):
        d = json.loads(obj_json)
        obj = cls(d['greet'])
        obj.fit(d['greet_this_'])
        return obj
        

obj = Greeter("hello")
obj.fit('world')
assert obj() == 'hello world'


obj_json = obj.to_json()
deserialized_obj = Greeter.from_json(obj_json)
assert deserialized_obj() == 'hello world' == obj()
0 голосов
/ 06 августа 2020

Я не могу придумать ни одной причины, по которой вы захотите позвонить __new__ напрямую. Но если вы спрашиваете, почему вы можете вернуть другой тип, отличный от __new__, одним из примеров может быть возврат прокси-объекта или другой реализации в зависимости от того, какие аргументы конструктора переданы. Другими словами, его можно использовать как альтернативу заводскому шаблону.

РЕДАКТИРОВАТЬ: Пример по запросу:

class _StandardSolver:
    def solve(self, x):
        print('Using standard algorithm')
        return x + 1

class _SpecialSolver:
    def solve(self, x):
        print('Using special algorithm')
        return x * x  # Imagine this algorithm is much more complicated, relying on a lot of class state, etc.

class ComplicatedProblemSolver:
    def __new__(cls, use_special_algo, *args, **kwargs):
        if use_special_algo:
            return _SpecialSolver(*args, **kwargs)
        else:
            return _StandardSolver(*args, **kwargs)

if __name__ == '__main__':
    solver = ComplicatedProblemSolver(use_special_algo=True)
    print('Solution:', solver.solve(5))
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...