0

I am writing a mixin in Python3.8:

class Cloneable:
    def change_field(self, **kwargs):
        """ Create a new object with only the specified properties changed."""
        argdict = {**kwargs, **{key: self.__getattribute__(key) for key in self.__slots__ if
                                key not in kwargs and key[:2] != '__'}}
        return self.__class__(**argdict) 

On PyCharm, I get a warning "Unexpected argument" on the last line. This is presumably because of keyword argument unpacking. If this were a function I could include **kwargs in the function signature, but it's a mixin, and I don't know which __init__ it will be relevant to in advance.

How can I suppress such annoying warnings?

Daniel Moskovich
  • 187
  • 2
  • 11
  • I have found this question that feels functionally identical. Maybe this question should be closed as duplicate? https://stackoverflow.com/questions/59606289/unexpected-argument-warning-on-mixin-with-python-3-6-flask-and-pycharm – Daniel Moskovich May 26 '20 at 07:17

2 Answers2

1

Inspect Module

Extend your mixin with the classes that utilize it, and use the inspect module.

With the below setup we:

  1. store a list of all the arg names from the __init__ of the subclass
  2. filter argdict against our list

We suppress the warnings by not creating any.

import inspect

class Mixin:
    @property
    def names(self):
        return self.__names

    def __init__(self):
        sig = inspect.signature(self.__init__)
        self.__names = list()
        for param in sig.parameters.values():             # Signature(a=None, b=None).values()
            self.__names.append(str(param).split('=')[0]) # str(Parameter(a=None)).split('=')[0]
        print(self.names)    # [a, b]

    def change_field(self, **kwargs):
        """ Create a new object with only the specified properties changed."""
        argdict = {**{key: self.__getattribute__(key) for key in self.__slots__}, **kwargs}
        return self.__class__(**{k:v for k, v in argdict.items() if k in self.names})


class App(Mixin):
    def __init__(self, a=None, b=None):
        Mixin.__init__(self)

App()

More

You were doing it backwards. You should let kwargs overwrite __slots__ keys. You are also trying to suppress keys that end in double underscores. Everything that doesn't belong (including dunders) will be removed in that final filter.

OneMadGypsy
  • 4,640
  • 3
  • 10
  • 26
  • Thank you for this! I tried it, but the warning is still there. – Daniel Moskovich May 25 '20 at 19:52
  • Namely, `self.__class__(**{k:v for k, v in argdict.items() if k in self.names})` still gives me an `Unexpected argument` warning in this code also. – Daniel Moskovich May 25 '20 at 20:02
  • I honestly don't know. Copy the final filter into a print call and see what is in it. Maybe you will notice a pattern. Take the filter out of the `__class__` call and type in one very basic and generic value that should never get an error ... did you get an error? if so, maybe it's time to print `self.__class__` and it's type. That `__class__` call has been the problem this entire time. – OneMadGypsy May 26 '20 at 03:52
  • When you say PyCharm is giving you a warning do you mean in the editor? For instance, you have a little orange mark on your scroll bar? If so, hover the warning, select 'more actions', click submenu arrow for 'ignore ...', choose the type of ignoring you would like to do. – OneMadGypsy May 26 '20 at 04:07
  • I've succeeded in solving it- thanks for your help! I will post my solution as another answer- please comment if you have any thoughts. – Daniel Moskovich May 26 '20 at 07:10
0

I have succeeded in solving the problem. Thanks to Michael Guidry for his input that the order of the dictionaries should be reversed!

The solution was to include a dummy __init__ method in the mixin that takes **kwargs as an argument:

class Cloneable:
    def __init__(self, **kwargs):
        ...

    def change_field(self, **kwargs):
        argdict = {key: self.__getattribute__(key) for key in self.__slots__}
        return self.__class__(**{**argdict, **kwargs})
Daniel Moskovich
  • 187
  • 2
  • 11