Last active
July 16, 2021 16:50
-
-
Save ccritchfield/a1f0de906a1ff8d6c503e48d1925cffa to your computer and use it in GitHub Desktop.
Python - Object-Oriented Programming Demo
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
-------------------------------------------- | |
Python Object-Oriented Programming Walk-Thru | |
-------------------------------------------- | |
.py files that demonstrate Object-Oriented programming | |
in Python in step-by-step fashion. | |
Python class in college was mix of undergrads and grads, | |
because Python was turned into the Intro to Programming | |
class for undergrads (due to push towards data sci), but | |
grad students that had intro to prog using Java still | |
had to go through Python 101 as a prereq to get into | |
Data Sci and Big Data classes. (College was making a big | |
push for Python for analytics.) | |
And, the "undergrad + grad" Python class was taught by | |
the grad-level Data Sci instructor. So, he blew through | |
things at an insane rate using Data Sci examples that | |
left the undergrads struggling. | |
So, no surprise, he blew through Python OO in one short evening. | |
There was no book for the class, just prof's slides, which were | |
very lacking. (EG: just barebones code examples | |
with hardly any comments). | |
Undergrads were completely lost, because for most of them it | |
was their first time learning to program (much less, OO program). | |
So, I went home, googled up how to do OO in Python, and hammered | |
out a massive demonstration of it one night to help them out. | |
These .py files are what I ended up with. | |
Since I had already gone through Java during my INSY undergrad, | |
I tried to relate the Python examples to Java examples they'd | |
see once they got to "Programming II" (which would be their | |
intro to Java). | |
Problem is, Java is OO-primary, so it's pretty strict about what | |
you can do OO-wise in it. Meanwhile, Python seems to have had | |
OO tacked on, and it sort of over-complicates it a bit and | |
also lets you make new vars inside an object on-the-fly while | |
also directly accessing supposedly "hidden" variables. | |
I tried to let students know that this was pretty unusual | |
( 07 - Oddities & Weirdness ). |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
###################################### | |
""" | |
Object-Oriented Programming | |
Best, short vid explanation I could find... | |
https://www.youtube.com/watch?v=pTB0EiLXUC8 | |
-------------------------------------------- | |
A bit more on Polymorphism & Abstraction... | |
https://www.youtube.com/watch?v=L1-zCdrx8Lk | |
(the vid may just confuse you more w/o a code example, but...) | |
The power of OO programming comes into play | |
when dealing with large systems and complex | |
stuff happening behind the scenes. | |
Let's say we're making a data reporting system. | |
It has 2 parts... | |
1) GUI that shows data | |
2) middle-ware that pulls data from various databases to pass to GUI | |
The data we're pulling can come from | |
SQL Server, MS Access, Oracle, Dbase, etc databases. | |
Each of those require different | |
ways to connect to them. | |
When the end-user fires up the | |
GUI, they have a dropdown to | |
select the data source. Then | |
they click "execute" and it | |
connects to the data source. | |
We could... code the GUI | |
front-end to have a massive | |
if/else to determine what | |
connection type we're doing | |
and how to execute it. | |
... OR ... | |
we can create a "connection" | |
object as a super-class, | |
and it has an empty method in | |
it called "makeConnection()" | |
All this means is that any | |
sub-class we make using the | |
connection blue-print must | |
have a method in it called | |
"makeConnection()" .. and | |
each sub-class can implement | |
it in their own way. | |
So... we start making sub-classes... | |
connection.sqlserverconn.makeConnection(){however it handles it} | |
connection.msaccessconn.makeConnection(){however it handles it} | |
connection.oracleconn.makeConnection(){however it handles it} | |
connection.dbaseconn.makeConnection(){however it handles it} | |
So, the super-class gives | |
the template sub-classes | |
have to stick to, but the | |
sub-classes actually implement | |
the code logic of how to | |
make and handle the connection | |
to the specific data source. | |
When these objects are kicked | |
off, we can still refer to them | |
as "connection' objects, b/c they | |
inherit and are encapsulated by the | |
super-class 'connection'. | |
So, the developer working on | |
the GUI front-end can do something like... | |
//--------------------------------- | |
// create generic connection object | |
// but use it to encapsulate specific | |
// sub-connection type user chose | |
//--------------------------------- | |
Connection conn = Connection(dropdownbox.selection) | |
// kick off the connection.. we don't | |
// care about the specifics, b/c the | |
// sub-class handles that stuff | |
conn.makeConnection() | |
//--------------------------------- | |
The back-end developer | |
will be the one coding all | |
the connection objects, | |
and they can make more | |
without it impacting the | |
front-end developer, | |
b/c the connection object | |
acts as an abstraction bridge | |
between the front-end and | |
back-end. | |
The front-end guy doesn't | |
care how the connection | |
sub-classes actually | |
do the connection. | |
And, he probably doesn't | |
care how many different | |
sub-connection types there are. | |
EG: | |
When the GUI starts up, | |
he'll simply have some | |
code iterate over all | |
the sub-connection types | |
to create a dropdown | |
that an end-user can pick | |
from.. so the front-end | |
code never has to get | |
modified if the back-end | |
guy adds more connection | |
sub-classes (eg: adds | |
a new one to handle Oracle | |
databases.) | |
And.. let's say a new version | |
of SQL Server rolls out, and it | |
totally changes how the connection | |
needs to be handled. | |
The back-end developer simply | |
updates the SQL Server connection | |
sub-object code, and there is | |
no need to update the | |
front-end code. B/c the code | |
logic is stored in the bakc-end | |
object instead of in the front-end. | |
So, it creates modular code | |
that's easier to maintain | |
without having to go "oops, | |
I think we need to update | |
fifteen systems if we make | |
this change in this one system." | |
And, it can create a generic | |
"bridge" between systems to | |
let multiple developers work | |
on various parts all at once | |
without having to know the | |
exact details of what's going | |
on in another area. | |
The GUI guy can code his GUI | |
to use connection object | |
without needing to know the | |
details of what's going on | |
in the back-end.. as long | |
as all the connection sub-objects | |
follow the connection super-class | |
template the back-end guy and | |
front-end guy agreed upon and | |
are coding their respective | |
pieces to use as the bridge. | |
You'll get into more on | |
this in Java b/c it's | |
more OO. And (if you go | |
into masters INSY developer | |
path) you'll have Adv Systems | |
Design class that really | |
looks at leveraging abstraction, | |
SOLID principles, etc in | |
design patterns developed | |
by Gang of Four to turn complex | |
systems into simple, modular things | |
that are easy to modify. | |
""" | |
###################################### |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
###################################### | |
""" | |
--------------------------------- | |
Objects have ... | |
* properties (attributes, fields ... containers that can hold data relevant to object) | |
* methods (actions, functions ... things that can do stuff, usually with the object's stuff) | |
--------------------------------- | |
The properties of the object are data that would be unique | |
to the object to do it's job. | |
--------------------------------- | |
Methods are whatever functions would help the object do it's job. | |
Basic methods most objects have are... | |
constructors .. used to kick off and make new instances of object | |
setters (mutators) .. used to set (mutate / change) the properties / attributes of the object | |
getters (accessors) .. used to get (access / return) the value of props / attrs of the object | |
"stringify" .. used to show a summary of the object.. what props it has and what they're set to | |
in Java it's "object.toString". In Python it let's you do "print(object)". | |
--------------------------------- | |
We'll talk about this stuff more, but for now let's kick off | |
an example... | |
UTA Java classes use Customer and Address a lot, so | |
let's create those in Python.... | |
""" | |
######################################## | |
######################################## | |
# US Postal Address object | |
######################################## | |
class AddressTest01: | |
# constructor ... default & main | |
def __init__( self, street = "", apt_po = "", city = "", state = "", zipcode = "" ): | |
self.street = street | |
self.apt_po = apt_po | |
self.city = city | |
self.state = state | |
self.zipcode = zipcode | |
######################################## | |
print("-" * 50) | |
print("AddressTest01 Test...") | |
#------------------------------- | |
# create new address instance using default values | |
#------------------------------- | |
a1 = AddressTest01() | |
# ... THEN set the address instance properties | |
a1.street = "1313 13th str." | |
a1.apt_po = "13" | |
a1.city = "portland" | |
a1.state = "OR" | |
a1.zipcode = "12345" | |
msg = "" | |
msg += "street = " + a1.street + "\n" | |
msg += "apt_po = " + a1.apt_po + "\n" | |
msg += "city = " + a1.city + "\n" | |
msg += "state = " + a1.state + "\n" | |
msg += "zipcode = " + a1.zipcode | |
print("-" * 50) | |
print("a1 ...\n" + msg) | |
#------------------------------- | |
# now we'll do by first creating vars to store inputs | |
# (eg: these could represent inputs from a user that | |
# we snag first before making the object). | |
#------------------------------- | |
ad = "100 n. lane" | |
po = "po box 6578" | |
ct = "dallas" | |
st = "tx" | |
zp = "75123" | |
# ... now create new address object by passing in our arguments | |
a2 = AddressTest01( ad, po, ct, st, zp) | |
msg = "" | |
msg += "street = " + a2.street + "\n" | |
msg += "apt_po = " + a2.apt_po + "\n" | |
msg += "city = " + a2.city + "\n" | |
msg += "state = " + a2.state + "\n" | |
msg += "zipcode = " + a2.zipcode | |
print("-" * 50) | |
print("a2 ...\n" + msg) | |
################################## | |
""" | |
In Java, there's usually 2 constructors: | |
1) default / empty .. let's you create a new instance | |
of the object using default values built into | |
the object. This is useful for when you want to load | |
an object into memory, and worry about populating | |
it's values later. | |
2) main ... pass values directly as arguments when | |
you create the object. | |
There's pro's and con's to doing it either way, and it | |
depends on what you need to do in your application. | |
(eg: in advanced design principles, you can create a default | |
object in memory, and then do shallow or deep copies of it | |
to "stamp out parts / clone" more objects from it... perhaps | |
tying back to the original's properties, so if the original's | |
properties are change the cloned objects reflect it, too, | |
or to where the clones have their own properties. EG: if | |
you had enemies in a game all with the same graphic model, | |
you cuold create an enemy object in memory with the graphic | |
assigned, then clone enemies off it, but have them track | |
their own damage. This wuold help reduce memory load, | |
since common properties are tracked by the original | |
object and the clones only track what's needed on their own.) | |
Maybe you need to gather a bunch of info from user, | |
and do a bunch of stuff with it before making an | |
object that stores that info. | |
Maybe you want to cut down on memory usage, so you | |
want to create the object first, since it essentially | |
contains variables to hold the data you want, and you | |
can then just populate the data directly instead of | |
creating middle-man variables to catch the data then | |
pass it to the object's properties. | |
It depends on what you need to do in your applications. | |
Also, in Java, you have properties outside of the constructor | |
methods. | |
//////////////////////////////// | |
public class Account | |
{ | |
// private / internal / hidden vars | |
private String name; | |
private double balance; | |
//---------------------------------------- | |
// constructors (overloading method) | |
//---------------------------------------- | |
// overloading just means we use the same | |
// function name multiple times, and the compiler | |
// knows which to use when called based on | |
// the arguments passed inside the function call. | |
// | |
// Python doesn't have overloading, but most C-style | |
// languages do. | |
//---------------------------------------- | |
// so if we call "Account a = Account()" it has | |
// no arguments passed, it knows to use default/empty here | |
public Account() // if no vals are set | |
{ | |
name = "(none)"; | |
balance = 0.0; | |
} | |
// if we call "Account a = Account("bob", 123.45)" it has | |
// arguments passed, it knows to use main here | |
public Account(String n, double b) // if users pass values | |
{ | |
name = n; | |
balance = b; | |
} | |
} | |
//////////////////////////////// | |
---------------------------------------- | |
In Python, __init__ acts as both a constructor method, | |
and can contain the properties that the object has. | |
(But, we'll change that here in a moment.) | |
Plus, since you can set the parameters of __init__ | |
to default values, you can use __init__ as both | |
a main and default/empty constructor. | |
---------------------------------------- | |
""" | |
######################################## |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
######################################## | |
""" | |
It's a pain to have to create a special | |
output variable to string together all the | |
object properties whenever we want to get | |
a summary of them. | |
So, OO languages often have a "stringify" | |
object method ... which just lets you | |
dump a summary .. what vars it has, | |
what they're set to.. maybe any extra | |
info a user (usually a programmer) | |
would want to know when using the | |
variable. | |
in Java you create toString() method. | |
In Python you have __str__ | |
(note that it's 2 double-scores before | |
and after. There's a __str___ | |
that has 2 double-scores before | |
and 3 after, but that returns | |
the object's memory address.) | |
If you try to print(object) | |
without having the __str__ | |
made, then you'll just | |
get the object memory address | |
spit out at you. | |
So, it's good to setup the __str__ | |
method so you can dictate what | |
the object shows when printed. | |
""" | |
################################### | |
class AddressTest02: | |
# constructor ... default & main | |
def __init__( self, street = "", apt_po = "", city = "", state = "", zipcode = "" ): | |
self.street = street | |
self.apt_po = apt_po | |
self.city = city | |
self.state = state | |
self.zipcode = zipcode | |
# print(object) ("stringify" object summary) | |
def __str__( self ): | |
msg = "" | |
msg += "street = " + self.street + "\n" | |
msg += "apt_po = " + self.apt_po + "\n" | |
msg += "city = " + self.city + "\n" | |
msg += "state = " + self.state + "\n" | |
msg += "zipcode = " + self.zipcode | |
return msg | |
###################################### | |
print("-" * 50) | |
print("AddressTest02 Test...") | |
# create new address instance using default values | |
a1 = AddressTest02() | |
# set the address instance properties | |
a1.street = "1313 13th str." | |
a1.apt_po = "13" | |
a1.city = "portland" | |
a1.state = "OR" | |
a1.zipcode = "12345" | |
# now we can get rid of all of this stuff... | |
""" | |
msg = "" | |
msg += "street = " + a1.street + "\n" | |
msg += "apt_po = " + a1.apt_po + "\n" | |
msg += "city = " + a1.city + "\n" | |
msg += "state = " + a1.state + "\n" | |
msg += "zipcode = " + a1.zipcode | |
""" | |
# and replace it with this... | |
print("-" * 50) | |
print("a1 ...") | |
print(a1) | |
# create vars to store inputs we'll pass | |
# in creating new Address object | |
# these can get changed to user inputs, | |
# or could be values passed in from another program | |
ad = "100 n. lane" | |
po = "po box 6578" | |
ct = "dallas" | |
st = "tx" | |
zp = "75123" | |
# create new address object by passing in our arguments | |
a2 = AddressTest02( ad, po, ct, st, zp) | |
# ditto | |
""" | |
msg = "" | |
msg += "street = " + a2.street + "\n" | |
msg += "apt_po = " + a2.apt_po + "\n" | |
msg += "city = " + a2.city + "\n" | |
msg += "state = " + a2.state + "\n" | |
msg += "zipcode = " + a2.zipcode | |
""" | |
# since our object is not a string, | |
# if we want to concat it with another | |
# string we have to convert it to a string | |
print("-" * 50) | |
print("a2 ...\n" + str(a2)) |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
###################################### | |
""" | |
Our address object as-is is "open by default", | |
which is bad.. it means anyone external can | |
directly access the internal attributes of | |
the object. So, someone can do the following... | |
print("-" * 50) | |
a1.state = "Who Cares" | |
print("a1.state = " + a1.state) | |
print("oops... we don't want folks setting that as a state") | |
---------------------------------------- | |
We want to avoid people directly accessing our object | |
attributes, b/c WE want to control what this object | |
can accept for properties, and perhaps hide some | |
special hidden properties within the object for | |
internal use that nothing else should see. | |
This is where setters and getters come in | |
handy, b/c they provide gateways to the | |
object, but lets the object developer | |
control the flow of access. | |
So, we need to make these properties private, | |
and create setters and getters to access them. | |
In Java, attributes you create inside | |
a class are considered "closed by default" | |
and automatically hidden. You create setters | |
to filter inputs to make sure they're ok | |
for what the object's property is needing, | |
and getters to show folks the value if it's | |
called. Once you make setters, you often | |
go back to your constructor and force | |
it to use the setters instead of allowing | |
the constructor to set values directly | |
when called.... | |
eg: | |
////////////////////////////////// | |
public class Account | |
{ | |
// vars | |
private String name; | |
private double balance; | |
//---------------------------------------- | |
// constructors (overloading method) | |
//---------------------------------------- | |
// with setters created, we now use | |
// them in the constructor to ensure | |
// data passed in goes through any | |
// validation / scrubbing necessary | |
// in the setters. | |
// this object has no scrubbing/validation, | |
// but is still setup just in case we add it later. | |
//---------------------------------------- | |
public Account() // if no vals are set | |
{ | |
setName("(none)"); | |
setBalance(0.0); | |
} | |
public Account(String n, double b) // if users pass values | |
{ | |
setName(n); | |
setBalance(b); | |
} | |
//---------------------------------------- | |
// setters | |
//---------------------------------------- | |
public void setName(String name) {this.name = name;} | |
public void setBalance(double balance) {this.balance = balance;} | |
//---------------------------------------- | |
// getters | |
//---------------------------------------- | |
public String getName() {return name;} | |
public double getBalance() {return balance;} | |
//---------------------------------------- | |
// other | |
//---------------------------------------- | |
public String toString() {return("Name " + name + ", Balance " + balance);} | |
} | |
////////////////////////////////// | |
But, in Python, they have a "everyone's | |
an adult here" philosophy that means | |
it's assumed everyone working in the | |
Python code knows what they're doing | |
and not trying to screw it up on purpose. | |
But, we can still privatize the attributes | |
and create getters and setters for them, | |
so we have more control over what can be | |
seen and what can be done with them. | |
The problem in doing so is that people | |
may have previous code that went... | |
a1.street = "blah" | |
If we change our object to now | |
force people to access the properties | |
via getters / setters, it will break | |
all code that was coded to access | |
them directly... | |
b/c... | |
a.street = "blah" # setting street directly | |
...will fail, and now have to get recoded as... | |
a.set_street("blah") # using a function to set street | |
So, Python uses an alternate method that | |
abstracts (hides) the setter/getter | |
functionality by making it seem | |
just like property manipulation directly | |
while still using functions... | |
You can read more about this here... | |
https://www.programiz.com/python-programming/property | |
But, for our Address example it's done like so... | |
""" | |
################################ | |
class AddressTest03: | |
#------------------------------------ | |
# constructor | |
#------------------------------------ | |
# we use the self.property same as before, | |
# but since we defined @property.setter | |
# functions further below, what this is | |
# doing now is calling those setter functions | |
# to pass them the values we're trying to set | |
# properties to, and is now using hidden | |
# __property variables to store things in | |
# that only this object can see internally. | |
def __init__( self, street = "", apt_po = "", city = "", state = "", zipcode = "" ): | |
self.street = street # same as self.street(street) | |
self.apt_po = apt_po # same as self.apt_no(apt_po) | |
self.city = city # etc | |
self.state = state | |
self.zipcode = zipcode | |
#------------------------------------ | |
# getters | |
#------------------------------------ | |
# for each property, we create getters that | |
# are used for returning the property's value | |
# when called in AddressTest03.street fashion. | |
# this lets us control what properties outside | |
# parties (code, developers, etc) can see | |
# and how the properties are returned. | |
# (eg: we may store a date as a string, but | |
# convert it to a date when it's called) | |
#------------------------------------ | |
@property | |
def street(self): | |
return self.__street | |
@property | |
def apt_po(self): | |
return self.__apt_po | |
@property | |
def city(self): | |
return self.__city | |
@property | |
def state(self): | |
return self.__state | |
@property | |
def zipcode(self): | |
return self.__zipcode | |
#------------------------------------ | |
# setters | |
#------------------------------------ | |
# for each property, we create a setter | |
# to allow us to control what values are | |
# allowed and how they are transformed | |
# for storage. But, they are referred | |
# to using the normal property name | |
# (eg: object.street). What they're | |
# really doing is creating and using | |
# hidden properties that only the | |
# object can see internally (eg: | |
# self.street(value) is setting | |
# the value to self.__street property | |
# behind the scenese now, b/c | |
# self.street is now a function | |
# instead of a property. | |
#------------------------------------ | |
@street.setter | |
def street( self, street ): | |
self.__street = street | |
@apt_po.setter | |
def apt_po( self, apt_po ): | |
self.__apt_po = apt_po | |
@city.setter | |
def city( self, city ): | |
self.__city = city | |
@state.setter | |
def state( self, state ): | |
self.__state = state | |
@zipcode.setter | |
def zipcode( self, zipcode ): | |
self.__zipcode = zipcode | |
#------------------------------------ | |
# stringify / toString / print(object) | |
#------------------------------------ | |
def __str__( self ): | |
msg = "" | |
msg += "street = " + self.street + "\n" | |
msg += "apt_po = " + self.apt_po + "\n" | |
msg += "city = " + self.city + "\n" | |
msg += "state = " + self.state + "\n" | |
msg += "zipcode = " + self.zipcode | |
return msg | |
###################################### | |
""" | |
what we did was turn the normal property names | |
(street, city, etc) into setter function calls, | |
and the real properties now have double-underscore | |
in front of them and hidden inside the object | |
only accessible from setters and getters. | |
but, anyone using our object doesn't know that. | |
.. that extra middle layer of setting and getting | |
is abstraction, and hides it. And, nobody is | |
the wiser, because a previous call of... | |
a1.street = "blah" | |
...still works.. it's just this time instead | |
of setting a property "street" directly, | |
it's passing the value through the setter. | |
same for the getter when someone does... | |
print(a1.street) | |
.. works just the same as before, but is | |
now passing through the getter middle layer. | |
""" | |
############################################### | |
#--------------------------------------------- | |
# let's test it out | |
#--------------------------------------------- | |
# same code as before, but using AddressTest03 instead of AddressTest02 | |
print("-" * 50) | |
print("AddressTest03 Test...") | |
# create new address instance using default values | |
a1 = AddressTest03() | |
# set the address instance properties | |
a1.street = "1313 13th str." | |
a1.apt_po = "13" | |
a1.city = "portland" | |
a1.state = "OR" | |
a1.zipcode = "12345" | |
# check object summary | |
print("-" * 50) | |
print("a1 ...") | |
print(a1) | |
# create vars to store inputs we'll pass | |
# in creating new Address object | |
# these can get changed to user inputs, | |
# or could be values passed in from another program | |
ad = "100 n. lane" | |
po = "po box 6578" | |
ct = "dallas" | |
st = "tx" | |
zp = "75123" | |
# create new address object by passing in our arguments | |
a2 = AddressTest03( ad, po, ct, st, zp) | |
# check object summary | |
print("-" * 50) | |
print("a2 ...") | |
print(a2) | |
print("-" * 50) | |
print("everything works the same, even though we're using setters / getters now") |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
################################ | |
""" | |
Now that we have the template of setters / getters | |
setup, we can leverage them. | |
EG: We can create a hidden / private / internal-only | |
attribute that contains US states, and use | |
that in the state setter to double-check its | |
validity before acceptance. | |
""" | |
################################ | |
class AddressTest04: | |
#------------------------------------ | |
# internal constants | |
#------------------------------------ | |
# setup state validation as tuple (immutable / unchageable) | |
__STATE_VALIDATION = ('AL','AK','AZ','AR','CA','CO','CT', | |
'DE','FL','GA','HI','ID','IL','IN', | |
'IA','KS','KY','LA','ME','MD','MA', | |
'MI','MN','MS','MO','MT','NE','NV', | |
'NH','NJ','NM','NY','NC','ND','OH', | |
'OK','OR','PA','RI','SC','SD','TN', | |
'TX','UT','VT','VA','WA','WV','WI', | |
'WY','') # we added '' since '' is our valid default value | |
#------------------------------------ | |
# constructor | |
#------------------------------------ | |
# self.property is calling setters below, | |
# so the values passed in as arguments, or the default | |
# values set in __init__ get passed into setters for | |
# validation, manipulation, etc before getting set | |
# to hidden properties behind the scenes. | |
def __init__( self, street = "", apt_po = "", city = "", state = "", zipcode = "" ): | |
self.street = street | |
self.apt_po = apt_po | |
self.city = city | |
self.state = state | |
self.zipcode = zipcode | |
#------------------------------------ | |
# getters | |
#------------------------------------ | |
@property | |
def street(self): | |
return self.__street | |
@property | |
def apt_po(self): | |
return self.__apt_po | |
@property | |
def city(self): | |
return self.__city | |
@property | |
def state(self): | |
return self.__state | |
@property | |
def zipcode(self): | |
return self.__zipcode | |
#------------------------------------ | |
# setters | |
#------------------------------------ | |
# we're going to standardize inputs | |
# to all uppercase, so we run each through | |
# str.upper() to convert them in their setters | |
# before storing them. | |
# | |
# While user can enter inputs in any way | |
# (upper, lower, mix), often data systems | |
# will store them in upper in order to | |
# try to speed up comparisons... it's | |
# faster when comparing uppers only | |
# eg: DOG = DOG | |
# instead of having to do mixed comparison | |
# eg: DOG can be Dog, dog, dOg, etc, etc. | |
# | |
# This was mostly used back in the old | |
# days to try to speed up data searches. | |
# some systems still use it as a hold-over | |
# and I'm just using it here to show | |
# how we can now use the setters to | |
# manipulate the inputs before storing | |
# them in our __property vars | |
@street.setter | |
def street( self, value ): | |
self.__street = str.upper(value) | |
@apt_po.setter | |
def apt_po( self, value ): | |
self.__apt_po = str.upper(value) | |
@city.setter | |
def city( self, value ): | |
self.__city = str.upper(value) | |
# we're also validating state | |
# input before accepting it. | |
# since our __STATE_VALIDATION list | |
# stores the state abbr's in uppercase | |
# we have to uppercase input before | |
# validating that, too. | |
@state.setter | |
def state( self, value ): | |
st = str.upper(value) | |
# if input is in validation list | |
# (we're calling the hidden validation | |
# list directly instead of using a getter)... | |
if st in self.__STATE_VALIDATION: | |
# accept it | |
self.__state = st | |
else: | |
# print statement just gives us feedback for debug. | |
# in a real-world scenario we may return an exception | |
# to the calling code or something to let them | |
# know they borked up and they need to correct | |
# it on their end and try again, or maybe | |
# we make our object code more robust to | |
# switch to a better default (eg: by looking | |
# up zip code and determing state from that). | |
print("-" * 50) | |
print("invalid state ... reverting to default of ''") | |
self.__state = "" # set to default value | |
# we don't care about uppercase'ing zip | |
# codes since they're numbers, but we store | |
# them as strings since they could be | |
# 00000-0000 format. | |
# | |
# later, we could hook this object | |
# into a zipcode validation system | |
# that would double-check to see | |
# if the zip is valid, and possibly | |
# auto-populate city / state from | |
# that system as well. | |
# | |
# if we did that, then we would only | |
# allow things to view (get) the state | |
# and city, but not set them, b/c | |
# we'd only allow city/state to get | |
# change by setting a new zip. | |
# thus we'd shut off the setters | |
# for the city/state then. | |
# | |
# But, for now, still bare-bones. | |
@zipcode.setter | |
def zipcode( self, value ): | |
self.__zipcode = value | |
#------------------------------------ | |
# stringify / toString / print(object) | |
#------------------------------------ | |
# since we control the stringify output | |
# we can prevent others from seeing our | |
# __STATE_VALIDATION list ... | |
# or, we can let them see it if we want... | |
# depends on how much we want them to see | |
def __str__( self ): | |
# each of the self.property calls below | |
# will call the @property function def | |
# for the respective property, returning | |
# whatever is in that function (which, | |
# for all of them currently, is just their | |
# double-underscore private property value). | |
msg = "" | |
msg += "street = " + self.street + "\n" | |
msg += "apt_po = " + self.apt_po + "\n" | |
msg += "city = " + self.city + "\n" | |
msg += "state = " + self.state + "\n" | |
msg += "zipcode = " + self.zipcode | |
return msg | |
###################################### | |
# again, everything we coded previously | |
# should work as usual...just using this new object | |
print("-" * 50) | |
print("AddressTest04 Test...") | |
# create new address instance using default values | |
a1 = AddressTest04() | |
# set the address instance properties | |
a1.street = "1313 13th str." | |
a1.apt_po = "13" | |
a1.city = "portland" | |
a1.state = "OR" | |
a1.zipcode = "12345" | |
# check object summary | |
print("-" * 50) | |
print("a1 ...") | |
print(a1) | |
# create vars to store inputs we'll pass | |
# in creating new Address object | |
# these can get changed to user inputs, | |
# or could be values passed in from another program | |
ad = "100 n. lane" | |
po = "po box 6578" | |
ct = "dallas" | |
st = "tx" | |
zp = "75123" | |
# create new address object by passing in our arguments | |
a2 = AddressTest04( ad, po, ct, st, zp) | |
# check object summary | |
print("-" * 50) | |
print("a2 ...") | |
print(a2) | |
#----------------------------------- | |
# now let's try inputting invalid state | |
#----------------------------------- | |
ad = "14409 Hampton Dr." | |
po = "" # we're going to treat this like a home addy, not an apt/po | |
ct = "Detroit" | |
st = "NeverNeverLand" # invalid state input | |
zp = "12345-6789" | |
# create new address object by passing in our arguments | |
a3 = AddressTest04( ad, po, ct, st, zp) | |
# check object summary | |
print("-" * 50) | |
print("a3 ...") | |
print(a3) |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
######################################### | |
""" | |
We've got a pretty good Address object designed... | |
So, we stripped out a lot of the comments bloat, | |
and created a separate import file out of it... | |
""" | |
######################################### | |
# we can import the Address object via... | |
# | |
# 1) importing the entire object file: | |
# | |
#import object_example_address | |
# | |
# 2) import specific stuff from it: | |
# | |
from object_example_address import Address | |
# we could also import the whole file by | |
# sending it * as a wild card (* = all) | |
# from object_example_address import * # everything | |
########################### | |
""" | |
We can use objects within objects... | |
So, we can use the Address object within a Customer object... | |
""" | |
########################### | |
class Customer01: | |
#------------------------------------ | |
# constructor | |
#------------------------------------ | |
# property = default | |
def __init__( self, id = "", | |
fname = "", | |
lname = "", | |
address = Address()): | |
#-------------------------------- | |
self.id = id | |
self.fname = fname | |
self.lname = lname | |
self.address = address | |
#-------------------------------- | |
#------------------------------------ | |
# getters | |
#------------------------------------ | |
@property | |
def id(self): | |
return self.__id | |
@property | |
def fname(self): | |
return self.__fname | |
@property | |
def lname(self): | |
return self.__lname | |
@property | |
def address(self): | |
return self.__address | |
#------------------------------------ | |
# setters | |
#------------------------------------ | |
# modification ... string inputs to uppercase | |
# (address already u-cases itself internally) | |
@id.setter | |
def id( self, value ): | |
self.__id = value # int | |
@fname.setter | |
def fname( self, value ): | |
self.__fname = str.upper(value) | |
@lname.setter | |
def lname( self, value ): | |
self.__lname = str.upper(value) | |
@address.setter | |
def address( self, value ): | |
self.__address = value # object | |
#------------------------------------ | |
# stringify / toString / print(object) | |
#------------------------------------ | |
def __str__( self ): | |
msg = "" | |
msg += "id = " + str(self.id) + "\n" | |
msg += "fname = " + self.fname + "\n" | |
msg += "lname = " + self.lname + "\n" | |
# address object already has it's own stringify | |
# so all we have to do is ... | |
msg += str(self.address) | |
return msg | |
########################### | |
""" | |
So.. we can create a customer in a few ways... | |
""" | |
########################### | |
#-------------------------------- | |
# 1 ) create an address first, then use it to create a customer | |
#-------------------------------- | |
addy = Address("1001 UTA dr.", "465", "Arlington", "TX", "78960") | |
# check object summary | |
print("-" * 50) | |
print("addy ...") | |
print(addy) | |
# I was watching Fast and Furious earlier today... | |
cust1 = Customer01(12345, "Paul", "Walker", addy) | |
# check object summary | |
print("-" * 50) | |
print("cust1 ...") | |
print(cust1) | |
#-------------------------------- | |
# 2 ) create blank customer and fill in values after | |
#-------------------------------- | |
# make default | |
cust2 = Customer01() | |
cust2.id = 65656 | |
cust2.fname = "Dom" | |
cust2.lname = "Torretto" | |
# when having to drill down into something to use | |
# it for a while, it's good to create a var | |
# directly referencing it... | |
# EG: we could have... | |
""" | |
cust2.address.street = "1111 UTA" | |
cust2.address.apt_po = "234" | |
cust2.address.city = "Arlington" | |
cust2.address.state = "TX" | |
cust2.address.zipcode = "78901" | |
""" | |
# .. or we can do .. | |
ca = cust2.address | |
ca.street = "1111 UTA" | |
ca.apt_po = "234" | |
ca.city = "Arlington" | |
ca.state = "TX" | |
ca.zipcode = "78901" | |
# check object summary | |
print("-" * 50) | |
print("cust2 ...") | |
print(cust2) | |
#-------------------------------- | |
# 3 ) pass everything in arguments all at once on creation | |
#-------------------------------- | |
# since address is an object, we create an Address object | |
# as an argument in the Customer constructor call and | |
# pass the Address the arguments | |
cust3 = Customer01( 34566, "John", "Smith", | |
Address("6565 Happy Dr.", "", "Seattle", "WA", "45345") | |
) | |
# check object summary | |
print("-" * 50) | |
print("cust3 ...") | |
print(cust3) | |
#-------------------------------- | |
# 4 ) some combination there-of | |
#-------------------------------- | |
# (not going to do an example, you get the idea) |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
########################### | |
""" | |
So we've got these objects using objects, | |
(yo dawg, I heard you like objects ...) | |
but we can make objects also have | |
extracurricular functions in them. | |
Let's say the Customer is an object | |
in a store interface, and they've | |
racked up charges. Now we need | |
to total those charges. | |
""" | |
########################### | |
# import the address object | |
from object_example_address import Address | |
########################### | |
class Customer: | |
#------------------------------------ | |
# constructor | |
#------------------------------------ | |
# property = default | |
def __init__( self, id = 0, | |
fname = "", | |
lname = "", | |
address = Address(), | |
charges = None): | |
#-------------------------------- | |
self.id = id | |
self.fname = fname | |
self.lname = lname | |
self.address = address | |
self.charges = charges # this will be a list | |
#-------------------------------- | |
#------------------------------------ | |
# getters | |
#------------------------------------ | |
@property | |
def id(self): | |
return self.__id | |
@property | |
def fname(self): | |
return self.__fname | |
@property | |
def lname(self): | |
return self.__lname | |
@property | |
def address(self): | |
return self.__address | |
@property | |
def charges(self): | |
return self.__charges | |
#------------------------------------ | |
# setters | |
#------------------------------------ | |
# modification ... string inputs to uppercase | |
# (address already u-cases itself internally) | |
@id.setter | |
def id( self, value ): | |
self.__id = value # int | |
@fname.setter | |
def fname( self, value ): | |
self.__fname = str.upper(value) | |
@lname.setter | |
def lname( self, value ): | |
self.__lname = str.upper(value) | |
@address.setter | |
def address( self, value ): | |
self.__address = value # object | |
@charges.setter | |
def charges( self, value ): | |
self.__charges = value # list | |
#------------------------------------ | |
# other functions | |
#------------------------------------ | |
# Total Charges (sums list of charges) | |
# very simply function.. just sums charges | |
# we don't want to print or anything else, | |
# we'll let other functions (like the __str__) | |
# handle what they want to do with the float | |
# sum we return | |
def totalCharges( self ): | |
return sum(self.charges) | |
#------------------------------------ | |
# stringify / toString / print(object) | |
#------------------------------------ | |
def __str__( self ): | |
msg = "" | |
msg += "id = " + str(self.id) + "\n" | |
msg += "fname = " + self.fname + "\n" | |
msg += "lname = " + self.lname + "\n" | |
# address object already has it's own stringify | |
# so all we have to do is ... | |
msg += str(self.address) + "\n" | |
msg += "charges = " + str(self.charges) + "\n" | |
msg += "tot chgs = " + str(self.totalCharges()) | |
return msg | |
############################# | |
# UTA student... look at those hundred dollar problems. | |
# must be buying books at the UTA bookstore. | |
chgs = [ 150.12, 211.00, 350.95 ] | |
addy = Address("1001 UTA dr.", "465", "Arlington", "TX", "78960") | |
cust = Customer(6578, "Vin", "Diesel", addy, chgs) | |
# check object summary | |
print("-" * 50) | |
print("cust ...") | |
print(cust) |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
########################### | |
# import the address object | |
from object_example_address import Address | |
# import the customer object | |
from object_example_customer import Customer | |
""" | |
if we didn't need to work with Address objects directly, | |
all we'd need to do is import the Customer object, | |
b/c it already imports the Address object. | |
In fact, if you comment out the Address object import, | |
the code below all still runs. But, the Spyder IDE | |
will give you a warning on the addy variable | |
saying Address is undefined (doesn't know what | |
it is). But, when you run the code, it seems | |
to figure it out by looking at the customer | |
object import. | |
However, it's usually safer to just import the | |
objects and things you need diretly in your code | |
if you use them instead of relying on other | |
objects chaining together imports. As something | |
gets update, a developer could change the | |
customer object to no longer need address | |
object import, so just importing customer | |
object would then break the addy variable | |
trying to make an Address object instantiation. | |
So, better safe then sorry. | |
""" | |
########################### | |
# prep from last time | |
chgs = [ 150.12, 211.00, 350.95 ] | |
addy = Address("1001 UTA dr.", "465", "Arlington", "TX", "78960") | |
cust = Customer(6578, "Vin", "Diesel", addy, chgs) | |
# check object summary | |
print("-" * 50) | |
print("cust ...") | |
print(cust) | |
############################# | |
""" | |
So... some weird Python behaviour to note... | |
""" | |
############################# | |
""" | |
we can look at individual cust properties, | |
b/c we created setters and getters for that. | |
""" | |
print("-" * 50) | |
print("cust.fname = " + cust.fname) | |
""" | |
but, in the Spyder IDE, you can see | |
the hidden / private / internal properties | |
in the Customer object model... | |
eg: you type cust. and it shows | |
the intellisense dropdown and in | |
the list you see ... __fname, __lname, etc | |
but, when you try to access them, you get an error... | |
'Customer' object has no attribute '__fname' | |
""" | |
# uncomment line below to see error msg | |
#print("cust.__fname = " + cust.__fname) | |
#----------------------------- | |
""" | |
But, what if we do this... | |
""" | |
#----------------------------- | |
cust.__id = 1 | |
cust.__fname = "dog" | |
cust.__lname = "cat" | |
# check object summary | |
print("-" * 50) | |
print("cust ...") | |
print(cust) | |
print("-" * 50) | |
print("cust shows same info as before...") | |
print("-" * 50) | |
print("But now we can print these things and get this...!!!") | |
print("cust.__id = " + str(cust.__id)) | |
print("cust.__fname = " + cust.__fname) | |
print("cust.__lname = " + cust.__lname) | |
#---------------------------- | |
""" | |
What the heck is going on?!?!? | |
We tried to do print(cust.__fname) the first | |
time and we got an error. Then we set it | |
and now we can print it. | |
Instantiated objects have a dictionary that | |
contains the property names and values. | |
We can print it to see what's going on... | |
""" | |
#---------------------------- | |
print("-" * 50) | |
print("we can look at the insantiated object's hidden stuff" + "\n" + | |
"by looking at the hidden __dict__ object.. a dictionary" + "\n" + | |
"in the object that stores all it's properties and methods...") | |
print("-" * 50) | |
print(cust.__dict__) | |
print() | |
print("... python created new, different vars in the cust object w/ same names as private ones on-the-fly... ugh") | |
""" | |
Notice the output... | |
{ | |
'_Customer__id': 6578, | |
'_Customer__fname': 'VIN', | |
'_Customer__lname': 'DIESEL', | |
'_Customer__address': <__main__.Address object at 0x000002CABF90D9B0>, | |
'_Customer__charges': [150.12, 211.0, 350.95], | |
'__id': 1, | |
'__fname': 'dog', | |
'__lname': 'cat' | |
} | |
anything starting with _Customer is valid... it's | |
valid private properties in the object. | |
The last 3 items... | |
'__id': 1, | |
'__fname': 'dog', | |
'__lname': 'cat' | |
... they didn't exist until we implicitly created them | |
just a moment ago when we did... | |
cust.__id = 1 | |
cust.__fname = "dog" | |
cust.__lname = "cat" | |
If you come from other languages like Java, C++, C#, Visual Basic, etc, | |
most of them force you to explicitly declare variables before you | |
can use them. | |
In Python, it creates them on-the-fly... even making new | |
ones in the object that didn't exist in the object | |
template! | |
The Customer object template itself doesn't have those | |
new BS variables we created. Only the cust instantiated | |
object. | |
So, we don't have direct access to the private variables | |
unless we did something like ... | |
""" | |
cust._Customer__id = 1 | |
print("-" * 50) | |
print(cust.__dict__) | |
print() | |
print("... we finally got access to a private property, but had to work-around the name mangling.. if someone is doing that to get around the property privacy, then you need to have a chat with them about how they're NOT supposed to do that") | |
""" | |
Note how in the __dict__ output the _Customer__id is now 1 | |
But, we had to bend over backwards to do that. | |
So, the private variables are private unless you bend over | |
backwards to break the name mangling that Python does | |
to hide them. | |
AND... | |
Python will be more then happy to create new | |
variables in your object on-the-fly, even ones with the | |
same names as your private variables! Which can make | |
you think you have direct access to them .. but you | |
don't. | |
Honestly.. the Spyder IDE shouldn't show the private | |
__prop variables in the first place, b/c that's | |
what causes some confusion of thinking you still | |
have direct access to them. So, it could be an IDE | |
thing. | |
But, it's just interesting to note. | |
So, if you're code's not doing what you think it | |
should, be sure to double-check your spelling. | |
Because you could end up doing something like... | |
""" | |
# update list of customer charges | |
chgs = [10, 20, 30, 40] | |
# apply to customer | |
cust.chargs = chgs | |
""" | |
and you think you're setting the customer's internal charges | |
list to the chgs list you just updated. | |
Then you total up the customer charges and see... | |
""" | |
print("-" * 50) | |
print("total charges = " + str( cust.totalCharges() ) ) | |
print() | |
print("... uh ... " + str(chgs) + " = " + str(sum(chgs)) + " not 712 .. wth") | |
""" | |
.. well crap | |
what you really did was create a NEW variable called | |
"chargs".. since you misspelled the word "charges". | |
""" | |
print("-" * 50) | |
print(cust.__dict__) | |
print() | |
print("...chgs was applied to a newly created var 'chargs' instead of 'charges'... awesome. smh") | |
""" | |
--------------------------------- | |
{ | |
'_Customer__id': 1, | |
'_Customer__fname': 'VIN', | |
'_Customer__lname': 'DIESEL', | |
'_Customer__address': <object_example_address.Address object at 0x000002CABF9C2898>, | |
'_Customer__charges': [150.12, 211.0, 350.95], | |
'__id': 1, | |
'__fname': 'dog', | |
'__lname': 'cat', | |
'chargs': [150.12, 211.0, 350.95] | |
} | |
... chargs ... *sigh* | |
--------------------------------- | |
Most other programming languages would throw back an error | |
complaining that you haven't first declared the variable | |
"chargs" | |
... but not Python. | |
Even in Visual Basic.. where it lets you create undeclared | |
vars on-the-fly.. it has an "Option Explicit" flag to force | |
you to declare vars explicitly before you can use them and | |
not just make them secretly / implicitly on-the-fly | |
... but not Python. | |
So... it's important to point out some oddities and nuances | |
that may throw you for a loop if you encountered them | |
w/o realizing what was going on. | |
""" | |
################################ | |
""" | |
You can get rid of an erroneously made property / attribute | |
by deleting it.. but, since most of our code will stop running | |
after it does what it does, and/or you've bug checked it and | |
will stop it running to go back and correct an erroneous | |
part of code that's kicking off new vars... it's pretty | |
pointless right now. But, useful to know just in case. | |
""" | |
del cust.__id | |
del cust.__fname | |
del cust.__lname | |
del cust.chargs | |
cust.charges = chgs | |
cust.id = 6578 | |
print("-" * 50) | |
print(cust.__dict__) | |
print() | |
print("...purged bogus variables, id back to original value, and updated charges properly") | |
# check object summary | |
print("-" * 50) | |
print("cust ...") | |
print(cust) |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
############################## | |
""" | |
Our Address Object after we've got it sorted out. | |
We can import it going forward to reduce code bloat. | |
""" | |
############################## | |
class Address: | |
#------------------------------------ | |
# internal constants | |
#------------------------------------ | |
# setup private state validation as tuple (immutable / unchageable) | |
__STATE_VALIDATION = ('AL','AK','AZ','AR','CA','CO','CT', | |
'DE','FL','GA','HI','ID','IL','IN', | |
'IA','KS','KY','LA','ME','MD','MA', | |
'MI','MN','MS','MO','MT','NE','NV', | |
'NH','NJ','NM','NY','NC','ND','OH', | |
'OK','OR','PA','RI','SC','SD','TN', | |
'TX','UT','VT','VA','WA','WV','WI', | |
'WY','') # we added '' since '' is our valid default value | |
#------------------------------------ | |
# constructor | |
#------------------------------------ | |
def __init__( self, street = "", apt_po = "", | |
city = "", state = "", zipcode = "" ): | |
self.street = street | |
self.apt_po = apt_po | |
self.city = city | |
self.state = state | |
self.zipcode = zipcode | |
#------------------------------------ | |
# getters | |
#------------------------------------ | |
@property | |
def street(self): | |
return self.__street | |
@property | |
def apt_po(self): | |
return self.__apt_po | |
@property | |
def city(self): | |
return self.__city | |
@property | |
def state(self): | |
return self.__state | |
@property | |
def zipcode(self): | |
return self.__zipcode | |
#------------------------------------ | |
# setters | |
#------------------------------------ | |
# modification ... all inputs to uppercase | |
@street.setter | |
def street( self, value ): | |
self.__street = str.upper(value) | |
@apt_po.setter | |
def apt_po( self, value ): | |
self.__apt_po = str.upper(value) | |
@city.setter | |
def city( self, value ): | |
self.__city = str.upper(value) | |
# validate state input | |
@state.setter | |
def state( self, value ): | |
st = str.upper(value) | |
if st in self.__STATE_VALIDATION: | |
# accept it | |
self.__state = st | |
else: | |
self.__state = "" # set to default value | |
@zipcode.setter | |
def zipcode( self, value ): | |
self.__zipcode = value | |
#------------------------------------ | |
# stringify / toString / print(object) | |
#------------------------------------ | |
def __str__( self ): | |
msg = "" | |
msg += "street = " + self.street + "\n" | |
msg += "apt_po = " + self.apt_po + "\n" | |
msg += "city = " + self.city + "\n" | |
msg += "state = " + self.state + "\n" | |
msg += "zipcode = " + self.zipcode | |
return msg | |
###################################### | |
# setup up some debug / testing code | |
# that will only run if this file | |
# is active as "main" | |
###################################### | |
if __name__ == "__main__": | |
BORDER_DASAH = "-" * 75 | |
print(BORDER_DASAH) | |
addy = Address() | |
print("address obj created ( default ):\n" + str(addy)) | |
print(BORDER_DASAH) | |
addy = Address( "123 45th St.", "Apt. 678", "Dallas", "TX", "75064") | |
print("address obj created ( param'd ):\n" + str(addy)) | |
print(BORDER_DASAH) | |
# show that we can make another STATE_VALIDATION | |
# variable, but Address objects won't use it | |
# for state validation, b/c their own internal | |
# one is used/called via self-STATE_VALIDATION | |
STATE_VALIDATION = "blah" | |
print("external STATE_VALIDATION var = " + STATE_VALIDATION) | |
# error, can't access property normally | |
# print(addy.__STATE_VALIDATION) | |
print(BORDER_DASAH) | |
# can access property by breaking name mangling | |
# (which things shouldn't do to bypass private) | |
print(addy._Address__STATE_VALIDATION) |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
################################## | |
""" | |
Our Customer object after we got it sorted out. | |
Can just import it going forward to reduce code bloat. | |
""" | |
################################## | |
# import the address object, b/c Customer uses it | |
from object_example_address import Address | |
################################## | |
class Customer: | |
#------------------------------------ | |
# constructor | |
#------------------------------------ | |
# property = default | |
def __init__( self, id = 0, | |
fname = "", | |
lname = "", | |
address = Address(), | |
charges = []): | |
#-------------------------------- | |
self.id = id | |
self.fname = fname | |
self.lname = lname | |
self.address = address | |
self.charges = charges # this will be a list | |
#-------------------------------- | |
#------------------------------------ | |
# getters | |
#------------------------------------ | |
@property | |
def id(self): | |
return self.__id | |
@property | |
def fname(self): | |
return self.__fname | |
@property | |
def lname(self): | |
return self.__lname | |
@property | |
def address(self): | |
return self.__address | |
@property | |
def charges(self): | |
return self.__charges | |
#------------------------------------ | |
# setters | |
#------------------------------------ | |
# modification ... string inputs to uppercase | |
# (address already u-cases itself internally) | |
@id.setter | |
def id( self, value ): | |
self.__id = value # int | |
@fname.setter | |
def fname( self, value ): | |
self.__fname = str.upper(value) | |
@lname.setter | |
def lname( self, value ): | |
self.__lname = str.upper(value) | |
@address.setter | |
def address( self, value ): | |
self.__address = value # object | |
@charges.setter | |
def charges( self, value ): | |
self.__charges = value # list | |
#------------------------------------ | |
# other functions | |
#------------------------------------ | |
# Total Charges (sums list of charges) | |
# very simply function.. just sums charges | |
# we don't want to print or anything else, | |
# we'll let other functions (like the __str__) | |
# handle what they want to do with the float | |
# sum we return | |
def totalCharges( self ): | |
return sum(self.charges) | |
#------------------------------------ | |
# stringify / toString / print(object) | |
#------------------------------------ | |
def __str__( self ): | |
msg = "" | |
msg += "id = " + str(self.id) + "\n" | |
msg += "fname = " + self.fname + "\n" | |
msg += "lname = " + self.lname + "\n" | |
# address object already has it's own stringify | |
# so all we have to do is ... | |
msg += str(self.address) + "\n" | |
msg += "charges = " + str(self.charges) + "\n" | |
msg += "tot chgs = " + str(self.totalCharges()) | |
return msg | |
###################################### | |
# setup up some debug / testing code | |
# that will only run if this file | |
# is active as "main" | |
###################################### | |
if __name__ == "__main__": | |
BORDER_DASAH = "-" * 75 | |
print(BORDER_DASAH) | |
cust1 = Customer() | |
print("customer obj created ( default ):\n" + str(cust1)) | |
print(BORDER_DASAH) | |
addy = Address( "123 45th St.", "Apt. 678", "Dallas", "TX", "75064" ) | |
chgs = [123.45, 987.65, 555.55] | |
cust2 = Customer( 1234, "Barry", "White", addy, chgs ) | |
print("customer obj created ( param'd ):\n" + str(cust2)) |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment