I am trying to figure out if there's a way to (unit test) verify that the property and the setter is actually called to set the name attribute.
class DummyName:
def __init__(self):
self.name = ''
#property
def name(self):
return self.name
#name.setter
def name(self, name):
if not isinstance(name, basestring):
raise Exception('Name must be a string.')
self.name = name
Trying to do something like this...
#mock.patch.object(DummyName, 'name', new_callable=PropertyMock)
def testNameProperty(self, mock_name):
MockName = Mock()
mock_name.return_value = MockName
dummyName = DummyName()
dummyName.name = 'test_name'
# assert setter is called to set the name
# assert name is called to get the name
# assert name is 'test_name'
Seems like name() and setter are never accessed. the Anyone has a better idea? Thanks!
By using mocks like that you've overwritten the code you're trying to test. Mocks are for calls that are external to the code under test.
An appropriate test for this code is to assert that the exception is raised if you pass something that isn't a string.
def testNameProperty(self):
dummyName = DummyName()
with self.assertRaises(Exception):
dummyName.name = 12345
Your class needs to inherit from object.
class DummyName(object):
def __init__(self):
self._name = ''
#property
def name(self):
return self._name
#name.setter
def name(self, name):
if not isinstance(name, basestring):
raise Exception('Name must be a string.')
self._name = name
You also need to use different variables for the name inside the class, or you'll hit maximum recursion.
Related
How can I set a property attribution from child class to a property attribution of parent class? for attribution, I know I can do something like
setattr(self.name, 'nickname', object). However, if I have one class like Animal that is inherited by Bird and include one property called name. Is it possible for me to create another property
under name for class Bird?
class Animal:
def __init__(self):
self._name = None
#property
def name(self):
return self._name
#name.setter
def name(self, value):
self._name = value
class Bird(Animal):
def __init__(self):
super().__init__()
# I need to create the other property under name attribution from Animal class as nickname
#so I can access as cat.name.nickname = 'i am nickname'
#print(cat.name.nickname) # 'i am nickname
##property
#def nickname(self):
# return self._name
#
##name.setter
#def name(self, value):
# self._name = value
cat = Animal()
cat.name = 'i am cat'
print(cat.name) # i am cat
Properties getters and setters can call the property methods on the superclass, with super -
This mean you can recreate the name property in the subclass, retrieve the super-class value, for compatibility, and wrap it on another class, which has the attributes you want.
However, the key _name would be taken in the instance dictionary to keep the value Animal.name property knows about - so we need another shadow name in the instance to keep the values for exclusive of the subclass.
That said, it is still needed to build a clever class that can wrap the original value of the property on the superclass, and know how to handle attribute setting and retrieval on the subclass - the Wrapper code bellow can do that:
class Wrapper(str):
def __new__(cls, original_str, *args):
return super().__new__(cls, original_str)
def __init__(self, original_str, name_in_parent, parent):
self._name = name_in_parent
self._parent = parent
# original_str is taken care of in `__new__`
def __setattr__(self, attrname, value):
if attrname.startswith("_"):
return super().__setattr__(attrname, value)
ns = getattr(self._parent, self._name, None)
if ns is None:
ns = {}
setattr(self._parent, self._name, ns)
ns[attrname] = value
def __getattr__(self, attrname):
return getattr(self._parent, self._name)[attrname]
And this will work with a simple property on the superclass like:
class Animal:
#property
def name(self):
return self._name
#name.setter
def name(self, value):
# just so that the property is not 100% meaningless
self._name = value.lower()
class Bird(Animal):
#property
def name(self):
return Wrapper(super().name, "_bird_name", self)
#name.setter
def name(self, value):
# this turned out to be the trickiest part - to retrieve
# the original property on the superclass so that we can
# call it's setter. `super()` did not work for this.
# We set just the core value - the specialized class
# with more attributes is only used upon reading the property back
super_property = [getattr(val, "name") for val in a.__class__.__mro__[1:] if hasattr(val, "name")][0]
super_property.__set__(self, value)
And this working:
In [511]: b = Bird()
In [512]: b.name = "Woodpecker"
In [513]: b.name
Out[513]: 'woodpecker'
In [514]: b.name.nickname = "Woody"
In [515]: b.__dict__
Out[515]: {'_name': 'woodpecker', '_bird_name': {'nickname': 'Woody'}}
In [516]: b.name.nickname
Out[516]: 'Woody'
If you want to restrict the accepted sub-attributes, just use plain if statements in Wrapper.__setattr__.
Is it a good style to create classes like that ? I read the PEP8 document but I didn't saw any good example. If not how is it a proper way ? Thanks for any answers.
class Zone:
def __init__(self, index=None, name=None):
self._index = index
self._name = name
#property
def index(self):
return self._index
#property
def name(self):
return self._name
#index.setter
def index(self, index):
self._index = index
#name.setter
def name(self, name):
self._name = name
Your setters and getters don't do anything. With your implementation, the user of this class does this:
z = Zone()
z.name = 'foo'
print(z.name)
Compare to this implementation:
class Zone:
def __init__(self, index=None, name=None):
self.index = index
self.name = name
z = Zone()
z.name = 'foo'
print(z.name)
It works exactly the same with a lot less code.
Unless you do anything in your setters and/or getters, you don't need them.
If what you intend doing is encapsulating your data and setting it with setters and getting it with getters, then what you did will not be helpful. you declared the _name and _index as protected, it does not mean it cannot be accessed by extenal functions, so functions outside the class can easily access and change them, making your getter and setter to be useless.
However, you can declare them as private by using one additional underscore in front, so that your property class will be removed and then the setters class will be useful, it will no longer be accessed by external functions.
class Zone:
def __init__(self,index=None,name=None):
self.__index = index
self.__name = name
def index(self, index):
self.__index = index
def name(self, name):
self.__name = name
def get_name(self):
return self.__name
zone=Zone()
zone.name('ben')
print(zone.get_name())
>>>ben
print(zone.__name)
>>> AttributeError: 'Zone' object has no attribute '__name'
How can I get slots to work with #property for the class below. I have several thousand instances of below class which is causing memory issues and so I added the slots
I created instances with data and then add location information later to the instances.
After adding slots my instance creation is not working and I am getting the following error
AttributeError: 'Host' object has no attribute '_location'
class Host(object):
__slots__ = ['data', 'location']
def __init__(self, data, location=''):
self.data = data
self.location = location
#property
def location(self):
return self._location
#location.setter
def location(self, value):
self._location = value.lower()
def __repr__(self):
if self.location == '':
self.loc = 'Not Found'
else:
self.loc = self.location
return 'Host(name={}, location={})'.format(self.name, self.loc)
__slots__ works by creating descriptors on the class that have direct access to the in-memory data structure of your instance. You are masking the location descriptor with your property object, and you defined a new attribute _location than is not in the slots.
Make _location the slot (as that is the attribute you are actually storing):
class Host(object):
__slots__ = ['data', '_location']
The location property (also a descriptor object) can then properly assign to self._location, an attribute backed by the slot descriptor.
Note that you do not need to use self.loc in the __repr__, just make that a local variable instead. You also are trying to use a self.name attribute which doesn't exist; it is not clear what value that is supposed to be however:
def __repr__(self):
loc = self.location or 'Not Found'
name = self.data['name'] # or some other expression
return 'Host(name={}, location={})'.format(name, loc)
The definition for __slots__ should have the names of the underlying attributes that will store the data referenced by your properties. In the example below, name mangling is invoked for variables that should not be accessed outside of the class. The code is similar to yours and has no errors according to the PEP8 online website.
#! /usr/bin/env python3
def main():
print(Host('Hello, world!', 'Earth'))
print(Host('Hello, Xyz!'))
class Host:
__slots__ = '__name', '__location'
def __init__(self, name, location=''):
self.name = name
self.location = location
def __repr__(self):
return '{!s}({!r}, {!r})'.format(
type(self).__name__,
self.name,
self.location
)
#property
def name(self):
return self.__name
#name.setter
def name(self, value):
self.__name = value
#property
def location(self):
return self.__location
#location.setter
def location(self, value):
self.__location = value.casefold() if value else 'Not Found'
if __name__ == '__main__':
main()
class Human:
def __init__(self) -> None:
self.name = None # type: str
def introduce(self):
print("I'm " + self.name)
class Alice(Human):
def __init__(self) -> None:
super().__init__()
self.name = "Alice"
class Bob(Human):
def __init__(self, rude: bool) -> None:
super().__init__()
self.rude = rude
#property
def name(self) -> str:
return "BOB!" if self.rude else "Bob"
if __name__ == '__main__':
alice = Alice()
alice.introduce()
bob = Bob(rude=True)
bob.introduce()
In the code above, there is an abstract Human class (in reality it is not a human and has more complex methods, not related to the problem). Most of its implementations would set their names by simply assigning a string to the name attribute (just as Alice). But there are few exceptions, like Bob, when there is more complex logic assigned (the value depends on the object state in the moment of resolving).
Therefore in Bob class I created a custom getter for the name property. But as an effect, it is impossible to create a class instance, because invoking the superconstructor results in the following error.
AttributeError: can't set attribute
And it is impossible to add a naive setter as well.
#name.setter
def name(self, name: str):
self.name = name
Why? Because it would result in an infinite loop. How to solve that issue?
why not make a dummy setter
#name.setter
def name(self, value):
pass
When self.name = None is executed it will call this setter and actually do nothing
If you're certain that your subclasses will assign name, then you can leave out the assignment in the parent constructor. Right now, Human is attempting to set to name, when there is no setter. If you removed it from the Human constructor, then Human can look like this:
class Human:
def introduce(self):
print("I'm " + self.name)
For class BobI would have used something like this in this case:
#name.setter
def name(self, name: str):
self._name = name
Afterwards you can do whatever you want in the more complex getter with the internal value. Or did I get the question wrong?
Executing the code would give:
I'm Alice
I'm BOB!
How to save code duplication in the following scenario ?
say Aand B are two classes having a common function(say) name
class A(object):
name = 'foo'
#property
def name(self): # the common function
return self.name
similarly B
class B(object):
name = 'bar'
#property
def name(self):
return self.name
One way would be to make a class from which both of them inherit from, and define name there.
Any good alternatives ?
If you're really determined to avoid inheritance, just define a function outside of either class:
def get_name(object):
return object.name
class A(object):
name = 'foo'
def get_name(self): # the common function
return self.name
class B(A):
pass
In this case B would inherit from A
Is there a reason you can't have B inherit from A?
class B(A):
name = 'bar'
Since you are decorating name with #property, I am assuming you want this to be an instance variable. If you want this to return a more private variable, let's call it _name, you have to do:
class A(object):
def __init__(self):
self._name = 'foo'
#property
def name(self):
return self._name
You can't have both a variable and a function have the same name, since the latter will simply override the former. If you want a base class that takes care of this, it would look like this:
class HasName(object):
def __init__(self, name):
self._name = name
#property
def name(self):
return self._name
class A(HasName):
def __init__(self):
self._name = 'foo'
class B(HasName):
def __init__(self):
self._name = 'bar'
You can also call the constructor in HasName.
Assuming self.name stands in for a more complex method, the easiest way to cut down on duplicated code is to move the function out to the module and have it take an arbitrary object as a parameter. Then, if you still want to tie the method directly to the class, you can add a short method that dispatches to the module function.
def _name(obj):
return obj.name
class A(object):
# ...
#property
def name(self):
return _name(self)
class B(object):
# ...
#property
def name(self):
return _name(self)
Note that this will not work well if A.name and B.name have completely different behaviors. If the _name function starts checking the type of the object given, reconsider whether you really want to abstract that functionality in the first place.