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

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)

Related

How does python3 diamond inheritance works for data fields? How to initialize the inherited fields with super()._init__?

I was checking this problem to understand multiple inheritance and I got stuck.
How can I set the fields of the inherited objects from the last class?
class Vehicle():
def __init__(self, name:str, seats:int):
self.name = name
self.seats = seats
def print_vehicle(self):
print(f'Vehicle {self.name} has {self.seats} seats')
class Boat(Vehicle):
def __init__(self, name:str, seats:int, engine_type:str):
super().__init__(name, seats)
self.engine_type = engine_type
def print_vehicle(self):
print(f'Boat {self.name} has {self.seats} seats and engine {self.engine_type}')
class Car(Vehicle):
def __init__(self, name:str, seats:int, fuel:str):
super().__init__(name, seats)
self.fuel = fuel
def print_vehicle(self):
print(f'Car {self.name} has fuel {self.fuel}')
class AnphibiousCar(Boat, Car):
def __init__(self, name, seats, engine_type, fuel):
super(AnphibiousCar, self).__init__(name, seats, engine_type) # ???
def print_vehicle(self):
print(f'Anphibious car {self.name} has {self.seats} seats and {self.engine_type} - {self.fuel} engine')
ac = AnphibiousCar('name', 4, 'piston', 'gas')
ac.print_vehicle()
The point is that each class should focus only on the stuff which is its direct responsibility; the rest should be delegated to superclasses (and note that, when you deal with such a cooperative inheritance with super(), your methods that call super() should not need to know what exactly are the actual superclasses, in particular the nearest one - as this can change, depending on the actual class of self).
So let's reimplement your classes (with a bunch of explanations in the comments):
class Vehicle:
# Added the `*,` marker to make `name` and `seats` *keyword-only*
# arguments (i.e., arguments that are identified only by their
# *names*, never by their positions in a call's arguments list).
def __init__(self, *, name: str, seats: int):
self.name = name
self.seats = seats
# We abstract out class-specific features into separate methods,
# keeping in the `print_vehicle()` method only the common stuff,
# so that in subclasses we'll need to customize only those methods
# (`list_features()`, `get_type_label()`), *not* `print_vehicle()`.
def print_vehicle(self):
vehicle_type_label = self.get_type_label()
features = ', '.join(self.list_features())
print(f'{vehicle_type_label} {self.name}: {features}.')
# Side note: the `list[str]` type annotation requires Python 3.9
# or newer (for compatibility with older versions you need to
# replace it with `List[str]`, using `from typing import List`).
def list_features(self) -> list[str]:
return [f'has {self.seats} seats']
# This implementation is, in fact, quite generic (so that
# in most subclasses we will *not* need to customize it).
def get_type_label(self) -> str:
return self.__class__.__name__
class Boat(Vehicle):
# Only `Boat`-specific arguments (as keyword-only ones, as above...)
# are declared here explicitly. Any other are treated as a "black
# box", just being passed into superclasses...
def __init__(self, *, engine_type: str, **kwargs):
super().__init__(**kwargs)
self.engine_type = engine_type
# Also here we focus only on this-class-specific stuff, handling
# other stuff as "agnostically" as possible...
def list_features(self) -> list[str]:
return super().list_features() + [f'has {self.engine_type} engine']
class Car(Vehicle):
# And analogously...
def __init__(self, *, fuel: str, **kwargs):
super().__init__(**kwargs)
self.fuel = fuel
def list_features(self) -> list[str]:
return super().list_features() + [f'needs {self.fuel} fuel']
class AmphibiousCar(Boat, Car):
# Note: here we get our `__init__()` and `list_features()`
# for free (!), as the superclasses provide all we need
# when it comes to those two methods.
# The only thing we may want to customize is:
def get_type_label(self) -> str:
return 'Amphibious car'
ac = AmphibiousCar(
name='Julia-III',
seats=4,
engine_type='piston',
fuel='gas')
# "Amphibious car Julia-III: has 4 seats, needs gas fuel, has piston engine."
ac.print_vehicle()
As a further reading, I'd recommend: https://rhettinger.wordpress.com/2011/05/26/super-considered-super/
You have some errors:
super(AnphibiousCar, self).__init__(name, seats, engine_type) could become
Boat.__init__(self, name, seats, engine_type) so calling the class you could
give information about how to initialize it.
there is a missing parameter in Boat where you should give a fuel argument
to the superclass Vehicle, like super().__init__(name, seats, "oil")
As you can note if you use super you don't need to pass self, if you use
the class name you are using it.
My point of view is that, yes, is good to understand, but don't loose to much
time as this kind of multiple inheritance is only theoretical and practically
not used in real coding. This in fact can cause a lot of confusion and add
boilerplate... "new" languages like, for example, Rust do not even provide
inheritance.
Just to say: "Yes, study it, but keep it simple" ^_^

Better way to pass default arguments to subclasses

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...

Wagtail - more class arguments than parameters? Whats going on?

Hi Stackoverflow Community
I have been trying to understand how Django (and Wagtail's Stream-field) work under the hood. Doing that I learned about metaclasses and believe to have a handle on the principle. There is however one piece of code that confuses me (see below).
When we follow the StreamField definitions this code seems to pass a list of tuples to a class constructor that doesn't appear to accommodate for a list of this kind. How can this work?
Any advice would be highly appreciated. Here the code:
models.py
This is where we define the StreamField. It takes a list of tuples of format ('title', blockType). Our objective is to follow the StreamField call:
class BlogPage(Page):
blogElement = StreamField([
('heading', blocks.CharBlock(classname="full title")),
('paragraph', blocks.TextBlock()),
('picture', ImageChooserBlock()),
], default=[])
fields.py>StreamField
When we follow the call to StreamField we arrive at the following class constructor. Here the call to StreamBlock(block_types) is where it gets confusing:
class StreamField(models.Field):
def __init__(self, block_types, **kwargs):
if isinstance(block_types, Block):
self.stream_block = block_types
elif isinstance(block_types, type):
self.stream_block = block_types()
else:
self.stream_block = StreamBlock(block_types)
super(StreamField, self).__init__(**kwargs)
StreamBlock
While the call to the class constructor takes block_types as an argument (contains a list of three tuples defined in models.py) the receiving class constructor takes a call to six.with_metaclass as an argument (code included below):
class StreamBlock(six.with_metaclass(DeclarativeSubBlocksMetaclass, BaseStreamBlock)):
pass
MY QUESTION
How is this possible? six.with_metaclass itself is a call to a method which takes two arguments <> and <>, both class constructors in their own right (code included below).
Shouldn't StreamBlock accommodate to receive arguments that fit the block_types which in turn contain the list of three tuples defined in models.py? I am sure i may be missing something here but just can't see it. Any advice would be highly appreciated.
Z
For Context
I included the code for the other code-pieces for context below. I figured out how the six.with_metaclass works in this post: [Wow does six.with_metaclass() work?
][1] but I am struggling with the question above.
six.with_metaclass
def with_metaclass(meta, *bases):
"""Create a base class with a metaclass."""
# This requires a bit of explanation: the basic idea is to make a dummy
# metaclass for one level of class instantiation that replaces itself with
# the actual metaclass.
class metaclass(meta):
def __new__(cls, name, this_bases, d):
print("In with_metaclass: %s " %d )
return meta(name, bases, d)
return type.__new__(metaclass, 'temporary_class', (), {})
DeclarativeSubBlocksMetaclass
class DeclarativeSubBlocksMetaclass(BaseBlock):
"""
Metaclass that collects sub-blocks declared on the base classes.
(cheerfully stolen from https://github.com/django/django/blob/master/django/forms/forms.py)
"""
def __new__(mcs, name, bases, attrs):
# Collect sub-blocks declared on the current class.
# These are available on the class as `declared_blocks`
current_blocks = []
for key, value in list(attrs.items()):
if isinstance(value, Block):
current_blocks.append((key, value))
value.set_name(key)
attrs.pop(key)
current_blocks.sort(key=lambda x: x[1].creation_counter)
attrs['declared_blocks'] = collections.OrderedDict(current_blocks)
new_class = (super(DeclarativeSubBlocksMetaclass, mcs).__new__(mcs, name, bases, attrs))
# Walk through the MRO, collecting all inherited sub-blocks, to make
# the combined `base_blocks`.
base_blocks = collections.OrderedDict()
for base in reversed(new_class.__mro__):
# Collect sub-blocks from base class.
if hasattr(base, 'declared_blocks'):
base_blocks.update(base.declared_blocks)
# Field shadowing.
for attr, value in base.__dict__.items():
if value is None and attr in base_blocks:
base_blocks.pop(attr)
new_class.base_blocks = base_blocks
return new_class
BaseStreamBlock
class BaseStreamBlock(Block):
def __init__(self, local_blocks=None, **kwargs):
self._constructor_kwargs = kwargs
super(BaseStreamBlock, self).__init__(**kwargs)
# create a local (shallow) copy of base_blocks so that it can be supplemented by local_blocks
self.child_blocks = self.base_blocks.copy()
if local_blocks:
for name, block in local_blocks:
block.set_name(name)
self.child_blocks[name] = block
self.dependencies = self.child_blocks.values()

How to create a method for a class that takes multiple objects of a class as arguments?

I'd like to define a special method within a class that takes two instances of the class as arguments. I'd also like to be able to call this function with method(object_a, object_b) rather than object_a.method(object_b). Let me illustrate with an example:
class Person:
def __init__(self, name, age):
self.name = name
self.age = age
def inside_class_age_diff(self, other):
return abs(self.age - other.age)
def outside_class_age_diff(person_a, person_b):
return abs(person_a.age - person_b.age)
Romeo = Person("Romeo", 20)
Juliet = Person("Juliet", 18)
print(Romeo.inside_class_age_diff(Juliet))
print(outside_class_age_diff(Romeo, Juliet))
So, in the above example outside_class_age_diff() takes two objects of the class Person as arguments, but it's defined outside of the class, which to me feels wrong because it's clearly a method that belongs to the class Person. On the other hand, even though inside_class_age_diff() is defined inside of the class, it needs to be called using the dot notation with an object of the class, which isn't very neat.
So, how can I get inside_class_age_diff(Romeo, Juliet) to work? Is it possible even?
Seems like you're playing around with design patterns. What you're looking for is a static method.
You'd define it like so:
class Person:
def __init__(self, name, age):
...
def inside_class_age_diff(self, other):
...
#staticmethod
def outside_class_age_diff(person_a, person_b):
return abs(person_a.age - person_b.age)
You can then use it like so:
Person.inside_class_age_diff(Romeo, Juliet)
It's still a method of the class, and thus needs to be called as such.
You can use a static method:
class Person:
def __init__(self, name, age):
self.name = name
self.age = age
#staticmethod
def inside_class_age_diff(person_a, person_b):
return abs(person_a.age - person_b.age)
Romeo = Person("Romeo", 20)
Juliet = Person("Juliet", 18)
print(Romeo.inside_class_age_diff(Romeo, Juliet))
A static method acts just like normal function, i.e. it is not bound to an instance. Hence the first argument, is not treated special in any way.

Python - Getting subclass from input. Do I have to write a separate function?

I'm working in Python 2.7.8. What follows is a slight variant of the problem I'm working on.
I have a large number of custom classes that I've written where the inheritance is like a tree. The behavior is well encapsulated by the following example:
import random
class Animal(object):
def __init__(self, name):
self.name = name
self.can_own_pets = False #most Animals cannot own pets
self.get_features()
def give_pet(self, pet):
if not self.can_own_pets:
print(self.name+' cannot own a pet!')
else:
self.pets.append(pet)
def is_hungry(self):
return random.choice([True, False])
def get_features(self):
"""
In some classes, get features will be a function
that uses self.name to extract features.
In my problem, the features are extracted
with regular expressions that are determined by
by the class.
"""
pass
class Human(Animal):
def __init__(self, name):
super(Human, self).__init__(name)
self.can_own_pets = True
self.pets = []
class Dog(Animal):
def __init__(self, name):
super(Dog, self).__init__(name)
def bark(self):
print 'WOOF'
def get_features(self):
if 'chihuahua' in self.name:
self.is_annoying = True
elif 'corgi' in self.name:
self.adorable = True
My program needs to take in a large number of animals and delegate them to the correct classes -- I need the correct attributes and methods. What I would like to do is modify the Animal constructor so that if the name argument is something like "Finn the Dog" or "Jake the Human", it (the constructor) returns an instance of the class "Dog" or "Human", complete with the appropriate methods and attributes. Now, I know that I could easily write a function that takes a string and class as arguments, constructs a dictionary where the keys are the names of the subclasses of the given class, looks up the element of the dictionary that is contained in the string, and returns an object of that class. My question is whether or not there is a way to code this into the Animal class itself, which seems more elegant to me (as well as easier to maintain).
Here's an implementation --
def _get_all_subclasses(cls):
for scls in cls.__subclasses__():
yield scls
for scls in _get_all_subclasses(scls):
yield scls
class Animal(object):
#staticmethod
def from_string(s):
for cls in _get_all_subclasses(Animal):
# Somehow pick the class based on the string... This is a really simple example...
if cls.__name__ in s:
return cls()
raise ValueError('Bummer. Animal has not been discovered.')
class Dog(Animal):
pass
class Cat(Animal):
pass
class Lion(Cat):
pass
print Animal.from_string('is a Dog')
print Animal.from_string('is a Cat')
print Animal.from_string('Lions!!!')
print Animal.from_string('Lockness Monster')
There are limitations here
All of the constructors need to be pretty much the same which means that Cat.__init__ needs to basically do the same thing that Human.__init__ does.
After you create the instance, your code needs to have logic to handle Cat, Human, Dog, etc. In some cases that's Ok (e.g. the code really only cares that it is working with an Animal), but frequently it isn't (after all, Cats can walk on fences, but Humans can't).
Generally, the principle that I like to live by is to try to make the inputs to my functions permissive (is it a list or a tuple? Who cares! Duck Typing FTW!) but to try to have really well defined outputs. I think that this makes interfaces easier to use in the long haul and the code that I wrote above would probably not pass a code review if I was the reviewer :-).
To build upon mgilson's answer
You can override the __new__ method so that you can instantiate the classes like normal without a static method.
class Animal(object):
#classmethod
def _get_all_subclasses(cls):
for scls in cls.__subclasses__():
yield scls
for scls in scls._get_all_subclasses():
yield scls
def __new__(cls, name):
cls_ = cls
for subcls in Animal._get_all_subclasses():
if subcls.__name__ in name:
cls_ = subcls
break
instance = object.__new__(cls_)
if not issubclass(cls_, cls):
instance.__init__(name)
return instance

Categories

Resources