I am trying to understand whether declaring variables inside constructors is an ok practice. I only need these variables inside the constructor. I am asking this because most of the times I've seen constructors they only contained self variables. I've tried to find an answer on the internet but had no luck.
Here is an example code
class Patient:
def __init__(self, all_images_path: str):
all_patient_images = os.listdir(all_images_path)
all_patient_images = [(all_images_path + x) for x in all_patient_images if 'colourlay' in x]
self.images: [Image] = [Image.open(img_path) for img_path in all_patient_images]
Is there anything wrong with the above code? If yes, what?
Thank you!
__init__ is just a normal function that has a special purpose (initializing the newly created object).
You can do (almost) whatever you like in there. You typically see lines like self.name = arg because assignments like these are often required to set up the internal state of the object. It's perfectly fine to have local variables though, and if you find they make your code cleaner, use them.
From a design standpoint, Patient.__init__ is doing too much. I would keep __init__ as simple as possible:
class Patient:
def __init__(self, images: list[Image]):
self.images = images
The caller of __init__ is responsible for producing that list of Image values. However, that doesn't mean your user is the one calling __init__. You can define a class method to handle the messy details of extracting images from a path, and calling __init__. (Note it gets less messy if you use the pathlib module.)
from pathlib import Path
class Patient:
def __init__(self, images: list[Image]):
self.images = images
#classmethod
def from_path(cls, all_images_path: Path):
files = all_images_path.glob('*colourlay*')
return cls([Image.open(f) for f in files])
Note that Image itself seems to take this approach: you aren't constructing an instance of Image directly, but using a class method to extract the image data from a file.
Related
I have a python file say
python_file_a.py
def load_content():
dir = "/down/model/"
model = Model(model_dir=dir)
return model
model = load_content()
def invoke(req):
return model.execute(req)
test_python_file_a.py
#patch("module.python_file_a.load_content")
#patch("module.python_file_a.model", Mock(spec=Model))
def test_invoke():
from module.python_file_a import model, invoke
model.execute = Mock(return_value="Some response")
invoke("some request")
This is still trying to load the actual model from the path "/down/model/" in the test. What is the correct way of patching so that the load_content function is mocked in the test?
Without knowing more about what your code does or how it's used it's hard to say exactly, but in this case the correct approach--and in many cases--is to not hard-code values as local variables in functions. Change your load_content() function to take an argument like:
def load_content(dirname):
...
or even give it a default value like
def load_content(dirname="/default/path"):
pass
For the test don't use the model instance instantiated at module level (arguably you should not be doing this in the first place, but again it depends on what you're trying to do).
Update: Upon closer inspect the problem really seems to stem from you instantiating a module-global instance at import time. Maybe try to avoid doing that and use lazy instantiation instead, like:
model = None
then if you really must write a function that accesses the global variable:
def invoke():
global model
if model is None:
model = load_content()
Alternatively you can use a PEP 562 module-level __getattr__ function.
Or write a class instead of putting everything at module-level.
class ModelInvoker:
def __init__(self, dirname='/path/to/content'):
self.dirname = dirname
#functools.cached_property
def model(self):
return load_content(self.dirname)
def invoke(self, req):
return model.execute(req)
Many other approaches to this depending on your use case. But finding some form of encapsulation is what you need if you want to be able to easily mock and replace parts of some code, and not execute code unnecessarily at import time.
I am wondering about this question for a while and still not sure about the appropriate answer.
If there is somewhere good answer already, sorry for that.
When is the good use case to use function or variable defined somewhere in module within the class instead of defining it inside as method/attribute?
Example:
PATH_TO_DIR = "abc\\def"
class Reader:
def __init__(self, file_name):
self.file_name = file_name
def read_file(self):
return pd.read_excel(os.path.join(PATH_TO_DIR, self.file_name))
or
class Reader:
PATH_TO_DIR = "abc\\def"
def __init__(self, file_name):
self.file_name = file_name
def read_file(self):
return pd.read_excel(os.path.join(self.PATH_TO_DIR, self.file_name))
The same problem is bothering me regarding function/method, for example we could define read_file() function and use it within class externally.
I feel like defining it as method/attribute make more sense, but I have seen a lot of codes where those parts was defined externally.
I would like to know the answer regarding good practices of python programming - I know that language is able to handle a lot of strange things, but its not the case ;)
I would lean towards option 3: pass the correct absolute path to Reader.__init__. The job of Reader, presumably, is to parse a file, not worry about file-system layout.
PATH_TO_DIR = "abc\\def"
class Reader:
def __init__(self, file_name):
self.file_name = file_name
def read_file(self):
return pd.read_excel(self.file_name)
r = Reader(os.path.join(PATH_TO_DIR, "foo.xl"))
I believe, that a good practice is to have it defined externally, because in that way you could reuse this function more easily. Also, you can reuse the same variable in other functions/classes.
In your first example you are defining variable that could be used in multiple classes. Also the same class could be imported by other script that you did not design for.
In second example - you can use this variable only in this function and if you want to reuse this function somewhere else - you have to overwrite this variable after initialization. And this means running __init__() method.
Personally, I avoid defining variables inside classes and functions.
I want to create a subclass of a class of an existing package (whose source code I don't want to/cannot change). The objects of the class are initialized just using a string and then populated later on using all kind of add functions. A minimal example could look like this (without any add functions):
import copy
class Origin(object):
def __init__(self, name):
self.name = name
self.dummy_list = [1, 2, 'a']
self.dummy_stuff = {'a': [12, 'yt']}
def make_copy(self):
return copy.deepcopy(self)
def dummy_function(self):
return len(self.dummy_list)
I want to create a subclass in such a way that I can initialize its instances using an instance of Origin. A straightforward way would be
class BasedOnOrigin(Origin):
def __init__(self, origin_instance, new_prop):
Origin.__init__(self, origin_instance.name)
self.dummy_list = copy.deepcopy(origin_instance.dummy_list)
self.dummy_stuff = copy.deepcopy(origin_instance.dummy_stuff)
self.new_prop = new_prop
The annoying thing there is, that I need to copy all kind of things which I need to know about in advance.
Another option would be
class BasedOnOrigin2(Origin):
def __init__(self, origin_instance, new_prop):
Origin.__init__(self, origin_instance.name)
self = origin_instance.make_copy()
self.new_prop = new_prop
but the self = part looks rather non-standard and new_prop is not set, so I would need an extra function for this.
Is there a standard way of doing this?
An alternative to the above would be to add the additional functions to existing instances using e.g.
from functools import partial
def add_function(obj, func):
setattr(obj, func.__name__, partial(func, obj))
but this can be annoying if there are (i) a lot of functions to add and (ii) a lot of instances to which one wants to add functions.
but the self = part looks rather non-standard and new_prop is not set
self is just a plain local variable, so rebinding it only effects the local scope indeed.
Is there a standard way of doing this?
From what you describe it looks like your real problem is that you have instances of class created by another lib that you don't want / cannot modify and what you really want is to add new methods (and eventually override some methods) to those objects, but cannot since you can tell this lib to use your own class instead.
If the point is purely and simply "replace" the original class with your own version of it (so all instances of the original class are impacted by the change), the canonical solution is to monkeypatch the original class:
from otherlib import TheClass
def patch_the_class():
# we do this in a function to avoid
# polluting the global namespace
# add a new method
def newmethod(self):
# code here
TheClass.newmethod = newmethod
# override an existing method
# keep a reference to the original so
# we can still use it:
_original = TheClass.some_method
def mymethod(self, arg):
something = _original(self, arg)
# additional stuff here
return something
TheClass.some_method = mymethod
patch_the_class()
Just make sure this is executed before any use of the patched class and you're done.
The pro of this solution (wrt/ patching each instance individually) is a lesser cost and the assurance that no one will ever forget to patch an instance.
Now note that monkeypatches are to be considered as either a temporary workaround or a last-resort hack. If the lib you are patching is OSS, you can modify it to either improve the original class or implement some way to make the concrete class to use configurable and contribute it back.
I think the best approach is defining a function that will extend original origin instance without copying it e.g.
def exdend(*origin_instances):
def my_function_one(self):
pass
def my_function_two(self):
pass
for origin_instance in origin_instances:
setattr(origin_instance, my_function_one.__name__, partial(my_function_one, origin_instance))
setattr(origin_instance, my_function_two.__name__, partial(my_function_two, origin_instance))
return origin_instances
I have a class called Image
class Image(object):
def __init__(self,rgb_img):
self.img_time = time.time()
self.img_colr = rgb_img
I want to store some more information on this class at a later time (image keypoints, grey scale, etc.) and believe it is more clear to make a new class altogether. But I don't know the best practice for this. Heres my guess:
img = Image(rgb_img)
class DetailedImage(object):
def __init__(self, img, kp, grey):
self = img
self.kp = kp
self.grey = grey
Still, this option is implicit. It is not immediately obvious that the img argument must be an Image instance.
What is the best practice for creating a new class using an instance from another class? (i.e. if I have a peasant, and he trains to become a warrior, how to I pass information from the instantiated peasant class to a separate warrior class?) Is this even a good idea to do?
As I've said in the comment: assigning to self makes me cringe, don't do that.
Apart from that you'll have many options how to get what you need.
Use composition:
class DetailedImage(object):
def __init__(self, img):
"""
:param Image img: Internal image
"""
self.img = img
In this case I specified parameter type using docstrings in sphinx format.
Use normal python inheritance.
Python inheritance (even multiple-inheritance) is usefull, and better than in other languages. I'd rathere refer you to the docs, than write too long answer. Or see this: How does Python's super() work with multiple inheritance?.
You can simply add any attribute to python class instances, you don't need to define a special class:
img = Image(rgb_img)
img.kp = kp
img.grey = grey
Ideally I would like to extend an instance of PIL's image class to include some custom methods; Image doesn't support this however, so I need to change tack. Delegation seems to be the most conventional alternative, but before I stumbled upon this aspect of python I had toyed with the idea of creating my custom class with an attribute that functioned as an instance of the PIL Image class. I've not seen this done anywhere else, but is this essentially the same thing as delegation?
import Image
MyImageClass():
def __init__(self, filepath):
self.attrA = ...
self.attrB = ...
self.attrc = ...
self.img = Image.open(filepath)
def method_A(self):
...
def method_B(self):
im = MyImageClass('/path/to/png')
im.self.img.thumbnail((100,100))
Sure. This is no different than:
class Foo(object):
def __init__(self):
self.bar = 'test'
im = Foo()
print im.bar.upper()
Notice that it is im.bar, not im.self.bar.
self in __init__ is the same as im so self.bar in __init__ is the same as im.bar outside of it.
Of course instances can be attributes of classes. But you say that you want an enhanced Image class - so extend Image:
#take this with a grain of salt - I didn't test the code
from PIL.Image import Image
MyImageClass(Image):
def __init__(self):
super(MyImageClass, self).__init__()
self.init_some = 'custom_stuff'
def method_A(self):
...
def method_B(self):
im = MyImageClass.open('/path/to/png')
im.thumbnail((100,100))
Update
As OP pointed out Image isn't designed to be subclassed by application code. Hence my example is just a general example for inheritance.
In Python, the boundaries beween delegation, composition, aggregation, and other has a repationships are pretty blurry, as they all tend to share the same syntax, and with garbage collection there are no worries about ownership. What you call it will mainly depend on what you do with the objects, i.e. if MyImageClass.method_A just calls self.img.method_A(), etc., it would be delegation.
I'm pretty sure you have seen this before, as every value in Python – including values of all attributes – is an instance of some type.
If your class is an Image, you're looking for inheritance. Your class should be a subclass of Image.