I am doing my college assignment and one of the things my lecture is looking for is encoding an object and dumping it as JSON, decoding it then loading it into the PassengerFlight class.
When I run the code it keeps showing
'PassengerFlight' object has no attribute 'airlineName'
When I decode it, VSCode is showing it has all the set function variables. The only class I can get it to run in is the Flight class when I remove the abstract method. Is there any way to run it within the PassengerFlight class and not show the above error?
from abc import ABC, abstractmethod
import json
from json import JSONEncoder
class Flight(ABC, object): # Flight Class (MAIN)
def __init__(self, airlineName, departureAirport, arrivalAirport): # Constructor Method
self.airlineName: str # Declaring attributes & Setting Types
self.departureAirport: str # Declaring attributes & Setting Types
self.arrivalAirport: str # Declaring attributes & Setting Types
def setAirlineName(self, airlineName): # Mutator Method
self.airlineName = airlineName
#abstractmethod # Marking this methods as abstract method, any concrete subclass will have to implement this method
def getPrice(self):
pass
def setDepartureAirport(self, departureAirport): # Mutator Method
self.departureAirport = departureAirport
def getDepartureAirport(self): # Accessor Method
if len(self.departureAirport) > 0: # Error Checking
return self.departureAirport
else:
return "No departure airport set"
def setArrivalAirport(self, arrivalAirport): # Mutator Method
self.arrivalAirport = arrivalAirport
def getArrivalAirport(self): # Accessor Method
if len(self.arrivalAirport) > 0: # Error Checking
return self.arrivalAirport
else:
return "No arrival airport set"
def __eq__(self, otherFlight):
return (self.airlineName == otherFlight.airlineName)
def __str__(self): # Overiding Method
return "\n==================================================\n++++++++++++++++++ Flight Info ++++++++++++++++++\n==================================================\nAirline Name: " + self.airlineName + "\Departure Airport: " + self.departureAirport + "\nArrival Airport: " + self.arrivalAirport
class PassengerFlight(Flight, object): # PassengerFlight Class (EXTENDS MAIN)
def __init__(self, airlineName, departureAirport, arrivalAirport, price, redEye, passengerList, flightType, baggageWeight): # Constructor Method
super().__init__(airlineName, departureAirport, arrivalAirport)
self.passengerList: list # Declaring attributes & Setting Types
self.flightType: str # Declaring attributes & Setting Types
self.baggageWeight: float # Declaring attributes & Setting Types
self.price: float # Declaring attributes & Setting Types
self.redEye: bool
def getAirlineName(self): # Accessor Method
if len(self.airlineName) > 0: # Error Checking
return self.airlineName
else:
return "No airline name set"
def setRedEye(self, redEye):
self.redEye = redEye
def isRedEye(self):
if self.redEye == True:
return "Flight is red eye"
else:
return "Flight is not a red eye"
def setPassengerName(self, passengerList): # Mutator Method
self.passengerList = passengerList
def getPassengerName(self): # Accessor Method
if len(self.passengerList) > 0: # Error Checking
self.passengerList.sort()
return self.passengerList
else:
return "Passenger List Not Set"
def setFlightType(self, flightType): # Mutator Method
self.flightType = flightType
def getFlightType(self): # Accessor Method
if len(self.flightType) > 0: # Error Checking
return self.flightType
else:
return "No Flight Type Set"
def setBaggageWeight(self): # Mutator Method
baggageWeight = 0
for i in range(len(self.passengerList)): # Iterates through passenger list
baggageWeight += 50.00 # For each passenger in the list add 50KG to baggage variable
self.baggageWeight = baggageWeight # Set our attribute equal to the variable
def getBaggageWeight(self): # Accessor Method
if self.baggageWeight == 0:
return "No Baggage Weight Set"
else:
return self.baggageWeight
def setPrice(self, price):
self.price = price * len(self.passengerList)
def getPrice(self):
if self.price != 0:
return self.price
else:
return "Price is not set"
def sortedNames(self):
return sorted(self.passengerList)
def __str__(self): # Overiding Method
return super().__str__() + "\nPassenger Names using sort(): " + str(self.getPassengerName()) + "\nFlight Type: " + self.flightType + "\nBaggage Weight: " + str(self.baggageWeight) + '(KG)\n' + "Money earned for this flight is: " + str(self.getPrice()) + "\nPassenger Names using Sorted(): " + str(self.sortedNames()) + '\n==================================================\n'
class FlightEncoder(JSONEncoder):
def default(self, o):
return o.__dict__
##########################################################
# Calling PassengerFlight Class #
##########################################################
# Initialize Class & Set to Variable
passenger = PassengerFlight(None, None, None, None, None, None, None, None)
passenger.setAirlineName('Aer Lingus') # Set mutators & values
passenger.setArrivalAirport('Chicago O\'Hare Intl') # Set mutators & values
passenger.setDepartureAirport('Dublin') # Set mutators & values
passenger.setRedEye(True)
passenger.setFlightType('Long Haul') # Set mutators & values
passenger.setPassengerName(
['Andrew Check', 'Graham Whitaker', 'John Eire', 'Vicky Bunsworth', 'Lorna Lopsworth']) # Set mutators & values
passenger.setBaggageWeight() # Set mutators & values
passenger.setPrice(200.00)
# uses the overridden _str_ method to print detail of the class
print(passenger)
##########################################################
passengerEncode = json.dumps(passenger, cls=FlightEncoder, indent=4)
passengerDecode = json.loads(passengerEncode)
passengerObj = PassengerFlight(**passengerDecode)
print(passengerObj)
Hint
def __str__(self): # Overiding Method
return (
super().__str__()
+ "\nPassenger Names using sort(): "
+ str(self.getPassengerName())
+ "\nFlight Type: "
+ self.flightType
+ "\nBaggage Weight: "
+ str(self.baggageWeight)
+ "(KG)\n"
+ "Money earned for this flight is: "
+ str(self.getPrice())
+ "\nPassenger Names using Sorted(): "
+ str(self.sortedNames())
+ "\n==================================================\n"
)
You call super().__str__(). self is an instance of PassengerFlight which inherited from Flight. super() means: "call a method defined in the parent class next in __mro__". Flight doesn't have the attribute.
super
The super() function is used to give access to methods and properties of a parent or sibling class. The super() function returns an object that represents the parent class.
OOP
Using child class attributes in the parent class breaks the "Dependency Inversion Principle" (SOLID).
In object-oriented design, a dependency inversion principle is a specific form of decoupling software modules. When following this principle, the conventional dependency relationships established from high-level, policy-setting modules to low-level, dependency modules are reversed, thus rendering high-level modules independent of the low-level module implementation details.
High-level modules should not depend on low-level modules. Both should depend on abstractions (e.g., interfaces).
Abstractions should not depend on details. Details (concrete implementations) should depend on abstractions.
Your abstraction depends on the higher-level implementation.
Solution
from abc import ABCMeta, abstractproperty
class Flight(metaclass=ABCMeta):
#abstractproperty
def airLineName(self):
raise NotImplementedError()
With the property declared in the abstraction you have to implement it in the child types. Based on this you can use the abstract properties in the abstraction without errors and risks to call not implemented attribute. Also, it satisfies SOLID principles.
Related
In this example, what should be done so that print(left_hand.number_of_fingers) returns 4 and not 5?
class Hand:
def __init__(self, fingers:list):
self.fingers = fingers
self.number_of_fingers = len(fingers)
left_hand = Hand(["thumb", "index", "middle", "ring", "pinkie"])
left_hand.fingers.pop()
print(left_hand.number_of_fingers) # I want this to actualize and be 4, not 5
I found a solution using #property
class Hand:
def __init__(self, fingers:list):
self.fingers = fingers
#property
def number_of_fingers(self):
return len(self.fingers)
But I'm not satisfied because of a computational power issue, if computing number_of_fingers was expensive we would only want to compute it whenever fingers is modified, not every time the user asks for the attribute number_of_fingers.
Now I found a not elegant solution to solve the issue with computational power:
class Hand:
def __init__(self, fingers:list):
self.fingers = fingers
self.old_fingers = fingers
self.number_of_fingers = len(fingers)
def get_number_of_fingers(self):
if self.fingers != self.old_fingers:
self.old_fingers = self.fingers
self.number_of_fingers = len(self.fingers)
return self.number_of_fingers
The problem is that the underlying list in your Hand class, i.e. self.fingers, is not sufficiently encapsulated so that any user can be modifying it, for example by calling left_hand.fingers.pop() or even by assigning to it a new list. Therefore, you cannot assume that it has not been modified between calls to number_of_fingers and therefore you have no choice but to compute its length in that call.
The solution is to control what clients of your class can and cannot do. The easiest way to do this is by using name mangling. That is, you prefix your attribute names with two leading underscore characters. This makes it difficult (although not impossible) for clients of your class to access these attributes from outside of the class (we assume that your users are not intentionally malicious). And therefore we have to provide now a pop method:
class Hand:
def __init__(self, fingers:list):
self.__fingers = fingers
self.__number_of_fingers = len(fingers)
def pop(self):
assert(self.__fingers)
self.__number_of_fingers -= 1
return self.__fingers.pop()
#property
def number_of_fingers(self):
return self.__number_of_fingers
left_hand = Hand(["thumb", "index", "middle", "ring", "pinkie"])
print(left_hand.pop())
print(left_hand.number_of_fingers)
Prints:
pinkie
4
I am not suggesting that you actually do the following, but if you wanted to you can get more elaborate by creating special class decorators #Private and #Public that will wrap your class in a new class and check access to your attributes ensuring that you are not accessing those attributes defined to be private. You use either the #Private decorator to define those attributes/methods that are private (everything else is considered public) or the #Public decorator to define those attributes/methods that are public (everything else is considered private), but not both. You would typically name your private attributes with a leading single underscore, which is the convention that tells users that the attribute/method is to be considered private.
This is meant more to catch inadvertent access of attributes that are meant to be private. If you execute the code with the -O Python flag, then no runtime checks will be made.
def accessControl(failIf):
def onDecorator(aClass):
if not __debug__:
return aClass
else:
class onInstance:
def __init__(self, *args, **kargs):
self.__wrapped = aClass(*args, **kargs)
def __getattr__(self, attr):
if failIf(attr):
raise TypeError('private attribute fetch: ' + attr)
else:
return getattr(self.__wrapped, attr)
def __setattr__(self, attr, value):
if attr == '_onInstance__wrapped':
self.__dict__[attr] = value
elif failIf(attr):
raise TypeError('private attribute change: ' + attr)
else:
setattr(self.__wrapped, attr, value)
return onInstance
return onDecorator
def Private(*attributes):
return accessControl(failIf=(lambda attr: attr in attributes))
def Public(*attributes):
return accessControl(failIf=(lambda attr: attr not in attributes))
#Private('_fingers', '_number_of_fingers')
class Hand:
def __init__(self, fingers:list):
self._fingers = fingers
self._number_of_fingers = len(fingers)
def pop(self):
assert(self._fingers)
self._number_of_fingers -= 1
return self._fingers.pop()
#property
def number_of_fingers(self):
return self._number_of_fingers
left_hand = Hand(["thumb", "index", "middle", "ring", "pinkie"])
print(left_hand.pop())
print(left_hand.number_of_fingers)
# Thsis will throw an exception:
print(left_hand._fingers)
Prints:
pinkie
4
Traceback (most recent call last):
File "C:\Booboo\test\test.py", line 50, in <module>
print(left_hand._fingers)
File "C:\Booboo\test\test.py", line 9, in __getattr__
raise TypeError('private attribute fetch: ' + attr)
TypeError: private attribute fetch: _fingers
Update
This is the OP's approach using a cache:
class Hand:
def __init__(self, fingers:list):
self._cache = {}
self.fingers = fingers
def get_number_of_fingers(self):
fingers = tuple(self.fingers) # can be key of a dictionary
fingers_length = self._cache.get(fingers)
if fingers_length:
print(self.fingers, 'in cache')
return fingers_length
fingers_length = len(fingers)
self._cache[fingers] = fingers_length
return fingers_length
left_hand_fingers = ["thumb", "index", "middle", "ring", "pinkie"]
right_hand_fingers = ["thumb", "middle", "ring", "pinkie"]
hand = Hand(left_hand_fingers)
print(hand.get_number_of_fingers())
hand.fingers = right_hand_fingers
print(hand.get_number_of_fingers())
hand.fingers = left_hand_fingers
print(hand.get_number_of_fingers())
hand.fingers = right_hand_fingers
print(hand.get_number_of_fingers())
hand.fingers = left_hand_fingers
print(hand.get_number_of_fingers())
Prints:
5
4
['thumb', 'index', 'middle', 'ring', 'pinkie'] in cache
5
['thumb', 'middle', 'ring', 'pinkie'] in cache
4
['thumb', 'index', 'middle', 'ring', 'pinkie'] in cache
5
So here in the first code (without using #property), you will get the output as 5 and not 4, because you are simply assigning the value of len(fingers) to number_of_fingers attribute while initialising a Hand object, and number_of_fingers attribute is not getting linked to fingers.
So even if left_hand.fingers is modified in between the code, it will have no effect on the value of number_of_fingers. One cannot change this behaviour.
Also you don't need that #property, I tested and found that there will be no error if it is not written.
And finally coming to
But I'm not satisfied, because if computing number_of_fingers was expensive we would only want to compute it whenever fingers is modified, not every time the user asks for the attribute number_of_fingers.
Where do you need so much computing power?
class Air:
def __init__(self,supplier,delivery,ensurance):
self.supplier = supplier
self.delivery = delivery
self.ensurance = ensurance
def rate_for_custom(self):
return (self.supplier + self.delivery + self.ensurance)
class Sea:
def __init__(self,supplier,delivery,ensurance,port_tax):
self.supplier = supplier
self.delivery = delivery
self.ensurance = ensurance
self.port_tax = port_tax
def rate_for_custom(self):
return (self.supplier + self.delivery + self.ensurance + self.port_tax)
so i'm trying to write a program that calculates the import taxes in israel.
There are two types: one in the sea and one in the air
they both share the same attributes except Sea needs to be calculated with another attribute.
I'm feeling like my code is not good(i'm new to pragramming started a week ago)
is it fine to use two classes in this case? if not what is the solution (by stil using OOP because I need to practice with it)
You can move common parts to a common parent class:
class Transport:
def __init__(self,supplier,delivery,ensurance):
self.supplier = supplier
self.delivery = delivery
self.ensurance = ensurance
def rate_for_custom(self):
return (self.supplier + self.delivery + self.ensurance)
class Air(Transport):
pass
class Sea(Transport):
def __init__(self,supplier,delivery,ensurance,port_tax):
super().__init__(supplier, delivery, ensurance)
self.port_tax = port_tax
def rate_for_custom(self):
return super().rate_for_custom() + self.port_tax
As you want to learn OOP, then you can start to see the concept of inheritance. Here is an example:
# generic class
class Custom:
def __init__(self,*args):
# collect all given parameters:
self.args = args
def rate_for_custom(self):
# just sum all numbers in given parameter:
return sum(self.args)
class Sea(Custom):
def __init__(self,supplier=0,delivery=0,insurance=0, port_tax = 0):
# Call Custom class and provide all relevant parameters:
super().__init__(supplier, delivery, insurance, port_tax)
class Air(Custom):
def __init__(self,supplier=0, delivery=0, insurance=0):
# Call Custom class and provide all relevant parameters:
super().__init__(supplier, delivery, insurance )
print(Custom(100,50,25).rate_for_custom())
# 175
print(Air(supplier=100,delivery=50,insurance=25).rate_for_custom())
# 175
print(Sea(supplier=100,delivery=50,insurance=25,port_tax=25).rate_for_custom())
# 200
Customclass is doing all the job, by summing all parameters it receives in init(). You can call this class providing the values to sum :Custom(100,50,25).rate_for_custom()
Two other classes Airand Sea are inheriting from the Customclass and are just an interface. Using them allows you to use keyword arguments instead of simple arguments: Sea(supplier=100,delivery=50,insurance=25,port_tax=25) which is more friendly.
I'm searching for an elegant way to replace setter/getter methodes handling complex data types by properties using the #property decorator.
The class I'm working on should represent some kind of (network) dimmer. It is possible to request/send "resources" addressed by a specific ID to control the device. I'd like to represent those "resources" as properties of my class hiding the request/send mechanism and the cryptical ID numbers.
Some of those "resources" are just primitive types (int, float, ...) but some are more complex, so I've just created simple classes for them.
This works fine, but there is an ugly source of error: It is not possible to change an attribute of that property directly, I have to set the property completely everytime.
DUMMY_DB = {0x0001: bytearray([0x00])}
class State:
def __init__(self, on, value):
self.on = on
self.value = value
#staticmethod
def from_int(val):
return State(bool(val & 0x80), val & 0x7f)
def __int__(self):
return self.on << 7 | self.value
class Dimmer:
#property
def state(self) -> State:
return State.from_int(self._request(0x0001)[0]) # ID 0x0001 => State
#state.setter
def state(self, val: State):
self._send(0x0001, [int(val)]) # ID 0x0001 => State
# several more properties...
def _request(self, ident) -> bytearray:
# usually get resource 'ident' from network/file/...
return DUMMY_DB[ident]
def _send(self, ident, value):
# usually set resource 'ident' on network/file/... using value
DUMMY_DB[ident] = value
if __name__ == '__main__':
dimmer = Dimmer()
print(dimmer.state.on, dimmer.state.value) # start state
dimmer.state.on = True
dimmer.state.value = 15
print(dimmer.state.on, dimmer.state.value) # state did not change
dimmer.state = State(True, 15)
print(dimmer.state.on, dimmer.state.value) # state changed
The first print is just to show the start state ("False 0"). But the second print shows that
dimmer.state.on = True
dimmer.state.value = 15
are useless. This is because dimmer.state returns a new mutable object which is modified and destroyed without further usage. Only through the complete property assignment the setter methode is called and Dimmer._send invoked.
I think this might be extremely unintuitive and error-prone. Do you have any suggestions for a better design?
Thanks everyone for your help so far. I've narrowed it down a bit. If you look at HERE in both the script and the class, and run the script, you'll see what is going on.
The ADD line print "789 789"
when it should be printing "456 789"
What appears to be happening, is in new the class is detecting the type of the incoming argument. However if the incoming object, has the same type as the constructor it appears to be paging the incoming object, into itself (at the class level) instead of returning the old object. That is the only thing I can think of that would cause 456 to get creamed.
So how do you detect something that is the same type of a class, within a constructor and decide NOT to page that data into the class memory space, but instead return the previously constructed object?
import sys
import math
class Foo():
# class level property
num = int(0)
#
# Python Instantiation Customs:
#
# Processing polymorphic input new() MUST return something or
# an object?, but init() cannot return anything. During runtime
# __new__ is running at the class level, while init is running
# at the instance level.
#
def __new__(self,*arg):
print ("arg type: ", type(arg[0]).__name__)
### functionally the same as isinstance() below
#
# if (type(arg[0]).__name__) == "type":
# if arg[0].__name__ == "Foo":
# print ("\tinput was a Foo")
# return arg[0] # objects of same type intercede
### HERE <-------------------------------------
#
# this creams ALL instances, because since we are a class
# the properties of the incoming object, seem to overwride
# the class, rather than exist as a separate data structure.
if (isinstance(arg[0], Foo)):
print ("\tinput was a Foo")
return arg[0] # objects of same type intercede
elif (type(arg[0]).__name__) == "int":
print ("\tinput was an int")
self.inum = int(arg[0]) # integers store
return self
elif (type(arg[0]).__name__) == "str":
print ("\tinput was a str")
self.inum = int(arg[0]) # strings become integers
return self
return self
def __init__(self,*arg):
pass
#
# because if I can do collision avoidance, I can instantiate
# inside overloaded operators:
#
def __add__(self,*arg):
print ("add operator overload")
# no argument returns self
if not arg:
return self
# add to None or zero return self
if not arg[0]:
return self
knowntype = Foo.Foo(arg[0])
# add to unknown type returns False
if not knowntype:
return knowntype
# both values are calculable, calculate and return a Foo
typedresult = (self.inum + knowntype.inum)
return Foo.Foo(typedresult)
def __str__(self): # return a stringified int or empty string
# since integers don't have character length,
# this tests the value, not the existence of:
if self.inum:
return str(self.inum)
# so the property could still be zero and we have to
# test again for no reason.
elif self.inum == 0:
return str(self.inum)
# return an empty str if nothing is defined.
return str("")
testfoo.py:
#! /usr/bin/python
import sys
import Foo
# A python class is not transparent like in perl, it is an object
# with unconditional inheritance forced on all instances that share
# the same name.
classhandle = Foo.Foo
# The distinction between the special class object, and instance
# objects is implicitly defined by whether there is a passed value at
# constructor time. The following therefore does not work.
# classhandle = Foo.Foo()
# but we can still write and print from the class, and see it propagate,
# without having any "object" memory allocated.
print ("\nclasshandle: ", classhandle)
print ("classhandle classname: ", classhandle.__name__) # print the classname
print ("class level num: ", classhandle.num) # print the default num
classhandle.classstring = "fdsa" # define an involuntary value for all instances
print ("\n")
# so now we can create some instances with passed properties.
instance1 = Foo.Foo(int(123)) #
print ("\ninstance1: ", instance1)
print ("involuntary property derived from special class memory space: ", instance1.classstring)
print ("instance property from int: ", instance1.inum)
print ("\n")
instance2 = Foo.Foo(str("456"))
print ("\ninstance2: ", instance2)
print ("instance2 property from int: ", instance2.inum)
#
# instance3 stands for (shall we assume) some math that happened a
# thousand lines ago in a class far far away. We REALLY don't
# want to go chasing around to figure out what type it could possibly
# be, because it could be polymorphic itself. Providing a black box so
# that you don't have to do that, is after all, the whole point OOP.
#
print ("\npretend instance3 is unknowningly already a Foo")
instance3 = Foo.Foo(str("789"))
## So our class should be able to handle str,int,Foo types at constructor time.
print ("\ninstance4 should be a handle to the same memory location as instance3")
instance4 = Foo.Foo(instance3) # SHOULD return instance3 on type collision
# because if it does, we should be able to hand all kinds of garbage to
# overloaded operators, and they should remain type safe.
# HERE <-----------------------------
#
# the creation of instance4, changes the instance properties of instance2:
# below, the instance properties inum, are now both "789".
print ("ADDING: ", instance2.inum, " ", instance4.inum)
# instance6 = instance2 + instance4 # also should be a Foo object
# instance5 = instance4 + int(549) # instance5 should be a Foo object.
How do I, at constructor time, return a non-new object?
By overriding the constructor method, __new__, not the initializer method, __init__.
The __new__ method constructs an instance—normally by calling the super's __new__, which eventually gets up to object.__new__, which does the actual allocation and other under-the-covers stuff, but you can override that to return a pre-existing value.
The __init__ method is handed a value that's already been constructed by __new__, so it's too late for it to not construct that value.
Notice that if Foo.__new__ returns a Foo instance (whether a newly-created one or an existing one), Foo.__init__ will be called on it. So, classes that override __new__ to return references to existing objects generally need an idempotent __init__—typically, you just don't override __init__ at all, and do all of your initialization inside __new__.
There are lots of examples of trivial __new__ methods out there, but let's show one that actually does a simplified version of what you're asking for:
class Spam:
_instances = {}
def __new__(cls, value):
if value not in cls._instances:
cls._instances[value] = super().__new__(cls)
cls._instances[value].value = value
return cls._instances[value]
Now:
>>> s1 = Spam(1)
>>> s2 = Spam(2)
>>> s3 = Spam(1)
>>> s1 is s2
False
>>> s1 is s3
True
Notice that I made sure to use super rather than object, and cls._instances1 rather than Spam._instances. So:
>>> class Eggs(Spam):
... pass
>>> e4 = Eggs(4)
>>> Spam(4)
<__main__.Eggs at 0x12650d208>
>>> Spam(4) is e4
True
>>> class Cheese(Spam):
... _instances = {}
>>> c5 = Cheese(5)
>>> Spam(5)
<__main__.Spam at 0x126c28748>
>>> Spam(5) is c5
False
However, it may be a better option to use a classmethod alternate constructor, or even a separate factory function, rather than hiding this inside the __new__ method.
For some types—like, say, a simple immutable container like tuple—the user has no reason to care whether tuple(…) returns a new tuple or an existing one, so it makes sense to override the constructor. But for some other types, especially mutable ones, it can lead to confusion.
The best test is to ask yourself whether this (or similar) would be confusing to your users:
>>> f1 = Foo(x)
>>> f2 = Foo(x)
>>> f1.spam = 1
>>> f2.spam = 2
>>> f1.spam
2
If that can't happen (e.g., because Foo is immutable), override __new__.
If that exactly what users would expect (e.g., because Foo is a proxy to some object that has the actual spam, and two proxies to the same object had better see the same spam), probably override __new__.
If it would be confusing, probably don't override __new__.
For example, with a classmethod:
>>> f1 = Foo.from_x(x)
>>> f2 = Foo.from_x(x)
… it's a lot less likely to be surprising if f1 is f2 turns out to be true.
1. Even though you define __new__ like an instance method, and its body looks like a class method, it's actually a static method, that gets passed the class you're trying to construct (which will be Spam or a subclass of Spam) as an ordinary first parameter, with the constructor arguments (and keyword arguments) passed after that.
Thanks everyone who helped! This answer was saught out to understand how to refactor an existing program that was already written, but that was having scalability problems. The following is the completed working example. What it demonstrates is:
The ability to test incoming types and avoid unneccessary object duplication at constructor time, given incoming types that are both user-defined and built-in. The ability to construct on the fly from a redefined operator or method. These capabilities are neccessary for writing scalable supportable API code. YMMV.
Foo.py
import sys
import math
class Foo():
# class level property
num = int(0)
#
# Python Instantiation Customs:
#
# Processing polymorphic input new() MUST return something or
# an object, but init() MAYNOT return anything. During runtime
# __new__ is running at the class level, while __init__ is
# running at the instance level.
#
def __new__(cls,*arg):
print ("arg type: ", type(arg[0]).__name__)
# since we are functioning at the class level, type()
# is reaching down into a non-public namespace,
# called "type" which is presumably something that
# all objects are ultimately derived from.
# functionally this is the same as isinstance()
if (type(arg[0]).__name__) == "Foo":
fooid = id(arg[0])
print ("\tinput was a Foo: ", fooid)
return arg[0] # objects of same type intercede
# at the class level here, we are calling into super() for
# the constructor. This is presumably derived from the type()
# namespace, which when handed a classname, makes one of
# whatever it was asked for, rather than one of itself.
elif (type(arg[0]).__name__) == "int":
self = super().__new__(cls)
self.inum = int(arg[0]) # integers store
fooid = id(self)
print ("\tinput was an int: ", fooid)
return (self)
elif (type(arg[0]).__name__) == "str":
self = super().__new__(cls)
self.inum = int(arg[0]) # strings become integers
fooid = id(self)
print ("\tinput was a str: ", fooid)
return (self)
# def __init__(self,*arg):
# pass
#
# because if I can do collision avoidance, I can instantiate
# inside overloaded operators:
#
def __add__(self,*arg):
argtype = type(arg[0]).__name__
print ("add overload in class:", self.__class__)
if argtype == "Foo" or argtype == "str" or argtype == "int":
print ("\tfrom a supported type")
# early exit for zero
if not arg[0]:
return self
# localized = Foo.Foo(arg[0])
# FAILS: AttributeError: type object 'Foo' has no attribute 'Foo'
# You can't call a constructor the same way from inside and outside
localized = Foo(arg[0])
print ("\tself class: ", self.__class__)
print ("\tself number: ", self.inum)
print ()
print ("\tlocalized class: ", localized.__class__)
print ("\tlocalized number: ", localized.inum)
print ()
answer = (self.inum + localized.inum)
answer = Foo(answer)
print ("\tanswer class:", answer.__class__)
print ("\tanswer sum result:", answer.inum)
return answer
assert(0), "Foo: cannot add an unsupported type"
def __str__(self): # return a stringified int or empty string
# Allow the class to stringify as if it were an int.
if self.inum >= 0:
return str(self.inum)
testfoo.py
#! /usr/bin/python
import sys
import Foo
# A python class is not transparent like in perl, it is an object
# with unconditional inheritance forced on all instances that share
# the same name.
classhandle = Foo.Foo
# The distinction between the special class object, and instance
# objects is implicitly defined by whether there is a passed value at
# constructor time. The following therefore does not work.
# classhandle = Foo.Foo()
# but we can still write and print from the class, and see it propagate,
# without having any "object" memory allocated.
print ("\nclasshandle: ", classhandle)
print ("classhandle classname: ", classhandle.__name__) # print the classname
print ("class level num: ", classhandle.num) # print the default num
classhandle.classstring = "fdsa" # define an involuntary value for all instances
print ("\n")
# so now we can create some instances with passed properties.
instance1 = Foo.Foo(int(123)) #
print ("\ninstance1: ", instance1)
print ("involuntary property derived from special class memory space: ", instance1.classstring)
print ("instance property from int: ", instance1.inum)
print ("\n")
instance2 = Foo.Foo(str("456"))
print ("\ninstance2: ", instance2)
print ("instance2 property from int: ", instance2.inum)
#
# instance3 stands for (shall we assume) some math that happened a
# thousand lines ago in a class far far away. We REALLY don't
# want to go chasing around to figure out what type it could possibly
# be, because it could be polymorphic itself. Providing a black box so
# that you don't have to do that, is after all, the whole point OOP.
#
print ("\npretend instance3 is unknowningly already a Foo\n")
instance3 = Foo.Foo(str("789"))
## So our class should be able to handle str,int,Foo types at constructor time.
print ("\ninstance4 should be a handle to the same memory location as instance3\n")
instance4 = Foo.Foo(instance3) # SHOULD return instance3 on type collision
print ("instance4: ", instance4)
# because if it does, we should be able to hand all kinds of garbage to
# overloaded operators, and they should remain type safe.
# since we are now different instances these are now different:
print ("\nADDING:_____________________\n", instance2.inum, " ", instance4.inum)
instance5 = instance4 + int(549) # instance5 should be a Foo object.
print ("\n\tAdd instance4, 549, instance5: ", instance4, " ", int(549), " ", instance5, "\n")
instance6 = instance2 + instance4 # also should be a Foo object
print ("\n\tAdd instance2, instance4, instance6: ", instance2, " ", instance4, " ", instance6, "\n")
print ("stringified instance6: ", str(instance6))
The following code:
class ParentModel(models.Model):
pass
class ChildA(ChildB):
pass
class ChildB(ParentModel):
pass
Obviously fails with the message.
NameError: name "ChildB" is not defined
Is there anyway to get around this issue, without actually reordering the class definitions? (The code is auto-generated, about 45K lines, and the order of classes is random).
Perfectionists look away!!
This is a workaround (hack); the solution would be to solve the incorrect declaration order.
WARNING: This is extremely daft.
Concept:
Imagine a namespace where anything can exist. Literally anything that is asked of it. Not the smartest thing usually but out-of-order declaration isn't smart either, so why not?
The key problem of out-of-sequence classes is that dependent classes were being defined before their dependencies, the base classes. At that point of evaluation, the base classes are undefined resulting in a NameError.
Wrapping each class in try except statements would take as much effort as rewriting the module anyway, so that can be dismissed out of hand.
A more efficient (in terms of programmer time) means of suppressing NameError must be used. This can be achieved by making the namespace totally permissible, as in, if a lookup object doesn't exist, it should be created thereby avoiding a NameError. This is the obvious danger of this approach as a lookup becomes a creation.
Implementation:
Namespaces in Python are dictionaries, I believe, and dictionaries methods can be overloaded, including the lookup function: __getitem__. So mr_agreeable is a dictionary subclass with an overloaded __getitem__ method which automatically creates a blank class when a lookup key doesn't exist. An instance of mr_agreeable is passed to execfile as the namespace for the classes.py script. The objects (aside from the builtins) created execfile call are merged with the globals() dict of the calling script: hack.py.
This works because Python doesn't care if class' base classes are changed after the fact.
This may be implementation dependent, I don't know. Tested on: Python 2.7.3 64bit on Win7 64bit.
Assuming your out-of-order classes are defined in classes.py:
class ParentModel(object):
name = "parentmodel"
class ChildC(ChildA):
name = "childc"
class ChildA(ChildB):
name = "childa"
class ChildB(ParentModel):
name = "childb"
The loader script, lets call it hack.py:
from random import randint
from codecs import encode
class mr_agreeable(dict):
sin_counter = 0
nutty_factor = 0
rdict = {0 : (0, 9), 200 : (10, 14), 500 : (15, 16), 550 : (17, 22)}
def __getitem__(self, key):
class tmp(object):
pass
tmp.__name__ = key
if(not key in self.keys()):
self.prognosis()
print self.insanity()
return self.setdefault(key, tmp)
def prognosis(self):
self.sin_counter += 1
self.nutty_factor = max(filter(lambda x: x < self.sin_counter, self.rdict.keys()))
def insanity(self):
insane_strs = \
[
"Nofbyhgryl", "Fher, jul abg?", "Sbe fher", "Fbhaqf terng", "Qrsvangryl", "Pbhyqa'g nterr zber",
"Jung pbhyq tb jebat?", "Bxl Qbnxl", "Lrc", "V srry gur fnzr jnl", "Zneel zl qnhtugre",
"Znlor lbh fubhyq svk gung", "1 AnzrReebe vf bar gbb znal naq n 1000'f abg rabhtu", "V'ir qbar qvegvre guvatf",
"Gur ebbz vf fgnegvat gb fcva", "Cebonoyl abg", "Npghnyyl, ab ..... nyevtug gura", "ZNXR VG FGBC",
"BU TBQ AB", "CYRNFR AB", "LBH'ER OERNXVAT CLGUBA", "GUVF VF ABG PBAFRAGHNY", "V'Z GRYYVAT THVQB!!"
]
return encode("ze_nterrnoyr: " + insane_strs[randint(*self.rdict[self.nutty_factor])], "rot13")
def the_act():
ns = mr_agreeable()
execfile("classes.py", ns)
hostages = list(set(ns.keys()) - set(["__builtins__", "object"]))
globals().update([(key, ns[key]) for key in hostages])
the_act()
mr_agreeable acts as the permissible namespace to the complied classes.py. He reminds you this is bad form.
My previous answer showed a loader script that executed the out of order script in execfile but provided a dynamic name space that created placeholder classes (these are typically base classes sourced before they are defined). It then loaded the changes from this name space in the loader's global namespace.
This approach has two problems:
1) Its a hack
2) The assumed class of the placeholders is the object class. So when:
class ChildC(ChildA):
name = "childc"
is evaluated, the namespace detects ChildA is undefined and so creates a placeholder class (an object subclass). When ChildA is actually defined (in the out-of-order script), it might be of a different base class than object and so rebasing ChildC to the new ChildA will fail if ChildA's base is not the object class (what ChildC was originally created with). See this for more info.
So I created a new script, which actually rewrites the input out-of-order script using a similar concept to the previous hack and this script. The new script is used by calling:
python mr_agreeable.py -i out_of_order.py -o ordered.py
mr_agreeable.py:
import os
import sys
from codecs import encode
from random import randint
import getopt
import inspect
import types
__doc__ = \
'''
A python script that re-orders out of sequence class defintions
'''
class rebase_meta(type):
'''
Rebase metaclass
Automatically rebases classes created with this metaclass upon
modification of classes base classes
'''
org_base_classes = {}
org_base_classes_subs = {}
base_classes = {}
base_classes_subs = {}
mod_loaded = False
mod_name = ""
mod_name_space = {}
def __init__(cls, cls_name, cls_bases, cls_dct):
#print "Making class: %s" % cls_name
super(rebase_meta, cls).__init__(cls_name, cls_bases, cls_dct)
# Remove the old base sub class listings
bases = rebase_meta.base_classes_subs.items()
for (base_cls_name, sub_dict) in bases:
sub_dict.pop(cls_name, None)
# Add class to bases' sub class listings
for cls_base in cls_bases:
if(not rebase_meta.base_classes_subs.has_key(cls_base.__name__)):
rebase_meta.base_classes_subs[cls_base.__name__] = {}
rebase_meta.base_classes[cls_base.__name__] = cls_base
rebase_meta.base_classes_subs[cls_base.__name__][cls_name] = cls
# Rebase the sub classes to the new base
if(rebase_meta.base_classes.has_key(cls_name)): # Is class a base class
subs = rebase_meta.base_classes_subs[cls_name]
rebase_meta.base_classes[cls_name] = cls # Update base class dictionary to new class
for (sub_cls_name, sub_cls) in subs.items():
if(cls_name == sub_cls_name):
continue
sub_bases_names = [x.__name__ for x in sub_cls.__bases__]
sub_bases = tuple([rebase_meta.base_classes[x] for x in sub_bases_names])
try:
# Attempt to rebase sub class
sub_cls.__bases__ = sub_bases
#print "Rebased class: %s" % sub_cls_name
except TypeError:
# The old sub class is incompatible with the new base class, so remake the sub
if(rebase_meta.mod_loaded):
new_sub_cls = rebase_meta(sub_cls_name, sub_bases, dict(sub_cls.__dict__.items() + [("__module__", rebase_meta.mod_name)]))
rebase_meta.mod_name_space[sub_cls_name] = new_sub_cls
else:
new_sub_cls = rebase_meta(sub_cls_name, sub_bases, dict(sub_cls.__dict__.items()))
subs[sub_cls_name] = new_sub_cls
#classmethod
def register_mod(self, imod_name, imod_name_space):
if(not self.mod_loaded):
self.org_base_classes = self.base_classes.copy()
self.org_base_classes_subs = self.base_classes_subs.copy()
self.mod_loaded = True
else:
self.base_classes = self.org_base_classes
self.base_classes_subs = self.org_base_classes_subs
self.mod_name = imod_name
self.mod_name_space = imod_name_space
# Can't subclass these classes
forbidden_subs = \
[
"bool",
"buffer",
"memoryview",
"slice",
"type",
"xrange",
]
# Builtin, sub-classable classes
org_class_types = filter(lambda x: isinstance(x, type) and (not x.__name__ in forbidden_subs) and x.__module__ == "__builtin__", types.__builtins__.values())
# Builtin classes recreated with Rebasing metaclass
class_types = [(cls.__name__, rebase_meta(cls.__name__, (cls,), {})) for cls in org_class_types]
# Overwrite builtin classes
globals().update(class_types)
class mr_quiet(dict):
'''
A namespace class that creates placeholder classes upon
a non existant lookup. mr_quiet doesnt say much.
'''
def __getitem__(self, key):
if(not key in self.keys()):
if(hasattr(__builtins__, key)):
return getattr(__builtins__, key)
else:
if(not key in self.keys()):
self.sanity_check()
return self.setdefault(key, rebase_meta(key, (object,), {}))
else:
return dict.__getitem__(self, key)
def sanity_check(self):
pass
class mr_agreeable(mr_quiet):
'''
A talkative cousin of mr_quiet.
'''
sin_counter = 0
nutty_factor = 0
rdict = {0 : (0, 9), 200 : (10, 14), 500 : (15, 16), 550 : (17, 22)}
def sanity_check(self):
self.prognogsis()
print self.insanity()
def prognogsis(self):
self.sin_counter += 1
self.nutty_factor = max(filter(lambda x: x < self.sin_counter, self.rdict.keys()))
def insanity(self):
insane_strs = \
[
"Nofbyhgryl", "Fher, jul abg?", "Sbe fher", "Fbhaqf terng", "Qrsvangryl", "Pbhyqa'g nterr zber",
"Jung pbhyq tb jebat?", "Bxl Qbnxl", "Lrc", "V srry gur fnzr jnl", "Zneel zl qnhtugre",
"Znlor lbh fubhyq svk gung", "1 AnzrReebe vf bar gbb znal naq n 1000'f abg rabhtu", "V'ir qbar qvegvre guvatf",
"Gur ebbz vf fgnegvat gb fcva", "Cebonoyl abg", "Npghnyyl, ab ..... nyevtug gura", "ZNXR VG FGBC",
"BU TBQ AB", "CYRNFR AB", "LBH'ER OERNXVAT CLGUBA", "GUVF VF ABG PBAFRAGHNY", "V'Z GRYYVAT THVQB!!"
]
return encode("ze_nterrnoyr: " + insane_strs[randint(*self.rdict[self.nutty_factor])], "rot13")
def coll_up(ilist, base = 0, count = 0):
'''
Recursively collapse nested lists at depth base and above
'''
tlist = []
if(isinstance(ilist, __builtins__.list) or isinstance(ilist, __builtins__.tuple)):
for q in ilist:
tlist += coll_up(q, base, count + 1)
else:
if(base > count):
tlist = ilist
else:
tlist = [ilist]
return [tlist] if((count != 0) and (base > count)) else tlist
def build_base_dict(ilist):
'''
Creates a dictionary of class : class bases pairs
'''
base_dict = {}
def build_base_dict_helper(iclass, idict):
idict[iclass] = list(iclass.__bases__)
for x in iclass.__bases__:
build_base_dict_helper(x, idict)
for cur_class in ilist:
build_base_dict_helper(cur_class, base_dict)
return base_dict
def transform_base_to_sub(idict):
'''
Transforms a base dict into dictionary of class : sub classes pairs
'''
sub_dict = {}
classes = idict.keys()
for cur_class in idict:
sub_dict[cur_class] = filter(lambda cls: cur_class in idict[cls], classes)
return sub_dict
recur_class_helper = lambda idict, ilist = []: [[key, recur_class_helper(idict, idict[key])] for key in ilist]
recur_class = lambda idict: recur_class_helper(idict, idict.keys())
class proc_func(list):
'''
Cmdline processing class
'''
def __init__(self, name = "", *args, **kwargs):
self.name = name
super(list, self).__init__(*args, **kwargs)
def get_args(self, *args):
self.extend(filter(lambda x: x, args))
def __call__(self, *args):
print self.name
print self
class proc_inputs(proc_func):
def get_args(self, *args):
self.extend(filter(os.path.isfile, args))
class proc_outputs(proc_func):
pass
class proc_helper(proc_func):
'''
Help function
Print help information
'''
def get_args(self, *args):
self()
def __call__(self, *args):
print __file__
print __doc__
print "Help:\n\t%s -h -i inputfile -o ouputfile" % sys.argv[0]
print "\t\t-h or --help\tPrint this help message"
print "\t\t-i or --input\tSpecifies the input script"
print "\t\t-o or --output\tSpecifies the output script"
sys.exit()
if __name__ == "__main__":
proc_input = proc_inputs("input")
proc_output = proc_outputs("output")
proc_help = proc_helper("help")
cmd_line_map = \
{
"-i" : proc_input,
"--input" : proc_input,
"-o" : proc_output,
"--ouput" : proc_output,
"-h" : proc_help,
"--help" : proc_help
}
try:
optlist, args = getopt.getopt(sys.argv[1:], "hi:o:", ["help", "input=", "output="])
for (key, value) in optlist:
cmd_line_map[key].get_args(value)
except getopt.GetoptError:
proc_help()
if(len(proc_input) != len(proc_output)):
print "Input files must have a matching output file"
proc_help()
elif(not proc_input):
proc_help()
else:
in_out_pairs = zip(proc_input, proc_output)
for (in_file, out_file) in in_out_pairs:
dodgy_module_name = os.path.splitext(in_file)[0]
sys.modules[dodgy_module_name] = types.ModuleType(dodgy_module_name)
sys.modules[dodgy_module_name].__file__ = in_file
# Make a fake space post haste
name_space = mr_agreeable\
(
[
("__name__", dodgy_module_name), # Needed for the created classes to identify with the fake module
("__module__", dodgy_module_name), # Needed to fool the inspect module
] + \
class_types
)
# Exclude these from returning
exclusions = name_space.keys()
# Associate the fake name space to the rebasing metaclass
rebase_meta.register_mod(dodgy_module_name, name_space)
# Run dodgy code
execfile(in_file, name_space)
# Bring back dodgy classes
import_classes = [cls if(isinstance(cls, type) and not cls_name in exclusions) else None for (cls_name, cls) in name_space.items()]
dodgy_import_classes = filter(lambda x: x, import_classes)
# Create base and sub class dictionaries
base_dict = build_base_dict(dodgy_import_classes)
sub_dict = transform_base_to_sub(base_dict)
# Create sets of base and sub classes
base_set = reduce(lambda x, y: x | y, map(set, base_dict.values()), set([]))
sub_set = reduce(lambda x, y: x | y, map(set, sub_dict.values()), set([]))
kings = list(base_set - sub_set) # A list of bases which are not subs
kingdoms = recur_class_helper(sub_dict, kings) # A subclass tree of lists
lineages = coll_up(kingdoms, 2) # Flatten the tree branches at and below 2nd level
# Filter only for the clases created in the dodgy module
inbred_lines = [filter(lambda x: x.__module__ == dodgy_module_name, lineage) for lineage in lineages]
# Load Source
for lineage in inbred_lines:
for cls in lineage:
setattr(cls, "_source", inspect.getsource(cls))
# Write Source
with open(out_file, "w") as file_h:
for lineage in inbred_lines:
for cls in lineage:
file_h.write(cls._source + "\n")