The other day I learned about 100 Days Of Code, which was popular on Twitter for a while. The purpose of this article is to keep a record and output how much I, as a beginner, can grow through 100 days of study. I think there are many mistakes and difficult to read. I would appreciate it if you could point out!
--Chapter 8 structure --Page 216 of this chapter
--Progress: Pages 91-95 --Chapter 4: Metaclasses and Attributes ――I will write down what I often forget or didn't know about what I learned today.
@propertyinstead of refactoring attributes
@property, you can change what was a simple numeric attribute so that you can perform the necessary calculations on the spot without changing the caller at all.
Now, to compare the normal implementation with using
@property, let's look at an example of a water allocation class from a leak bucket.
from datetime import timedelta, datetime class Bucket(object): def __init__(self, period): ''' Parameters ---------- period: int Set the time until water leaks from the bucket ''' self.period_delta = timedelta(seconds=period) #Set the time until water leaks from the bucket self.reset_time = datetime.now() #Reset the current time_Assign to time self.quota = 0 def __repr__(self): # __repr__Is a special method that returns the output result as a string return 'Bucket(quota=%d)' % self.quota def fill(bucket, amount): ''' Put a certain amount of water in the bucket Treat the if block if the ghost water is leaking Parameters ---------- bucket: Bucket amount: int ''' now = datetime.now() if now - bucket.reset_time > bucket.period_delta: #The time from initialization to calling fill is the period_Process this block if it is greater than the time set in delta bucket.quota = 0 bucket.reset_time = now #Reset the current time_Assign to time bucket.quota += amount #Add amount to quota def deduct(bucket, amount): ''' If it is not spilled from the bucket and the amount of water is more than the amount, spit out the amount of water. Parameters ---------- bucket: Bucket amount: int Returns ------- True or False ''' now = datetime.now() if now - bucket.reset_time > bucket.period_delta: return False if bucket.quota - amount < 0: return False bucket.quota -= amount return True bucket = Bucket(60) #60 seconds to leak from the bucket fill(bucket, 100) #Add 100 water to the bucket print(bucket) # Bucket(quota=100)
Use deduct to spit out the required water.
if deduct(bucket, 99): print('Had 99 quota') else: print('Not enough for 99 quota') print(bucket)
Had 99 quota Bucket(quota=1) #Amount of water in the remaining bucket
If you try to spit out 3 more water here
if deduct(bucket, 3): print('Had 3 quota') else: print('Not enough for 3 quota') print(bucket)
Not enough for 3 quota Bucket(quota=1)
The problem with this implementation is that if you try to drain more water than the bucket, you will get stuck and you will not see the bucket situation at all. So improve the class to record the quota and the amount of water consumed in variables.
class Bucket(object): def __init__(self, period): self.period_delta = timedelta(seconds=period) self.reset_time = datetime.now() self.max_quota = 0 #Record maximum amount self.quota_consumed = 0 #Record the amount of water discharged def __repr__(self): return ('Bucket(max_quota=%d, quota_consumed=%d)' % (self.max_quota, self.quota_consumed)) # @Calculate the new attributes you added using the property method @property def quota(self): return self.max_quota - self.quota_consumed #Write the process so that it matches the interface of the class used in fill and deduct @quota.setter def quota(self, amount): delta = self.max_quota - amount if amount == 0: #Reset quota due to new period self.quota_consumed = 0 self.max_quota = 0 elif delta < 0: #Add an appropriate amount for a new period assert self.quota_consumed == 0 self.max_quota = amount else: #Consumed in appropriate amount within the period assert self.max_quota >= self.quota_consumed self.quota_consumed += delta
Run the same code as before.
bucket = Bucket(60) fill(bucket, 100) print(bucket) if deduct(bucket, 99): print('Had 99 quota') else: print('Not enough for 99 quota') print(bucket) if deduct(bucket, 3): print('Had 3 quota') else: print('Not enough for 3 quota') print(bucket)
Bucket(quota=100) Had 99 quota Bucket(quota=1) Not enough for 3 quota Bucket(quota=1) Bucket(max_quota=100, quota_consumed=0) Had 99 quota Bucket(max_quota=100, quota_consumed=99) Not enough for 3 quota Bucket(max_quota=100, quota_consumed=99)
@property, you can see the internal situation.
If you're messing with a class, why not just add methods that correspond to fill and deduct normally? I thought, but in reality, it seems that in many cases,
@property is used to gradually improve the model.
If you overuse
@property and your code becomes hard to read, it seems like it's time to consider refactoring the class and all callers.
I understand that it is more efficient to refactor all at once while improving sequentially with
@property rather than improving the class.