In the code below, I am trying to find a way to eliminate self.owner from the L3Address() class... the idea is that I want to know who owns the IP address on a given Vlan or Interface without having to explicitly call it out.
Is there a better way to derive the IPv4 addresses' owner (perhaps through introspection)?
import ipaddr
class SomethingOwned(object):
def __init__(self, owner=None):
self._owner = owner
# Build self.owner (the object instance that owns the subclass object)
self._create_owner()
def _create_owner(self):
"""
create self.owner and ensure the subclass was called correctly
"""
if not (self._owner is None):
self.owner = self._owner
else:
raise ValueError, "%s() must be called with an 'owner' arg, which cannot be None" % self.__class__.__name__
class L3Address(SomethingOwned):
"""
A Layer3 IP PDU address that has an owning object
"""
def __init__(self, version=None, owner=None, addr=None, masklen=None):
# Call SomethingOwned.__init__(owner=owner) to add an owner attribute
super(L3Address, self).__init__(owner=owner)
self._addr = addr
self._masklen = masklen
self._version = version
# Build self._obj
self._create_ip_object()
self.addr = self._obj.ip
self.netmask = self._obj.netmask
self.masklen = self._obj.prefixlen
def __repr__(self):
return "<IPv%i %s/%s>" % (self._version, self.addr, self.masklen)
def _create_ip_object(self):
"""
create self._obj and ensure the subclass was called with the correct version
"""
if self._version==4:
if (self._masklen is None):
self._obj = ipaddr.IPv4Network(self._addr)
else:
self._obj = ipaddr.IPv4Network("%s/%s" % (self._addr, self._masklen))
elif self._version==6:
if (self._masklen is None):
self._obj = ipaddr.IPv6Network(self._addr)
else:
self._obj = ipaddr.IPv6Network("%s/%s" % (self._addr, self._masklen))
else:
raise ValueError, "Version must be 4 or 6"
class IPv4(L3Address):
def __init__(self, **kwargs):
## Initialize the IPv4 network object instance
super(IPv4, self).__init__(version=4, **kwargs)
class IPv6(L3Address):
def __init__(self, **kwargs):
## Initialize the IPv6 network object instance
super(IPv6, self).__init__(version=6, **kwargs)
class Vlan(object):
def __init__(self, name=None, id=None, ipv4=None):
self.id = id
self.name = name
if not (ipv4 is None):
### NOTE: I am trying to eliminate the need for the owner arg here
self.ipv4 = IPv4(owner=self, addr=ipv4)
def __repr__(self):
return "Vlan %s (name: %s)" % (self.id, self.name)
class Interface(object):
def __init__(self, id=None, ipv4=None):
self.id = id
self.ipv4 = None
if not (ipv4 is None):
### NOTE: I am trying to eliminate the need for the owner arg here
self.ipv4 = IPv4(owner=self, addr=ipv4)
def __repr__(self):
return "Interface %s" % self.id
if __name__=='__main__':
def find_owner(ip_instance):
print "Owner of '%s' is '%s'" % (ip_instance, ip_instance.owner)
find_owner(Interface(id='ge-0/0/0', ipv4='1.1.1.1').ipv4)
find_owner(Vlan(id='25', name='accounting', ipv4='1.1.1.2/25').ipv4)
Execution Results:
[mpenning#hotcoffee ~]$ python cisco.py
Owner of <IPv4 1.1.1.1/32> is 'Interface ge-0/0/0'
Owner of <IPv4 1.1.1.2/25> is 'Vlan 25 (name: accounting)'
[mpenning#hotcoffee ~]$
Your current approach using owner is the probably the cleanest solution.
That being said, if you need to find out who owns an IP address, then gc.get_referrers() may be of help.
There is no way to just remove the owner field from L3Protocol and just magically determine it at runtime. What you're essentially asking is "what object has a reference to this one?". In the general case of course that doesn't even make sense, as there could be any number of objects that contain references to the L3Protocol instance (and each of them could even contain any number of references to the L3Protocol instance).
So either you're going to have to record the owner of each L3Protocol in some way, or you're going to have to only ask for the owner of an L3Protocol in a context in which more information is available; owner could theoretically be a function that takes an L3Protocol and a universe of potential owners and returns the one that owns the L3Protocol (or None). If you have an object that records all the Interfaces and Vlans, then making it a method on that object would work.
But it looks like the way you're doing it is pretty much the most straightforward way I can think of offhand, so long as you make sure you keep the bi-directional links (owner -> owned and owned -> owner) consistent, if they ever change.
Related
Here in this code I am just calling out my static method, but it prints my instance variable too. Could you please explain the reason for that, and how to avoid them being printed?
Like below:
I am a static Method
None
class Player:
def __init__(self, name = None):
self.name = name # creating instance variables
#staticmethod
def demo():
print("I am a static Method")
p1 = Player()
print(p1.demo())
As Python docs says:
Print objects to the text stream file, separated by sep and followed
by end. sep, end, file, and flush, if present, must be given as
keyword arguments.
So you can return your message in method and then just print it:
class Player:
def __init__(self, name = None):
self.name = name # creating instance variables
#staticmethod
def demo():
return "I am a static Method"
p1 = Player()
print(p1.demo())
Why does the usage of this descriptor cause Recursion Error?
class Capitalized:
def __set__(self, instance, value):
instance.name = value.upper()
class Person(object):
name = Capitalized()
def __init__(self, name, age):
self.name = name
self.age = age
def __repr__(self):
return f'{self.name} is {self.age} yr(s) old'
Person('dave', 25) # !Error
I get the following error:
RecursionError: maximum recursion depth exceeded while calling a Python object
However, if I don't use name = Capitalized(), everything is fine (well, except that I can't use the descriptor). I am using python v3.9.1.
You are constructing your classes in a way, so that __set__ is calling itself again and again. instance.name = value.upper() is calling Capitalized.__set__
To avoid this you may use a private attribute like this:
class Capitalized:
def __set__(self, instance, value):
print('setting')
instance._name = value.upper()
def __get__(self, instance, objtype=None):
return instance._name
Change instance._name to instance.name and you will see the infinite recursion.
What is the correct way to implement a data descriptor inside a metaclass? In the following (trivial) example, I wish to always append a question mark to the desired value before setting it:
class AddQDesc:
def __init__ (self, name):
self.name = name
def __get__ (self, instance, owner=None):
obj = instance if instance != None else owner
return obj.__dict__[self.name]
def __set__ (self, instance, value):
# What should go here ?
#setattr(instance, self.name, "{}?".format(value)) <- This gives me recursion error
#instance.__dict__[self.name] = "{}?".format(value) <- This gives me proxymapping error
pass
class Meta (type):
var = AddQDesc("var")
class C (metaclass=Meta):
var = 5
print(C.var)
C.var = 1
print(C.var)
First, it looks like the descriptor was not used when I initialized var to 5. Can I somehow apply descriptor protocol here as well? (Make it "5?")
Second, how should the value be updated in the __set__ method? Updating the __dict__ gives me "TypeError: 'mappingproxy' object does not support item assignment" and using setattr gives me "RecursionError: maximum recursion depth exceeded while calling a Python object".
As I commented in the question, this is tricky - because there is no way from Python code to change a class' __dict__ attribute directly - one have to call setattr and let Python set a class attribute - and, setattr will "see" the descriptor in the metaclass, and call its __set__ instead of modifying the value in the class __dict__ itself. So, you get an infinite recursion loop.
Therefore, if you really require that the attribute proxied by the descriptor will "live" with the same name in the class'dict, you have to resort to: when setting the value, temporarily remove the descriptor from the metaclass, call setattr to set the value, and then restoring it back.
Also, if you want the values set in the class body to be handled
through the descriptor, they have to be set with setattr after the
class is created - type.__new__ won't check for the descriptor
as it builds the initial class __dict__.
from threading import Lock
class AddQDesc:
def __init__ (self, name):
self.name = name
self.lock = Lock()
def __get__ (self, instance, owner=None):
obj = instance if instance != None else owner
return obj.__dict__[self.name]
def __set__ (self, instance, value):
owner_metaclass = type(instance)
with self.lock:
# Temporarily remove the descriptor to avoid recursion problems
try:
# Note that a metaclass-inheritance hierarchy, where
# the descriptor might be placed in a superclass
# of the instance's metaclass, is not handled here.
delattr(owner_metaclass, self.name)
setattr(instance, self.name, value + 1)
finally:
setattr(owner_metaclass, self.name, self)
class Meta (type):
def __new__(mcls, name, bases, namespace):
post_init = {}
for key, value in list(namespace.items()):
if isinstance(getattr(mcls, key, None), AddQDesc):
post_init[key] = value
del namespace[key]
cls = super().__new__(mcls, name, bases, namespace)
for key, value in post_init.items():
setattr(cls, key, value)
return cls
var = AddQDesc("var")
class C (metaclass=Meta):
var = 5
print(C.var)
C.var = 1
print(C.var)
If you don't need the value to live in the class' __dict__, I'd suggest just storing it elsewhere - a dictionary in the descriptor instance for example, having the classes as keys, will suffice - and will be far less weird.
class AddQDesc:
def __init__ (self, name):
self.name = name
self.storage = {}
def __get__ (self, instance, owner):
if not instance: return self
return self.storage[instance]
def __set__ (self, instance, value):
self.storage[instance] = value + 1
I am not able to understand how age = AgeDescriptor() line is working .
At first glance It looks like we have three variables, name(instance variable),
age(instance variable), and age(class variable)) . class variable age is initialized to AgeDiscriptori i.e age = AgeDescriptor(). How this code is working internally and how __set__ and __get__ methods of discriptor is getting called when we are setting and getting the age. how this binding is performed internally.
class AgeDescriptor:
def __init__(self):
self._age = 0
def __set__(self, instance, value):
print('setter')
self._age = value
def __get__(self, instance, owner):
print('getter')
return self._age
class Person:
age = AgeDescriptor()
def __init__(self, name, age):
self.name = name
self.age = age
def get_age(self):
return self.age
def set_age(self, value):
self.age = value
if __name__ == "__main__":
e1 = Person("ankit", 29)
From "Programming in Python 3: A Complete Introduction" (chapter 8) by M. Summerfield:
Descriptors are classes which provide access control for the attributes of other classes. Any class that implements one or more of the descriptor special methods, __get__(), __set__(), and __delete__(), is called (and can be used as) a descriptor.
The built-in property() and classmethod() functions are implemented using descriptors. The key to understanding descriptors is that although we create an instance of a descriptor in a class as a class attribute, Python accesses the descriptor through the class’s instances.
So when you access age attribute like e1.age (or self.age) what happens is Person.age.__get__(...) because Python finds that the Person class has a descriptor with the name age and so uses the descriptor to get the attribute’s value. Similarly when you want to change age attribute like e1.age = 100 (or self.age = 100) what happens is Person.age.__set__(...).
I am playing around with classes and I want to do the following:
class Hi (object):
def __init__(self, bloop):
self.bloop = bloop
thing = Hi("bloop")
name = input("Input a thing") # Let`s assume you input thing
print(name.bloop)
What I want it to do is for it to print "bloop" because you entered thing and it gets bloop from thing kinda like thing.bloop.
How about storing thing in a dict:
class Hi:
def __init__(self, bloop):
self.bloop = bloop
things = {'thing': Hi('bloop')}
name = input("Input a thing") # Let`s assume you input 'thing'
try:
print(things[name].bloop)
except KeyError:
print("Sorry, I dont know that thing :(")
There are, of course, other ways to call an attribute of a class. You could implement a __call__() method, which allows you to call the class instance like a function, and in your case return .bloop:
class Hi:
def __init__(self, bloop):
self.bloop = bloop
def __call__(self):
return self.bloop
things = {'thing': Hi('bloop')}
name = input("Input a thing") # Let`s assume you input 'thing'
try:
print(things[name]())
except KeyError:
print("Sorry, I dont know that thing :(")
This way, you can rename your attribute later, or return another attribute (or do something entirely different altogether), without having to replace .bloop in your main python file.
A third way, which directly relates to your question's title calling attributes from[of] classes in Python: you can use the #Property decorator to make your .bloop attribute callable like a function like so:
class Hi:
def __init__(self, bloop):
self.bloop = bloop
#Property
def bloop():
return self.bloop
#bloop.setter
def bloop(self, new_value)
self.bloop = new_value
things = {'thing': Hi('bloop')}
name = input("Input a thing") # Let`s assume you input 'thing'
try:
print(things[name].bloop())
except KeyError:
print("Sorry, I dont know that thing :(")
To fully understand the Property decorator, I recommend this article.
Btw, according to the tags on your question, you're using Python3.x - you don't have to declare that your class inherits from Object in Python3 and later.