Steven Liao
Steven Liao

Reputation: 3787

Instance attribute attribute_name defined outside __init__

I split up my class constructor by letting it call multiple functions, like this:

class Wizard:
    def __init__(self, argv):
        self.parse_arguments(argv)
        self.wave_wand() # declaration omitted

    def parse_arguments(self, argv):
        if self.has_correct_argument_count(argv):
            self.name = argv[0]
            self.magic_ability = argv[1]
        else:
            raise InvalidArgumentsException() # declaration omitted

# ... irrelevant functions omitted

While my interpreter happily runs my code, Pylint has a complaint:

Instance attribute attribute_name defined outside __init__

A cursory Google search is currently fruitless. Keeping all constructor logic in __init__ seems unorganized, and turning off the Pylint warning also seems hack-ish.

What is a/the Pythonic way to resolve this problem?

Upvotes: 237

Views: 170293

Answers (7)

Ofer Elboher
Ofer Elboher

Reputation: 27

Although the definition of instance variables outside __init__ isn't recommended in general, there are rare cases in which it is natural. For example, when you have a parent class that defines several variables that its child classes won't use, and whose definition will make its child waste time or resources, or will be simply unaesthetic.

One possible solution to this is using an init-extension function that each child class may override, and in this function use function setattr in order to define the class-unique instance variables. Maybe this is not too aesthetic as well, but it eliminates the linting warning discussed here.

Upvotes: 1

AKX
AKX

Reputation: 169416

Define the fields as annotations in the class body to tell PyCharm (and your readers) that you intend for these to exist (with the type given).

class Motor:
    speed_value: float
    hours_value: float

    def __init__(self):
        self.reset_all_values()

    def reset_all_values(self):
        self.speed_value = DEFAULT_SPEED_VALUE
        self.hours_value = DEFAULT_HOURS_VALUE

Upvotes: 11

W Kenny
W Kenny

Reputation: 2089

The best practice to solve this question is you need to build the parameter in Init part first, Then adjust it in the Def

class MainApplication(tk.Frame):
    def __init__(self, master):
        self.master = master
        tk.Frame.__init__(self, self.master)
        self.settingsFrame = None
        self.create_widgets(master)

    def create_widgets(self, master):
        # frame Container
        self.settingsFrame = tk.Frame(self.master, width=500, height=30, bg='white')

Upvotes: 3

lowzhao
lowzhao

Reputation: 411

If you are using Python 3, you can try

class Wizard:
    def __init__(self, argv):
        self.name: str = str()
        self.magic_ability: str = str()
        self.parse_arguments(argv)
        self.wave_wand() # declaration omitted

    def parse_arguments(self, argv):
        if self.has_correct_argument_count(argv):
            self.name = argv[0]
            self.magic_ability = argv[1]
        else:
            raise InvalidArgumentsException() # declaration omitted

# ... irrelevant functions omitted

Although not as pythonic as the accepted answer, but it should get away the Pylint alert.

And if you don't concern about type and don't want to create a new object with object() use:

class Wizard:
    def __init__(self, argv):
        self.name = type(None)()
        # ...

As None will cause type not match error.

Upvotes: -3

Ben
Ben

Reputation: 5238

For each attribute you want to set via function, call the function from the init. For example, the following works for me to set the attribute ascii_txt...

def __init__(self, raw_file=None, fingerprint=None):
    self.raw_file = raw_file
    self.ascii_txt = self.convert_resume_to_ascii()

def convert_resume_to_ascii(self):
    ret_val = self.raw_file.upper()
    return ret_val

Upvotes: 0

sthenault
sthenault

Reputation: 15125

The idea behind this message is for the sake of readability. We expect to find all the attributes an instance may have by reading its __init__ method.

You may still want to split initialization into other methods though. In such case, you can simply assign attributes to None (with a bit of documentation) in the __init__ then call the sub-initialization methods.

Upvotes: 249

roippi
roippi

Reputation: 25974

Just return a tuple from parse_arguments() and unpack into attributes inside __init__ as needed.

Also, I would recommend that you use Exceptions in lieu of using exit(1). You get tracebacks, your code is reusable, etc.

class Wizard:
    def __init__(self, argv):
        self.name,self.magic_ability = self.parse_arguments(argv)

    def parse_arguments(self, argv):
        assert len(argv) == 2
        return argv[0],argv[1]

Upvotes: 43

Related Questions