Join us and get access to thousands of tutorials and a community of expert Pythonistas.

Unlock This Lesson

This lesson is for members only. Join us and get access to thousands of tutorials and a community of expert Pythonistas.

Unlock This Lesson

Hint: You can adjust the default video playback speed in your account settings.
Hint: You can set your subtitle preferences in your account settings.
Sorry! Looks like there’s an issue with video playback 🙁 This might be due to a temporary outage or because of a configuration issue with your browser. Please refer to our video player troubleshooting guide for assistance.

Functions: Miscellaneous

For more on exec() and eval(), check out:

00:00 Miscellaneous.

00:05 Miscellaneous: exec().

00:10 exec() is a function which allows you to execute any Python code. You may have seen warnings about using this. Initially it looks like a really great idea because you can execute any Python code that you input into it, which allows you to construct the code from the program and then execute it.

00:28 But you have to be extremely careful about this because it can execute any code, and this toy example will show you why this can be dangerous. In this case, elsewhere in my program I happened to have imported the os module, which gives you lots of useful features to do with the operating system of the computer the program is running on, and it gives us access to lots of really useful functions.

00:52 It’s quite common to import os as it allows you to construct file paths, et cetera. So let’s say that’s happened, and now the rest of our program allows us to input some code which will then be executed. So we’ll have job = input('Tell me a task to do: '), and now I’m going to be able to input any Python I want, and then we’re going to execute that using exec().

01:16 That looks great! So now, instead of me having to write some Python code, I can just enter it and that will run it for me. So let’s see that working. Here we see it running in the manner I intended. It asks for tasks to do, and as you can see, I can enter print('Hello World!'),

01:37 and it will execute that, and that works, and that’s great. But the problem is we’ve now allowed the execution of any Python code. Unfortunately, in this case, because I’ve imported the os module I can execute a program on the system that it’s running on using os.system() and in this case, just 'ls' , which lists the directory in which the program is running in and we can see all the files that are there. So now running it again, I can use another os.system() call and remove one of the files which is present—in this case, test.txt.

02:16 That has deleted that file, and just to run it again, pass it os.system('ls') to see a listing, and you can see that test.txt, which was previously present, is no longer there. It’s now gone.

02:34 And this is why you have to be extremely careful when you use exec()—because it’s possible to do something which can damage the system. So you have to be very, very careful to ensure that what’s going into exec() has been completely sanitized, because if not, you could open up yourself to things such as deleting the entire system.

02:57 With one command, it would be possible to delete many of the files off the Mac that this is running on. I’m not going to give that as a demonstration. So while exec() is powerful, it’s best avoided unless you’re sure you’re being really careful with it.

03:12 Miscellaneous: eval().

03:17 Similarly to exec(), eval() allows you to evaluate the value of a Python function. It’s often the kind of thing you’d use for a calculator, et cetera.

03:28 In this case, another toy example is going to show this in action. So calc = input('Give me some maths to do: '), and then we’re going to print the value of eval() of calc().

03:45 It will work out calc() and then print that out. Let’s see that working, and here we can see eval.py runs and if we give it some simple maths, 5 + 7, it works.

03:59 But as we’ve seen with exec(), there is a danger. If elsewhere in our code we’ve imported a library such as os, that will be accessible.

04:11 So we can do much the same as we saw before, so if I enter os.system('ls'), we’ll get a result—and you can also see we have the result 0 because it didn’t return a mathematical value—but it did return the listing.

04:28 And again, we can attack our system using other system commands. Here I can delete deleteme.txt by using os.system('rm') and then the name of the file.

04:44 Lo and behold, that’s gone. If we list that, you can see that deleteme no longer exists. It’s been deleted and it’s gone forever. There is a way to limit this with eval(). So after the argument which will be evaluated, you can pass a dictionary.

05:02 So here, an empty dictionary will mean that no named functions will be accessible. So now our previous attack using os will not be accessible, because it’s not in that list of functions. So if I try it this time,

05:18 you can see that we get a NameError because os is not defined. To add a function that you would like to access, you can add a dictionary in this format. Here’s the name it will be accessible by, and then you can add the function in—remember, no brackets on the end, as we want to add the function itself.

05:40 And now we can use pow(). So in this case, we can enter pow(3, 2), and as you can see, that returns a value we would expect. So eval() can be made safer using this technique, but it’s important to ensure with anything which will execute code, that you are not executing unwanted code at any point.

06:03 This is clearly not going to be a problem if you’re the only user of the code, but many has been a time when such code has then been allowed to be used by others and problems have arisen. So be really careful with both exec() and eval().

06:20 Miscellaneous: hash().

06:26 The hash() function is extremely useful. It may be something that you’ve heard of when downloading files et cetera for checksums, but simply put, a hash is a one-way function which gives a summary of the contents that it’s been passed. So in this context, it could be a str, an int, or any other variable or object that has been passed.

06:49 In Python, it’s easily accessible using the hash() function, and here you can see that printing the hash(123) leads to the value 123.

07:02 The hash of any int will actually be the value of the int, so any integer, as you can see here with 123456, is the value of the number itself.

07:14 As soon as you work with text, as seen here, the hash is a lot bigger,

07:24 but now you’re going to see how this can be useful. If you have two pieces of text which are very similar—so here, the text 'hello from real python!' with an exclamation mark and here, the same text with a full stop ('.') at the end. One ends in an exclamation mark, one in a full stop, and that’s the only difference between the two. Running this shows that these two hashes are completely different, despite this small difference between the two inputs.

07:56 As a result, it’s very quick and easy to see if there’s a difference between two supposedly identical files or inputs. So in that previous example, it was easy to see the difference between the two visually, but here you’re going to see it applied to the entire content of a book, with a single character’s difference between the two. This is going to make use of the file open() command, which you saw earlier on in the course, but it will be placed inside a context manager. Again, if you’re not sure how to do this, have a look at Real Python’s course on reading and writing files.

08:30 Here we’re going to use the with context manager, open the 'moby_dick.txt' text in read mode using 'r', and then that’s going to be referred to as file_obj in this context.

08:47 And then the moby_list, which will be a list of each of the lines in that text file, is created using the file_obj.readlines() method, which reads all of the lines from file_obj and places them into moby_list.

09:03 That’s the first step, and the file has now been closed, and now we’re going to create two texts. The first one, moby_1, is using the .join() method with moby_list, so that will turn moby_list into a single string.

09:20 And moby_2 is going to be exactly the same.

09:25 At this point, these two are exactly the same, and we can check that by checking the equivalencies of the hashes. So here, the print statement contains the equivalence of hash one and hash two, and if they’re the same, we should see the output True.

09:42 Let’s see that in action.

09:48 And we see True because they’re identical. Next, moby_2 will have a single space (' ') added to the end. This is simple to do using moby_2 += ' '.

10:05 And now we’ll repeat that, checking of the hash equivalence between the two, and run that. And we can see that True before, but afterwards the hashes are different.

10:21 Now let’s print the length of moby_1,

10:25 and you can see that this is over 1.2 million characters, which it’s checked quickly and told us that they’re not the same. Now clearly, doing that by hand would be incredibly long.

10:38 Even writing a for loop that checked through each character would be a very long-winded way, whereas using hash() allows you to check quickly and accurately whether two objects are identical—not the same object, but whether their contents’ are identical.

kenyonej on April 13, 2020

Hi, I heard you say don’t put [] at the end of the code, what is the purpose of brackets, or may i ask, when or when not to use brackets? I know they’re used to create a list, but what other scenarios do we need to use them? Sorry if i missed your explanation of this…

Darren Jones RP Team on April 14, 2020

Hi. Can you point me to the time that you’re referring to for some context? [] will typically be used to either create a list (whether in a comprehension or direct assignment, but also for indexing elements or slices. They’re often used to access dictionary items:

my_dict["one"] = "Ein"
print(my_dict["one"])

but using the .get() method of the dictionary is generally a safer way to work.

kenyonej on April 16, 2020

Hi Darren,

I should’ve made note of the reference to which video at which time when you indicated it, but you are correct, you could’ve been simply referring to the context in which you used the []. I didn’t make note of it, so I’d at least have to go through the module again and listen. But, your explanation actually answers what I was asking. Thanks!!

Adam on May 3, 2020

it is very clear explanation.Thanks

eleven2 on May 18, 2020

at time 5:30 it was. What Darren meant was not to use pow() as a dict value, rather pow without (). print(eval(calc, {‘pow’:pow()})) would not allow to use the function itself.

jckichuk on July 4, 2020

Hi Darren, when checking if too strings are the same, why bother hashing them first? Does python not allow direct comparisons between the strings themselves? Could you not print(moby_1 == moby_2) ?

@jckichuk, Python does allow direct comparisons, but in his example the usage of a direct comparison on a book’s contents would not have made sense, I believe, when it comes to performance. It should be much quicker to hash and then compare whether they are identical.

pamizu2 on Oct. 25, 2020

Well then what’s the difference of using between id() and hash()?

Bartosz Zaczyński RP Team on Oct. 26, 2020

@pamizu2 It’s a question of identity versus value. You can have two distinct string objects at different memory addresses but having the same value, for example:

>>> a = "Hello world"
>>> b = "Hello world"
>>> id(a) == id(b)  # Same as "a is b"
False
>>> hash(a) == hash(b)  # Same as "a == b"
True

The id() tells you the object’s identity, which is guaranteed to be unique among simultaneously existing objects. In CPython, it’s the memory address of the object.

The hash() function, on the other hand, calculates a number based on the object’s value. In more complex data types, hashing may sometimes consider only a few attributes of the object to calculate that number.

DoubleA on Jan. 20, 2021

Hello guys,

Thank you for creating such a useful summary of the most commonly used functions. My question is about the hash() function:

am I right saying that this function accepts immutable data types only, such as tuples, strings, frozensets? The logic behind this would be that the output of the hash() function cannot relate to something which is mutable in the first place, e.g. a list or a dict? If I try this code: print(hash({'a':1, 'b':2})) I get

TypeError: unhashable type: 'dict'

But if I pass a string or a tuple to the hash() function then I get a proper hash value associated with it:

print(hash((1,'test'))

-7658849480319937646

Thank you!

Bartosz Zaczyński RP Team on Jan. 21, 2021

@DoubleA Mutability and hashability are distinct but related concepts.

The hash() function only cares about the latter, so it can accept mutable objects such as instances of your custom class:

>>> class Person:
...     def __init__(self, name):
...         self.name = name
...     def __hash__(self):
...         return hash(self.name)
... 
>>> jdoe = Person("Joe")
>>> hash(jdoe)
-6168151968020744048
>>> jdoe.name = "Bob"
>>> hash(jdoe)
4782845501210272724

An object passed to hash() can change its internal state at any time, which may or may not result in a different hash value depending on how the .__hash__() method was implemented.

Python’s built-in types that are mutable don’t implement hashing, which makes them unhashable.

Natasha Kaplan on Jan. 3, 2024

Hi! can you explain what each thing is doing? For scopes, you never explained what a global scope and local is, just how to access it and it was really confusing!

Bartosz Zaczyński RP Team on Jan. 3, 2024

@Natasha Kaplan Check out these additional resources to learn more about scopes in Python:

Become a Member to join the conversation.