使用比基类更少的参数重写子类中的方法-Python 3

使用比基类更少的参数重写子类中的方法-Python 3,python,inheritance,method-signature,Python,Inheritance,Method Signature,我正在构建我的第一个应用程序(天气预报),我正在使用tkinter和Python3.6。我是Python新手,所以我想确保我不会学习坏习惯,这些坏习惯以后将不得不改掉:) 如果我的代码有任何明显的问题,请发表评论-我需要知道如何改进:)谢谢 我已经为要放置在tkinter画布上的对象构建了一个基类,该对象与画布上已经存在的其他对象相关。其想法是能够轻松地将它们粘贴到画布上已存在的其他对象旁边,而无需绝对坐标 我有一个方法move_rel_to_obj_y,其目的是将子类实例的y坐标居中于画布上已

我正在构建我的第一个应用程序(天气预报),我正在使用tkinter和Python3.6。我是Python新手,所以我想确保我不会学习坏习惯,这些坏习惯以后将不得不改掉:)

如果我的代码有任何明显的问题,请发表评论-我需要知道如何改进:)谢谢

我已经为要放置在tkinter画布上的对象构建了一个基类,该对象与画布上已经存在的其他对象相关。其想法是能够轻松地将它们粘贴到画布上已存在的其他对象旁边,而无需绝对坐标

我有一个方法move_rel_to_obj_y,其目的是将子类实例的y坐标居中于画布上已存在的相对对象的y坐标的中心

基方法应该仅由子类使用。已经有2个子类,我想避免复制粘贴这个方法到它们中

现在在基类中,该方法将(self、obj、rel_obj)作为参数。 但是基类中的self不是我想要操作的子类的实例(在画布上移动图像),所以我的重写方法采用(self,rel_obj),因为我们现在假设obj变成了我想要移动的self(子类的实例)

因此,我提出了下面的解决方案

我的问题是:

  • 是否有其他方法可以将子实例传递给基方法而不必执行我所做的操作,而且这种方法更加优雅

  • 在基方法的重写中,我使用的参数少于基类中的参数,并且方法的签名会更改(正如Pycharm警告的那样:)。我添加了**kwargs以保持参数的数量不变,但如果不这样做,它将起作用(我已经测试过,不需要)。当参数数量发生变化时,实际执行这种继承的适当方式是什么?我应该不惜一切代价避免它吗? 如果我们应该避免它,那么如何解决我需要将子实例传递给基方法的问题呢

  • 谢谢你帮助我

    托马斯克卢茨科夫斯基

    基类:

    class CanvasObject(object):
    """Base class to create objects on canvas.
    
    Allows easier placement of objects on canvas in relation to other objects.
    
    Args:
        object (object): Base Python object we inherit from.
    
    """
    
    def __init__(self, canvas, coordinates=None, rel_obj=None,
                 rel_pos=None, offset=None):
        """Initialise class - calculate x-y coordinates for our object.
    
        Allows positioning in relation to the rel_obj (CanvasText or CanvasImg object).
        We can give absolute position for the object or a relative one.
        In case of absolute position given we will ignore the relative parameter.
        The offset allows us to move the text away from the border of the relative object.
    
        Args:
            canvas (tk.Canvas): Canvas object to which the text will be attached to.
            image (str): String with a path to the image.
            coordinates (tuple): Absolute x, y coordinates where to place text in canvas. Overrides any parameters
                given in relative parameters section.
            rel_obj (CanvasText / CanvasImg): CanvasText / CanvasImg object which will be used
                as a relative one next to which text is meant to be written.
            rel_pos (str): String determining position of newly created text in relation to the relative object.
                Similar concept to anchor.
                TL - top-left, TM - top-middle, TR - top-right, CL - center-left, CC - center-center,
                CR - center-right, BL - bottom-left, BC - bottom-center, BR - bottom-right
            offset (tuple): Offset given as a pair of values to move the newly created object
                away from the relative object.
    
        :Attributes:
        :canvas (tk.Canvas): tkinter Canvas object.
        :pos_x (int): X coordinate for our object.
        :pos_y (int): Y coordinate for our object.
    
        """
        self.canvas = canvas
        pos_x = 0
        pos_y = 0
    
        if offset:
            offset_x = offset[0]
            offset_y = offset[1]
        else:
            offset_x = 0
            offset_y = 0
        if coordinates:
            pos_x = coordinates[0]
            pos_y = coordinates[1]
        elif rel_obj is not None and rel_pos is not None:
            # Get Top-Left and Bottom-Right bounding points of the relative object.
            r_x1, r_y1, r_x2, r_y2 = canvas.bbox(rel_obj.id_num)
            # TL - top - left, TM - top - middle, TR - top - right, CL - center - left, CC - center - center,
            # CR - center - right, BL - bottom - left, BC - bottom - center, BR - bottom - right
    
            # Determine position of CanvasObject on canvas in relation to the rel_obj.
            if rel_pos == "TL":
                pos_x = r_x1
                pos_y = r_y1
            elif rel_pos == "TM":
                pos_x = r_x2 - (r_x2 - r_x1) / 2
                pos_y = r_y1
            elif rel_pos == "TR":
                pos_x = r_x2
                pos_y = r_y1
            elif rel_pos == "CL":
                pos_x = r_x1
                pos_y = r_y2 - (r_y2 - r_y1) / 2
            elif rel_pos == "CC":
                pos_x = r_x2 - (r_x2 - r_x1) / 2
                pos_y = r_y2 - (r_y2 - r_y1) / 2
            elif rel_pos == "CR":
                pos_x = r_x2
                pos_y = r_y2 - (r_y2 - r_y1) / 2
            elif rel_pos == "BL":
                pos_x = r_x1
                pos_y = r_y2
            elif rel_pos == "BC":
                pos_x = r_x2 - (r_x2 - r_x1) / 2
                pos_y = r_y2
            elif rel_pos == "BR":
                pos_x = r_x2
                pos_y = r_y2
            else:
                raise ValueError("Please use the following strings for rel_pos: TL - top - left, "
                                 "TM - top - middle, TR - top - right, CL - center - left,"
                                 " CC - center - center, CR - center - right, BL - bottom - left, "
                                 "BC - bottom - center, BR - bottom - right")
        self.pos_x = int(pos_x + offset_x)
        self.pos_y = int(pos_y + offset_y)
    
    def move_rel_to_obj_y(self, obj, rel_obj):
        """Move obj relative to rel_obj in y direction. 
        Initially aligning centers of the vertical side of objects is supported.
    
        Args:
            obj (CanvasText | CanvasImg): Object which we want to move.
            rel_obj (CanvasText | CanvasImg): Object in relation to which we want to move obj. 
    
        Returns:
            None
    
        """
        # Find y coordinate of the center of rel_obj.
        r_x1, r_y1, r_x2, r_y2 = self.canvas.bbox(rel_obj.id_num)
        r_center_y = r_y2 - (r_y2 - r_y1) / 2
    
        # Find y coordinate of the center of our object.
        x1, y1, x2, y2 = self.canvas.bbox(obj.id_num)
        center_y = y2 - (y2 - y1) / 2
    
        # Find the delta.
        dy = int(r_center_y - center_y)
    
        # Move obj.
        self.canvas.move(obj.id_num, 0, dy)
        # Update obj pos_y attribute.
        obj.pos_y += dy
    
    儿童班:

    class CanvasImg(CanvasObject):
    """Creates image object on canvas.
    
    Allows easier placement of image objects on canvas in relation to other objects.
    
    Args:
        CanvasObject (object): Base class we inherit from.
    
    """
    
    def __init__(self, canvas, image, coordinates=None, rel_obj=None,
                 rel_pos=None, offset=None, **args):
        """Initialise class.
    
        Allows positioning in relation to the rel_obj (CanvasText or CanvasImg object).
        We can give absolute position for the image or a relative one.
        In case of absolute position given we will ignore the relative parameter.
        The offset allows us to move the image away from the border of the relative object.
        In **args we place all the normal canvas.create_image method parameters.
    
        Args:
            canvas (tk.Canvas): Canvas object to which the text will be attached to.
            image (str): String with a path to the image.
            coordinates (tuple): Absolute x, y coordinates where to place text in canvas. Overrides any parameters
                given in relative parameters section.
            rel_obj (CanvasText / CanvasImg): CanvasText / CanvasImg object which will be used
                as a relative one next to which text is meant to be written.
            rel_pos (str): String determining position of newly created text in relation to the relative object.
                Similar concept to anchor.
                TL - top-left, TM - top-middle, TR - top-right, CL - center-left, CC - center-center, 
                CR - center-right, BL - bottom-left, BC - bottom-center, BR - bottom-right
            offset (tuple): Offset given as a pair of values to move the newly created text
                away from the relative object.
            **args: All the other arguments we need to pass to create_text method.
    
        :Attributes:
            :id_num (int): Unique Id number returned by create_image method which will help us identify objects
                and obtain their bounding boxes.
    
        """
        # Initialise base class. Get x-y coordinates for CanvasImg object.
        super().__init__(canvas, coordinates, rel_obj, rel_pos, offset)
    
        # Prepare image for insertion. Should work with most image file formats.
        img = Image.open(image)
        self.img = ImageTk.PhotoImage(img)
        id_num = canvas.create_image(self.pos_x, self.pos_y, image=self.img, **args)
        # Store unique Id number returned from using canvas.create_image method as an instance attribute.
        self.id_num = id_num
    
    def move_rel_to_obj_y(self, rel_obj, **kwargs):
        """Move instance in relation to rel_obj. Align their y coordinate centers.
        Override base class method to pass child instance as obj argument automatically.
    
    
        Args:
            rel_obj (CanvasText | CanvasImg): Object in relation to which we want to move obj. 
    
            **kwargs (): Not used
    
        Returns:
            None
        """
        super().move_rel_to_obj_y(self, rel_obj)
    
    现在在基类中,该方法将(self、obj、rel_obj)作为参数。但是基类中的self不是我想要操作的子类的实例

    为什么???我认为你并不了解继承实际上是如何工作的
    self
    是对已调用方法的实例的引用,方法被继承的事实不会改变任何东西:

    # oop.py
    class Base(object):
        def do_something(self):
            print "in Base.do_something, self is a %s" % type(self)
    
    class Child(Base):
        pass
    
    class Child2(Base):
        def do_something(self):
            print "in Child2.do_something, self is a %s" % type(self)
            super(Child2, self).do_something()
    
    Base().do_something()
    Child().do_something()
    Child2.do_something()
    
    结果是:

    # python oop.py
    >>> in Base.do_something, self is a <class 'oop.Base'> 
    >>> in Base.do_something, self is a <class 'oop.Child'>
    >>> in Child2.do_something, self is a <class 'oop.Child2'>
    >>> in Base.do_something, self is a <class 'oop.Child2'>
    
    第二种方法看似更加复杂,但从长远来看,它确实有一些优势:

    • 很明显,CanvasImage不是CanvasObject定位器
    • 您可以单独测试CanvasObject定位器
    • 您可以通过模拟CanvasObject定位器单独测试CanvasImage
    • 你可以传递任何你想要浏览图像的“定位器”(只要它有正确的api),因此如果需要,你可以有不同的定位策略(这称为策略设计模式)
    现在在基类中,该方法将(self、obj、rel_obj)作为参数。但是基类中的self不是我想要操作的子类的实例

    为什么???我认为你并不了解继承实际上是如何工作的
    self
    是对已调用方法的实例的引用,方法被继承的事实不会改变任何东西:

    # oop.py
    class Base(object):
        def do_something(self):
            print "in Base.do_something, self is a %s" % type(self)
    
    class Child(Base):
        pass
    
    class Child2(Base):
        def do_something(self):
            print "in Child2.do_something, self is a %s" % type(self)
            super(Child2, self).do_something()
    
    Base().do_something()
    Child().do_something()
    Child2.do_something()
    
    结果是:

    # python oop.py
    >>> in Base.do_something, self is a <class 'oop.Base'> 
    >>> in Base.do_something, self is a <class 'oop.Child'>
    >>> in Child2.do_something, self is a <class 'oop.Child2'>
    >>> in Base.do_something, self is a <class 'oop.Child2'>
    
    第二种方法看似更加复杂,但从长远来看,它确实有一些优势:

    • 很明显,CanvasImage不是CanvasObject定位器
    • 您可以单独测试CanvasObject定位器
    • 您可以通过模拟CanvasObject定位器单独测试CanvasImage
    • 你可以传递任何你想要浏览图像的“定位器”(只要它有正确的api),因此如果需要,你可以有不同的定位策略(这称为策略设计模式)

    如果这个问题基本上是关于改进代码的,那么应该转到codereview.stackexchange.com您的子类实现的
    move\u rel\u to\u obj\u y
    没有完成任何事情,也没有必要<代码>self在传递给父类的方法时仍然是子类的一个实例。我刚刚删除了对该方法的重写,并将签名更改为self,rel_obj in base,它可以完美地工作。我没有完全理解self是调用该方法的任何对象的实例,即使该方法位于基类内部。谢谢你的回答。如果这个问题基本上是关于改进你的代码的,那么应该转到codereview.stackexchange.com你的子类实现的
    move\u rel\u to\u obj\u y
    没有完成任何事情,也没有必要<代码>self在传递给父类的方法时仍然是子类的一个实例。我刚刚删除了对该方法的重写,并将签名更改为self,rel_obj in base,它可以完美地工作。我没有完全理解self是调用该方法的任何对象的实例,即使该方法位于基类内部。谢谢你的回复。谢谢你非常详细和及时的回答。这就是我喜欢stackoverflow的原因:)。谢谢你给我一个非常详细和及时的回答。这就是我喜欢stackoverflow的原因:)。