Python: communication between classes and design patterns - python-3.x

I have 4 classes that handle logic for a large project. I have Product that clients buy and i have to bill them. Nevertheless the price of those products is varying greatly according to many variables. I have create a class PriceGenerator that handles the pricing of the products, an Inventory class that checks if the 'Product' is still available and a 'Cart' class that contains a list of 'Product' for a total bill if the client buys many 'Product'
Class PriceGenerator:
def get_price(*args)
Class Product:
def prod_bill()
Class Inventory:
def get_inventory(*args)
Class Cart:
self.list_product = [product1, product2, product3,...]
def cart_bill(*args)
my first option:
pg = PriceGenerator()
pd = Product()
inv = Inventory()
cart = Cart()
I could pass the PriceGenerator and Inventory as argument of Cart
def cart_bill(pg, inv, amount):
bill = 0
for prod in self.list_product:
px = prod.prod_bill(pg)
bill_p = px * min(amount, inv.get_inventory(product_args))
bill += bill_p
Obviously as the number of methods grows in Product, it becomes very complicated to keep track of all the arguments you have to pass. you pass PriceGenerator and Inventory to Cart that are then passed to the product prod.prod_bill(pg), all those nested dependencies are very cumbersome to pass through all the objects and it makes the code much more complicated.
I could call pg and inv without passing it as arguments for example in Product as a global variable
def produce_bill(self):
price = pg.get_price(product_args)
inventory = inv.get_inventory(product_args)
but i really don't like not knowing what are the class/arguments required, necessary for the method.
As the project grows, what design pattern would you suggest?

I would recommend implementing a context object containing anything that is relevant to your process. I am assuming this is about placing an e-commerce order in the following example:
class OrderContext:
price_calculator: PriceCalculator
inventory: Inventory
cart: Cart
Now you can pass this object around for all operations that are involved in your process:
cart.add_product(context, product, amount)
This allows you to add/remove further bits to the context without modifying the signature of all your functions. The drawback is that this has the potential to massively increase the number of dependencies within your application (depending on how disciplined the programmers in your team are).

Related

Django: Return all users based on number of occurrences in another table

Given the two models
class User(AbstractBaseUser, PermissionsMixin):
Name = models.CharField(max_length=65)
trial_days = models.IntegerField(default=21)
...
class BlogPost(models.Model):
user = models.ForeignKey('User', on_delete=models.CASCADE)
text = models.TextField(max_length=65)
Date = models.DateField()
How can I return all users that have at-least three blog posts?
I am currently iterating all the users and checking if they have three posts however I can't help to think there may be a more pythonic way to do this using Django..
You can use annotate to annotate each User with the number of related `BlogPosts. You can then use this annotation in a filter to get all instances where this value is greater or equal to three
from django.db.models import Count
User.objects.annotate(num_posts=Count('blogpost')).filter(num_posts__gte=3)

Merge two redundant models in Django

I'm working on an online shop at the moment. The shop is written in Django, and was programmed by another person, so I spent days trying to make sense of what he did. At the moment the shop sells only two articles, on two different pages (meaning they have to be bought separately), so the shop is heavily oriented towards this situation. The problem is, the shop's owner expressed his interest in selling everything in one page in the near future, and in adding more products. And that's where my problems start.
The model structure was something like this (extremely simplified):
class Category1(BaseModel):
name = models.CharField(max_length=32)
class Category2(BaseModel):
name = models.CharField(max_length=64)
class Price1(BaseModel):
category = models.ForeignKey(Category1, on_delete=models.CASCADE)
price = models.DecimalField(max_digits=16, decimal_places=2)
currency = models.CharField(max_length=3)
class Price2(BaseModel):
category = models.ForeignKey(Category2, on_delete=models.CASCADE)
price = models.DecimalField(max_digits=16, decimal_places=2)
currency = models.CharField(max_length=3)
class Order1(BaseModel):
[personal information fields]
class Order2(BaseModel):
[personal information fields]
class Article1(BaseModel):
price = models.ForeignKey(Price1, on_delete=models.CASCADE)
order = models.ForeignKey(Order1, on_delete=models.CASCADE, related_name='articles')
class Article2(BaseModel):
price = models.ForeignKey(Price2, on_delete=models.CASCADE)
order = models.ForeignKey(Order2, on_delete=models.CASCADE, related_name='articles')
There is much more than this, of course, but this should be enough to show the relationships between the models. The complete structure of course makes more sense than this one. BaseModel is a class that contains an ID, creation time and last edit.
I managed to put all the common elements into abstract classes BaseCategory, BasePrice, BaseOrder and BaseArticle, but this is not enough if I want to really expand the shop. Finishing this work is just a matter of time and patience, but how should I proceed once I'm in this situation?
class BaseCategory(BaseModel):
name = models.CharField(max_length=64)
class Meta:
abstract = True
class Category1(BaseCategory):
pass
class Category2(BaseCategory):
pass
class BasePrice(BaseModel):
price = models.DecimalField(max_digits=16, decimal_places=2)
currency = models.CharField(max_length=3)
class Meta:
abstract = True
class Price1(BasePrice):
category = models.ForeignKey(Category1, on_delete=models.CASCADE)
class Price2(BasePrice):
category = models.ForeignKey(Category2, on_delete=models.CASCADE)
class BaseOrder(BaseModel):
[personal information fields]
class Meta:
abstract = True
class Order1(BaseOrder):
pass
class Order2(BaseOrder):
pass
class BaseArticle(BaseModel):
class Meta:
abstract = True
class Article1(BaseArticle):
price = models.ForeignKey(Price1, on_delete=models.CASCADE)
order = models.ForeignKey(Order1, on_delete=models.CASCADE, related_name='articles')
class Article2(BaseArticle):
price = models.ForeignKey(Price2, on_delete=models.CASCADE)
order = models.ForeignKey(Order2, on_delete=models.CASCADE, related_name='articles')
I need to get rid of the specific classes completely, otherwise when I will add new articles, I will have to create new classes, and this is not a scalable solution.
My problems are the following:
How do I get rid of the empty specific classes like Price1 or Order1 without losing any information? I know I will have to get rid of the abstract variable, but I don't know what to do next.
How do I manage the foreign keys in the remaining classes? I'm experimenting a bit with GenericForeignKey at the moment, and this would probably let me move the declarations into the base classes, but I'm not sure if changing a definition will reset all fields.
Just to be clear, the shop is already up and running. We can't stop it, and we can't lose data. We sell services, so the customers have to be able to access their products even long after the purchase.
Thanks in advance for your interest and your time.
To keep this answer short we will only discuss one model here. In this instance Category. Firstly add a new model Category (keep your other models for now):
class Category(BaseModel):
name = models.CharField(max_length=64)
Next run makemigrations this would generate a migration file to make this new table in the database. After this you need to make a Data Migration [Django docs] to copy the data from the other two tables that you have.
To do this first run:
python manage.py makemigrations --empty yourappname
This will generate a migration file that does nothing for now. We will edit this migration file and add some code to copy the data from your other tables to this new table. In the end your migration file would look something like:
from django.db import migrations
def populate_category(apps, schema_editor):
Category1 = apps.get_model('yourappname', 'Category')
Category2 = apps.get_model('yourappname', 'Category')
Category = apps.get_model('yourappname', 'Category')
# add all fields except the pk in values(), i.e. values('field1', 'field2')
for category in Category1.objects.values('name'):
Category.objects.create(**category) # Add some field indicating this object is of Category1 if needed
for category in Category2.objects.values('name'):
Category.objects.create(**category) # Add some field indicating this object is of Category2 if needed
class Migration(migrations.Migration):
dependencies = [
('yourappname', '0001_initial'),
]
operations = [
migrations.RunPython(populate_category, reverse_code=migrations.RunPython.noop),
]
Now you can simply run python manage.py migrate and you would have a new table Category which has all the data from Category1 and Category2. (This might take some time if there are many rows). After this you can remove the models Category1 and Category2 and migrate again to remove those tables.
Note: Perform these operations carefully, and make sure you have got the data properly in the new table before deleting the old ones.
Refer the documentation linked above for more information on
migrations. (Test this on a local development server before doing it
on production to be safe)

Can anyone explain me about __init_ in Python?

List item
class Car:
def __init__(self, color, brand, number_of_seats):
self.color = color
self.brand = brand
self.number_of_seats = number_of_seats
self.number_of_wheels = 4
self.registration_number = GenerateRegistrationNumber()
Hi all,
1)Referring to the above example, could anyone tell me what is the difference between specific attributed and "the other" attributes? What will happen if registration_number is treated as a specific attribute?
2)
class MyInteger:
def __init__(self, newvalue):
# imagine self as an index card.
# under the heading of "value", we will write
# the contents of the variable newvalue.
self.value = newvalue
If we consider this example, shouldn't it be self.newvalue = newvalue?
I think I know what you're asking (let me know if I'm wrong), but I think you're asking what the difference is between the attributes that are assigned by the parameters of __init__ (Instance Attributes), ones that are assigned inside the __init__ method but not with parameters (also Instance Attributes), and ones that are not assigned in the initialiser at all (Class Attributes). The difference here is that all (well, pretty much all) cars have 4 wheels, and the number plate is generated, not supplied. You could also do this, for example:
class Car:
number_of_wheels = 4
def __init__(self, color, brand, number_of_seats):
self.color = color
self.brand = brand
self.number_of_seats = number_of_seats
self.registration_number = GenerateRegistrationNumber()
As the number of wheels here is always assigned to the same value, across all instances, it is said to be a "Class Attribute" in this case. All other attributes here are “Instance Attributes” as they are specifically assigned to each instance. For a slightly better explanation, I recommend reading this:
https://www.geeksforgeeks.org/class-instance-attributes-python/
It doesn't actually matter what the instance attribute (self.value here) is called, you could call it whatever you want and it'd still work, but in most cases, you would indeed want to name the attribute the same as the parameter.
init function also called as magic function is a constructor function for a class. if you remember in java whenever we make a class the constructor method should have the same name as the classname but this is not the scenario in python . In python we make use of the init method
the difference between the class attributes and instance attributes is that the class attributes are common to every object that is created but the instance attributes are only accessible by the object that is created.
consider a example where data of students in a class is stored. In such case the class division will be same for all the students of that particular class so it can be common but names of all students are different and also their marks and so on and hence they should be different for everyone
in previous scenario the class division can be class attribute and the data of student like name , marks has to be instance attributes
examples of class attribute is as shown
class A:
Division = 5A
here the division is a class attribute
class B:
def __init__(self,name,marks):
self.name = name
self.marks = marks
here the name and marks are instance variables
now here we can also write self.username = name because we are storing the value of name variable in self.username so you can write any name there is no constraint on that
Also whenever you write __ in front of method or variable it means that the attribute is private and accessible by only class.

Most efficient way of adding objects together

I was wondering if there is a quick way of adding attributes in objects together. If I had a 'Person' class like this and I was trying to add the 'age' attribute together from multiple 'Person' objects, then I could override the add function:
class Person:
def __init__(self, name, age):
self.name = name
self.age = age
def __add__(self, other):
return self.age + other.age
This works well for two objects, but if I had a list of 'Person' objects and I was trying to sum all the age attributes using the reduce() fuction for example, then this approach wouldn't work (to my knowledge), as the overriden add method would be trying to sum different types together as we are returning a float from the add function, not a 'Person' object.
I could get the age attribute from each 'Person' object and add it together but I was wondering if there was a more efficient way of handling this.
It does not logically make sense to add two people when what you want to add is their ages. Your problem is because if p1 + p2 is a number, then (p1 + p2) + p3 is a number plus a person, and you only defined how to add a person to a person, not add a person to a number.
But the root cause is that this is not a sensible case for overloading __add__. That only makes sense when the result would be another instance of the same class, not a number. Instead, just get the ages and add those. Note that this is not less efficient than operator overloading; it doesn't involve any attribute lookups that wouldn't have to be done either way.
p1.age + p2.age
# or on a list:
sum(p.age for p in people)
"Explicit is better than implicit."

locks needed for multithreaded python scraping?

I have a list of zipcodes that I want to pull business listings for using the yelp fusion api. Each zipcode will have to make at least one api call ( often much more) and so, I want to be able to keep track of my api usage as the daily limit is 25000. I have defined each zipcode as an instance of user defined Locale class. This locale class has a class variable Locale.pulls, which acts as a global counter for the number of pulls.
I want to multithread this using the multiprocessing module but I am not sure if I need to use locks and if so, how would I do so? The concern is race conditions as I need to be sure each thread sees the current number of pulls defined as the Zip.pulls class variable in the pseudo code below.
import multiprocessing.dummy as mt
class Locale():
pulls = 0
MAX_PULLS = 20000
def __init__(self,x,y):
#initialize the instance with arguments needed to complete the API call
def pull(self):
if Locale.pulls > MAX_PULLS:
return none
else:
# make the request, store the returned data and increment the counter
self.data = self.call_yelp()
Locale.pulls += 1
def main():
#zipcodes below is a list of arguments needed to initialize each zipcode as a Locale class object
pool = mt.Pool(len(zipcodes)/100) # let each thread work on 100 zipcodes
data = pool.map(Locale, zipcodes)
A simple solution would be to check that len(zipcodes) < MAP_PULLS before running the map().

Resources