Switching to Classes object and attribute problems

This is the place for queries that don't fit in any of the other categories.

Switching to Classes object and attribute problems

Postby KHarvey » Thu Apr 11, 2013 3:52 pm

I'm starting to attempt to use classes. So I am having a little problem figuring out classes. I have read through around a dozen tutorials and I kind of follow but I am still having a lot problems.

I am using classes to try and limit my global variables and as glorified functions. At least I think that is what I am supposed to use them for. Kind of like a blue print to my code.

I'm also trying to make this class generic enough that it will work with multiple things. In my case multiple scan methods and multiple different types of scan data.

At first I thought that I would be able to create a class like this:
Code: Select all
class ScanData(object):
   def __init__(self, ip, fqdn, scan_type, raw_data, parsed_data):
      self.ip = ip
      self.ip.fqdn = fqdn
      self.ip.scan_type = scan_type
      self.ip.raw_data = raw_data
      self.ip.parsed_data = parsed_data
      
snmp_scan_data = ScanData("10.1.2.3", "EMail01", "snmp", "Random Junk Windows 6.1", "")
snmp_scan_data = ScanData("10.1.2.4", "EMail02", "snmp", "Random Junk Windows 5.0", "")
print snmp_scan_data.ip
print snmp_scan_data.ip.fqdn
print snmp_scan_data.ip.scan_type
print snmp_scan_data.ip.raw_data
print snmp_scan_data.ip.parsed_data

The goal was to be able to loop through each of the IP's in snmp_scan_data and pull snmp_scan_data.ip.fqdn
Of course this does not work because I can't assign self.ip.fqdn because fqdn is a string and not an attribute. So I think that I need to create the attribute.

My next attempt was to create a dictionary inside the class like this:
Code: Select all
class ScanData(object):
   def __init__(self):
      self.scan_data = {}
      
   def ip_add(self, ip):
      self.scan_data[ip] = ""
      
   def fqdn_add(self, ip, fqdn):
      self.scan_data[ip].fqdn = fqdn
   
snmp_scan = ScanData()
snmp_scan.ip_add("10.1.2.3")
snmp_scan.fqdn_add("10.1.2.3", "EMail01")

But once again this code fails as well, as self.scan_data[ip].fqdn has no attribute fqdn.
I think that this code is closer to what I should be doing though, other than it being wrong.

I think my issue is that I do not know how to properly assign attributes to objects. Or rather I do not know how to tier attributes on objects.

I thought about doing something like this:
Code: Select all
class ScanData(object):
   def __init__(self, ip, fqdn, scan_type, raw_data, parsed_data):
      self.ip = ip
      self.fqdn = fqdn
      self.scan_type = scan_type
      self.raw_data = raw_data
      self.parsed_data = parsed_data
      
snmp_scan_data = ScanData("10.1.2.3", "EMail01", "snmp", "Random Junk Windows 6.1", "")
snmp_scan_data = ScanData("10.1.2.4", "EMail02", "snmp", "Random Junk Windows 5.0", "")

But then I am overwriting each variable everytime I assign anything, and then there is no linkage between the IP and info associated with the IP.

I could just move my dictionaries completely into a class like this:
Code: Select all
class ScanData(object):
   def __init__(self):
      self.scan_data = {}
      
   def ip_add(self, ip):
      self.scan_data[ip] = []
      
   def fqdn_add(self, ip, fqdn):
      self.scan_data[ip].append(fqdn)
      
   def scan_type_add(self, ip, scan_type:
      self.scan_data[ip].append(scan_type)
   
snmp_scan = ScanData()
snmp_scan.ip_add("10.1.2.3")
snmp_scan.fqdn_add("10.1.2.3", "EMail01")
snmp_scan.scan_type_add("10.1.2.3", "snmp")

This kind of works, but I am not sure if this is how I should be doing this. It feels like I am just moving my code straight into a class rather than refactoring it into a class and using objects and attributes.

Once I am able to get the attributes assigned to the objects, then I will create another function under the class that parses the data that looks something like this:
Code: Select all
class ScanData(object):
   xxxx Code that I can't figure out xxxx
   
   def parse_add(self, ip, find_criteria):
      for criteria, value in find_criteria:
         if criteria in self.scan_data[ip].raw_data:
            self.scan_data[ip].parsed_data = value
            
find_criteria = ("Windows 6.1":"Windows 2008", "Windows 5.0":"Windows 2003")
snmp_scan.parse_parse("10.1.2.3", find_criteria)

Or something along those lines. I will need to think about the parse_add function a little bit more to make sure that it is generic enough to identify OS, MACs, etc..

I've tried several iterations of each piece of code above. I think I understand classes, I just can't figure out the data flow through the classes or proper assignments of objects and attributes.

I will be tiering my attribute creation as well. Meaning I will loop through a database to generate the IP and FQDN. After that I will run a scan based on the IP and then add in the scan_type and raw_data. Once all that is complete then I will add in the parsed_data.

Maybe I need to be using __dict__ and define my own dictionary?

I'll keep playing around with this, but if you have any suggestions, or tutorials that I can try reading. I have already read the first two pages of tutorials of a Google search for Python class. As I learn more I will re-read the tutorials to see if they make any more sense.

Now that I remember there was a tutorial section here as well. I'll go try reading through it now, but since I have already typed all of this up I am still posting it :P
KHarvey
 
Posts: 34
Joined: Tue Mar 19, 2013 5:13 pm
Location: US

Re: Switching to Classes object and attribute problems

Postby KHarvey » Thu Apr 11, 2013 4:08 pm

Aw crap. I should have read the tutorials here first.
ichabod801 You did an awesome job.

I haven't completed the tutorial yet, but I figured I should post the direction that I am going now:
Code: Select all
class ScanData(object):
   def __init__(self, fqdn, scan_type, raw_data, parsed_data):
      self.fqdn = fqdn
      self.scan_type = scan_type
      self.raw_data = raw_data
      self.parsed_data = parsed_data
      
scan_data = {}
ip = "10.1.2.3"
scan_data[ip] = ScanData("EMail01", "snmp", "Windows 6.1", "")

print scan_data[ip].fqdn
print scan_data[ip].scan_type


Or more precisely:
Code: Select all
class ScanData(object):
   def __init__(self):
      pass
   
   def fqdn_add(self, fqdn)
      self.fqdn = fqdn
      
   def scan_type_add(self, scan_type)
      self.scan_type = scan_type
      
scan_data = {}
ip = "10.1.2.3"
scan_data[ip] = ScanData()
scan_data[ip].fqdn_add("EMail01")
scan_data[ip].scan_type_add("snmp")

print scan_data[ip].fqdn
print scan_data[ip].scan_type


So is this the proper way that I should be doing this?
KHarvey
 
Posts: 34
Joined: Tue Mar 19, 2013 5:13 pm
Location: US

Re: Switching to Classes object and attribute problems

Postby KHarvey » Thu Apr 11, 2013 6:17 pm

After trying to refactor some of my code I came to a realization. The reason that I should be using classes is that it makes my code more extensible.

For example if my starting data set is:
Code: Select all
ip_address = "10.1.2.3"
mac_address = "00:a0:b1:c2"
dns = "EMail01"

ip_address = "10.1.2.4"
mac_address = "00:a1:b2:c3"
dns = "EMail02"


And I create a dictionary using a tuple with this data:
Code: Select all
data["10.1.2.3"] = ("00:a0:b1:c2", "EMail01")
data["10.1.2.4"] = ("00:a1:b2:c3", "EMail02")

Then I would not be able to update anything unless I pop the dictionary key and value and readd it.

But if I use a class then I can be fully extensible by just adding another function to the class:
Code: Select all
class ScanData(object):
   def __init__(self):
      pass
   
   def mac_add(self, mac)
      self.mac = mac
      
   def dns_add(self, dns)
      self.dns = dns
      
scan_data = {}
scan_data["10.1.2.3"] = ScanData()
scan_data["10.1.2.4"] = ScanData()
scan_data["10.1.2.3"].dns_add("EMail01")
scan_data["10.1.2.3"].mac_add("00:a0:b1:c2")
scan_data["10.1.2.3"] = ScanData()
scan_data["10.1.2.4"].dns_add("EMail02")
scan_data["10.1.2.4"].mac_add("00:a1:b2:c3")


If I want to update a value I would just:
Code: Select all
scan_data["10.1.2.3"].dns = "MailServer01")


If I wanted to add another attribute then I could just:
Code: Select all
class ScanData(object):
   def __init__(self):
      pass
   
   def mac_add(self, mac)
      self.mac = mac
      
   def dns_add(self, dns)
      self.dns = dns

   def status_add(self, status)
      self.status = status
      
scan_data = {}
scan_data["10.1.2.4"].status_add("deprecated")


So I am starting to see more of the benefits, I guess I was being complacent and stubborn for no real benefit.

As long as I am not horribly misunderstanding something, then I am now debating how much of my code I should move into classes. Do I move my functions? Do I move the parsing of data?

Right now I am playing around with a simple ping sweep:
Code: Select all
class ScanData(object):
   def __init__(self):
      pass
   
   def scan_type_add(self, scan_type)
      self.scan_type = scan_type
      
   def dns_add(self, dns)
      self.dns = dns

network_segments = ("10.1.2.3", "10.1.2.4")
nmap_ips = []
for network_segment in network_segments:
   nmap_command = ("sudo nmap %s -sP"
         % network_segment
   )
   nmap_ip_scan = subprocess.Popen(
         nmap_command, stdin=None, stdout=-1,
         stderr=None, shell=True
   )
   while True:
      read_line = nmap_ip_scan.stdout.readline()
      if not read_line: break
      nmap_ips.append(read_line.strip())
      
scan_data = {}
for nmap_ip in nmap_ips:
   #Raw data looks like
   #Nmap scan report for eccol0016.eccogroup.corp (10.1.2.114)
   if "Nmap scan report" in nmap_ip:
      ip = nmap_ip[regIP.search(nmap_ip.start():
            regIP.search(nmap_ip.end()]
      scan_data[ip] = ScanData()
      nmap_lines = nmap_ip.split(" ")
      if len(nmap_lines) == 6:
         nmap_dns = nmap_lines[4].split(".")[0]
         scan_data[ip].dns_add(nmap_dns)
         scan_data[ip].scan_type_add("nmap")

Minus the fact that I have a better way of doing the parsing now. Should I move all of that processing to a class? Would I move it before the if "Nmap scan report" in nmap_ip? Or should I move the parsing itself?
If I put it in a class I would probably put it into an inherited class
Code: Select all
class NmapScanData(ScanData)
   def nmap_processing(self, raw):
      ip = nmap_ip[regIP.search(nmap_ip.start():
            regIP.search(nmap_ip.end()]
      scan_data[ip] = ScanData()
      nmap_lines = nmap_ip.split(" ")
      if len(nmap_lines) == 6:
         nmap_dns = nmap_lines[4].split(".")[0]
         scan_data[ip].dns_add(nmap_dns)
         scan_data[ip].scan_type_add("nmap")

Or maybe just dump the whole nmap_ips into a class.

In theory I am making progress.

Any suggestions.
KHarvey
 
Posts: 34
Joined: Tue Mar 19, 2013 5:13 pm
Location: US

Re: Switching to Classes object and attribute problems

Postby setrofim » Thu Apr 11, 2013 10:31 pm

You are getting there. I suggest you keep going through ichabod801's tutorials.

Here are a few pointers:
  • The main reason for using classes is encapsulation. Classes are useful because they can abstract away the implementation detains and expose only the high level concepts. They allow you to introduce problem domain language into your solution.
  • You should definitely not indiscriminately stick everything into a class -- that would defeat the point.
  • Each class should represent a coherent entity. Broadly speaking, you would be mapping the real world objects you're dealing with onto classes.
  • Whenever you have several variables that represent different aspects of some one thing in the real world, you should probably have a class Thing to encapsulate those variables, and you would use methods to manipulate them in a coherent manner.
  • Methods are the verbs of the OO language (they are something that the classes can do) and their names should reflect that, so rather than 'nmap_processing', it should be 'process_nmap'. Also, since it's a member of an NmapScanData class, you shouldn't keep repeating 'nmap' in method names, so it becomes just 'process'. This:
    Code: Select all
    nmap_data.process(raw_value)

    reads more naturally than
    Code: Select all
    nmap_data.nmap_processing(raw_value)

    As with natural languages, there is a kind of grammar to it: subject.verb(object).
  • These kinds of methods:
    Code: Select all
       def scan_type_add(self, scan_type)
          self.scan_type = scan_type

    Are called setters (or mutators, if you want to get all computer science-y). They are a part of the classic Object Oriented model, but their use in Python is strongly discouraged. For simple cases such as this, you would typically assign the member variable directly. So instead of
    Code: Select all
    scan_data[ip].scan_type_add("snmp")

    you would do
    Code: Select all
    scan_data[ip].scan_type = "snmp"

    Technically this breaks encapsulation in the classic OO sense, but in Python, there is no downside to it and it results in cleaner code.
    For more complicated cases (e.g. if you want to validate the value before assigning it), you would use proprieties. The client code would look exactly the same as above, but you would need to implement the class slightly differently: you basically implement what are essentially a getter and a setter and then wrap them in a special construct to make them look like a regular member variable from the outside. See this
setrofim
 
Posts: 288
Joined: Mon Mar 04, 2013 7:52 pm


Return to General Coding Help

Who is online

Users browsing this forum: Google [Bot], snippsat and 4 guests