if I create a class called Car
class Car():
'''car information summary'''
def __init__(self, make, model, year):
self.make = make
self.model = model
self.year = year
self.odometer = 0
I learned that self.odometer=0 allowed me creating a new instance without putting a value for odometer. Every new instance will start with an odometer reading at 0.
But what if I want to create an new instance with a specified odometer reading?
car_1 = Car('Audi', 'S4', '2017', 5000)
It won't allow me to do so. What I am trying to do is to use it like a default value for a function: You don't have to give a value because there is a default, but when you do, you can overwrite the default.
And I do understand that I can revise the attribute afterwards, or write a method to change the attribute. But that's not my concern for now.
Is this a wrong idea to have for OOP?
Python supports the concept of parameters with a default value. You can write a parameter, and give it a default value if it is not specified. In this case it looks like:
class Car():
'''car information summary'''
def __init__(self, make, model, year, odometer=0):
self.make = make
self.model = model
self.year = year
self.odometer = odometer
So in case you call it with three parameters, Python will see that you did not provide a value for the odometer parameter, and it will implicitly assign zero to it.
A point that sometimes causes confusion is that the default values are only evaluated once, at interpretation time, not each time you call the __init__ function.
For example if we write:
def f(x=[]):
return x
Then Python will evaluate [] only once. And each time you do not provide a value for x, you will obtain a reference to the same list (not a new list every time).
Therefore it is usally dangerous to use mutable types (an int is not a mutable type, so we are safe for the odometer=0).
If we thus for instance .append(3) to the outcome of f(), then from now on, f() will return that list ([3]). Which is usually not the intended effect. Some IDEs will give warnings if you use a mutable object.
If you want a new empty list each time x is not provided, a common pattern is:
def f(x=None):
if x is None:
x = []
return x
Related
I'm working in small Python program with several classes and subclasses. The main point is that I need to calculate the value of the main class with the attributes of the subclasses.
class Product:
class Extra_1:
value = 5
base_value = 25
final_value = base_value + Extra1.value
The expected output for base_value it would be 30.
However I need to call this class from other file through an import, when I do that I'm not able to get the expected output (50) Instead of that I get 30. It seems like Python its not calculating the value with the formula.
import myprogram
myprogram.Product.Extra.value = 25
print(myprogram.Product.final_value) #Output = 30
I tried to create a function to calculate the final_value and assigning the return as value but I still have the same problem.
class Product:
class Extra_1:
value = 5
creates a class Product with an inner class Extra_1. Technically
a class is also an object, and a code like
base_value = 25
final_value = base_value + Product.Extra_1.value
references an attribute of an object Product and an attribute of a
an object Product.Extra_1. But usually classes are templates to create new objects. A code
x = Product()
y = Product()
creates (instantiates) two new objects of class Product. To achieve
what you want, you can define a special method named __init__ (instantiation automatically invokes this method)
and redesign your program to use instantiation. See
https://docs.python.org/3/tutorial/classes.html
Python Class-Level Variables
There are some things I don't understand. First of all, are Extra, Extra1 and Extra_1 the same subclass? Assuming this and that the last two lines of the first code are inside of the Product class, then this is the expected behavior. Remember that those are class level variables, and, as is, they are evaluated just once. No matter what you do with base_value after the second line got interpreted. And this is true also if you make an instance of the Product class, the class-level variable final_value will not change unless you change it directly.
So, if you still want to use the static subclass attribute base_value and the static class attribute value to calculate your final value (I don't know why) you have to make at least final_value an instance level attribute:
class Product:
class Extra_1:
value = 5
def __init__(self):
self.final_value = Product.base_value + Product.Extra_1.value
base_value = 25
This way, every new instance of Product will calculate its final_value according with the actual value of base_value and value.
import myprogram
#First Product instance will calculate its result with the original values
p1 = myprogram.Product()
#Changing the original value
myprogram.Product.Extra_1.value = 25
#After the modification, creating a new instance
p2 = myprogram.Product()
#This will print the original 30
print(p1.final_value)
#This, in the other hand, will print 50
print(p2.final_value)
Python documentation Class and Instance Variables
I created a class, something like below -
class child:
def __init__(self,lists):
self.myList = lists
def find_mean(self):
mean=np.mean(self.myList)
return mean
and when I create an onject something like below -
obj=child()
it gives the error -
TypeError: __init__() missing 1 required positional argument: 'lists'
if I create object like below then it works well -
obj=child([44,22,55)
or If I create the class like below -
class child:
def find_mean(self,myList):
mean=np.mean(myList)
return mean
and then I create the object like below -
obj=child()
then also it works well, however I need to make it in the way I explained in the very begining. Can you please help me understand this context?
In the first example, the __init__ method expects two parameters:
self is automatically filled in by Python.
lists is a parameter which you must give it. It will try to assign this value to a new variable called self.myList, and it won't know what value it is supposed to use if you don't give it one.
In the second example, you have not written an __init__ method. This means that Python creates its own default __init__ function which will not require any parameters. However, the find_mean method now requires you to give it a parameter instead.
When you say you want to create it in the way you explained at the beginning, this is actually impossible: the class requires a value, and you are not giving it one.
Therefore, it is hard for me to tell what you really want to do. However, one option might be that you want to create the class earlier, and then add a list to it later on. In this case, the code would look like this:
import numpy as np
class Child:
def __init__(self, lists=None):
self.myList = lists
def find_mean(self):
if self.myList is None:
return np.nan
mean = np.mean(self.myList)
return mean
This code allows you to create the object earlier, and add a list to it later. If you try to call find_mean without giving it a list, it will simply return nan:
child = Child()
print(child.find_mean()) # Returns `nan`
child.myList = [1, 2, 3]
print(child.find_mean()) # Returns `2`
the code you have at the top of your question defines a class called child, which has one attribute, lists, which is assigned at the time of instance creation in the __init__ method. This means that you must supply a list when creating an instance of child.
class child:
def __init__(self, lists):
self.myList = lists
def find_mean(self):
mean=np.mean(self.myList)
return mean
# works because a list is provided
obj = child([44,22,55])
# does not work because no list is given
obj = child() # TypeError
If you create the class like in your second example, __init__ is no longer being explicitly specified, and as such, the object has no attributes that must be assigned at instance creation:
class child:
def find_mean(self, myList):
mean=np.mean(myList)
return mean
# does not work because `child()` does not take any arguments
obj = child([44,22,55]) # TypeError
# works because no list is needed
obj = child()
The only way to both have the myList attribute, and not need to specify it at creation would be to assign a default value to it:
class child:
def find_mean(self,myList=None):
mean=np.mean(myList)
return mean
# now this will work
obj = child()
# as will this
obj = child([24, 35, 27])
I apologise if the title is cryptic, I could not think of a way to describe my problem in a sentence. I am building some code in python2.7 that I describe below.
Minimal working example
My code has a Parameter class that implements attributes such as name and value, which looks something like this.
class Parameter(object):
def __init__(self, name, value=None, error=None, dist=None, prior=None):
self.name = name
self._value = value # given value for parameter, this is going to be changed very often in an MCMC sampler
self.error = error # initial estimate of error for the parameter, will only be set once
self._dist = dist # a distribution for the parameter, will only be set once
self.prior = prior
#property
def value(self):
return self._value
#property
def dist(self):
return self._dist
The class also has several properties that returns the mean, median, etc. of Parameter.dist if a distribution is given.
I have another class, e.g. ParameterSample, that creates a population of different Parameter objects. Some of these Parameter objects have their attributes (e.g. value, error) set using the Parameter.set_parameter() function, but some other Parameter objects are not explicitly set, but their value and dist attributes depend on some of the other Parameter objects that are set:
class ParameterSample(object):
def __init__(self):
varied_parameters = ('a', 'b') # parameter names whose `value` attribute is varied
derived_parameters = ('c',) # parameter names whose `value` attribute is varied, but depends on `a.value` and `b.value`
parameter_names = varied_parameters + derived_parameters
# create `Parameter` objects for each parameter name
for name in parameter_names:
setattr(self, name, Parameter(name))
def set_parameter(self, name, **kwargs):
for key, val in kwargs.items():
if key == 'value':
key = '_'.join(['', key]) # add underscore to set `Parameter._value`
setattr(getattr(self, name), key, val) # basically does e.g. `self.a.value = 1`
I can now create a ParameterSample and use them like this:
parobj = ParameterSample()
parobj.set_parameter('a', value=1, error=0.1)
parobj.set_parameter('b', value=2, error=0.5)
parobj.a.value
>>> 1
parobj.b.error
>>> 0.5
parobj.set_parameter('b', value=3)
parobj.b.value
>>> 3
parobj.b.error
>>> 0.5
What I want
What I ultimately want, is to use Parameter.c the same way. For example:
parobj.c.value
>>> 4 # returns parobj.a.value + parobj.b.value
parobj.c.dist
>>> None # returns a.dist + b.dist, but since they are not currently set it is None
c therefore needs to be a Parameter object with all the same attributes as a and b, but where its value and dist are updated according to the current attributes of a and b.
However, I should also mention that I want to be able to set the allowed prior ranges for parameter c, e.g. parobj.set_parameter('c', prior=(0,10)) before making any calls to its value -- so c needs to be an already defined Parameter object upon the creation of the ParameterSample object.
How would I implement this into my ParameterSample class?
What I've tried
I have tried looking into making my own decorators, but I am not sure if that is the way to go since I don't fully understand how I would use those.
I've also considered adding a #property to c that creates a new Parameter object every time it is called, but I feel like that is not the way to go since it may slow down the code.
I should also note that the ParameterSample class above is going to be inherited in a different class, so whatever the solution is it should be able to be used in this setting:
class Companion(ParameterSample)
def __init__(self, name):
self.name = name
super(Companion, self).__init__()
comp = Companion(name='Earth')
comp.set_parameter('a', value=1)
comp.set_parameter('b', value=3)
comp.c.value
>>> 4
I could not get this to work in Python 2 - the setattr calls never seemed to propagate the attributes to the child classes (Companion would have no c attribute).
I was more successful with Python 3 though. Since you have two parameter types (varied vs. derived), it makes sense IMO to have two classes to implement the behavior, instead of treating them all as one.
I added a DerivedParameter class, inheriting from Parameter that takes a dependents argument (along with its parent class' args/kwargs), but redefining value and dist to give dependent behavior:
class DerivedParameter(Parameter):
def __init__(self, name, dependents, **kwargs):
self._dependents = dependents
super().__init__(name, **kwargs)
#property
def value(self):
try:
return sum(x._value for x in self._dependents if x is not None)
except TypeError:
return None
#property
def dist(self):
try:
return sum(x._dist for x in self._dependents if x is not None)
except TypeError:
return None
Then I adjusted how your parameter objects are added:
class ParameterSample:
def __init__(self):
# Store as instance attributes to reference later
self.varied_params = ('a', 'b') # parameter names whose `value` attribute is varied
self.derived_params = ('c',) # parameter names whose `value` attribute is varied, but depends on `a.value` and `b.value`
# No more combined names
# create `Parameter` objects for each varied parameter name
for name in self.varied_params:
setattr(self, name, Parameter(name))
# Create `DerivedParameter` objects for each derived parameter
# Derived parameters depend on all `Parameter` objects. It wasn't
# clear if this was the desired behavior though.
params = [v for _, v in self.__dict__.items() if isinstance(v, Parameter)]
for name in self.derived_params:
setattr(self, name, DerivedParameter(name, params))
def set_parameter(self, name, **kwargs):
for key, val in kwargs.items():
if key == 'value':
key = '_'.join(['', key]) # add underscore to set `Parameter._value`
setattr(getattr(self, name), key, val) # basically does e.g. `self.a.value = 1`
From this, I could then replicate your given example desired behavior:
>>> comp = Companion(name='Earth')
>>> comp.set_parameter('a', value=1)
>>> comp.set_parameter('b', value=3)
>>> print(comp.c.value)
>>> print(comp.c.dist)
4
None
>>> comp.set_parameter('c', prior=(0,10))
>>> print(comp.c.prior)
(0, 10)
As I pointed out in the comments, the design above ends up causing all derived parameters to use all varied parameters as their dependents - effectively making c and a potential d identical. You should be able to fix this fairly easily with some parameters/conditions.
Overall, I would have to agree with #Error - Syntactical Remorse though. This is a pretty complicated way to go about designing classes and would make maintenance confusing at best. I would strongly encourage you to reconsider your design and try to find an adaptable general solution that doesn't involve dynamic creation of attributes like this.
I have created a class distance_neighbor in which one of the attributes is a list of objects of class Crime. That is the value for all attributes I get from database query result.
At first, I have set data_Crime list as the value for attribute **Crime on class distance_neighbor, and I used del to clear data_Crime list after used, so that the data_Crime list can used in the next loop.
This is my code:
conn = psycopg2.connect("dbname='Chicago_crime' user='postgres' host='localhost' password='1234'")
cur= conn.cursor()
minDistance=float(input("Nilai minimum distance : "))
cur.execute("""SELECT id_objek1, objek1, id_objek2, objek2, distance from tb_distance1 where distance<'%f'""" %(minDistance))
class Crime:
def __init__(self, id_jenis, jenis):
self.id_jenis=id_jenis
self.jenis=jenis
class distance_neighbor (Crime):
def __init__(self, distance, **Crime):
self.distance = distance
self.Crime = Crime
data_Crime =[]
data_distance = []
for id_objek1, objek1, id_objek2, objek2, distance in cur.fetchall():
data_Crime.append(Crime(id_objek1,objek1))
data_Crime.append(Crime(id_objek2,objek2))
data_distance.append(distance_neighbor(distance, data_Crime))
del data_Crime[:]
error Message:
data_distance.append(distance_neighbor(distance, data_Crime))
TypeError: __init__() takes exactly 2 arguments (3 given)
I have fixed my code using below answers guys, Thank you
This should be closer to what you want:
class Crime(object):
def __init__(self, id_jenis, jenis):
self.id_jenis=id_jenis
self.jenis=jenis
class DistanceNeighbor(object):
def __init__(self, distance, crimes):
self.distance = distance
self.crimes = crimes
data_distance = []
for id_objek1, objek1, id_objek2, objek2, distance in cur.fetchall():
crimes = [Crime(id_objek1,objek1), Crime(id_objek2,objek2)]
data_distance.append(DistanceNeighbor(distance, crimes))
Classes in Python 2 should always inherit from object. By convention, class names are in CamelCase.
The inheritance of DistanceNeighbor from Crime seems unnecessary. I changed this.
Attributes to instance should be lower case, therefore I used crimes instead of the very confusing reuse of the class name Crime.
This line:
def __init__(self, distance, **Crime):
takes your list of Crime instance apart as separate arguments.
In your case it means the __init__ receives:
distance, data_Crime[0], data_Crime[0]
this causes this error message:
TypeError: init() takes exactly 2 arguments (3 given)
The instantiation of Crime is pretty short. So, instead of the two appends you can create the list of the two Crime instances in one line:
crimes = [Crime(id_objek1,objek1), Crime(id_objek2,objek2)]
Since this creates a new list in each loop, there is no need to delete the list content in each loop, as you did with del data_Crime[:].
You've defined your __init__ method in distance_neighbor as taking arguments (self, distance, **Crime). The ** before Crime tells Python to pack up any keyword arguments you're passed into a dictionary named Crime. That's not what you're doing though. Your call is distance_neighbor(distance, data_Crime) where data_Crime is a list. You should just accept that as a normal argument in the __init__ method:
class distance_neighbor (Crime):
def __init__(self, distance, crime):
self.distance = distance
self.crime = crime
This will mostly work, but you'll still have an issue. The problem is that the loop that's creating the distance_neighbor objects is reusing the same list for all of them (and using del data_Crime[:] to clear the values in between). If you are keeping a reference to the same list in the objects, they'll all end up with references to that same list (which will be empty) at the end of the loop.
Instead, you should create a new list for each iteration of your loop:
for id_objek1, objek1, id_objek2, objek2, distance in cur.fetchall():
data_Crime = [Crime(id_objek1,objek1), Crime(id_objek2,objek2)]
data_distance.append(distance_neighbor(distance, data_Crime))
This will work, but there are still more things that you probably want to improve in your code. To start with, distance_neighbor is defined as inheriting from Crime, but that doesn't seem appropiate since it contains instance of Crime, rather than being one itself. It should probably inherit from object (or nothing if you're in Python 3 where object is the default base). You may also want to change your class and variable names to match Python convention: CamelCase for class names and lower_case_with_underscores for functions, variables and attributes.
def __init__(self, distance, **Crime):
**Crime is a keyword argument, and expects named arguments. You don't need that, remove the asterisks.
Also, rename the argument, it's very confusing that it has the same name as the class:
class distance_neighbor(Crime):
def __init__(self, distance, c):
self.distance = distance
self.Crime = c
I defined a class Factor in the file factor.py:
class Factor:
def __init__(self, var, value):
self.var = var # hold variable names
self.value = value # hold probability values
For convenience and code cleanliness, I want to define a constant variable and be able to access it as Factor.empty
empty = Factor([], None)
What is the common way to do this? Should I put in the class definition, or outside? I'm thinking of putting it outside the class definition, but then I wouln't be able to refer to it as Factor.empty then.
If you want it outside the class definition, just do this:
class Factor:
...
Factor.empty = Factor([], None)
But bear in mind, this isn't a "constant". You could easily do something to change the value of empty or its attributes. For example:
Factor.empty = something_else
Or:
Factor.empty.var.append("a value")
So if you pass Factor.empty to any code that manipulates it, you might find it less empty than you wanted.
One solution to that problem is to re-create a new empty Factor each time someone accesses Factor.empty:
class FactorType(type):
#property
def empty(cls):
return Factor([], None)
class Factor(object):
__metaclass__ = FactorType
...
This adds an empty property to the Factor class. You are safe to do what you want with it, as every time you access empty, a new empty Factor is created.