How '#decorator" actually works in python? - python

How '#decorator' actually works?
I just knew it works like this:
def deco(func):...
#deco
def func():...
# same
def func():...
func = deco(func)
But it doesn't work well in '#class.method'
Below is an example:
class Person:
def __init__(self, name):
self._name = name
#property
def name(self):
return self._name
#name.setter
def name(self.value):
self._name = value
class Person:
def __init__(self, name):
self._name = name
## #propery
def name(self):
return self._name
name = property(name)
## #name.setter
def name(self, value):
self._name = value
name = name.setter(name) <----- error
The error here is because name has been changed to point to "def name(self.value):..."
I'd like to know how '#property.method' actually works.
I just guessed like that:
class Person:
def __init__(self, name):
self._name = name
## #propery
def name(self):
return self._name
name = property(name)
## #name.setter
x = name.setter
def name(self, value):
self._name = value
name = x.setter(name)
del x
Is it right or is there other way to work that I don't know?
Thank you for reading!

Related

Achieving single-responsibility principle with python abstract classes

I want to separate the DB models from the actual classes. But i need two static functions for fetching data from the DB regardless of the subclass type. the implementation for both functions are the same across all DB models.
pyright showing an error that cls inside get() and get_all() functions doesn't have a db property.
from abc import ABC, abstractstaticmethod
class DogsDB:
lists = ["DOG1", "DOG2", "DOG3"]
#classmethod
def get(cls, id):
return cls.lists[id]
class CatsDB:
lists = ["CAT1", "CAT2", "CAT3"]
#classmethod
def get(cls, id):
return cls.lists[id]
class Animal(ABC):
def __init__(self, name):
self.name = name
#abstractstaticmethod
def save(m):
pass
#abstractstaticmethod
def _from_model(obj):
pass
#classmethod
def get(cls, id):
obj = cls.db.get(id)
return cls._from_model(obj)
#classmethod
def get_all(cls):
objs = cls.db.lists
lists = []
for obj in objs:
e = cls._from_model(obj)
lists.append(e)
return lists
def __repr__(self):
return self.name
class DogSound:
def __init__(self, name):
self.name = name
def sound(self):
print(self.name, ": DOG SOUND!!")
class Dog(Animal, DogSound):
db = DogsDB
def __init__(self, name, age):
super(Dog, self).__init__(name)
self.age = age
#staticmethod
def save(m):
print(m)
#staticmethod
def _from_model(obj):
return Dog(obj, 4)
class Cat(Animal):
db = CatsDB
def __init__(self, name, age):
super().__init__(name)
self.age = age
#staticmethod
def save(m):
print(m)
#staticmethod
def _from_model(obj):
return Cat(obj, 4)
print(Cat.get(1))
print(Dog.get(1))
print(Cat.get_all())
print(Dog.get_all())
Dog.get(1).sound()
I cannot duplicate your first error.
Your second issue is a result of method sound implicitly returning None since it has no return statement and you have print(Dog.get(1).sound()), which will print out the return value from that method. You either want to change this to just Dog.get(1).sound() or modify the sound method to return what it is currently being printed and remove the print statement (my choice).
As an aside, I found this class structure a bit difficult to follow. Why do you need a separate DogSound class with a name attribute which should belong to Animal? Also, it seems to me that age could/should be an attribute of Animal since both cats and dogs have an age.
from abc import ABC, abstractstaticmethod
class DogsDB:
lists = ["DOG1", "DOG2", "DOG3"]
#classmethod
def get(cls, id):
return cls.lists[id]
class CatsDB:
lists = ["CAT1", "CAT2", "CAT3"]
#classmethod
def get(cls, id):
return cls.lists[id]
class Animal(ABC):
def __init__(self, name, age):
self.name = name
self.age = age
#abstractstaticmethod
def save(m):
pass
#abstractstaticmethod
def _from_model(obj):
pass
#classmethod
def get(cls, id):
obj = cls.db.get(id)
return cls._from_model(obj)
#classmethod
def get_all(cls):
objs = cls.db.lists
lists = []
for obj in objs:
e = cls._from_model(obj)
lists.append(e)
return lists
def __repr__(self):
return self.name
class Dog(Animal):
db = DogsDB
def __init__(self, name, age):
super().__init__(name, age)
def sound(self):
return f"{self.name}: DOG SOUND!!"
#staticmethod
def save(m):
print(m)
#staticmethod
def _from_model(obj):
return Dog(obj, 4)
class Cat(Animal):
db = CatsDB
def __init__(self, name, age):
super().__init__(name, age)
self.age = age
#staticmethod
def save(m):
print(m)
#staticmethod
def _from_model(obj):
return Cat(obj, 4)
print(Cat.get(1))
print(Dog.get(1))
print(Cat.get_all())
print(Dog.get_all())
print(Dog.get(1).sound())
Prints:
CAT2
DOG2
[CAT1, CAT2, CAT3]
[DOG1, DOG2, DOG3]
DOG2: DOG SOUND!!
If for some reason you want DogSound to be a separate class, then there is no need for the name attribute to be duplicated:
...
class DogSound: # A "Mixin" class
def sound(self):
return f"{self.name}: DOG SOUND!!"
class Dog(Animal, DogSound):
db = DogsDB
def __init__(self, name, age):
super().__init__(name, age)
#staticmethod
def save(m):
print(m)
#staticmethod
def _from_model(obj):
return Dog(obj, 4)
...

Why do I keep getting this Attribute Error?

Every time I run my code it pops up the message saying "'ICU' object has no attribute '_name'. Did you mean: 'name'?" I can not figure out how to fix it. I've tried changing the name of the accessors and mutators but still can't figure out how to solve it. Any suggestions?
Here's my code:
class Patient:
def __init__(self, name, age):
self.name = name
self.age = age
self.weight = 150
#property
def age(self):
return self._age
#age.setter
def age(self, newValue):
if newValue > 0:
self._age = newValue
else:
self._age = 0
#property
def weight(self):
return self._weight
#weight.setter
def weight(self, newValue):
if newValue >=0 and newValue <= 1400:
self._weight = newValue
#IncreaseAge
def increaseAge(self):
self.age = self.age + 1
class In(Patient):
def __init__(self, name, age, stay):
self.name = name
self.age = age
self.stay = stay
#property
def stay(self):
return self._stay
#stay.setter
def stay(self, value):
self._name = value
def __str__(self):
print("IN-" + self._name + self._age + self.weight + self._stay)
class Out(Patient):
def __init__(self, name, age):
self.name = name
self.age = age
def __str__(self):
print("OUT-" + self._name + self._age + self._weight)
class ICU(In):
def __init__(self, name, age):
self.name = name
self.age = age
self.days = 5
class CheckUp(Out):
def __init__(self, name, age):
self.name = name
self.age = age
Here's the rest of the instance:
# Create three patient objects and print them out
p1 = ICU("Ben Dover", 0)
p2 = ICU("Helen Hywater", -15)
p3 = CheckUp("Amanda Lynn", 45)
p4 = ICU("Chester Minit", 12)
p5 = In("Don Keigh", 89, 10)
p6 = Out("Kay Oss ", 45)
print ("\tStatus\tName\t\tAge\tWeight\tStay")
print ("-" * 55)
print ("p1:\t{}".format(p1))
print ("p2:\t{}".format(p2))
print ("p3:\t{}".format(p3))
print ("p4:\t{}".format(p4))
print ("p5:\t{}".format(p5))
print ("p6:\t{}".format(p6))
print ("-" * 55)
# Change their ages and print them out
p1.age = -5
p2.age = 100
for i in range(6):
p3.increaseAge()
p4.age = 0
p5.increaseAge()
p6.age = 42
print ("p1:\t{}".format(p1))
print ("p2:\t{}".format(p2))
print ("p3:\t{}".format(p3))
print ("p4:\t{}".format(p4))
print ("p5:\t{}".format(p5))
print ("p6:\t{}".format(p6))
print ("-" * 55)
# Change other instance variables and print them out
p1.weight = 2000
p1.stay = 3
p2.name = "Justin Thyme"
p2.weight = 220
p2.stay = 0
p3.weight = -50
p4.weight = 1400
p5.weight = 0
p5.stay = 21
p6.weight = 1401
print ("p1:\t{}".format(p1))
print ("p2:\t{}".format(p2))
print ("p3:\t{}".format(p3))
print ("p4:\t{}".format(p4))
print ("p5:\t{}".format(p5))
print ("p6:\t{}".format(p6))
print ("-" * 55)
It's because your variable name is different.
Replace you code from:
#stay.setter
def stay(self, value):
self._name = value
To:
#stay.setter
def stay(self, value):
self.name = value
In Python, constructors - like all other methods - can be overridden. That is once you define __init__ in child classes, the base class method is never called. This is what's causing the error.
You need to explicitly call the base class like like this:
class ICU(In):
def __init__(self, name, age):
self.name = name
self.age = age
self.days = 5
In.__init__(self, name, age, 10) # stay = 10 since it's not an input parameter in the ICU __init__ method.
This needs to be done in every base class. So you'd do something similar in the In class as well.
The problem comme to the fact that "format" is calling "__ str__" on your instances but when "__ str__" get called, some of your instance doesn't have a value for "_name" or "_stay" or "_weight"...see your " __ init __ " method for each instance and execute " __ str __" after you will see the problem. so to handle this case you have the following simple solution
class In(Patient):
def __init__(self, name, age, stay):
self.name = name
self.age = age
self.stay = stay
#property
def stay(self):
return self._stay
#stay.setter
def stay(self, value):
self._name = value
def __str__(self):
x = (
getattr(self, '_name', ''),
getattr(self, '_age', ''),
self.weight or ''
getattr(self, '_stay', ''),
)
return ("IN-%s %s %s %s")%(*x)
class Out(Patient):
def __init__(self, name, age):
self.name = name
self.age = age
def __str__(self):
x = (
getattr(self, '_name', ''),
getattr(self, '_age', ''),
getattr(self, '_stay', ''),
)
return "OUT- %s %s %s"%(*x)
But your classes are not well designed, see below something interesting
class Patient:
def __init__(self, name, age, weight=150):
self._name= name
self._age = age
self._weight = weight
#property
def name(self):
return self._name
#name.setter
def name(self, value):
assert isinstance(value, str)
self._name = value
#property
def age(self):
return self._age
#age.setter
def age(self, value):
assert isinstance(value, int)
self._age = value
#property
def weight(self):
return self._weight
#weight.setter
def weight(self, value):
assert isinstance(value, int)
self._weight = value
def __str__(self):
return f"{self.__class__.__name__.upper()}-{self.name} {self.age} {self.weight}"
class Out(Patient):
pass
class In(Patient):
def __init__(self, name, age, stay, weight=150):
super().__init__(name, age, weight=weight)
self._stay = stay
#property
def stay(self):
return self._stay
#stay.setter
def stay(self, value):
assert isinstance(value, int)
self._stay = value
def __str__(self):
return f"{super().__str__()} {self.stay}"
class ICU(In):
def __init__(self, name, age):
super().__init__(name, age, 5)
class CheckUp(Out):
def __init__(self, name, age):
super().__init__(name, age)
Also note that "increaseAge" method is not defined on your instances

How Can a function act like a descriptor?

def typed_property(name, expected_type):
storage_name = '_' + name
#property
def prop(self):
return getattr(self, storage_name)
#prop.setter
def prop(self, value):
if not isinstance(value, expected_type):
raise TypeError('{} must be a {}'.format(name, expected_type))
setattr(self, storage_name, value)
return prop
class Person:
name = typed_property('name', str)
age = typed_property('age', int)
def __init__(self, name, age):
self.name = name
self.age = age
Function typed_property() acts like a descriptor. Why prop() is called when executing this code line (name = typed_property('name', str))?
I don't know what you mean by "descriptor". typed_property allows a property to call a function for additional processing. prop() is not called when executing the line you mentioned. It is called when executing self.name = name. The #prop.setter makes it so the object can respond to property calls like that.
When you call typed_property to set the value of the class properties name and age, you are really defining those to be methods to use to access the instance values self.name and self.age. This is the same as below omitting age for simplicity:
class Person:
def __init__(self, name):
self.name = name
#property
def name(self):
print("=== ACESSING")
return self.name
#name.setter
def name(self, name):
print("=== MUTATING")
self.name = name
This marks the name(self) method as the accessor for self.name, and name(self, val) as the mutator. The mutator is called whenever you try to change (mutate) the value of its assigned property, in this case self.name. This includes when you are calling it in the __init__ method. However, using the class as defined above will result in an infinite recursion because I am calling the mutator from inside the mutator. So "=== MUTATING" will be printed ending in a recursion error. So a small adjustment is needed:
class Person:
def __init__(self, name):
self._name = name
#property
def name(self):
print("=== ACCESSING")
return self._name
#name.setter
def name(self, val):
print("=== MUTATING")
self._name = val
Now that underlying property is name _name rather than name the mutator will set the value of _name rather than setting it for name and recur into itself infinitely. For example, using the class as defined above:
>>> p = Person("joshmeranda")
>>> p.name
=== ACCESSING
"joshmeranda"

Python, delete a list item from class method

I have my class.
I want to create a method inside to delete a list item by code attribute.
class MyClass(Base):
def __init__(self, code, name):
self.__code = code
self.__name = name
#property
def code(self):
return self.__code
#property
def name(self):
return self.__name
#code.setter
def code(self, new_code):
self.__code=new_code
def __repr__(self):
x = f"Code: {self.__code} and Name:{self.__name}"
return(x)
def __deleteitem__(self, code):
print("The code: {self.__code} was deleted")
list=[]
list.append(MyClass(1234,"Aijio"))
list.append(MyClass(123,"Anodnd"))
list.append(MyClass(1236,"Jfifi"))
list.append(MyClass(1238,"Roberto"))
print(list)
lista.deleteitem(123)
How I can create a method who deletes the code that I send?
Regards
You can try this below :
class MyClass(Base):
def __init__(self, code, name):
self.__code = code
self.__name = name
#property
def code(self):
return self.__code
#property
def name(self):
return self.__name
#code.setter
def code(self, new_code):
self.__code=new_code
def __repr__(self):
x = f"Code: {self.__code} and Name:{self.__name}"
return(x)
def __deleteitem__(self, code):
# Logic for deletion
for obj in list:
if obj.code == code:
list.remove(obj)
print("The code: "+code+" was deleted")
list=[]
list.append(MyClass(1234,"Aijio"))
list.append(MyClass(123,"Anodnd"))
list.append(MyClass(1236,"Jfifi"))
list.append(MyClass(1238,"Roberto"))
myclass = MyClass(None, None)
myclass.__deleteitem__(123)

Property setter method not working upon class creation

I'm trying to set an attribute name on my class Attachment. The name attribute needs to be set based on whether it's a zip file or not. If it is a zip file I need to return the unzipped filename rather than the zip filename. Here is the class:
from os.path import splitext
class Attachment(object):
def __init__(self, name):
self.__name = name
if self.__name.endswith(".zip"):
self.zip_contents = {"content":"test", "name":"testing.txt"}
#property
def extension(self):
_, ext = splitext(self.__name)
return ext.lower()
#property
def name(self):
print('Called getter')
return self.__name
#name.setter
def name(self, name):
print('Calling setter')
if name.endswith(".zip"):
self.__name = self.zip_contents["name"]
else:
self.__name = name
#name.deleter
def name(self):
del self.__name
test = Attachment("testing.zip")
print test.name
I am receiving the following when I try printing test.name
Called getter
testing.zip
Can someone explain what I am doing wrong here and why the setter is not being called? Thanks!

Categories