How to "push" a tuple in one line of code? - python

Why can I not replace the following first code:
def conversion7(x,k):
l=list(x)
l.append(k)
return tuple(l)
t=(1,2,3,4)
print(conversion7(t,7))
With the second one:
def conversion7(x,k):
return tuple(list(x).append(k))
t=(1,2,3,4)
print(conversion7(t,7))
The first code works. Here is the compiler output of the second code:
Traceback (most recent call last):
File "<string>", line 5, in <module>
File "<string>", line 2, in conversion7
TypeError: 'NoneType' object is not iterable
>
The purpose of the codes is to push a tuple by converting it to a list, pushing the list and then converting that back to a tuple.

Since append does not return a reference to the list, as chepner has pointed out, you could do the following:
def conversion7(x,k):
return tuple(list(x) + [k])
Or skip list conversion and immediately add tuples:
def conversion7(x,k):
return x + (k,)

Related

Is the function "next" a good practice to find first occurrence in a iterable?

I've learned about iterators and such and discovered this quite interesting way of getting the first element in a list that a condition is applied (and also with default value in case we don't find it):
first_occurence = next((x for x in range(1,10) if x > 5), None)
For me, it seems a very useful, clear way of obtaining the result.
But since I've never seen that in production code, and since next is a little more "low-level" in the python structure I was wondering if that could be bad practice for some reason. Is that the case? and why?
It's fine. It's efficient, it's fairly readable, etc.
If you're expecting a result, or None is a possible result (so using None as a placeholder makes it hard to figure out if you got a result or got the default) it may be better to use the EAFP form rather than providing a default, catching the StopIteration it raises if no item is found, or just letting it bubble up if the problem is from the caller's input not meeting specs (so it's up to them to handle it). It looks even cleaner at point of use that way:
first_occurence = next(x for x in range(1,10) if x > 5)
Alternatively, when None is a valid result, you can use an explicit sentinel object that's guaranteed unique like so:
sentinel = object() # An anonymous object you construct can't possibly appear in the input
first_occurence = next((x for x in range(1,10) if x > 5), sentinel)
if first_occurence is not sentinel: # Compare with is for performance and to avoid broken __eq__ comparing equal to sentinel
A common use case for this one of these constructs to replace a call to any when you not only need to know if any item passed the test, but which item (any can only return True or False, so it's unsuited to finding which item passed).
We can wrap it up in a function to provide an even nicer interface:
_raise = object()
# can pass either an iterable or an iterator
def first(iterable, condition, *, default=_raise, exctype=None):
"""Get the first value from `iterable` which meets `condition`.
Will consume elements from the iterable.
default -> if no element meets the condition, return this instead.
exctype -> if no element meets the condition and there is no default,
raise this kind of exception rather than `StopIteration`.
(It will be chained from the original `StopIteration`.)
"""
try:
# `iter` is idempotent; this makes sure we have an iterator
return next(filter(condition, iter(iterable)))
except StopIteration as e:
if default is not _raise:
return default
if exctype:
raise exctype() from e
raise
Let's test it:
>>> first(range(10), lambda x: x > 5)
6
>>> first(range(10), lambda x: x > 11)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "<stdin>", line 4, in first
StopIteration
>>> first(range(10), lambda x: x > 11, exctype=ValueError)
Traceback (most recent call last):
File "<stdin>", line 4, in first
StopIteration
The above exception was the direct cause of the following exception:
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "<stdin>", line 9, in first
ValueError
>>> first(range(10), lambda x: x > 11, default=None)
>>>

How does the list.append work?

alist = []
def show(*args, **kwargs):
alist.append(*args, **kwargs)
print(alist)
>>> show('tiger')
['tiger']
>>> show('tiger','cat')
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "<stdin>", line 2, in show
TypeError: append() takes exactly one argument (2 given)
>>> show('tiger','cat', {'name':'tom'})
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "<stdin>", line 2, in show
TypeError: append() takes exactly one argument (3 given)
Since the method append of alist only accepts one argument, why not detect a syntax error on the line alist.append(*args, **kwargs) in the definition of the method show?
It's not a syntax error because the syntax is perfectly fine and that function may or may not raise an error depending on how you call it.
The way you're calling it:
alist = []
def show(*args, **kwargs):
alist.append(*args, **kwargs)
print(alist)
>>> show('tiger')
['tiger']
>>> show('tiger','cat')
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "<stdin>", line 2, in show
TypeError: append() takes exactly one argument (2 given)
A different way:
alist = []
def show(*args, **kwargs):
alist.append(*args, **kwargs)
print(alist)
>>> show('tiger')
['tiger', 'tiger']
>>> class L: pass
...
>>> alist = L()
>>> alist.append = print
>>> show('tiger','cat')
tiger cat
<__main__.L object at 0x000000A45DBCC048>
Python objects are strongly typed. The names that bind to them are not. Nor are function arguments. Given Python's dynamic nature it would be extremely difficult to statically predict what type a variable at a given source location will be at execution time, so the general rule is that Python doesn't bother trying.
In your specific example, alist is not in the local scope. Therefore it can be modified after your function definition was executed and the changes will be visible to your function, cf. code snippets below.
So, in accord with the general rule: predicting whether or not alist will be a list when you call .append? Near-impossible. In particular, the interpreter cannot predict that this will be an error.
Here is some code just to drive home the point that static type checking is by all practical means impossible in Python. It uses non-local variables as in your example.
funcs = []
for a in [1, "x", [2]]:
def b():
def f():
print(a)
return f
funcs.append(b())
for f in funcs:
f()
Output:
[2] # value of a at definition time (of f): 1
[2] # value of a at definition time (of f): 'x'
[2] # value of a at definition time (of f): [2]
And similarly for non-global non-local variables:
funcs = []
for a in [1, "x", [2]]:
def b(a):
def f():
print(a)
a = a+a
return f
funcs.append(b(a))
for f in funcs:
f()
Output:
2 # value of a at definition time (of f): 1
xx # value of a at definition time (of f): 'x'
[2, 2] # value of a at definition time (of f): [2]
It's not a syntax error because it's resolved at runtime. Syntax errors are caught initially during parsing. Things like unmatched brackets, undefined variable names, missing arguments (this is not a missing argument *args means any number of arguments).
show has no way of knowing what you'll pass it at runtime and since you are expanding your args variable inside show, there could be any number of arguments coming in and it's valid syntax! list.append takes one argument! One tuple, one list, one int, string, custom class etc. etc. What you are passing it is some number elements depending on input. If you remove the * it's all dandy as its one element e.g. alist.append(args).
All this means that your show function is faulty. It is equipped to handle args only when its of length 1. If its 0 you also get a TypeError at the point append is called. If its more than that its broken, but you wont know until you run it with the bad input.
You could loop over the elements in args (and kwargs) and add them one by one.
alist = []
def show(*args, **kwargs):
for a in args:
alist.append(a)
for kv in kwargs.items():
alist.append(kv)
print(alist)

TypeError: 'int' object is not callable for a recursive function

a = 3
def f(x):
x = (x**3-4*x)/(3(x**2)-4)
return x
while True:
print(a)
a = f(a)
I'm getting a type error here, and I'm not sure why. I'm trying to run this recursive function, is there any way to fix this?
You need a * operator after your parentheses. Multiplication is only implied in mathematical notation in this context, in Python it looks like you're trying to call a function.
3(x**2)
So it would be
3*(x**2)
For example
>>> 3(5*2)
Traceback (most recent call last):
File "<pyshell#0>", line 1, in <module>
3(5*2)
TypeError: 'int' object is not callable
>>> 3*(5*2)
30

Python TypeError: sequence item 0: expected str instance, NoneType found

I copied this code from a book:
lyst = {'Hi':'Hello'}
def changeWord(sentence):
def getWord(word):
lyst.get(word, word)
return ''.join(map(getWord, sentence.split()))
I get this error:
Traceback (most recent call last):
File "<pyshell#4>", line 1, in <module>
cambiaPronome('ciao')
File "C:\Python33\2.py", line 6, in cambiaPronome
return ''.join(map(getWord, list(frase)))
TypeError: sequence item 0: expected str instance, NoneType found
What is wrong?
The getWord function doesn't explicitly return anything, so it implicitly returns None. Try returning something, e.g.:
def getWord(word):
return lyst.get(word, word)
The problem is you're trying to write to a string a type which is NoneType. That's not allowed.
If you're interested in getting the None values as well one of the things you can do is convert them to strings.
And you can do it with a list comprehension, like:
return ''.join([str(x) for x in map(getWord, sentence.split())])
But to do it properly in this case, you have to return something on the inner function, else you have the equivalent to return None

Why sum start value is not zero value of iterable?

Why is sum not able to take correct zero value automatically?
>>> sum((['1'], ['2']))
Traceback (most recent call last):
File "<pyshell#13>", line 1, in <module>
sum((['1'], ['2']))
TypeError: unsupported operand type(s) for +: 'int' and 'list'
>>> sum((['1'], ['2']), [])
['1', '2']
It is simple to implement like this:
>>> def sum(s, start=None):
it = iter(s)
n = next(it)
if start is None:
start = type(n)()
return n + __builtins__.sum(it, start)
>>> sum((['1'], ['2']))
['1', '2']
>>>
But sum does not anyway join strings, so maybe it is just to encourage to use proper methods for different 'summings'.
On the other hand if it is meant to be used only for numbers, why not sum_numbers not sum as name to make it clear.
EDIT: to handle empty sequence we must add little code:
>> sum([])
Traceback (most recent call last):
File "<pyshell#36>", line 1, in <module>
sum([])
File "<pyshell#28>", line 3, in sum
n = next(it)
StopIteration
>>> def sum(s, start=None):
it = iter(s)
try:
n= next(it)
except:
return 0
if start is None:
start = type(n)()
return n + __builtins__.sum(it, start)
>>> sum([])
0
>>>
Inferring the zero value is impossible in the general case. What if the iterable produces instances of a user-defined class that has no zero-argument constructor? And as you've shown, it's easy to provide it yourself.

Categories

Resources