Function returns – Python

On April 23, 2014, in Python, by Darren

Truth, or not

Defined functions in Python will always return something, even if you don’t specifically return something. If nothing is returned, Python will actually return a value of ‘None’ which is still something. To illustrate this I’ll create a very simple function:

def test(x):
	pass

I’ll create a string and then pass it through the function and do an action based on the return value.

if test(a) is True:
	print(a,"is True")
else:
	print(a,"is not True")

	
string is not True

Python has a few handy shortcuts. Instead of evaluating if test(a) is True, I can simply say if test(a):

if test(a):
	print(a,"is True")
else:
	print(a,"is not True")

	
string is not True

If I return nothing in a function, ‘None’ is actually returned. Any 0 value, False, or None are all returned as non-true values. Let’s test a few return values and see what we get:

def test(x):
	return 1
if test(a):
	print(a,"is True")
else:
	print(a,"is not True")

	
string is True1

1def test(x):
	return -1
if test(a):
	print(a,"is True")
else:
	print(a,"is not True")

	
string is True

I can also implicitly return False values. The following three all have the same result:

def test(x):
	return 0

def test(x):
	return False

def test(x):
	return None
if test(a):
	print(a,"is True")
else:
	print(a,"is not True")

	
string is not True

Note that True, False, None, etc, are keywords and have their first letter capitalised.

Multiple returns

How are multiple values returned from a defined function? Let's check:

def test():
	a = 1
	b = 2
	c = 3
	return a,b,c
y = test()
type(y)
<class 'tuple'>

I'm simply calling the function. When a defined function returns multiple values, those values are returned in a tuple. As before, if any value is a false value we could use that later:

def test():
	a = 1
	b = 2
	c = 0
	return a,b,c
y = test()
>>> for i in y:
	if i:
	    print(i)
		
1
2

The value returned by c is not printed as its not True.

What goes in...

The parameters of a defined function does not have to match the argument we pass into it. The return value also doesn't have to match it:

def say_hello(x):
	a = "Hello "+x
	return a
print(say_hello("Darren"))
Hello Darren

Here I have passed in the string "Darren" as argument 'x'. The function creates a new variable 'a' which concatenates the string "Hello" with string "Darren" passed in. The value returned is the concatenation.
Another important thing to note here is that 'a' returns a value, 'a' itself is not returned. 'a' does not have scope outside the function:

a
Traceback (most recent call last):
  File "<pyshell#109>", line 1, in <module>
    a
NameError: name 'a' is not defined

I could create a global variable that takes the value of the output of the function:

 b = say_hello("Darren")
b
'Hello Darren'
Tagged with:  

There have been numerous debates recently about how important the CCIE is going forward. My views seem to differ form a lot of others so allow me to get on my own soapbox.

What I am sick of hearing is how a CCIE is just a ‘cli jockey’ – I have not, and have never, considered myself to be that. I’m also sick of hearing that you’re either this or you’re that. People are far more complicated that being either one thing or another.

Different people have different reasons for pursuing the CCIE. I did it because I wanted to prove to myself I could do it. Some people do it to get more money, others to get a job, others for who knows what reason. The CCIE is not an end into itself, it’s merely something you can do if you want.

Does having a CCIE mean that all I’m good at is punching some commands into a router? No, and I find is extremely insulting for anyone to think such a thing. I took a view of learning how things work and fit together. The CLI is there simply to allow you to get to your end goal. The actual commands for me were the easiest part of the entire lab exam. Learning how things fit together and how protocols operate is the real business side of the CCIE in my opinion. This is perhaps why I was able to do the CCIE SP so quickly after doing my JNCIE-SP. The commands, defaults, and capabilities of each is the only difference. The technology is the same.

The same goes for programming really. You have something you want to do. You could use a load of different languages to get there. The language you use is irrelevant to the underlying problem you are trying to solve.

If you think that the CCIE is merely there to teach you to be a ‘cli jockey’ then I feel pretty sorry for you.

The thought that a programmer is inherently better at designing a network than a network engineer is, is laughable. The very thought that you are either one or the other is also laughable. The thought that a programmer is a better problem solver because they are able to break down big problems into smaller ones is also incorrect. We’ve all been doing this for ages. The very act of troubleshooting and designing large networks is all about this. You don’t just draw one big cloud and say there’s your network. The devils in the smaller details and how each small part operates in the bigger picture to give you your end goal. This could be applied to so many things.

For me, learning itself is one of my favourite processes. Learning anything. Heck if I could somehow spend 6 months every year doing nothing but attending lectures and studying I would do it at the drop of a hat. It’s what I love to do. CCIE before, Python next. Then what? Maybe Physics? Who knows. Will it help me with my career? Probably not, but I’m pretty sure I’ll enjoy it nonetheless!

 

Passing an argument into Python

On April 16, 2014, in Python, by Darren

Up until now, I’ve been reading a file of a list of devices to log into and do some work on. That’s ideal when I have a load of routers to check, but sometimes I’d like to check one router quickly directly.

Using my OSPF Checker as an example,I currently iterate through a list of lines in devices.txt that the app then logs into. I can instead make it so that if an argument is passed in, that becomes the list and the file is ignored. If no argument is passed, I can read in the original list of devices.

To demonstrate, I’ll set up a really simple script:

#!/usr/bin/python3
import sys
print("You have passed in",sys.argv)

If I run this now form the cli I get the following:

darreno@Jumpbox:~/git/ospf_checker$ ./arg.py
You have passed in ['./arg.py']

There is only a single argument, as the script itself counts itself as arg location 0. If I pass in an argument:

darreno@Jumpbox:~/git/ospf_checker$ ./arg.py test
You have passed in ['./arg.py', 'test']

Most of the time I want to do some work on what’s been added after the script name. We can just be sure to look at that:

#!/usr/bin/python3
import sys
print("You have passed in",sys.argv[1])

Now when run:

darreno@Jumpbox:~/git/ospf_checker$ ./arg.py test
You have passed in test

If I run the script without any arguments, I’ll get a traceback as I’m referencing an index value that doesn’t exist:

darreno@Jumpbox:~/git/ospf_checker$ ./arg.py
Traceback (most recent call last):
  File "./arg.py", line 3, in 
    print("You have passed in",sys.argv[1])
IndexError: list index out of range

I can use this indexerror to do something else if no argument exists:

#!/usr/bin/python3
import sys
try:
    print("You have passed in",sys.argv[1])
except IndexError:
    print("No argument was passed, I'll do something else")
darreno@Jumpbox:~/git/ospf_checker$ ./arg.py test
You have passed in test

darreno@Jumpbox:~/git/ospf_checker$ ./arg.py
No argument was passed, I'll do something else

Very handy.

I’ve just scratched the surface of arguments so I’ll do a more in depth post sometime in the future

Tagged with:  

Defined Functions – Python

On April 14, 2014, in Python, by Darren

Currently my apps pretty much have a single main body in which all the data is collected, processed, and displayed/saved. A more Pythonic way of doing this is to get functions to do the work in a separate part of the application. In the main body I can then ask these functions to crunch some data I pass to it, and then receive a result back. At first this makes my code look a bit longer, but it should make it easier to debug in future as each defined function can be troubleshooted separately.

Let’s take a look at a very simple defined function:

>>> def square(x):
	x*=x
	return x

A function is defined named square. It accepts one argument. Whatever argument is passed to the function is squared, and the result passed back. I can now call square from the main body of the app and pass it my argument and I should get the square of that argument back:

>>> square(10)
100

If I have a variable with a value, I can pass the variable:

>>> a = 50
>>> square(a)
2500

It’s hard to see the benefits when I have a small app like above, but it makes a lot of sense in a much bigger app. I’ve now updated my OSPF Checker app to use definitions instead. The following is a few examples of the code:

def getip(i):
    ip = re.findall(r'Internet Address (\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})',i)
    return ip

def getarea(i):
    area = re.findall(r'Area ([\s]{0,3}[0-9]{1,5})',i)
    return area

for i in ospf:
    ip = getip(i)
    area = getarea(i)

Each definition can now be changed as a separate ‘app’ – A great example is when I ran my script on an LNS box with virtual-access interfaces. The getip function didn’t take into account the slightly different output:

Virtual-Access111 is up, line protocol is up
  Interface is unnumbered. Using address of Loopback56146 (10.9.0.250), Area 0, Attached via Network Statement

I can go in and just change the getip function:

def getip(i):
    ip = re.findall(r'Internet Address (\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})',i)
    if not ip:
        ip = re.findall(r'Interface is unnumbered. Using address of Loopback[0-9]{0,5}',i)
    return ip

App now picks up those sorts of interfaces just fine:

Int:	Virtual-Access266 
IP:	Interface is unnumbered. Using address of Loopback78437
Area:	0
Type:	POINT_TO_POINT
Cost:	1
Hello:	10
Dead:	40
Tagged with:  

Regex – Lookahead Assertions – Python

On April 14, 2014, in Python, by Darren

While trying to scrape information out of a router output, I realised that sometimes I just think about things in the wrong way. Basically I end up making my life difficult when there is usually an easier way to go.

Ultimately I’m not even happy with my result at the moment. I’m trying to work our better ways to get the info I need, but it’s a learning process so I’m happy with that.

Let’s start with this block of text:

cr1.123456#sh ip ospf interface
Loopback0 is up, line protocol is up
  Internet Address 10.90.3.38/32, Area 303953, Attached via Network Statement
  Process ID 1, Router ID 10.90.3.38, Network Type LOOPBACK, Cost: 1
  Topology-MTID    Cost    Disabled    Shutdown      Topology Name
        0           1         no          no            Base
  Loopback interface is treated as a stub Host
GigabitEthernet0/1 is up, line protocol is up
  Internet Address 172.16.13.150/29, Area 303953, Attached via Network Statement
  Process ID 1, Router ID 10.90.3.38, Network Type BROADCAST, Cost: 1
  Topology-MTID    Cost    Disabled    Shutdown      Topology Name
        0           1         no          no            Base
  Transmit Delay is 1 sec, State DR, Priority 1
  Designated Router (ID) 10.90.3.38, Interface address 172.16.13.150
  No backup designated router on this network
  Timer intervals configured, Hello 10, Dead 40, Wait 40, Retransmit 5
    oob-resync timeout 40
    No Hellos (Passive interface)
  Supports Link-local Signaling (LLS)
  Cisco NSF helper support enabled
  IETF NSF helper support enabled
  Index 3/3, flood queue length 0
  Next 0x0(0)/0x0(0)
  Last flood scan length is 0, maximum is 0
  Last flood scan time is 0 msec, maximum is 0 msec
  Neighbor Count is 0, Adjacent neighbor count is 0
  Suppress hello for 0 neighbor(s)
  Message digest authentication enabled
      No key configured, using default key id 0
GigabitEthernet0/0.2561 is up, line protocol is up
  Internet Address 10.22.0.117/30, Area 303953, Attached via Network Statement
  Process ID 1, Router ID 10.90.3.38, Network Type POINT_TO_POINT, Cost: 1
  Topology-MTID    Cost    Disabled    Shutdown      Topology Name
        0           1         no          no            Base
  Transmit Delay is 1 sec, State POINT_TO_POINT
  Timer intervals configured, Hello 10, Dead 40, Wait 40, Retransmit 5
    oob-resync timeout 40
    Hello due in 00:00:05
  Supports Link-local Signaling (LLS)
  Cisco NSF helper support enabled
  IETF NSF helper support enabled
  Index 1/1, flood queue length 0
  Next 0x0(0)/0x0(0)
  Last flood scan length is 1, maximum is 1
  Last flood scan time is 0 msec, maximum is 0 msec
  Neighbor Count is 1, Adjacent neighbor count is 1
    Adjacent with neighbor 217.196.224.51
  Suppress hello for 0 neighbor(s)
  Message digest authentication enabled
    Youngest key id is 1

I would like to extract information from the above text. I want my app to list each OSPF enabled interface, plus its properties.

Initially I had my script look for various expressions, and then iterate through to give me what I wanted. However there was a serious issue with this:

 i = re.findall('(?:GigabitEthernet|FastEthernet|Serial|Tunnel|Loopback|Dialer|BVI)[0-9]{1,4}/?[0-9]{0,4}.?[0-9]{0,4}/?[0-9]{0,3}/?[0-9]{0,3}/?[0-9]{0,3}:?[0-9]{0,3}',output)
ip = re.findall(r'(\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})',output)
A = re.findall(r'Area[\s]{0,3}[0-9]{1,5}',output)
n = re.findall(r'Network Type[\s]{0,3}[a-zA-Z_]{0,20}',output)
C = re.findall(r'Cost:[\s]{0,3}[0-9]{1,5}',output)
h = re.findall(r'Hello[\s][0-9]{1,3}',output)
D = re.findall(r'Dead[\s][0-9]{1,3}',output)
for a,b,c,d,e,f,g in zip(i,ip,A,n,C,h,D):
    print("\n"+a)
    print(b)
    print(c)
    print(d)
    print(e)
    print(f)
    print(g)

The problem with the above code is that Loopback interface don’t have hello and dead timers. This meant the output scraped ended up showing the first capture of a Hello: being matched with the Loopback0 interface. It also meant the last few interfaces were not shown. Basically the output was completely wrong:

Darrens-MacBook-Pro:Desktop darrenoconnor$ python3 1.py

Loopback0
10.90.3.38
Area 30395
Network Type LOOPBACK
Cost: 1
Hello 10
Dead 40

GigabitEthernet0/1
10.90.3.38
Area 30395
Network Type BROADCAST
Cost: 1
Hello 10
Dead 40

My first thought was to try and split the output into three separate strings. Each interface would be the start of the string and I could then search in the smaller string. Essentially like this:

cr1.123456#sh ip ospf interface

======== Begin String One ==========
Loopback0 is up, line protocol is up
  Internet Address 10.90.3.38/32, Area 303953, Attached via Network Statement
  Process ID 1, Router ID 10.90.3.38, Network Type LOOPBACK, Cost: 1
  Topology-MTID    Cost    Disabled    Shutdown      Topology Name
        0           1         no          no            Base
  Loopback interface is treated as a stub Host
========= End String One ===========

======== Begin String Two ==========
GigabitEthernet0/1 is up, line protocol is up
  Internet Address 172.16.13.150/29, Area 303953, Attached via Network Statement
  Process ID 1, Router ID 10.90.3.38, Network Type BROADCAST, Cost: 1
  Topology-MTID    Cost    Disabled    Shutdown      Topology Name
        0           1         no          no            Base
========= End String Two ===========

======== Begin String ETC ==========

At first this turned out to be a lot hardher than I thought it would be. Initially I tried to count the lines in which the interface name appeared. I would then split from 0 to the first point, then from the first point to the second point. this turned out to be a lot more difficult than I thought. Counting the lines where the interface names appeared was easy:

int = re.compile(r'GigabitEthernet|FastEthernet|Serial|Tunnel|Loopback|Dialer|BVI')
for (i,line) in enumerate(output):
   if int.match(line):
      for match in int.findall(line):
         print (i,line)

This gives me:

Darrens-MacBook-Pro:Desktop darrenoconnor$ python3 1.py
1 Loopback0 is up, line protocol is up

7 GigabitEthernet0/1 is up, line protocol is up

29 GigabitEthernet0/0.2561 is up, line protocol is up

But thinking about this, this simply cannot be very efficient. Get some output, convert to string, convert to list, convert back to string based on line numbers? Also Python doesn’t seem to make it very easy to create strings based on lines numbers of a list. At least I haven’t found.

So after trying for a few hours to get the above to work, I though there simply had to be a better way. Why not read the router output into a string, and then split that string based on the first occurence of the interface name. That should give me separte strings for each interface.

output_split = re.split(r'GigabitEthernet|FastEthernet|Serial|Tunnel|Loopback|Dialer|BVI', output)
print(len(output_split))
for i in output_split:
    print(i)

However this creates a big issue. When splitting on a matching regex group, the part matched will be removed from the string. I did get the right amount of splits at least. I would expect a list length of 4 as there are three interfaces as well as the first line of show ip ospf interfaces:

Darrens-MacBook-Pro:Desktop darrenoconnor$ python3 1.py
4

However look at my actual output:

 is up, line protocol is up
  Internet Address 10.90.3.38/32, Area 303953, Attached via Network Statement
  Process ID 1, Router ID 10.90.3.38, Network Type LOOPBACK, Cost: 1
  Topology-MTID    Cost    Disabled    Shutdown      Topology Name
        0           1         no          no            Base

0/1 is up, line protocol is up
  Internet Address 172.16.13.150/29, Area 303953, Attached via Network Statement
  Process ID 1, Router ID 10.90.3.38, Network Type BROADCAST, Cost: 1
  Topology-MTID    Cost    Disabled    Shutdown      Topology Name

The interface names matched are now gone!

Python’s re module allows you to do a lookahead assertion. Basically this means it can look forward and match a regex, but the matched regex is NOT removed when doing the split. This is exactly what we want.

Now I’ll match on a newline followed immidiately by my interface name. I’ll split on the newline, but do a lookahead assertion on the interface name itself. To do a lookahead assertion you match the lookahead with (?=) I still want to remote the newline so that will come before the lookahead. For now I’ll also add a couple of * and newlines so you can see where the split occurs when printed out.

output_split = re.split(r'\n(?=GigabitEthernet|FastEthernet|Serial|Tunnel|Loopback|Dialer|BVI)', output)
print(len(output_split))
for i in output_split:
    print("\n******\n"+i)

I should now have a list length of 4, and my interface names intact:

Darrens-MacBook-Pro:Desktop darrenoconnor$ python3 1.py
4

******
cr1.123456#sh ip ospf interface

******
Loopback0 is up, line protocol is up
  Internet Address 10.90.3.38/32, Area 303953, Attached via Network Statement
  Process ID 1, Router ID 10.90.3.38, Network Type LOOPBACK, Cost: 1
  Topology-MTID    Cost    Disabled    Shutdown      Topology Name
        0           1         no          no            Base
  Loopback interface is treated as a stub Host

******
GigabitEthernet0/1 is up, line protocol is up
  Internet Address 172.16.13.150/29, Area 303953, Attached via Network Statement
  Process ID 1, Router ID 10.90.3.38, Network Type BROADCAST, Cost: 1
  Topology-MTID    Cost    Disabled    Shutdown      Topology Name
        0           1         no          no            Base

The rest of the app is now easy. I can iterate through my split list. If no hello/dead timer is found in the loopback split, it doesn’t add it to the loopback section. My interfaces will now all be shown with the correct values.

Ultimately after cleaning up some outputs my output now gives me:

Darrens-MacBook-Pro:Desktop darrenoconnor$ python3 1.py

Int:	Loopback0
IP:	10.90.3.38
Area:	30395
Type:	LOOPBACK
Cost	1

Int:	GigabitEthernet0/1
IP:	172.16.13.150
Area:	30395
Type:	BROADCAST
Cost	1
Hello:	10
Dead:	40

Int:	GigabitEthernet0/0.2561
IP:	10.22.0.117
Area:	30395
Type:	POINT_TO_POINT
Cost	1
Hello:	10
Dead:	40

I’ve moved the above script now to github

There are still a large amount of changes I’d like to make. The next step is to move the regular expressions to some definitions. For now it’s doing what I want it to do.

Tagged with:  

© 2009-2014 Darren O'Connor All Rights Reserved