Accessing parent objects

Jugurtha Hadjar jugurtha.hadjar at gmail.com
Sun Mar 25 11:01:57 EDT 2018


On 03/25/2018 03:25 PM, Terry Reedy wrote:
> On 3/25/2018 7:42 AM, Jugurtha Hadjar wrote:
>
>> class C2(object):
>>      def __init__(self, parent=None):
>>          self.parent = parent
>
> Since parent is required, it should not be optional.
>

You can still call it the way you'd call it if it were a positional 
argument.

i.e:

def foo(keyword_argument=None):
     print(keyword_argument)

foo(3)
foo(keyword_argument=3)


Just because it's a keyword argument doesn't mean it's not required. I 
could have provided a default using `self.parent = parent or C2`, but I 
didn't want to assume C2 was defined in that namespace and wanted to 
give as generic a code as I could.

Furthermore, the only case I'd use a positional argument is if I were 
100% certain the code will not change, which I'm not. Plus when the API 
will change (and it will), it will be harder to deal with it that if it 
were a keyword argument. If you change the order you'd have to change 
the whole codebase where the class/function is instantiated/called, 
whereas if you use a keyword argument, you could just catch the old way 
call, issue a deprecation warning, then route the call for the new way call.

What benefit does the positional argument provide?



>>      def foo(self):
>>          print("I am {self.__class__.__name__} foo".format(self=self))
>>          self.parent.foo()
>
> None.foo will raise AttributeError.
>

Right.. As I said, I tried to assume as little as possible about OP's 
code and namespace. Didn't want to include C1 in __init__ signature 
because I wasn't sure it was available in the namespace.

It is easy to address, though:

Example A:

class C2(object):
      def __init__(self, parent=C2):
          self.parent = parent

Example B:

class C2(object):
     def __init__(self, parent=None):
         self.parent = parent or C2



Furthermore, having a positional argument will not save us. We can still 
break the code if we do the following:

class C2(object):
     def __init__(self, parent):
         self.parent = parent
     def foo(self):
         self.parent.foo()

c1 = C1()
c2 = C2(None)
c2.foo()

Making it positional didn't fix our wickedness.


>> class C1(object):
>>      def __init__(self, child_class=None):
>>          self.child = child_class(parent=self)
>
> Ditto.  None() will raise TypeError
>

Ditto, so would it if your C2 instance is None for whatever reason.

> If your intent is to force passing parent/child_class by name rather 
> than by position, use *, as in
>     def __init__(self, *, child_class):
>
>
>>      def foo(self):
>>          print("I am {self.__class__.__name__} foo".format(self=self))
>>
>> c1 = C1(child_class=C2)
>> c1.child.foo()  # I am C2 foo
>>                  # I am C1 foo
>
>

I don't want to force using named params vs keyword params, or I would 
have used the asterisk in the first place. I write that code, as I said, 
because I have zero to gain from using a positional argument (which 
using keyword arguments behaves the same way as far as I'm concerned if 
I call the params in order), and a lot to lose: when I change the API, 
I'd have to change the whole parts where I instantiate the class or call 
the method.

Suppose I have a class like this:

class Foo(object):
     def __init__(self, name, height, address):
         self.name = name
         self.height = height
         self.address = address

Suppose I then want to change it to:


class Foo(object):
     def __init__(self, status, name, address, height):
         self.status = status
         self.name = name
         self.height = height
         self.address = address


This would totally screw up other parts of the code where the class is 
used. I tend to use positional arguments when it's *way* unlikely the 
code will ever change, and even then I probably wouldn't.

-- 
~ Jugurtha Hadjar,




More information about the Python-list mailing list