简体   繁体   English

Python - 继承方法在覆盖 __init__ 时中断

[英]Python - Inherited methods break when overriding __init__

I have a geometric base class ExtrudedSurface and a child class Cylinder , which is a 'kind of' extruded surface.我有一个几何基础 class ExtrudedSurface和一个孩子 class Cylinder ,这是一种“某种”挤压表面。 The base class has a method to translate itself (not in-place) by constructing a modified version of itself.基础 class 有一种通过构建自身的修改版本来translate自身(不是就地)的方法。

I would like to re-use this method by the child class Cylinder , and have it return a new, translated Cylinder .我想通过子 class Cylinder重新使用此方法,并让它返回一个新的翻译后的Cylinder However, as implemented now this does not work because Cylinder has a different __init__ signature which is called in translate .但是,正如现在实施的那样,这不起作用,因为Cylinder具有不同的__init__签名,该签名在translate中调用。

What is the best way to achieve this?实现这一目标的最佳方法是什么? How can Cylinder use the inherited method translate and return a new Cylinder object? Cylinder如何使用继承的方法translate并返回一个新的Cylinder object?

EDIT 1: I think it has to do with LSP violation but I'm not sure.编辑 1:我认为这与违反 LSP 有关,但我不确定。

class ExtrudedSurface:
    def __init__(self, profile: Curve, direction: Vector, length: float):
        """
        An extruded surface geometry created by sweeping a 3D 'profile' curve along a 
        'direction' for a given 'length'.
        """

        self.profile = profile
        self.direction = direction.normalize()
        self.length = length

    def translate(self, translation: Vector) -> ExtrudedSurface:
        """Return a translated version of itself."""

        return self.__class__(
            self.profile.translate(translation),
            self.length,
            self.direction
        )



class Cylinder(ExtrudedSurface):
    def __init__(self, point: Point, direction: Vector, radius: float, length: float):
        """Cylinder surface.

        Args:
            point: Center point of extruded circle.
            direction: Direction vector of the cylinder (axis).
            radius: Cylinder radius.
            length: Extrusion length in 'direction'.
        """

        direction = direction.normalize()

        profile = Circle(point, direction, radius)

        super().__init__(profile, direction, length)

Short story short: the by-the-book approach there is to override the translate() method, as well, and call the updated constructor from there.简而言之:按照书本的方法也可以覆盖translate()方法,并从那里调用更新的构造函数。

Now, you can refactor your class initialization and separate attribute setting from other needed actions, and then create a new class-method to clone an instance with all the attributes from a first instance, and just call this initialization, if needed.现在,您可以重构 class 初始化并将属性设置与其他需要的操作分开,然后创建一个新的类方法来克隆一个具有第一个实例的所有属性的实例,并在需要时调用此初始化。

If no initialization is needed, just a "clone" method is needed.如果不需要初始化,只需要一个“克隆”方法。 If you happen to call this method __copy__ , then you can use the copy.copy call for that, which is almost as if it was an operator in Python, and it can, by itself, have value for your end-users.如果您碰巧调用此方法__copy__ ,那么您可以为此使用copy.copy调用,这几乎就像它是 Python 中的运算符一样,它本身可以对您的最终用户有价值。

Moreover -- if your class requires no initialization besides setting plain attributes, and no calculations at all, copy.copy will just work out of the box with your instances - no extra __copy__ method needed:此外——如果你的 class 除了设置普通属性之外不需要初始化,并且根本不需要计算, copy.copy将直接与你的实例一起工作——不需要额外的__copy__方法:

from copy import copy

class ExtrudedSurface:
    def __init__(self, profile: Curve, direction: Vector, length: float):
        """
        An extruded surface geometry created by sweeping a 3D 'profile' curve along a 
        'direction' for a given 'length'.
        """

        self.profile = profile
        self.direction = direction.normalize()
        self.length = length

    def translate(self, translation: Vector) -> ExtrudedSurface:
        """Return a translated version of itself."""

        new_profile = self.profile.translate(translation)
        new_instance = copy(self)
        new_instance.profile = new_profile
        return new_instance
     


class Cylinder(ExtrudedSurface):

    def __init__(self, point: Point, direction: Vector, radius: float, length: float):
        ...
        

Just attempt to the fact that copy will not recursively copy the attributes, so, if the self.direction vector is a mutable object in the framework you are using, this will keep both clones bound to the same vector, and if it changes in the original, that change will be reflected in the clone.只是尝试copy不会递归复制属性的事实,因此,如果self.direction向量在您使用的框架中是可变的 object,这将使两个克隆绑定到同一向量,并且如果它在原始的,该更改将反映在克隆中。 By the nature of your code I am assuming everything is immutable there and all transforms create new instances: then this will work.根据您的代码的性质,我假设那里的一切都是不可变的,并且所有转换都会创建新实例:那么这将起作用。 Otherwise, you should also copy the original .direction attribute into the new instance.否则,您还应该将原始的.direction属性复制到新实例中。

In time: yes, that is an LSP violation - but I always think that LSP is given more importance than it should when it come to practical matters.及时:是的,这是违反 LSP 的行为——但我一直认为,在实际问题上,LSP 被赋予了比应有的更多的重要性。

The more generic code I described, should your initialization be more complex would be:如果您的初始化更复杂,我描述的更通用的代码将是:


class Base:
    def __init__(self, base_args):
        #code to set initial attributes
        ...
        # call inner init with possible sideeffects:
        self.inner_init()
    
    def inner_init(self):
        # code with side effects (like, attaching instance to a container)
        # should be written in order to be repeat-proof - calls on the same instance have no effect
        ...

   def __copy__(self):
      new_instance = self.__class__.new()  
      new_instance.__dict__.update(self.__dict__)
      self.inner_init()

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM