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,

--
https://mail.python.org/mailman/listinfo/python-list

Reply via email to