4

Person.py

class Person:
    def __init__(self, pname):
        self.name = pname

    @classmethod
    def parse(cls, name):
        return cls(name)

Employee.py

class Employee(Person):
    def __init__(self, ename, esalary):
        super().__init__(ename)
        self.salary = esalary

    @classmethod
    def parse(cls, data):
        person = super().parse(data["name"])
        person.salary = data["salary"]
        return person

Customer.py

class Customer(Person):
    def __init__(self, ename, country):
        super().__init__(ename)
        self.country = country

    @classmethod
    def parse(cls, data):
        person = super().parse(data["name"])
        person.country = data["country"]
        return person

main.py

emp_data = {
    "name": "john",
    "salary": 1000
}
emp = Employee.parse(emp_data)
print(type(emp))
print(emp.name)
print(emp.salary)


cust_data = {
    "name": "peter",
    "country": "USA"
}
cust = Customer.parse(cust_data)
print(type(cust))
print(cust.name)
print(cust.country)

errors

TypeError                                 Traceback (most recent call last)
<ipython-input-32-a5abd51d9870> in <module>()
     36     "salary": 1000
     37 }
---> 38 emp = Employee.parse(emp_data)
     39 print(type(emp))
     40 print(emp.name)

<ipython-input-32-a5abd51d9870> in parse(cls, data)
     14     @classmethod
     15     def parse(cls, data):
---> 16         person = super().parse(data["name"])
     17         person.salary = data["salary"]
     18         return person

<ipython-input-32-a5abd51d9870> in parse(cls, name)
      5     @classmethod
      6     def parse(cls, name):
----> 7         return cls(name)
      8 
      9 class Employee(Person):

TypeError: __init__() missing 1 required positional argument: 'esalary'

This example is just for reproducing the problem in the actual code. The actual parse functions involve complex logic.

In the above example, Employee and Customer classes extend Person class. According to the error, init method of Person class which is invoked by parse form same class, is expecting esalary. But there is no esalary property on Person init method.

What's wrong with my program? I think I did not understand inheritance properly. Please, correct me. First of all, did I structure my program correctly?

12
  • 3
    cls will be Employee not Person Commented May 1, 2018 at 20:24
  • why? could you explain that as an answer? Commented May 1, 2018 at 20:26
  • @What first argument for what? Commented May 1, 2018 at 20:28
  • Take a look :) : stackoverflow.com/questions/26788214/… Commented May 1, 2018 at 20:28
  • @LokeshCherukuri Im not 100% sure thats why I removed my comment but normally every first argument of a object refers to self. So when you pass cust_data, you actually only pass to self. I don't know the edge-cases however. Commented May 1, 2018 at 20:34

2 Answers 2

4

Your problem is that parse calls cls(name), which only has one argument, but cls is Employee or Customer, not necessarily Person. You must either provide the additional arguments (salary/country) at construction time (when you call parse), or give them a default value when they aren't provided.

Either provide the additional needed arguments to the constructor via parse:

@classmethod
def parse(cls, name, *args, **kwargs):
    return cls(name, *args, **kwargs)

which would allow you do something like:

Employee.parse(name, salary)  # Really no different than Employee(name, salary)

Or add defaults to those parameters in the child constructors so that they can be constructed with only a name:

class Customer(Person):
    def __init__(self, ename, country=None):
        # ...

class Employee(Person):
    def __init__(self, ename, esalary=None):
        # ...

Note that this might result in you having None values floating around in these attributes, but that's inevitable if you want to construct a new, say, Employee object and don't want to also provide a salary at the same time.

Sign up to request clarification or add additional context in comments.

6 Comments

adding *args, **kwargs to Person's parse did not solve the problem
Question: if cls is Employee, i should call cls(name, salary). what if salary data is not available at that moment. what if i want to initialize salary only after crating employee object?
Then add default values, like I suggest in the second code block. A reasonable default would be None, e.g., def __init__(self, ename, esalary=None):
*args and **kwargs only pass on extra arguments that got passed to parse. If they didn't get passed to parse, then there's nothing to pass on, so the constructor still receives too few parameters. If you're willing to tolerate missing data in these classes, at least briefly, add default values of None to these extra parameters in your child classes.
adding default value None solved the problem. Finally, Could you explain in simple terms why cls is Employee here in your answer? is that how inheritance works?
|
3

Consider the following:

class Foo():
    @classmethod
    def p(cls):
        print(cls.__name__)

 class Bar(Foo):
     @classmethod
     def p(cls):
         super().p()

bar = Bar()
bar.p()

This will print "Bar". The class object passed to Foo.p when called from within Bar.p is actually the Bar class, not the Foo class.

So in your code, when you call Person.parse through Employee.parse, your line in Person.parse, return cls(name) is actually calling Employee(name), but Employee's init take 2 positional arguments.

For your code to work with this structure, your __init__s have to have the same signature, or at least compatible signatures (for example by adding *args to Person's __init__ method and passing them to the constructor).

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.