Create object with variable attributes in a cleaner way in python - python

Am new to python and I had to create an object only with certain attributes that are not None. Example:
if self.obj_one is None and self.obj_two is None:
return MyObj(name=name)
elif self.obj_one is not None and self.obj_two is None:
return MyObj(name=name, obj_one=self.obj_one)
elif self.obj_one is None and self.obj_two is not None:
return MyObj(name=name, obj_two=self.obj_two)
else:
return MyObj(name=name, obj_one=self.obj_one, obj_two=self.obj_two)
Coming from a Java land I know python is full of short hand so wanted to know if there is a cleaner way for writing the above? Of course my actual object has plenty more attributes. I tried searching but couldn't find anything helpful so am in doubt if its possible or not cause this doesn't scale if there are more than 2 variable attributes.

One way could be using the double-star operator, like this:
kwargs = {'name': name}
if self.obj_one is not None:
kwargs['obj_one'] = self.obj_one
if self.obj_two is not None:
kwargs['obj_two'] = self.obj_two
return MyObj(**kwargs)
In plain words: you construct a dictionary with your keyword arguments, and then pass that dictionary preceded by ** to the callable.
However, None is often (not always) used as a default value for optional arguments. Probably this will work too:
return MyObj(name=name, obj_one=self.obj_one, obj_two=self.obj_two)
without any if or that sort of stuff.

Related

Pythonic way to maintain variable assignment

I was writing a small file utility earlier, and ran into an issue with passing by reference. After reading How do I pass a variable by reference?, I set the variable I wanted to pass through as an argument and also as the return value. Within the code below, it is the line:
diff = compareDir(path0, path0List, path1, path1List, diff)
where diff is the variable I wished to pass by reference.
While this works, it feels rather awkward. I think there must be a better way. In many other languages, I could just set compareLists() to have no return value, and use the side-effect of modifying the pass-by-reference argument. Python's pass-by-assignment seems to disallow this.
I am relatively new to python, and would like to know if there is a more pythonic way to resolve my issue. Would it require rethinking the functions entirely? Or is there a nice statement I am unaware of? I'd like to stay away from global variables.
I welcome any and all constructive criticisms and comments. Thanks!
Relevant Code:
def comparePaths(path0, path1):
path0List = os.listdir(path0)
path1List = os.listdir(path1)
diff = False
diff = compareDir(path0, path0List, path1, path1List, diff)
print()
diff = compareDir(path1, path1List, path0, path0List, diff)
return diff
def compareDir(basePath, baseList, comparePath, compareDir, diffVar):
for entry in baseList:
#compare to the other folder
if (not (entry in compareDir)):
if (not (diffVar)):
diffVar = True
print ("Discreptancies found. The following files are different:")
print (str(entry) + " doesn\'t exist in " + str(comparePath))
else:
print (str(entry) + " doesn\'t exist in " + str(comparePath))
return diffVar
Since in Python, the bool type is by definition immutable, the only way to modify a bool variable inside a function without reassigning it (and without defining it as a global variable) is to store it in a mutable type instance. ie:
Storing it in a mutable data structure (list, dict, ...) and pass this data structure to the function.
Having it as an attribute of a mutable object, and pass this object to the function.
Your problem has multiple possible solutions.
You can add nonlocal modifier (global prior to python3) for your diff variable to modify from inside function and have changes visible from outside.
diff = False
def compareDir(basePath, baseList, comparePath, compareDir):
nonlocal diff
for entry in baseList:
...
diff = True
compareDir(path0, path0List, path1, path1List)
print()
compareDir(path1, path1List, path0, path0List)
return diff
Or you can have OOP solution with differ object and self.diff as explicit state of that object.
class differ(object):
def __init__(self):
self.diff = False
def compareDir(self, basePath, baseList, comparePath, compareDir):
...
self.diff = True
...
def comparePaths(self, path0, path1):
Latter solution is super helpful if you need to do a lot of work in some 'context' and frequently need to change shared state.

Cast value if it is not None in python

If you need to parse some XML which has or hasn't some entries you often end up with patterns like this:
planet = system.findall('.//planet')
row['discoveryyear'] = int(planet.findtext("./discoveryyear")) if planet.findtext("./discoveryyear") else None
Is there a nicer way to do that? I would like to avoid the second planet.findtext call but also don't want to write another line of text to store the variable first
Instead of the try/except solution, I propose a helper function:
def find_int(xml, text):
found = xml.findtext(text)
return int(found) if found else None
row['discoveryyear'] = find_int(planet, "./discoveryyear")
(note that found is also falsy if it's '', which is good case to return None for as well)
This will do (except if it's discovered in year 0 haha):
row['discoveryyear'] = int(planet.findtext("./discoveryyear") or 0) or None
To avoid the extra function call you could wrap it in a try/except
try:
row['discoveryyear'] = int(planet.findtext("./discoveryyear"))
except TypeError: #raised if planet.findtext("./discoveryyear") is None
row['discoveryyear'] = None
This also doesn't store the return value in a seperate variable

Is taking advantage of the one-time binding of function arguments a bad idea?

New python users often get tripped up by mutable argument defaults. What are the gotchas and other issues of using this 'feature' on purpose, for example, to get tweakable defaults at runtime that continue to display properly in function signatures via help()?
class MutableString (str):
def __init__ (self, value):
self.value = value
def __str__ (self):
return self.value
def __repr__ (self):
return "'" + self.value + "'"
defaultAnimal = MutableString('elephant')
def getAnimal (species=defaultAnimal):
'Return the given animal, or the mutable default.'
return species
And in use:
>>> help(getAnimal)
getAnimal(species='elephant')
Return the given animal, or the mutable default.
>>> print getAnimal()
elephant
>>> defaultAnimal.value = 'kangaroo'
>>> help(getAnimal)
getAnimal(species='kangaroo')
Return the given animal, or the mutable default.
>>> print getAnimal()
kangaroo
First, read Why are default values shared between objects. That doesn't answer your question, but it provides some background.
There are different valid uses for this feature, but they pretty much all share something in common: the default value is a transparent, simple, obviously-mutable, built-in type. Memoization caches, accumulators for recursive calls, optional output variables, etc. all look like this. So, experienced Python developers will usually spot one of these use cases—if they see memocache={} or accum=[], they'll know what to expect. But your code will not look like a use for mutable default values at all, which will be as misleading to experts as it is to novices.
Another problem is that your function looks like it's returning a string, but it's lying:
>>> print getAnimal()
kangaroo
>>> print getAnimal()[0]
e
Of course the problem here is that you've implemented MutableString wrong, not that it's impossible to implement… but still, this should show why trying to "trick" the interpreter and your users tends to open the door to unexpected bugs.
--
The obvious way to handle it is to store the changing default in a module, function, or (if it's a method) instance attribute, and use None as a default value. Or, if None is a valid value, use some other sentinel:
defaultAnimal = 'elephant'
def getAnimal (species=None):
if species is None:
return defaultAnimal
return species
Note that this is pretty much exactly what the FAQ suggests. Even if you inherently have a mutable value, you should do this dance to get around the problem. So you definitely shouldn't create a mutable value out of an inherently immutable one to create the problem.
Yes, this means that help(getAnimal) doesn't show the current default. But nobody will expect it to.
They will probably expect you to tell them that the default value is a hook, of course, but that's a job for a docstring:
defaultAnimal = 'elephant'
def getAnimal (species=None):
"""getAnimal([species]) -> species
If the optional species parameter is left off, a default animal will be
returned. Normally this is 'elephant', but this can be configured by setting
foo.defaultAnimal to another value.
"""
if species is None:
return defaultAnimal
return species
The only useful use I've seen for it is as a cache:
def fibo(n, cache={}):
if n < 2:
return 1
else:
if n in cache:
return cache[n]
else:
fibo_n = fibo(n-1) + fibo(n-2) # you can still hit maximum recursion depth
cache[n] = fibo_n
return fibo_n
...but then it's cleaner to use the #lru_cache decorator.
#lru_cache
def fibo(n):
if n < 2:
return 1
else:
return fibo(n-1) + fibo(n-2)

How to write pop(item) method for unsorted list

I'm implementing some basic data structures in preparation for an exam and have come across the following issue. I want to implement an unsorted linked list, and have already implemented a pop() method, however I don't know, either syntactically or conceptually, how to make a function sometimes take an argument, sometimes not take an argument. I hope that makes sense.
def pop(self):
current = self.head
found = False
endOfList = None
while current != None and not found:
if current.getNext() == None:
found = True
endOfList = current.getData()
self.remove(endOfList)
self.count = self.count - 1
else:
current = current.getNext()
return endOfList
I want to know how to make the statement unsortedList.pop(3) valid, 3 being just an example and unsortedList being a new instance of the class.
The basic syntax (and a common use case) for using a parameter with a default value looks like this:
def pop(self, index=None):
if index is not None:
#Do whatever your default behaviour should be
You then just have to identify how you want your behaviour to change based on the argument. I am just guessing that the argument should specify the index of the element that should be pop'ed from the list.
If that is the case you can directly use a valid default value instead of None e.g. 0
def pop(self, index=0):
First, add a parameter with a default value to the function:
def pop(self, item=None):
Now, in the code, if item is None:, you can do the "no param" thing; otherwise, use item. Whether you want to switch at the top, or lower down in the logic, depends on your logic. In this case, item is None probably means "match the first item", so you probably want a single loop that checks item is None or current.data == item:.
Sometimes you'll want to do this for a parameter that can legitimately be None, in which case you need to pick a different sentinel. There are a few questions around here (and blog posts elsewhere) on the pros and cons of different choices. But here's one way:
class LinkedList(object):
_sentinel = object()
def pop(self, item=_sentinel):
Unless it's valid for someone to use the private _sentinel class member of LinkedList as a list item, this works. (If that is valid—e.g., because you're building a debugger out of these things—you have to get even trickier.)
The terminology on this is a bit tricky. Quoting the docs:
When one or more top-level parameters have the form parameter = expression, the function is said to have “default parameter values.”
To understand this: "Parameters" (or "formal parameters") are the things the function is defined to take; "arguments" are things passed to the function in a call expression; "parameter values" (or "actual parameters", but this just makes things more confusing) are the values the function body receives. So, it's technically incorrect to refer to either "default parameters" or "parameters with default arguments", but both are quite common, because even experts find this stuff confusing. (If you're curious, or just not confused yet, see function definitions and calls in the reference documentation for full details.)
Is your exam using Python specifically? If not, you may want to look into function overloading. Python doesn't support this feature, but many other languages do, and is a very common approach to solving this kind of problem.
In Python, you can get a lot of mileage out of using parameters with default values (as Michael Mauderer's example points out).
def pop(self, index=None):
prev = None
current = self.head
if current is None:
raise IndexError("can't pop from empty list")
if index is None:
index = 0 # the first item by default (counting from head)
if index < 0:
index += self.count
if not (0 <= index < self.count):
raise IndexError("index out of range")
i = 0
while i != index:
i += 1
prev = current
current = current.getNext()
assert current is not None # never happens if list is self-consistent
assert i == index
value = current.getData()
self.remove(current, prev)
##self.count -= 1 # this should be in self.remove()
return value

Deal with undefined arguments more elegantly

The accepted paradigm to deal with mutable default arguments is:
def func(self, a = None):
if a is None:
a = <some_initialisation>
self.a = a
As I might have to do this for several arguments, I would need to write very similar 3 lines over and over again. I find this un-pythonically a lot of text to read for a very very standard thing to do when initialising class objects or functions.
Isn't there an elegant one-liner to replace those 3 lines dealing with the potentially undefined argument and the standard required copying to the class instance variables?
If a "falsy" value (0, empty string, list, dict, etc.) is not a valid value for a, then you can cut down the initialization to one line:
a = a or <initialize_object>
Another way of doing the same thing is as follows:
def func(self,**kwargs):
self.a=kwargs.get('a',<a_initialization>)
...
This has the added bonus that the value of a passed to the function could be None and the initialization won't overwrite it. The disadvantage is that a user using the builtin help function won't be able to tell what keywords your function is looking for unless you spell it out explicitly in the docstring.
EDIT
One other comment. The user could call the above function with keywords which are not pulled out of the kwargs dictionary. In some cases, this is good (if you want to pass the keywords to another function for instance). In other cases, this is not what you want. If you want to raise an error if the user provides an unknown keyword, you can do the following:
def func(self,**kwargs):
self.a=kwargs.pop('a',"Default_a")
self.b=kwargs.pop('b',"Default_b")
if(kwargs):
raise ... #some appropriate exception...possibly using kwargs.keys() to say which keywords were not appropriate for this function.
You could do this
def func(self, a=None):
self.a = <some_initialisation> if a is None else a
But why the obsession with one liners? I would usually use the 3 line version even if it gets repeated all over the place because if makes your code very easy for experienced Python programmers to read
just a little solution I came up by using an extra function, can be improved of course:
defaultargs.py:
def doInit(var, default_value,condition):
if condition:
var = default_value
return var
def func(a=None, b=None, c=None):
a = doInit(a,5,(a is None or not isinstance(a,int)))
b = doInit(b,10.0,(a is None or not isinstance(a,float)))
c = doInit(c,"whatever",(a is None or not isinstance(c, str)))
print a
print b
print c
if __name__ == "__main__":
func(10)
func(None,12341.12)
func("foo",None,"whowho")
output:
10
10.0
whatever
5
10.0
whatever
5
10.0
whowho
I like your question. :)
Edit: If you dont care about the variables type, please dont use isinstance().

Categories

Resources