Python Forum
Why doesn't calling a parent constructor work with arbitrary keyword arguments?
Thread Rating:
  • 0 Vote(s) - 0 Average
  • 1
  • 2
  • 3
  • 4
  • 5
Why doesn't calling a parent constructor work with arbitrary keyword arguments?
#1
I have a module named user_admin_privileges.py with 3 classes in it: Privileges, User, and Admin.

In my program I import the class Admin from the module, create an instance called admin passing 2 positional arguments in parameters fname and lname and one arbitrary keyword argument. But for some reason I get an error saying that there's a type error in parent constructor, __init__() function, and that it takes 3 positional arguments but 4 were given.

TypeError: __init__() takes 3 positional arguments but 4 were given

user_admin_privileges.py:
class Privileges:
    def __init__(self):
        self.privileges = ["can add a post", "can delete a post", "can ban a user"]

    def show_privileges(self):
        print("The privileges are:")
        for privilege in self.privileges:
            print(f"\t- {privilege}")


class User:
    def __init__(self, fname, lname, **data):
        self.data = data
        self.data["first name"] = fname
        self.data["last name"] = lname
        self.data["login_attempts"] = 0

    def describe_user(self):
        print("The user's data:")
        for key, val in self.data.items():
            print(f"{key}: {val}")
        print()

    def greet_user(self):
        print(f"Hello dear " + self.data["first name"] + "!")
        print()

    def increment_login_attempts(self):
        self.data["login_attempts"] += 1

    def reset_login_attempts(self):
        self.data["login_attempts"] = 0


class Admin(User):
    def __init__(self, fname, lname, **data):
        self.data = data
        self.data["first name"] = fname
        self.data["last name"] = lname
        self.privileges = Privileges()
        super().__init__(fname, lname, data)
And the program itself:
from user_admin_privileges import Admin

admin = Admin("Derrick", "Brown", location="Florida, USA")
admin.privileges.show_privileges()
What did I do wrong? How to fix this issue?
Reply
#2
I think in Admin.__init__() it should be
super().__init__(fname, lname, **data)
However, the strange thing is that your Admin.__init__() function seems to duplicate the work made by User.__init__()
Reply
#3
(Jun-24-2023, 11:07 AM)Gribouillis Wrote: I think in Admin.__init__() it should be
super().__init__(fname, lname, **data)
However, the strange thing is that your Admin.__init__() function seems to duplicate the work made by User.__init__()

I changed it to **data as you promised, and now it works. Thank you!

And, yes, I messed up with a constructor and it does duplicate the parent constructor. I changed that.

Now it looks like this (I hope it's correct now):


class Admin(User):
    def __init__(self, fname, lname, **data):
        self.privileges = Privileges()
        super().__init__(fname, lname, **data)
Thank you very much!
Reply
#4
UPD: Proposed, not promised, I misspelled xD
Reply
#5
It would be strange to write a class that accepts "arbitrary keyword" arguments for the __init__(). But you aren't doing that. You are replacing the __dict__ object that normally holds all the class attributes with an external dictionary. That is even stranger. Why are you doing this?

Normally a class is aware of what attributes instances of the class are likely to have. Methods of the class can perform operations on the attributes with some confidence that the attributes exist. Your User can only be confident that it can access attributes that it added to the data dictionary. For example, I create two users that have a location, but I make a typo when entering one of the users. Flo Rida has a location attribute, but there is no location attribute for Junior, and that raises an exception when we ask User Junior for their location.
class User:
    def __init__(self, fname, lname, **data):
        self.data = data
        self.data["first name"] = fname
        self.data["last name"] = lname
        self.data["login_attempts"] = 0

    def __getitem__(self, index):
        """Adds [] indexing to class."""
        return self.data[index]
 
    def __str__(self):
        return "\n".join(f"{key} : {value}" for key, value in self.data.items())

flo_rida = User("Flo", "Rida", location="Florida")
junior_rida = User("Junior", "Rida", loc="Florida")
print(flo_rida, "\n")
print(junior_rida, "\n")
print(flo_rida["location"])
print(junior_rida["location"])
Output:
location : Florida first name : Flo last name : Rida login_attempts : 0 loc : Florida first name : Junior last name : Rida login_attempts : 0 Florida
Error:
Traceback (most recent call last): File ..., line 19, in <module> print(junior_rida["location"])
Classes should offer more than dictionaries. They should guarantee a commonality amongst their instances and provide an API for using instances of the class. I would write your code like this:
class Privileges:
    def __init__(self, *args):
        self.privileges = list(*args)

    def __str__(self):
        return str(self.privileges)


class User:
    def __init__(self, fname, lname, location=None, priv=None):
        self.first_name = fname
        self.last_name = lname
        self.login_attempts = 0
        self.location = location
        self.privileges = Privileges(priv or [])
 
    def __str__(self):
        return "\n".join((
            self.__class__.__name__,
            f"First Name:  {self.first_name}",
            f"Last Name:   {self.last_name}",
            f"Location:    {self.location}",
            f"Priviledges: {self.privileges}"
        ))

    def increment_login_attempts(self):
        self.login_attempts += 1
 
    def reset_login_attempts(self):
        self.login_attempts = 0


class Admin(User):
    def __init__(self, *args, priv=None, **kwargs):
        priv = priv or ("can add a post", "can delete a post", "can ban a user")
        super().__init__(*args, priv=priv, **kwargs)

admin = Admin("Derrick", "Brown", location="Florida, USA")
print(admin, "\n")

user = User("Flo", "Rida", priv=["can add a post"])
print(user)
Output:
Admin First Name: Derrick Last Name: Brown Location: Florida, USA Priviledges: ['can add a post', 'can delete a post', 'can ban a user'] User First Name: Flo Last Name: Rida Location: None Priviledges: ['can add a post']
I would also consider making User and Administrator dataclasses..
from dataclasses import dataclass


class Privileges:
    def __init__(self, *args):
        self.privileges = list(*args)

    def __str__(self):
        return str(self.privileges)


@dataclass
class User:
    first_name: str
    last_name: str
    location: str = None
    privileges: Privileges = Privileges()
    login_attempts: int = 0

    def increment_login_attempts(self):
        self.login_attempts += 1
 
    def reset_login_attempts(self):
        self.login_attempts = 0

@dataclass
class Admin(User):
    def __init__(self, *args, privileges=None, **kwargs):
        privileges = privileges or ("can add a post", "can delete a post", "can ban a user")
        super().__init__(*args, privileges=privileges, **kwargs)


admin = Admin("Derrick", "Brown", location="Florida, USA")
print(admin, "\n")

user = User("Flo", "Rida", privileges=["can add a post"])
print(user)
Output:
Admin(first_name='Derrick', last_name='Brown', location='Florida, USA', privileges=('can add a post', 'can delete a post', 'can ban a user'), login_attempts=0) User(first_name='Flo', last_name='Rida', location=None, privileges=['can add a post'], login_attempts=0)
And if, for some reason, you really want to have a class that assigns some arbitrary attributes in the __init__(), you can do that like this:
class Privileges:
    def __init__(self, *args):
        self.privileges = list(*args)
 
    def __str__(self):
        return str(self.privileges)
 
 
class User:
    def __init__(self, fname, lname, location=None, priv=None, extras=None):
        self.first_name = fname
        self.last_name = lname
        self.login_attempts = 0
        self.location = location
        self.privileges = Privileges(priv or [])
        if extras:
            for key, value in extras.items():
                setattr(self, key, value)

    def __str__(self):
        fields = [self.__class__.__name__] + [f"{key:15}: {value}" for key, value in self.__dict__.items()]
        return "\n".join(fields)
 
    def increment_login_attempts(self):
        self.login_attempts += 1
  
    def reset_login_attempts(self):
        self.login_attempts = 0
 
 
class Admin(User):
    def __init__(self, *args, priv=None, **kwargs):
        priv = priv or ("can add a post", "can delete a post", "can ban a user")
        super().__init__(*args, priv=priv, **kwargs)
 
user = User("Flo", "Rida", priv=["can add a post"], extras={"occupation":"Rapper"})
print(user)
Output:
User first_name : Flo last_name : Rida login_attempts : 0 location : None privileges : ['can add a post'] occupation : Rapper
Reply


Possibly Related Threads…
Thread Author Replies Views Last Post
  Why doesn't list require global keyword? johnywhy 9 834 Jan-15-2024, 11:47 PM
Last Post: sgrey
  Find a specific keyword after another keyword and change the output sgtmcc 5 854 Oct-05-2023, 07:41 PM
Last Post: deanhystad
  calling external function with arguments Wimpy_Wellington 7 1,452 Jul-05-2023, 06:33 PM
Last Post: deanhystad
  Why doesn't this code work? What is wrong with path? Melcu54 7 1,809 Jan-29-2023, 06:24 PM
Last Post: Melcu54
  color code doesn't work harryvl 1 896 Dec-29-2022, 08:59 PM
Last Post: deanhystad
  client.get_all_tickers() Doesn't work gerald 2 1,724 Jun-16-2022, 07:59 AM
Last Post: gerald
  pip doesn't work after Python upgrade Pavel_47 10 4,239 May-30-2022, 03:31 PM
Last Post: bowlofred
  For Loop Works Fine But Append For Pandas Doesn't Work knight2000 2 2,027 Dec-18-2021, 02:38 AM
Last Post: knight2000
  Class Method to Calculate Age Doesn't Work gdbengo 1 1,717 Oct-30-2021, 11:20 PM
Last Post: Yoriz
  Process doesn't work but Thread work ! mr_byte31 4 2,641 Oct-18-2021, 06:29 PM
Last Post: mr_byte31

Forum Jump:

User Panel Messages

Announcements
Announcement #1 8/1/2020
Announcement #2 8/2/2020
Announcement #3 8/6/2020