Better way to pass default arguments to subclasses - python

Suppose I have some class which I subclass, that has some default (perhaps a flag-like) argument. What's the best way to handle passing such an argument around? I can think of doing
class Dog():
def __init__(self, noisy = False):
self.noisy = noisy
def bark(self):
if self.noisy:
print('YAP')
else:
print('yap')
class Beagle(Dog):
def __init__(self, noisy = False):
super().__init__(noisy)
dave = Beagle(noisy = True)
dave.bark()
But this uses noisy seven times, and I feel there must be a better way.

First of all, you can drop the noisy = in the instantiation of Beagle(), it's unneeded:
dave = Beagle(True)
Secondly, given your implementation, your Beagle class has no reason to exist. It does not add any functionality and does not specialize Dog in any way. If anything, possible subclasses of Dog that make sense would be:
class NoisyDog(Dog):
def __init__(self):
super().__init__(True)
class QuietDog(Dog):
def __init__(self):
super().__init__(False)
You could also keep the noisy= in the calls to super().__init__() for better readability, but again that's unneeded.
Other than that, there isn't really much else you can do. If you need a class property, you'll have to assign it to the class (self.foo = bar) and then reference it using its name...

Related

Can I use a class insted of an instance of a class for better readability?

I have a situation where i can make an object as a class:
class BenjaminFranklin(Cat):
def __init__(self):
super().__init__()
self.ishungry = False
self.legs = 4
self.name_eng = 'Benjamin Franklin'
or an instance of a class:
benjamin_franklin = Cat()
benjamin_franklin.ishungry = False
benjamin_franklin.legs = 4
benjamin_franklin.name_eng = 'Benjamin Franklin'
The 'Correct' options seems to be using an instance of a class because there is no group of cats with the name "Benjamin Franklin"; there is only one and only cat. But it is much less readable, especially when using long names.
Tell me please:
"You can use class in this case for better readability, because..."
"The only correct option is to use an instance, because..."
Something else
Thanks
I assume, Benjamin Franklin is a single Cat. Therefore it should be an instance of the class cat.
One way you could do this would be:
class Cat:
def __init__(self, name_eng, is_hungry=False, legs=4):
self.ishungry = is_hungry
self.legs = legs
self.name_eng = name_eng
And then initialize your instance by:
benjamin_franklin = Cat("Benjamin Franklin") # is_hungry and legs do not to be passed, as they have the values you asked for defined as default
Rather than building benjamin_franklin the way you are, I would change your Cat constructor so it can take the arguments necessary to properly build out the object. Then, you could do something like benjamin_franklin = Cat('Benjamin Franklin') which would be much more readable. Such a constructor would look like:
def __init__(self, name_eng, is_hungry = False, legs = 4):
self.ishungry = is_hungry
self.legs = legs
self.name_eng = name_eng
However, if that is not an option, you could embed your code into a factory method:
def BenjaminFranklin():
benjamin_franklin = Cat()
benjamin_franklin.ishungry = False
benjamin_franklin.legs = 4
benjamin_franklin.name_eng = 'Benjamin Franklin'
return benjamin_franklin
If you have an object with many attributes on it, you could also try encapsulating the object into sub-objects and passing those in on your constructor. For instance, suppose your Cat had an identity with a name_eng and name_cha:
class Identity:
def __init__(self, name_eng, name_cha):
self.name_eng = name_eng
self.name_cha = name_cha
class Cat:
def __init__(self, id, ishungry = False, legs = 4):
self.identity = id
self.is_hungry = ishungry
self.legs = legs
which you could initialize like this:
benjamin_franklin = Cat(Identity('Benjamin Franklin', '猫'))
this is somewhat more verbose but it means that you can spread out your construction over several objects (and maybe create some constant objects that are the same over your most instantiations of your class).
Neither option feels right. The name BenjaminFranklin suggests that this is an instance of something. However, the question is why no_legs, is_hungry etc are specific to Benjamin Franklin, and not to all cats. It seems that those attributes should me moved up to the parent Cat class, and Benjamin Franklin should be an instance of it.
Moreover, if only benjamin franklin has a num_legs variable, what does he do with it? There is no added function that uses the new fields. In that sense, this seems more like a dict, or a namedtuple than an instance of a class.
However, it's not possible to tell what the best way is without seeing the functionality of the Cat class, and how you use the extra attributes of benjamin franklin.

Struggling with Class Inheritance & super().__init__

Very new to Python, trying to create a game in which any number of armies can be created, but each army will pre-render the names of the soldiers.
I think I need to be using super init to really cut down on duplicate code, but I cannot for the life of me figure out how to make it work. From what I understand my class Army should be the Parent class, with RedArmy and Scout as subclasses. I'm just struggling to understand where the super().__init__() should come in?
class Army:
def __init__(self):
self.color = None
self.scoutname = None
self.demomanname = None
self.medicname = None
def train_scout(self, weapon):
return Scout(self.color, self.scoutname, weapon)
class RedArmy(Army):
def __init__(self):
self.color = "Red"
self.scoutname = "Yankee"
self.demomanname = "Irish"
self.medicname = "Dutch"
class BlueArmy(Army):
pass
class Scout:
specialization = "fast captures"
def __init__(self, color, scoutname, weapon):
self.color = color
self.scoutname = scoutname
self.weapon = weapon
def introduce(self):
return (f'Hi I\'m {self.scoutname}, I do {self.specialization} and I wield a {self.weapon}')
my_army = RedArmy()
soldier_1 = my_army.train_scout("baseball bat")
print(soldier_1.introduce())
Where to put the super().__init__ (if anywhere) depends on the specifics of your class hierarchy.
Most commonly, if it matters where you put it, you want it at the very start of the subclass's __init__ method. This makes sure that all of the base-class variables are set up, all of its invariants are met, and all of its methods can be called by the rest of the subclass __init__ code.
In your case, it matters because you're setting the same attributes in the base class and the subclass. You obviously want the subclass version to be the one that takes, so it has to come after the default assignments, not before:
class RedArmy(Army):
def __init__(self):
super().__init__()
self.color = "Red"
self.scoutname = "Yankee"
self.demomanname = "Irish"
self.medicname = "Dutch"
However, it's worth considering whether you really want the base class to set these variables to None in the first place.
I assume that in your real code, BlueArmy isn't just going to pass, but is instead going to do the same thing as RedArmy, replacing all of those values with some strings.
Also, the rest of your code is presumably going to assume that there are valid strings there, not None. An exception like TypeError: '<' not supported between instances of 'NoneType' and 'str' is harder to debug than AttributeError: 'GreenArmy' object has no attribute 'scoutname', not easier, so why not just leave the defaults out? Then you can eliminate Army.__init__ entirely, and you don't have to worry about calling super in the subclass initializers in the first place.
Or, alternatively, you could have Army.__init__ take parameters that are used to assign the values, and have the subclasses call super().__init__("Red", "Yankee", "Irish", "Dutch"). Then, Army() will raise a TypeError instead of creating an invalid Army instance. Or you could make an #abstractmethod named self._setup() that Army.__init__ calls and expects each subclass to provide, so Army() will raise an even more meaningful TypeError about instantiating an abstract class. These refinements make it easier to debug your Army subclasses—if you just have two of them, they may just be a waste of time, but if you have a bunch of them, which will be developed by a variety of people or over a long period of time, it might be worth it.
Passing values as arguments makes the most sense if you want your classes to take advantage of super and init:
class Army:
def __init__(self, color=None, scoutname=None, demomanname=None,
medicname=None):
self.color = color
self.scoutname = scoutname
self.demomanname = demomanname
self.medicname = medicname
def train_scout(self, weapon):
return Scout(self.color, self.scoutname, weapon)
class RedArmy(Army):
def __init__(self, color="Red", scoutname="Yankee", demomanname="Irish",
medicname="Dutch"):
super().__init__(color, scoutname, demomanname, medicname)
class Scout:
specialization = "fast captures"
def __init__(self, color, scoutname, weapon):
self.color = color
self.scoutname = scoutname
self.weapon = weapon
def introduce(self):
return (
f'Hi I\'m {self.scoutname}, I do {self.specialization} and I wield '
f'a {self.weapon}')
my_army = RedArmy()
soldier_1 = my_army.train_scout("baseball bat")
print(soldier_1.introduce())

Trouble with specific class inheritance behaviour

So I am trying to get my data structure set up for an automated generator I am writing for a roleplaying game and I am having trouble with some specific inheritance quirks. Here is an excerpt of the data structure.
class data():
def __init__(self):
self.races = Races()
class Races(data):
def __init__(self):
self.humans = Humans()
class Humans(Races):
def __init__(self):
self.Characteristics = {
'Brawn':2,
'Agility':2,
'Intellect':2,
'Cunning':2,
'Willpower':2,
'Presence':2
}
There is a lot more in the structure but this is just a bottom to top overview. I also know it is indented weirdly but that is strictly stack overflow.
Now I wish to have two behaviors from this object.
The ability to call any characteristic with
data.races.humans.Characteristic['brawn']
as the calling format.
And too also be able to iterate through subclasses with a generator like:
(subclass for subclass in data.races.__subclasses__())
obviously after I have instantiated the object.
Now I have tried changing the structure several times and I can get it so EITHER I can call it with dot notation, but it returns AttributeError: 'Races' object has no attribute '__subclasses__'
Or vice versa by completely separating it into a more traditional structure but then I cannot call in dot notation and this makes it very hard to keep everything organized and readable.
Can anyone suggest what I am doing wrong or a more Pythonic way to approach the problem?
Let's start in the middle. Presumably, a character of any race has the same attributes, just different values for those attributes.
class Race:
def __init__(self):
self.life = 100 # 100% healthy
class Humanoid(Race):
def __init__(self):
super().__init__()
self.legs = 2
class Insectoid(Race):
def __init__(self):
super().__init__()
self.legs = 8
class Human(Humanoid):
def __init__(self):
super().__init__()
self.brawn = 2
self.agility = 2
self.intellect = 2
self.cunning = 2,
self.willpower = 2
self.presence = 2
class Elf(Humanoid):
def __init__(self):
super.__init__()
self.brawn = 1
self.agility = 3
self.intellect = 3
self.cunning = 2
self.willpower = 3
self.presence = 1
Now, any particular character would be instantiated as the correct class:
some_elf_1 = Elf()
some_human_1 = Human()
some_human_2 = Human()
for character in [some_elf_1, some_human_1, some_human_2]:
print("Brawn: ", character.brawn)
In the preceding, it doesn't matter what the actual type of each character is; as long as you know that it is some subclass of Race (or an instance of Race itself), it will have a brawn attribute that you can access.
You data class doesn't really seem necessary without more detail.
So, While the answer given put me on the right track I realized what I needed and am just throwing in my lot for any poor souls.
Firstly - I realized what was wrong with my generator, I was calling on the initialized object instead of the class object. Objects do not have a subclasses attrib and I was mis-informed by most of the guides I read!
Secondly, I considered using a metaclass to get the iterating behavior I wanted from my objects can simply be achieved with a registry attribute that is a dict of all the initialized subclasses.
lass Races(data):
def __init__(self):
self.humans = Humans()
self.droids = Droids()
self.twileks = Twileks()
self.registry = {
'humans':self.humans,
'droids':self.droids,
'twileks':self.twileks
}
This allows me to iterate through certain values for different races after they have been initialized.
Thanks for all the great answers!

Python: How to update the calls of a third class to the overriden method of the original class?

class ThirdPartyA(object):
def __init__(self):
...
def ...():
...
-------------------
from xxx import ThirdPartyA
class ThirdPartyB(object):
def a(self):
...
#call to ThirdPartyA
....
def b(self):
...
#call to ThirdPartyA
...
def c(self):
...
#call to ThirdPartyA
...
-----------------------------------
from xxx import ThirdPartyA
class MyCodeA(ThirdPartyA):
def __init__(self):
# overriding code
When overriding the __init__ method of A class, how could I instruct B class that it should call MyCodeA instead of ThirdPartyA in all its methods?
The real code is here:
CLass Geoposition: ThirdPartyA
Class GeopositionField: ThirdPartyB
My override to class Geoposition so it returns max 5 decimal digits:
class AccuracyGeoposition(Geoposition):
def __init__(self, latitude, longitude):
if isinstance(latitude, float) or isinstance(latitude, int):
latitude = '{0:.5f}'.format(latitude)
if isinstance(longitude, float) or isinstance(longitude, int):
longitude = '{0:.5f}'.format(longitude)
self.latitude = Decimal(latitude)
self.longitude = Decimal(longitude)
From your updated code, I think what you're trying to do is change GeopositionField. to_python() so that it returns AccuracyGeoposition values instead of Geoposition values.
There's no way to do that directly; the code in GeopositionField explicitly says it wants to construct a Geoposition, so that's what happens.
The cleanest solution is to subclass GeopositionField as well, so you can wrap that method:
class AccuracyGeopositionField(GeopositionField):
def topython(self, value):
geo = super(AccuracyGeopositionField, self).topython(value)
return AccuracyGeoposition(geo.latitude, geo.longitude)
If creating a Geoposition and then re-wrapping the values in an AccuracyGeoposition is insufficient (because accuracy has already been lost), you might be able to pre-process things before calling the super method as well/instead. For example, if the way it deals with list is not acceptable (I realize that's not true here, but it serves as a simple example), but everything else you can just let it do its thing and wrap the result, you could do this:
class AccuracyGeopositionField(GeopositionField):
def topython(self, value):
if isinstance(value, list):
return AccuracyGeoposition(value[0], value[1])
geo = super(AccuracyGeopositionField, self).topython(value)
return AccuracyGeoposition(geo.latitude, geo.longitude)
If worst comes to worst, you may have to reimplement the entire method (maybe by copying, pasting, and modifying its code), but hopefully that will rarely come up.
There are hacky alternatives to this. For example, you could monkeypatch the module to globally replace the Geoposition class with your AccuracyGeoposition class But, while that may save some work up front, you're almost certain to be unhappy with it when you're debugging things later. Systems that are designed for aspect-oriented programming (which is basically controlled monkeypatching) are great, but trying to cram it into systems that were designed to resist it will give you headaches.
Assuming your real code works like your example—that is, every method of B creates a new A instance just to call a method on it and discard it—well, that's a very weird design, but if it makes sense for your use case, you can make it work.
The key here is that classes are first-class objects. Instead of hardcoding A, store the class you want as a member of the B instance, like this:
class B(object):
def __init__(self, aclass=A):
self.aclass = aclass
def a(self):
self.aclass().a()
Now, you just create a B instance with your subclass:
b = B(OverriddenA)
Your edited version does a different strange thing: instead of constructing a new A instance each time to call methods on it, you're calling class methods on A itself. Again, this is probably not what you want—but, if it is, you can do it:
class B(object):
def __init__(self, aclass=A):
self.aclass = aclass
def a(self):
self.aclass.a()
However, more likely you don't really want either of these. You want to take an A instance at construction time, store it, and use it repeatedly. Like this:
class B(object):
def __init__(self, ainstance):
self.ainstance = ainstance
def a(self):
self.ainstance.a()
b1 = B(A())
b2 = B(OverriddenA())
If this all seems abstract and hard to understand… well, that's because we're using meaningless names like A, B, and OverriddenA. If you tell us the actual types you're thinking about, or just plug those types in mechanically, it should make a lot more sense.
For example:
class Vehicle(object):
def move(self):
print('I am a vehicle, and I am moving')
class Operator(object):
def __init__(self, vehicle):
self.vehicle = vehicle
def move(self):
print('I am moving my vehicle')
self.vehicle.move()
class Car(object):
def move(self):
print('I am a car, and I am driving')
driver = Operator(Car())
driver.move()

Is there a "pythonic" approach to required properties (OOP)?

I'm struggling to find a "pythonic" approach to the following class organization:
I have a base class with properties initialized in its constructor, for example:
class Animal(object):
def __init__(self, class_, species, is_domesticated):
self.class_ = class_
self.species = species
self.is_domesticated = is_domesticated
Then, when I subclass, I would like to "hard-code" one or more of these properties, like so:
class Mammal(Animal):
def __init__(self, species, is_domesticated):
Animal.__init__(self, 'Mammal', species, is_domesticated)
A Mammal is thus instantiated like so:
monkey = Mammal('Primate', false)
The problem is, I would like to use *args so as to leave any derived classes alone when altering the base class definition. Thus the definition of Mammal becomes:
class Mammal(Animal):
def __init__(self, *args):
Animal.__init(self, *(args + (class_='Mammal',)))
Which (needless to say) looks horrible. Some tips would be appreciated =)
If you only have a fixed set of arguments in the base class, there isn't much need to worry about variable arguments. Just do what you did in your first example and it's fine. If you want to be able to randomly add arguments to the base class, but add them as positional arguments and without defaults, there's no hope; you can't just change the base class willy-nilly and expect all derived classes to keep working.
However, there is a fairly common intermediate case where you might have a large set of attributes, various combinations of which may be passed to any class in the hierarchy. You might want to add new arguments to the base class, but they'll have defaults so that derived classes don't need to know about them explicitly; they'll just gracefully degrade to the base-class default. In such a case it's usually a better idea to use **kwargs rather than *args.
class Animal(object):
def __init__(self, **kwargs):
self.class_ = kwargs['class_']
self.species = kwargs['species']
# etc.
class Mammal(Animal):
def __init__(self, **kwargs):
Animal.__init__(self, class_="Mammal", **kwargs)
This requires the arguments to passed by keyword:
>>> Animal(class_='Fish', species='barracuda', is_domesticated=False)
4: <__main__.Animal object at 0x0177ABF0>
>>> Mammal(species="monkey", is_domesticated=False)
5: <__main__.Mammal object at 0x0177AFB0>
. . . but this is better if there are a lot of them, because no one will remember which order to pass them in if you have 10 different things getting passed in positionally. It also means that you can add new arguments easily; no one has to know where in the list to put the new ones, they can just add them anywhere by keyword.
In Python 2 you have to manually extract the kwargs as I did above. In Python 3 you can use keyword-only arguments to make this even easier.
Well, why don't you just do what you said you want? Make Mammal.__init__() take a *args argument, then use that. Here's the code:
class Animal(object):
def __init__(self, class_, species, is_domesticated):
self.class_ = class_
self.species = species
self.is_domesticated = is_domesticated
def __str__(self):
s_dom = "dom" if self.is_domesticated else "wild"
return ("Animal(%s, %s, %s)" % (self.class_, self.species, s_dom))
class Mammal(Animal):
def __init__(self, *args):
Animal.__init__(self, 'Mammal', *args)
cat = Mammal("cat", True)
print(cat)
lion = Mammal("lion", False)
print(lion)
The output:
Animal(Mammal, cat, dom)
Animal(Mammal, lion, wild)

Categories

Resources