suppose I have a class:
class Cat:
def __init__(self, name = "default", age = 0):
self.name = name
self.age = age
I also have a list of Cats:
l = [Cat('Joe')]
Now I can't call the following:
if 'Joe' in l: # the right syntax would be if Cat('Joe') in list
Which operator do I need to overload to be able to identify objects of class Cat by their member variable name?
You have to define the __eq__ method, as shown below:
class Cat:
def __init__(self, name = "default", age = 0):
self.name = name
self.age = age
def __eq__(self, other):
if isinstance(other, str):
return self.name == other
elif isinstance(other, Cat):
return self.name == other.name
So that when you run your check:
l = [Cat('Joe')]
'Joe' in l
#True
__contains__ on list objects is implemented by checking for equality, so override __eq__:
class Cat:
def __init__(self, name = "default", age = 0):
self.name = name
self.age = age
def __eq__(self, other):
return self.name == other
This works irrespective of ordering because equality checking swaps its arguments when the left side does not support the operation.
If you want it to work with hash-based containers (e.g. set, dict) you'll have to override __hash__ as well:
def __hash__(self):
return hash(self.name)
Related
class animal():
def__init__(self, name, species, legs):
self.name = name
self.breed = species
self.legs = legs
def__eq__(self, other):
return self.breed == other.species and self.name == other.name
def__repr__(self):
return f"{self.name}: {self.speies}: {self.legs}"
def leg_count(animals, multiple):
return
Above class, how to return a list of name that have number of legs are greater than 4??
group1 = animal("Adam","dog",4)
gruop2 = animal("Besty","cat", 4)
group3 = animal("Alex","bird", 2)
animals = [group1, group2, group3]
leg_count(animals, 4)
and also is it possible with one line?
class animal:
def __init__(self, name, species, legs):
self.name = name
self.breed = species
self.legs = legs
def __eq__(self, other):
return self.breed == other.species and self.name == other.name
def __repr__(self):
return f"{self.name}: {self.breed}: {self.legs}"
def leg_count(animals, multiple):
result = [item.name for item in animals if item.legs >= multiple]
return result
Above class returns animal object and leg_count function returns a list that contains object.name which has greater or equal amount of legs you specify in multiple value.
For Example:
group1 = animal("Adam", "dog", 4)
group2 = animal("Besty", "cat", 4)
group3 = animal("Alex", "bird", 2)
animals = [group1, group2, group3]
print(leg_count(animals, 4))
output is :
['Adam', 'Besty']
To briefly answer your question using list comprehension, consider the following solution:
def leg_count(animals, multiple):
return [a.name for a in animal if a.legs > multiple]
Besides that, you seem to have a problem in your __eq__ and __repr__ methods, namely, there is no species attribute, I think you mean to access the attribute breed.
I am needing to have the child class inherit from the parent class. I continue to either get "TypeError: init() missing 1 required positional argument: 'species'" or the name is often getting assigned to the name and name continues to return back as none.
import unittest
import time
class Mammal:
""" A Mammal class to further populate our animal kingdom """
def __init__(self, species, name):
""" mammal constructor can initialize class attributes """
self.species = species
self.name = None
def eat(self, food):
""" a method that will 'eat' in O(n) time """
i = food
print(self.name, "the", self.species, "is about to eat")
while i >= 1:
time.sleep(0.1)
i = i // 2
print(" ", self.name, "is done eating!")
def makeNoise(self):
""" a method that should be implemented by children classes """
raise NotImplementedError("this method should be implemented by child class")
ADD ANY OTHER BASE CLASS METHODS YOU NEED/WANT TO HERE
def __eq__(self, object):
return isinstance(object, Mammal) and object.species == self.species
THE FOLLOWING TWO CLASSES NEED TO BE COMPLETED, AND YOU
NEED TO REPLACE/DELETE ALL OF THE ELLIPSES SHOWN BELOW
class Hippo(Mammal):
def __init__(self, name, species):
self.name = name
self.species = 'hippo'
def getName(self):
return self.name
def setName(self, h):
self.name = h
def makeNoise(self):
return 'grunting'
class Elephant(Mammal):
def __init__(self, name, species):
self.name = name
self.species = 'elephant'
def getName(self):
return self.name
def setName(self, e):
self.name = e
def makeNoise(self):
return 'trumpeting'
class TestMammals(unittest.TestCase):
""" a class that is derived from TestCase to allow for unit tests to run """
def testInheritance(self):
""" confirm that Elephant and Hippo are children classes of Mammal """
self.assertTrue(issubclass(Elephant, Mammal) and issubclass(Hippo, Mammal))
def testEqOperator(self):
hip1 = Hippo('John')
hip2 = Hippo('Arnold')
self.assertEqual(hip1, hip2)
def main():
""" a 'main' function to keep program clean and organized """
print("-------------------- start main --------------------")
e = Elephant("Ellie")
h = Hippo("Henry")
if(e == h):
print(e.getName(), "and", h.getName(), "are of the same species")
else:
print(e.getName(), "and", h.getName(), "are *not* of the same species")
def listenToMammal(Mammal):
print(Mammal.makeNoise())
listenToMammal(e)
listenToMammal(h)
e.eat(100)
print("--------------------- end main ---------------------")
if __name__ == "__main__":
main()
unittest.main()
enter image description here
this is what the output should look like but im confused
You are still defining Hippo.__init__ to take 2 arguments, even though you ignore the species argument. You can drop that one. You should also use Mammal.__init__ rather than setting the attributes yourself.
class Hippo(Mammal):
def __init__(self, name):
super().__init__(self, name, species='hippo')
def getName(self):
return self.name
def setName(self, h):
self.name = h
def makeNoise(self):
return 'grunting'
getName and setName aren't necessary; you can access the name attribute directly
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)
I want to call a method when an attribute of an object is written. For example:
class Person:
def __init__(self, name, age):
self.name = name
self.age = age
def isAdult(self):
print(True if self.age>=21 else False)
If i want to call .isAdult() when an object's age is written a value, how can I achieve this?
I heard of people suggesting to use decorators, but they didn't give more details.
What you want to have is called a setter. You must use new-style classes and extend object to use them:
class Person(object):
def __init__(self, name, age):
self.name = name
self._age = age
def isAdult(self):
print(self.age >= 21)
#property
def age(self):
return self._age
#age.setter
def age(self, value):
self._age = value
self.isAdult()
Then, for example:
p = Person("John Doe", 12)
p.age = 16
p.age = 25
will print
False
True
you can use decorator in class like below.
In below code, isAdultDeco will be call when you create Person object.
class Person:
def isAdultDeco(func):
def foo(self, name, age):
func(self, name, age)
print(True if self.age>=21 else False)
return foo
#isAdultDeco
def __init__(self, name, age):
self.name = name
self.age = age
Person('a',22) # True
Person('a',20) # False
If I have a class
class Kid():
def __init(name):
self.name = name
what should I add to it to be able to do this:
def is_cool(kid):
cool_kids = {"Jim","Bill","Nebuchadnezzar II"}
return kid in cool_kids
Do I have to inherit Kid from str?
ADD 1: I know I can write return kid.name in cool_kids, but I am looking for a little syntax sugar for my code. I want a way to check for obj in set of very different objects.
How about this:
class Kid():
def __init__(self, name):
self.name = name
def __eq__(self, other):
return self.name == other
def __ne__(self, other):
return not self.name == other
def __hash__(self):
return hash(self.name)
def is_cool(kid):
cool_kids = {"Jim","Bill","Nebuchadnezzar II"}
return kid in cool_kids
if __name__ == "__main__":
print is_cool(Kid("Bob"))
print is_cool(Kid("Jim"))
print is_cool(Kid("Bill"))
Result:
False
True
True
You need to override both __eq__ and __hash__, because both need to be satisfied from an element to be member of a hashtable. When Python evaluates if an element is a member of a hashtable, it first looks at whether hash matches, and if it does then it looks at equality. Overriding __ne__ is not necessary for this example, but it is a good practice to do so, you don't want equals and not equals to be out of sync.
You can do it like this:
class Kid:
def __init__(self, name):
self.name = name
def is_cool(kid):
cool_kids = {"Jim", "Bill", "Nebuchadnezzar II"}
return kid.name in cool_kids
print(is_cool(Kid("Daniel")))
print(is_cool(Kid("Jim")))
Output
False
True
Your code has a few issues, you need to remove the parenthesis from the Kid class definition and change the method __init to __init__ passing self as the first parameter.
UPDATE
If you want to inherit from str you can do it like this:
class Kid(str):
def __new__(cls, *args, **kw):
return str.__new__(cls, *args, **kw)
def is_cool(kid):
cool_kids = {"Jim", "Bill", "Nebuchadnezzar II"}
return kid in cool_kids
print(is_cool(Kid("Daniel")))
print(is_cool(Kid("Jim")))
Output
False
True
You can find more about inheriting from str here
I suspect that the following will do what you wish:
def is_cool(kid):
cool_kids = {"Jim","Bill","Nebuchadnezzar II"}
return kid.name in cool_kids