In this example, am I violating LSP? Since straight up replacing the last two lines with an instance of a subclass will give me an error(as wage isn't initialised)?
person_1 = Employee('Brad')
person_1.print_name()
#dataclass
class Person:
name: str
def print_name(self):
print(self.name)
#dataclass
class Employee(Person):
wage: int
person_1 = Person('Brad')
person_1.print_name()
If so, then how can there ever be a non-violation of LSP when extending the constructor of a class (aside from placing optional attributes thereafter)?.
LSP says, that if something is true for a Person object (e.g. it has a name, the name is a string, it can print its name), it must be also true for an Employee object. In other words, every Employee is also a Person.
It does not state that an Employee object must be created the same way as a Person object. Every Employee is not only a Person. It has not only the name, but also a wage.
The second question:
If the Employee.print_name() method were redefined not to print the name, but for instance to return it as a string, that would break the principle.
Note that breaking the LSP does not require a code change, for instance if the Person's name format were changed from e.g. "first_name last_name" to "last_name, first_name" in the Employee, and that would cause the program to give incorrect output.
I know it's been answered already but i wanted to emphasize:
We need to differentiate between 2 relationships. One is the relationship between instances of Person and instances of Employee. The second one is the relationship between the 2 instances of type (The Person class itself, and the Employee class itself.)
In your case, LSP deals only with the former (everything that we can do with Person instances, we need to be able to do in the exact same way with Employee instances). It says nothing about the classes themselves.
Now, since python is very dynamic you could technically argue "Hey just wait second! There is something I can do with one and not the other!". Take a look at the following example:
# Assume we have an instance of either Person or Employee here
instance = _
# The following will work with Person instances but will raise an exception for Employee instances
instance_copy = type(instance)(instance.name)
I would say that you shouldn't count this kind of stuff. Let's call it an "unreasonable expectation of usage" that shouldn't be accounted for when considering the validity of your code structure in the vast vast majority of use cases.
The most important thing to remember is this:
B inherits from A != A (the class object itself) can be substituted by B (the class itself)
It depends on what you mean by the LSP.
Does it mean the strict LSP, like in Barbara Liskov's original paper, where the behaviour of the program should be unchanged by type substitution? (and even then it's a desired property, not an absolute requirement)
Or does it mean following the Person interface, in which case it would not be a violation, since you can't remove functions for the Person class in the Employee class? (Well, you technically could, but it's not a good idea to do that).
Related
What do we call the relationship of a class towards its associated metaclass?
I'd like to fill the blank in the following two lines:
Class A is the metaclass of class B.
Class B is the ________ of class A.
In the documentation of the class, I am documenting a metaclass I am currently writing. I find my self typing "the class associated to this metaclass" very often in the python docstrings. Is there a single word which I can use to denote this kind of relationship in a more concise manner?
Condensed example in which I'd like to use a more concise nomenclature:
def __init__(mcl, what, bases=None, dict=None):
"""
Raises an exception if >> the class associated to this metaclass <<
contains a valid set of configuration decorators.
...
"""
The term "metaclass" itself already captures much of the relationship you are looking. A metaclass isn't really a special thing; it's just another type. The "magic" is that instances of this type also happen to be types. (What is a type? It's just a class which provides methods for objects.)
If anything, classes are the special things. You could imagine that metametaclasses exist: types whose instances are metaclasses, whose instances are classes. You could build up an infinite hierarchy of such metameta...classes. (In practice, this doesn't exist in Python. The root metaclass type has itself as its own metaclass, not some other higher-order metametaclass.) A class, then, is unique as the only sort of type whose instances cannot also serve as types.
The relationship you are looking for is just "class B is an instance of (meta)class A".
(Nothing I say here should be taken as a contradiction of jsbueno's answer; I think it's just a different perspective.)
There is no official nomenclature for that. The only way to go is the full formal way: "the class B for which A is the metaclass" or equivalent.
Technically one could just say that "class B" is an "instance of "class A" - but no other context given it would be very hard for anyone to figure out you are talking about class-metaclass relationship.
For the specific case you mention, though, it would work, I think - you could replace ">> the class associated to this metaclass << " for ">> the class which is an instance of this metaclass << "
So, I'm fairly new to Python, and I am trying to learn how the OOP paradigm works there.
One thing that confuses me at this stage are the class variables. To be more specific, consider the following example taken from the Python 3 Object-Oriented Programming book by Dusty Phillips (Chapter 3 - When objects are alike):
class Contact:
all_contacts = []
def __init__(self, name, email):
self.name = name
self.email = email
Contact.all_contacts.append(self)
So, here we see a simple example of a class that has the class variable all_contacts which is an empty list. Besides that there are two instance variables, name and email.
Now, if we were to create two different instances of the class Contact, e.g.
c1 = Contact('John', 'john#gmail.com')
c2 = Contact('Mark', 'mark#yahoo.com')
there are two ways how we can access properties name and email, i.e.
c1.name
c1.all_contacts[0].name
But this is where I have a problem - all_contacts is a class variable, meaning that it is common to all instances of the class Contact. Not only that it is common to all instances, but due to the way how this list is being populated, there is nothing that prevents me from doing this:
c1.all_contacts[1].name
which gives me the name property of the instance c2.
In fact, it seems that I can go deep as much as I want:
c1.all.contacts[0].all_contacts[1].all_contacts[0]...
In other words, I am worried if in Python we have a class with a class variable as a list and then in the initializer method we store self to that list, are we then creating a structure that can reference itself infinite many times? Is there some kind of workaround for this?
I would really appreciate if anyone can comment on this behavior.
Kind regards
Say I have 2 classes: Water and Substance. one or more Substances can be dissolved in Water; a Water instance has a substances attribute containing a list of Substance instances. The diffusion constant of a Substance depends on the attributes of the Water it is dissolved in as well as some attributes of the Substance itself. Should I then make a get_diffusion_constant method on Water with an instance of Substance as its attribute, or should I add the method to Substance where Water is its argument? Or is there a different approach altogether?
You wrote:
The diffusion constant of a Substance depends on the attributes of the Water
This let think that the diffusion constant is a characteristic of the substance, so the Substance class should own the method that allows to compute it (with a parameter to provide a Water instance since it depends on it).
This works well for most simple case, if a real concepts owns a characteristic, its model (a class in this case) should own the related attribute or method.
Some design-patterns and/or more complex needs could justify to break this "rule" to introduce more astraction.
Additionally, to answer your comment: it is not a problem (IMO at least) to pass self to a function.
self is just a reference to the current instance and as nothing special except it is a widely respected convention for naming the current instance, and thus the first argument of (instance) methods.
To explain a bit more: an instance method must accept as first argument a reference to the related instance. By convention, this positional argument is named self, but you could decide to name it this, instance or whatever you want, it would be the same. Then you just would have to use the correct parameter name inside the method.
See the code below. It uses very bad names for the instance argument but it works as if self had been used:
class Foo:
def __init__(current_instance):
current_instance.bar = 'baz'
def __str__(this_name_is_ridiculous):
return this_name_is_ridiculous.bar
print(Foo()) # prints baz
I assume that Water inherits some stuff from Substance. So then you could have a diffusion_constant in each Substanceand also a diffuse function with takes one or more Substances
EDIT:
class Water:
def diffuse(self, *args):
#check if args is greater 0 or None and then iterate and apply diffusion_constants
class Substance:
diffusion_constant = 0 #base constant
class Sirup(Substance):
diffusion_constant = 3
#somewhere later then
corn_sirup = Sirup()
sugary_sirup = Sirup()
water = Water()
water.diffuse(corn_sirup, sugary_sirup)
EDIT:
Due to the comment I changed the code. Python has duck-typing, so as long your Substances have a diffusion_constant attribute, it is possible to access it, no matter what it is. This should then do it
Say I have different instances of a class;
class Person(object):
def __init__(self, id):
self.id = id
Joe = Person('123')
Sarah = Person('321')
My question now is, how would I be able use one of the instances without knowing the name before hand, for example, I could have something that requests for a name or even id. How would I associate it with the appropriate object? Say an input of an id, '123' was given, how do I get to know that it belongs to the Joe object and then use that object dynamically? I am running Python 3.6 if that info is of much use.
As you study computer programming, you will probably hear a not-really-a-joke that there are only three important numbers: 0, 1, and infinity. In other words, you can have none of something or one of something, but if you can have more than one, you have to be prepared to handle any number of them.
When you can only have zero or one of something, putting it in a variable is fine. But this solution quickly becomes unwieldy when you can have any number of something. The solution favored by programmers is to put the objects in a container, like a list or dictionary.
For example, the dictionary approach:
people = {"123": Person("123"), "321": Person("321")}
Then Joe is people["123"].
In reality you want to store the person's name in the object as well, otherwise you can't tell who it actually represents, so you'd add that to your Person class. You could also have the object add itself to the master people dict when it's instantiated:
people = {}
class Person(object):
def __init__(self, id, name):
self.id = id
self.name = name
people[id] = self
joe = Person("123", "Joe")
sarah = Person("321", "Sarah")
Now when you instantiate objects they are added to the people dict by their ID automatically, and you can access them as such. (You could also add them by name, assuming the names are unique: even to the same dictionary if IDs and names can never clash.)
N.B. "calling" has a specific meaning in programming: you call a function. You are not calling anything here.
In the class below, the self parameter in class method cost is replaced with another name 'insta' but still it works. Why?
class car():
model='sedan'
year=2016
price=775000
def cost(insta):
print "Price of car is: ",insta.price
c1=car()
c1.model='SUV'
c1.year=2017
c1.price=120000
c1.cost()
c2=car()
c2.model='hatchback'
c2.year=2018
c2.price=600000
c2.cost()
Naming self the first parameter of a class method is nothing more than a convention. Your code is strictly tantamount to:
def cost(self):
print "Price of car is: ",self.price
However, this convention is respected everywhere, and although I knew it was only a convention, I think it's the first time I see a code not naming the instance self.
When you instantiate c1 to be an object of class car, c1 gets those 3 things (attributes) you gave it: a model, a year, and a price. In order for c1 to know that you want its cost() method to access its price and not c2's price for example, you (correctly) defined the cost() method to associate the price printed with the price of the object that cost() is called on.
The word self is common, perhaps because given Python syntax, when you call a method on a class object with dot notation as in classObject.classMethod(), it might feel more like you are "calling classMethod() on classObject" than more typically "passing someArgument to someFunction" let's say, since you don't put classObject inside the parenthesis of classMethod. But the key piece is the association you make in the class definition (which will work with "self" or "insta" or "puppy", as long as you use the chosen word consistently). Perhaps a useful naming that would help illuminate what is going on in this particular context would be
def cost(myCar):
print ("Price of car is: ", myCar.price)
since you want the cost of myCar to be the price of myCar. This is what self does in a more general (and thus more readable and maintainable) way.