What is this construct called in python: ( x, y ) - python

What is this called in python:
[('/', MainPage)]
Is that an array .. of ... erhm one dictionary?
Is that
()
A tuple? ( or whatever they call it? )

Its a list with a single tuple.

Since no one has answered this bit yet:
A tuple? ( or whatever they call it? )
The word "tuple" comes from maths. In maths, we might talk about (ordered) pairs, if we're doing 2d geometry. Moving to three dimensions means we need triples. In higher dimensions, we need quadruples, quintuples, and, uh, whatever the prefix is for six, and so on. This starts to get to be a pain, and mathematicians also love generalising ("let's work in n dimensions today!"), so they started using the term "n-tuple" for an ordered list of n things (usually numbers).
After that, a bit of natural laziness is all you need to drop the "n-" and we end up with tuples.

Note that this:
("is not a tuple")
A tuple is defined by the commas, except in the case of the zero-length tuple. This:
"is a tuple",
because of the comma at the end. The parentheses just enforce grouping (again, except in the case of a zero-length tuple.

That's a list of tuples.
This is a list of integers: [1, 2, 3, 4, 5]
This is also a list of integers: [1]
This is a (string, integer) tuple: ("hello world", 42)
This is a list of (string, integer) tuples: [("a", 1), ("b", 2), ("c", 3)]
And so is this: [("a", 1)]
In Python, there's not much difference between lists and tuples. However, they are conceptually different. An easy way to think of it is that a list contains lots of items of the same type (homogeneous) , and a tuple contains a fixed number of items of different types (heterogeneous). An easy way to remember this is that lists can be appended to, and tuples cannot, because appending to a list makes sense and appending to a tuple doesn't.
Python doesn't enforce these distinctions -- in Python, you can append to a tuple with +, or store heterogeneous types in a list.

Yes, it's a tuple.
They look like this:
()
(foo,)
(foo, bar)
(foo, bar, baz)
etc.

[('/', MainPage)]
That's a list consisting of a two element tuple.
()
That's a zero element tuple.

It is a list of tuple(s). You can verify that by
x=[('/', MainPage)]
print type(x) # You will find a <list> type here
print type(x[0]) # You will find a <tuple> type here
You can build a dictionary from this type of structure (may be more tuple inside the list) with this code
my_dict = dict(x) # x=[('/',MainPage)]

It is a list of tuples containing one tuple.
A tuple is just like a list except that it is immutable, meaning that it can't be changed once it's created. You can't add, remove, or change elements in a tuple. If you want your tuple to be different, you have to create a new tuple with the new data. This may sound like a pain but in reality tuples have many benefits both in code safety and speed.

It's a list of just one tuple. That tuple has two elements, a string and the object MainPage whatever it is.
Both lists and tuples are ordered groups of object, it doesn't matter what kind of object, they can be heterogeneous in both cases.
The main difference between lists and tuples is that tuples are immutable, just like strings.
For example we can define a list and a tuple:
>>> L = ['a', 1, 5, 'b']
>>> T = ('a', 1, 5, 'b')
we can modify elements of L simply by assigning them a new value
>>> print L
['a', 1, 5, 'b']
>>> L[1] = 'c'
>>> print L
['a', 'c', 5, 'b']
This is not true for tuples
>>> print T
('a', 1, 5, 'b')
>>> T[1] = 'c'
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: 'tuple' object does not support item assignment
This is because they are immutable.
Tuples' elements may be mutable, and you can modify them, for example:
>>> T = (3, ['a', 1, 2], 'lol')
>>> T[1]
['a', 1, 2]
>>> T[1][0] = 'b'
>>> T
(3, ['b', 1, 2], 'lol')
but the list we edited is still the same object, we didn't replaced the tuple's element.

Related

Usage of "__add__" method in Tuple class

python noob here and playing around with the limitations of Tuples and Lists.
I don't have a problem, just a general query about usage of the __methodname__ methods in the Tuple class and/or in general.
I am aware that you cannot modify Tuples and in order to do so, you have to convert it to a list, modify said list, then convert back to Tuple but I had a play around with the __add__ method and found that it works. What are issues and limitations with using this to create new Tuples with modifications to an existing one?
CODE:
myTuple = ('item1', 2, 'item3', ['list1', 'list2'])
tupleModification = myTuple.__add__(('newTupleItem1','newTupleItem2'))
This outputs the following:
('item1', 2, 'item3', ['list1', 'list2'], 'newTupleItem1', 'newTupleItem2')
which is correct but i'm wondering if i'm playing with fire because I haven't seen this solution posted anywhere in relation to modifying Tuples.
EDIT: I am aware that you cannot modify existing Tuples and that this will create a new instance of one. I think I may have confused people with my naming conventions.
__add__ is the method which is called when you do:
myTuple + ("newTupleItem1", "newTupleItem2")
So this does not modify myTuple but creates a new tuple whose content is the content of myTuple concatenated with ("newTupleItem1", "newTupleItem2").
You can print myTuple to see that it has not been modified:
>>> myTuple
('item1', 2, 'item3', ['list1', 'list2'])
And you can check that myTuple and tupleModification are not the same object:
>>> myTuple is tupleModification
False
You cannot modify tuples, that's right. However, you can concatenate two existing tuples into a new tuple. This is done with the + operator, which in turn calls the __add__ method. The resulting tuple will not be a "modification" of any of the original ones, but a new distinct tuple. This is what the code you posted does. More concisely, you can just do:
myTuple = ('item1', 2, 'item3', ['list1', 'list2'])
tupleModification = myTuple + ('newTupleItem1','newTupleItem2')
print(tupleModification)
# ('item1', 2, 'item3', ['list1', 'list2'], 'newTupleItem1', 'newTupleItem2')
EDIT: Just as a clarification, you cannot "edit" a tuple anyhow, that is, add or remove elements from it, or change its contents. However, if your tuple contains a mutable object, such as a list, then that inner object can be modified:
myTuple = (1, [2, 3])
myTuple[1].append(4)
print(myTuple)
# (1, [2, 3, 4])
I think fundamentally you're confused about the difference between creating a new object with modifications (__add__ in this case) and modifying an existing object (extend for example).
__add__
As other answers already mentioned, the __add__ method implements the + operator, and it returns a new object. Tuples and lists each have one. For example:
>>> tuple_0 = (1,)
>>> tuple_1 = tuple_0.__add__((2,))
>>> tuple_1 is tuple_0
False
>>>
>>> list_0 = [1]
>>> list_1 = list_0.__add__([2])
>>> list_1 is list_0
False
extend
Lists, which are mutable, have an extend method, which modifies the existing object and returns None. Tuples, which are immutable, don't. For example:
>>> list_2 = [4, 5, 6]
>>> id_save = id(list_2)
>>> list_2.extend([7])
>>> id(list_2) == id_save
True
>>>
>>> tuple_2 = (4, 5, 6)
>>> tuple_2.extend([7])
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
AttributeError: 'tuple' object has no attribute 'extend'
Lists have other methods which change the existing object, like append, sort, pop, etc, but extend is the most similar to __add__.
There is a difference between 1) modifying an existing tuple (in-place), and 2) leaving the original alone, but creating a new modified copy. These two different paradigms can be seen throughout computer programming, not just python tuples. For example, consider increment a number by one. You could modify the original, or you could leave the original alone, create a copy and then modify the copy.
MODIFYING IN-PLACE
BEFORE:
x == 5
AFTER:
x == 6
CREATING A MODIFIED COPY
BEFORE:
x == 5
AFTER:
x == 5 (unchanged)
y == 6
tuple.__add__ concatenates tuples. For example, (x, y) + (a, b, c) returns (x, y, a, b, c)
tuple.__add__ does not modify the original tuples. It leaves the original tuples alone, and creates a new tuple which is the concatenation of the original two. This is contrast to something like list.append or list.extend which modifies the original list instead of returning a modified copy.
Tuple methods generally do the following:
copy the original
modify the copy in some way
leave the original alone.

choosing a random number from a tuple + some numbers

I have a tuple of numbers, and I want to choose a random number from my tuple in addition with a certain number. For example:
my_tuple = (1,2,3)
and I have the number 4. I want to choose a random number from the numbers 1,2,3,4 (without changing the tuple of course).
I tried:
my_tp = (1, 2, 3)
a = random.choice(list(my_tp).append(4))
print(a)
I'm new to python. I tried converting the tuple to a list, and then performing the random function.
The code above didn't work. Got the error
object of type 'NoneType' has no len()
Would love some help.
list.append returns none
Once converting to a list as you have done, appending will modify that list but return none so that is the source of your error.
To get round that you can either convert the tuple to a list then append 4 to it, then use random.choice, or in just one step, you can concatenate a list of [4] with the + operand.
This approach is much simpler:
import random
my_tuple = (1,2,3)
random.choice(list(my_tuple) + [4])
Hope this helps and clears some things up! :)
Update:
If you want to just randomly select from the tuple without the last item, then just slice the list with the normal syntax:
random.choice(list(my_tuple)[:-1])
The method list.append alters the provided list and returns None, which explains the exception you got. To get the desired result, you can implicitly use the tuple.__add__ method, which will return a new tuple.
values = (1, 2, 3)
random.choice(values + (4,))
If you want to remove values in a concise-ish fashion, using a set might be appropriate.
values = {1, 2, 3}
random.choice(list(values - {3}))
You can try:
my_tuple = (1,2,3)
random.choice((*my_tuple, 4))
Where (*my_tuple, 4) creates a new tuple with the unpacked content of my_tuple and 4.

Python converting a tuple (of strings) of unknown length into a list of strings

I have a recursive tuple of strings that looks like this:
('text', ('othertext', ('moretext', ('yetmoretext'))))
(it's actually a tuple of tuples of strings - it's constructed recursively)
And I'd like to flatten it into a list of strings, whereby foo[1] would contain "text", foo[2] "othertext" and so forth.
How do I do this in Python?
The duplicate is about a 2D list of lists, but here I'm dealing with a recursive tuple.
I've found the answer myself, I'll provide it here for future reference:
stringvar = []
while type(tuplevar) is tuple:
stringvar.append(tuplevar[0])
tuplevar=tuplevar[1]
stringvar.append(tuplevar) # to get the last element.
Might not be the cleanest/shortest/most elegant solution, but it works and it seems quite "Pythonic".
If you're happy that the level of recursion isn't going to get horrible (and you're using an up to date version of Python):
def unpack(obj):
for x in obj:
if isinstance(x, str):
yield x
elif isinstance(x, tuple):
yield from unpack(x)
else:
raise TypeError
x = ('text', ('othertext', ('moretext', ('yetmoretext',))))
result = list(unpack(x))
print(result)
Will give you:
['text', 'othertext', 'moretext', 'yetmoretext']
This will also work if there are more than 1 strings before the next tuple, or if there are tuples directly in tuples, or strings after tuples etc. You can also easily modify it to work with other types if you need, I've probably unnecessarily erred on the side of caution.
This is how I would approach it. This is very similar to a previous answer, however it's more general in application, as it allows any type of iterable to be flattened, except for string-type objects (i.e., lists and tuples), and it also allows for the flattening of lists of non-string objects.
# Python 3.
from collections import abc
def flatten(obj):
for o in obj:
# Flatten any iterable class except for strings.
if isinstance(o, abc.Iterable) and not isinstance(o, str):
yield from flatten(o)
else:
yield o
data = ('a', ('b', 'c'), [1, 2, (3, 4.0)], 'd')
result = list(flatten(data))
assert result == ['a', 'b', 'c', 1, 2, 3, 4.0, 'd']

Why do copied dictionaries point to the same directory but lists don't?

Can someone tell me why when you copy dictionaries they both point to the same directory, so that a change to one effects the other, but this is not the case for lists?
I am interested in the logic behind why they would set up the dictionary one way, and lists another. It's confusing and if I know the reason behind it I will probably remember.
dict = {'Dog' : 'der Hund' , 'Cat' : 'die Katze' , 'Bird' : 'der Vogel'}
otherdict = dict
dict.clear()
print otherdict
Which results in otherdict = {}.So both dicts are pointing to the same directory. But this isn't the case for lists.
list = ['one' , 'two' , 'three']
newlist = list
list = list + ['four']
print newlist
newlist still holds on to the old list. So they are not pointing to the same directory. I am wanting to know the rationale behind the reasons why they are different?
Some code with similar intent to yours will show that changes to one list do affect other references.
>>> list = ['one' , 'two' , 'three']
>>> newlist = list
>>> list.append('four')
>>> print newlist
['one', 'two', 'three', 'four']
That is the closest analogy to your dictionary code. You call a method on the original object.
The difference is that with your code you used a separate plus and assignment operator
list = list + ['four']
This is two separate operations. First the interpreter evaluates the expression list + ['four']. It must put the result of that computation in a new list object, because it does not anticipate that you will assign the result back to list. If you had said other_list = list + ['four'], you would have been very annoyed if list were modified.
Now there is a new object, containing the result of list + ['four']. That new object is assigned to list. list is now a reference to the new object, whereas newlist remains a reference to the old object.
Even this is different
list += ['four']
The += has the meaning for mutable object that it will modify the object in place.
Your two cases are doing different things to the objects you're copying, that's why you're seeing different results.
First off, you're not really copying them. Your simply making new "references" or (in more Pythonic terms) binding new names to the same objects.
With the dictionary, you're calling dict.clear, which discards all the contents. This modifies the existing object, so you see the results through both of the references you have to it.
With the list, you're rebinding one of the names to a new list. This new list is not the same as the old list, which remains unmodified.
You could recreate the behavior of your dictionary code with the lists if you want. A slice assignment is one way to modify a whole list at once:
old_list[:] = [] # empties the list in place
One addendum, unrelated to the main issue above: It's a very bad idea to use names like dict and list as variables in your own code. That's because those are the names of the builtin Python dictionary and list types. By using the same names, you shadow the built in ones, which can lead to confusing bugs.
In your dictionary example, you've created a dictionary and store it in dict. You then store the same reference in otherdict. Now both dict and otherdict point to the same dictionary*. Then you call dict.clear(). This clears the dictionary that both dict and otherdict point to.
In your list example, you've created a list and store it in list. You then store the same reference in otherlist. Then you create a new list consisting of the elements of list and another element and store the new list in list. You did not modify the original list you created. You created a new list and changed what list pointed to.
You can get your list example to show the same behavior as the dictionary example by using list.append('four') rather than list = list + ['four'].
Do you mean this?
>>> d = {'test1': 1, 'test2': 2}
>>> new_d = d
>>> new_d['test3'] = 3
>>> new_d
{'test1': 1, 'test3': 3, 'test2': 2}
>>> d # copied over
{'test1': 1, 'test3': 3, 'test2': 2}
>>> lst = [1, 2, 3]
>>> new_lst = lst
>>> new_lst.append(5)
>>> new_lst
[1, 2, 3, 5]
>>> lst # copied over
[1, 2, 3, 5]
>>> new_lst += [5]
>>> lst # copied over
[1, 2, 3, 5, 5]
>>> my_tuple = (1, 2, 3)
>>> new_my_tuple = my_tuple
>>> new_my_tuple += (5,)
>>> new_my_tuple
(1, 2, 3, 5)
>>> my_tuple # immutable, so it is not affected by new_my_tuple
(1, 2, 3)
Lists DO pass reference, not the object themselves. Most (hesitant on saying all) mutable (can be changed, such as lists and dictionaries) objects pass references, whereas immutable (cannot be changed, such as tuples) objects pass the object themselves.

Append a tuple to a list

Given a tuple (specifically, a functions varargs), I want to prepend a list containing one or more items, then call another function with the result as a list. So far, the best I've come up with is:
def fn(*args):
l = ['foo', 'bar']
l.extend(args)
fn2(l)
Which, given Pythons usual terseness when it comes to this sort of thing, seems like it takes 2 more lines than it should. Is there a more pythonic way?
You can convert the tuple to a list, which will allow you to concatenate it to the other list. ie:
def fn(*args):
fn2(['foo', 'bar'] + list(args))
If your fn2 took varargs also, you wouldn't need to build the combined list:
def fn2(*l):
print l
def fn(*args):
fn2(1, 2, *args)
fn(10, 9, 8)
produces
(1, 2, 10, 9, 8)

Categories