[Python-ideas] Mitigating 'self.' Method Pollution

Oscar Benjamin oscar.j.benjamin at gmail.com
Sun Jul 12 14:56:39 CEST 2015


On 12 July 2015 at 01:04, Nick Coghlan <ncoghlan at gmail.com> wrote:
> On 12 July 2015 at 05:41, David Mertz <mertz at gnosis.cx> wrote:
>> As someone else points out, if you WANT local variables in a method, you are
>> welcome to use them.  E.g.:
>>
>>     def times_matrix(self, matrix):
>>         x, y, z = self.x, self.y, self.z
>>         # Many more than one line of implementation
>>         # ... stuff with matrix indexing and referencing x
>>         # etc.
>>         return result_matrix
>
<snip>
>
> I like this angle, as it encourages thinking about mutating operations
> on instances as a "read, transform, write" cycle, rather than multiple
> in-place mutations (although the latter may of course still happen
> when the attributes being manipulated are themselves mutable objects).

I prefer this particular style. Following object-oriented code means
keeping track of state. In any non-trivial method this approach makes
it easier to see what state serves as additional input to the method
and what state is changed as output of the method. This is part of the
implicit "signature" of a method on top of its input arguments and
return values so it's good to separate it out from any internal
calculations.

Also modifying state in place is inappropriate for objects being used
in a "physics" situation as the motivating examples does:

def keep_moving_gravity(self):
    self.y += self.gravity
    self.y = max(self.y, 0)
    self.y = min(self.y, height - 1)

The problem is that this method simultaneously calculates the new
position and overwrites the old one (in a non-invertible way). In a
program where there is only one variable that might be fine. However
if we need to detect collisions with other objects then this won't
work. Also if the velocity/acceleration of this object depends on e.g.
the position of some other object then we end up with order-dependent
physics: if I update ball1 first and then ball2 I get different
results than I would if I call the update methods in the reverse
order.

Similarly if you write something like this:

def update(self, dt):
    self.vy += self.gravity * dt
    self.y += self.vy * dt

Then the result depends on the order of the two lines of code in the
method. It should really be something like:

def update(self, dt):
    # read
    y, vy = self.y, self.vy

    # calculate new values from old
    new_vy = vy + self.gravity * dt
    new_y = y + vy * dt  # Using vy not new_vy on RHS

    # update state atomically
    self.y, self.vy = new_y, new_vy

A better general approach is that each object reports what velocity it
thinks it should have:

def get_velocity(self, system_state):
    return (0, self.gravity)  # Assuming 2D

Then a controller can pass the current state of the system to each
object and ask what velocity it has and then update all objects
simultaneously checking for and resolving collisions etc. as it does
so. Building the update logic into every class means that every class
needs to be changed in order to redesign anything in the main physics
loop (to add collision detection, variable timestep etc).

--
Oscar


More information about the Python-ideas mailing list