I try to use composition even the relationship is: is-a.
So I have a Animal class and I have a Zebra class:
class Name:
pass
class Age:
pass
class Zebra():
pass
class Animal:
def __init__(self, name_animal, age_animal) -> None:
self.name_animal = name_animal
self.age_animal = age_animal
self.name = Name()
self.age = Age()
self.zebra = Zebra()
def __repr__(self):
return "My name is {} and I am {} years old".format((self.name_animal), (self.age_animal))
zebra1 = Zebra('Zebra', 37)
print(zebra1)
but then of course it fails because Zebra has no arguments.
So is it possible to use the repr method also for Zebra without inheritcance but with compostion?
Because I get now this error:
TypeError: Zebra() takes no arguments
I don't recommend to use composition in this case. This is a use-case for inheritance. But academic questions also deserve an answer.
Add a constructor to Zebra that initializes and stores an Animal instance and delegate __repr__:
class Animal:
def __init__(self, name_animal, age_animal) -> None:
self.name_animal = name_animal
self.age_animal = age_animal
def __repr__(self):
return "My name is {} and I am {} years old".format((self.name_animal), (self.age_animal))
class Zebra():
def __init__(self, name_animal, age_animal) -> None:
self.animal = Animal(name_animal, age_animal)
def __repr__(self):
return self.animal.__repr__()
zebra1 = Zebra('Zebra', 37)
print(zebra1)
Related
person.py
class Person:
"""---A class representing a person---"""
# Person constructor
def __init__(self,n,a):
self.full_name = n
self.age = a
class Student(Person):
# Student constructor
def __init__(self,n,a,s):
Person.__init__(self,n,a)
self.school = s
driver.py
from person import *
a = Student("Alice", 19, "Univ")
It throws TypeError: __init__() takes 3 positional arguments but 4 were given
I tried to change Student class to the following:
class Student(Person):
# Student constructor
def __init__(self,n,a,s):
super().__init__(n,a)
self.school = s
The error still exists.
Why does this happen? Is super() keyword required to add new attributes?
EDIT: The problem is solved. There was an indentation issue in the source code rendering this strange behavior, hence the question should be closed.
This line:
Person.__init__(self,n,a)
Is the problem. Recall that methods are automatically passed a reference to themselves, so you just passed a second one.
There's also a well-established pattern for this:
class Person
def __init__(self, name, age):
self.name = name
self.age = age
class Student(Person):
def __init__(self, school, *args):
super().__init__(*args)
self.school = school
student = Student('Washington Elementary', "Johnny Go'gettem", 10)
although note that simply removing your reference to self in the Person.__init__ call inside Student.__init__ would be sufficient.
Note that you can override the default method behavior with a couple of decorators that become quite useful in certain situations. Neither apply here, but just a bit of knowledge to tease your brain a bit:
def SomeClass:
attr = "class-scoped"
def __init__(self):
self.attr = "instance-scoped"
def some_method(self):
return self.attr == "instance-scoped"
#classmethod
def some_classmethod(cls):
return cls.attr == "class-scoped"
#staticmethod
def some_staticmethod():
return "I'm not given a \"self\" parameter at all!"
classmethods are particularly useful as alternate constructors
class Person:
def __init__(self, name, age):
self.name = name
self.age = age
#classmethod
def from_tuple(cls, tup) -> "Person":
"""Expects a tuple of (name, age) and constructs a Person"""
name, age = tup
return cls(name, age)
#classmethod
def from_dict(cls, dct) -> "Person":
"""Expects a dictionary with keys "name" and "age" and constructs a Person"""
try:
name = dct['name']
age = dct['age']
except KeyError:
raise ValueError(f"Dictionary {dct} does not have required keys 'name' and 'age'")
else:
return cls(name, age)
class Human:
def __init__(self) -> None:
self.name = None # type: str
def introduce(self):
print("I'm " + self.name)
class Alice(Human):
def __init__(self) -> None:
super().__init__()
self.name = "Alice"
class Bob(Human):
def __init__(self, rude: bool) -> None:
super().__init__()
self.rude = rude
#property
def name(self) -> str:
return "BOB!" if self.rude else "Bob"
if __name__ == '__main__':
alice = Alice()
alice.introduce()
bob = Bob(rude=True)
bob.introduce()
In the code above, there is an abstract Human class (in reality it is not a human and has more complex methods, not related to the problem). Most of its implementations would set their names by simply assigning a string to the name attribute (just as Alice). But there are few exceptions, like Bob, when there is more complex logic assigned (the value depends on the object state in the moment of resolving).
Therefore in Bob class I created a custom getter for the name property. But as an effect, it is impossible to create a class instance, because invoking the superconstructor results in the following error.
AttributeError: can't set attribute
And it is impossible to add a naive setter as well.
#name.setter
def name(self, name: str):
self.name = name
Why? Because it would result in an infinite loop. How to solve that issue?
why not make a dummy setter
#name.setter
def name(self, value):
pass
When self.name = None is executed it will call this setter and actually do nothing
If you're certain that your subclasses will assign name, then you can leave out the assignment in the parent constructor. Right now, Human is attempting to set to name, when there is no setter. If you removed it from the Human constructor, then Human can look like this:
class Human:
def introduce(self):
print("I'm " + self.name)
For class BobI would have used something like this in this case:
#name.setter
def name(self, name: str):
self._name = name
Afterwards you can do whatever you want in the more complex getter with the internal value. Or did I get the question wrong?
Executing the code would give:
I'm Alice
I'm BOB!
I am new to OOP. My idea was to implement the following class:
class name(object, name):
def __init__(self, name):
print name
Then the idea was to create two instances of that class:
person1 = name("jean")
person2 = name("dean")
I know, that is not possible, but how can I pass an input-argument into an instance of a class?
The problem in your initial definition of the class is that you've written:
class name(object, name):
This means that the class inherits the base class called "object", and the base class called "name". However, there is no base class called "name", so it fails. Instead, all you need to do is have the variable in the special init method, which will mean that the class takes it as a variable.
class name(object):
def __init__(self, name):
print name
If you wanted to use the variable in other methods that you define within the class, you can assign name to self.name, and use that in any other method in the class without needing to pass it to the method.
For example:
class name(object):
def __init__(self, name):
self.name = name
def PrintName(self):
print self.name
a = name('bob')
a.PrintName()
bob
>>> class name(object):
... def __init__(self, name):
... self.name = name
...
>>> person1 = name("jean")
>>> person2 = name("dean")
>>> person1.name
'jean'
>>> person2.name
'dean'
>>>
You just need to do it in correct syntax. Let me give you a minimal example I just did with Python interactive shell:
>>> class MyNameClass():
... def __init__(self, myname):
... print myname
...
>>> p1 = MyNameClass('John')
John
Remove the name param from the class declaration. The init method is used to pass arguments to a class at creation.
class Person(object):
def __init__(self, name):
self.name = name
me = Person("TheLazyScripter")
print me.name
Actually you can!
How about this?
class name(str):
def __init__(self, name):
print (name)
# ------
person1 = name("jean")
person2 = name("dean")
print('===')
print(person1)
print(person2)
Output:
jean
dean
===
jean
dean
Python Classes
class name:
def __init__(self, name):
self.name = name
print("name: "+name)
Somewhere else:
john = name("john")
Output:
name: john
class Person:
def init(self,name,age,weight,sex,mob_no,place):
self.name = str(name)
self.age = int(age)
self.weight = int(weight)
self.sex = str(sex)
self.mob_no = int(mob_no)
self.place = str(place)
Creating an instance to class Person
p1 = Person(Muthuswamy,50,70,Male,94*****23,India)
print(p1.name)
print(p1.place)
Output
Muthuswamy
India
Say I have a pair of instances that reference one another mutually. Is there a preferable manner to structure this relationship than the following.
class Human():
def __init__(self, name):
self.name = name
self.pet = Dog('Sparky', self)
def pet(self, animal):
self.pet.receive_petting()
class Dog(Pet):
def __init__(self, name, owner):
self.name = name
self.owner = owner
def receive_petting(self):
pass
def bark_at(self, person):
"do something"
The thing I don't like is that the relationship needs to be specified in two places. Any ideas on how to make this dryer?
I would break this into three classes:
class Human():
def __init__(self, name):
self.name = name
class Dog(Pet):
def __init__(self, name):
self.name = name
def bark_at(self, person):
"do something"
class OwnerPetRelation():
def __init__(self, dog, human):
self.owner=human
self.pet=dog
Now, one owner can also have many dogs, we just need to define as many OwnerPetRelations.
Similarly, a dog can also belong to multiple owners now.
I would create a method on Human that allows you to add pets (since a human might have many pets):
class Human():
def __init__(self, name):
self.name = name
self.pets = []
def add_pet(self, pet):
pet.owner = self
self.pets.append(pet)
def pet(self, animal):
for pet in self.pets:
pet.receive_petting()
class Dog(Pet):
def __init__(self, name):
self.name = name
self.owner = None
def receive_petting(self):
pass
def bark_at(self, person):
"do something"
This can be used as follows
human = Human('Jim')
human.add_pet(Dog('Woof'))
This approach can of course also be used for just a single pet and one could also extend it to allow pets to be owned by many humans.
There's nothing really Python-specific here; this is just a limitation of constructor-based dependency injection. It's hard to inject a reference to another object that cannot have been created yet. Instead, you can create an object that has a reference to something that will have a reference to the other object. For instance, you can pass a function to the constructor that will be able to return the value:
class Human():
def __init__(self,name,dog):
self.name = name
self._dog = dog
#property
def dog(self):
return self._dog()
class Dog():
def __init__(self,name,human):
self.name = name
self._human = human
#property
def human(self):
return self._human()
Then you can use it like this:
human = None
dog = Dog('fido',lambda: human)
human = Human('john',lambda: dog)
print(dog.human.name)
print(human.dog.name)
john
fido
It is not hard to update this so that the property function caches the value, of course. E.g.:
class Dog():
def __init__(self,name,human):
self.name = name
self._human = human
#property
def human(self):
try:
return self._human_
except AttributeError:
self._human_ = self._human()
return self._human_
Is it possible to add a base class to an object instance (not a class!) at runtime? Something along the lines of how Object#extend works in Ruby:
class Gentleman(object):
def introduce_self(self):
return "Hello, my name is %s" % self.name
class Person(object):
def __init__(self, name):
self.name = name
p = Person("John")
# how to implement this method?
extend(p, Gentleman)
p.introduce_self() # => "Hello, my name is John"
This dynamically defines a new class GentlePerson, and reassigns p's class to it:
class Gentleman(object):
def introduce_self(self):
return "Hello, my name is %s" % self.name
class Person(object):
def __init__(self, name):
self.name = name
p = Person("John")
p.__class__ = type('GentlePerson',(Person,Gentleman),{})
print(p.introduce_self())
# "Hello, my name is John"
Per your request, this modifies p's bases, but does not alter p's original class Person. Thus, other instances of Person are unaffected (and would raise an AttributeError if introduce_self were called).
Although it was not directly asked in the question, I'll add for googlers and curiosity seekers, that it is also possible to dynamically change a class's bases but (AFAIK) only if the class does not inherit directly from object:
class Gentleman(object):
def introduce_self(self):
return "Hello, my name is %s" % self.name
class Base(object):pass
class Person(Base):
def __init__(self, name):
self.name = name
p = Person("John")
Person.__bases__=(Gentleman,object,)
print(p.introduce_self())
# "Hello, my name is John"
q = Person("Pete")
print(q.introduce_self())
# Hello, my name is Pete
Slightly cleaner version:
def extend_instance(obj, cls):
"""Apply mixins to a class instance after creation"""
base_cls = obj.__class__
base_cls_name = obj.__class__.__name__
obj.__class__ = type(base_cls_name, (base_cls, cls),{})
Although it's already answered, here is a function:
def extend(instance, new_class):
instance.__class__ = type(
'%s_extended_with_%s' % (instance.__class__.__name__, new_class.__name__),
(instance.__class__, new_class),
{},
)