1302 lines
35 KiB
Plaintext
1302 lines
35 KiB
Plaintext
{
|
|
"cells": [
|
|
{
|
|
"cell_type": "markdown",
|
|
"id": "33fdf2a8-ee0f-4340-8cec-a43546275939",
|
|
"metadata": {},
|
|
"source": [
|
|
"# Class Methods & Properties"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "markdown",
|
|
"id": "98e7e0ac-17fc-46ec-98ab-9435789a471c",
|
|
"metadata": {},
|
|
"source": [
|
|
"### Class Attributes\n",
|
|
"\n",
|
|
"Sometimes we want to share data between all instances of a given class.\n",
|
|
"\n",
|
|
"All cars have 4 wheels, so we could define a shared variable accessible to all instances of the `Car` class.\n",
|
|
"\n",
|
|
"To do this, we create them within the `class` body, usually right above the `__init__`."
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "code",
|
|
"execution_count": 26,
|
|
"id": "aea2b16e-3012-4ce5-a806-7a2f0f712c08",
|
|
"metadata": {
|
|
"tags": []
|
|
},
|
|
"outputs": [],
|
|
"source": [
|
|
"import datetime\n",
|
|
"\n",
|
|
"class Car:\n",
|
|
" # class attribute\n",
|
|
" wheels = 4\n",
|
|
" registrations = []\n",
|
|
"\n",
|
|
" def __init__(self, make, model, year):\n",
|
|
" self.make = make \n",
|
|
" self.model = model \n",
|
|
" self.year = year\n",
|
|
" #self.wheels = 0\n",
|
|
" Car.registrations.append(self)\n",
|
|
" #self.registrations = []\n",
|
|
" \n",
|
|
" def compute_age(self):\n",
|
|
" return datetime.date.today().year - self.year \n",
|
|
" \n",
|
|
" \n",
|
|
"car1 = Car(\"Honda\", \"Accord\", 2019)\n",
|
|
"car2 = Car(\"Toyota\", \"RAV4\", 2006)"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "code",
|
|
"execution_count": 27,
|
|
"id": "a3aee627-9404-4c08-9571-6c1206ed9790",
|
|
"metadata": {
|
|
"tags": []
|
|
},
|
|
"outputs": [
|
|
{
|
|
"name": "stdout",
|
|
"output_type": "stream",
|
|
"text": [
|
|
"4\n",
|
|
"4\n",
|
|
"4\n"
|
|
]
|
|
}
|
|
],
|
|
"source": [
|
|
"# class attribute can be accessed on instances, or the class itself\n",
|
|
"\n",
|
|
"print(Car.wheels)\n",
|
|
"print(car1.wheels)\n",
|
|
"print(car2.wheels)"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "code",
|
|
"execution_count": 28,
|
|
"id": "61f681e4-0105-4b20-8d72-dfd5a347df8a",
|
|
"metadata": {},
|
|
"outputs": [
|
|
{
|
|
"data": {
|
|
"text/plain": [
|
|
"True"
|
|
]
|
|
},
|
|
"execution_count": 28,
|
|
"metadata": {},
|
|
"output_type": "execute_result"
|
|
}
|
|
],
|
|
"source": [
|
|
"# these are all the same variable\n",
|
|
"Car.wheels is car1.wheels"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "code",
|
|
"execution_count": 4,
|
|
"id": "2bf5dbb8-c432-49e3-970c-3b6ecfdef5f7",
|
|
"metadata": {
|
|
"tags": []
|
|
},
|
|
"outputs": [
|
|
{
|
|
"name": "stdout",
|
|
"output_type": "stream",
|
|
"text": [
|
|
"3\n",
|
|
"3\n"
|
|
]
|
|
}
|
|
],
|
|
"source": [
|
|
"# which means changes to the class attribute\n",
|
|
"# will modify for all classes \n",
|
|
"\n",
|
|
"Car.wheels = 3\n",
|
|
"print(car1.wheels)\n",
|
|
"print(car2.wheels)"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "code",
|
|
"execution_count": 31,
|
|
"id": "9997fc8a-6562-4e0a-8be9-9dc34446f5bd",
|
|
"metadata": {
|
|
"tags": []
|
|
},
|
|
"outputs": [
|
|
{
|
|
"name": "stdout",
|
|
"output_type": "stream",
|
|
"text": [
|
|
"False\n",
|
|
"4\n",
|
|
"4\n"
|
|
]
|
|
}
|
|
],
|
|
"source": [
|
|
"# note: assigning to an instance attribute makes a new attribute\n",
|
|
"\n",
|
|
"# creates a new instance variable!\n",
|
|
"car2.wheels = 2\n",
|
|
"print(car2.wheels is car1.wheels)\n",
|
|
"print(car1.wheels)\n",
|
|
"print(Car.wheels)"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "markdown",
|
|
"id": "a222dcb1-fcfa-43c8-bf4b-13ad9626b6c3",
|
|
"metadata": {},
|
|
"source": [
|
|
"### Class Methods\n",
|
|
"\n",
|
|
"It can also be useful to provide methods that are accessible to all instances of a class.\n",
|
|
"\n",
|
|
"Class methods are similar to instance methods with a few distinctions:\n",
|
|
"\n",
|
|
"1. They can not access instance methods or attributes.\n",
|
|
"2. The first argument to the method is not `self`, but instead `cls` by convention. `cls` is the class object itself (e.g. `Car`)\n",
|
|
"3. Class methods are declared with the `@classmethod` decorator."
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "code",
|
|
"execution_count": 32,
|
|
"id": "95bcb4f6-d11e-4074-90d3-63dc6a0f19eb",
|
|
"metadata": {
|
|
"tags": []
|
|
},
|
|
"outputs": [],
|
|
"source": [
|
|
"from datetime import date\n",
|
|
"\n",
|
|
"class Car: \n",
|
|
" \n",
|
|
" # wheels class attribute \n",
|
|
" wheels = 4\n",
|
|
" # tire pressure class attribute \n",
|
|
" psi = 35 \n",
|
|
" \n",
|
|
" def __init__(self, make, model, year):\n",
|
|
" self.make = make \n",
|
|
" self.model = model \n",
|
|
" self.year = year\n",
|
|
" \n",
|
|
" def compute_age(self):\n",
|
|
" print(self)\n",
|
|
" current_year = int(date.today().year)\n",
|
|
" return current_year - self.year \n",
|
|
" \n",
|
|
" @classmethod \n",
|
|
" def tire_description(cls):\n",
|
|
" print(cls)\n",
|
|
" return f'Car has {cls.wheels} wheels with a tire pressure of {Car.psi}' \n",
|
|
" \n",
|
|
"car1 = Car(\"Honda\", \"Accord\", 2019)\n",
|
|
"car2 = Car(\"Toyota\", \"RAV4\", 2006)"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "code",
|
|
"execution_count": 35,
|
|
"id": "0805ad8c-4b7a-48e2-9ff9-be57a03067dc",
|
|
"metadata": {
|
|
"tags": []
|
|
},
|
|
"outputs": [
|
|
{
|
|
"name": "stdout",
|
|
"output_type": "stream",
|
|
"text": [
|
|
"<class '__main__.Car'>\n",
|
|
"Car has 4 wheels with a tire pressure of 35\n",
|
|
"<__main__.Car object at 0x1034b7e50>\n",
|
|
"5\n"
|
|
]
|
|
}
|
|
],
|
|
"source": [
|
|
"print(Car.tire_description())\n",
|
|
"#print(car1.tire_description())\n",
|
|
"print(car1.compute_age())"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "markdown",
|
|
"id": "cdea319d-db59-4342-bbc3-41d1bf17e1c7",
|
|
"metadata": {},
|
|
"source": [
|
|
"Notice that we can use `Car.psi` or `cls.wheels` to access class attributes. `cls` is generally preferred, both to avoid repetition and for reasons we'll see when we get to inheritance.\n",
|
|
"\n",
|
|
"Finally, note that we can access class methods and instances from within instance methods. (but not vice-versa!)"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "code",
|
|
"execution_count": 8,
|
|
"id": "718fb1d6-7bd8-47db-b3c6-5f2c4b69056e",
|
|
"metadata": {},
|
|
"outputs": [],
|
|
"source": [
|
|
"from datetime import date\n",
|
|
"class Car: \n",
|
|
" \n",
|
|
" # wheels class attribute \n",
|
|
" wheels = 4\n",
|
|
" \n",
|
|
" # tire pressure amount \n",
|
|
" psi = 35 \n",
|
|
" \n",
|
|
" def __init__(self, make, model, year):\n",
|
|
" self.make = make \n",
|
|
" self.model = model \n",
|
|
" self.year = year\n",
|
|
" \n",
|
|
" def compute_age(self):\n",
|
|
" current_year = int(date.today().year)\n",
|
|
" return current_year - self.year \n",
|
|
" \n",
|
|
" @classmethod \n",
|
|
" def tire_description(cls):\n",
|
|
" return f'Car has {cls.wheels} wheels, each with a tire pressure of {Car.psi}' \n",
|
|
"\n",
|
|
" def __repr__(self): \n",
|
|
" instance_str = f'Car(make={self.make}, model={self.model}, year={self.year}, '\n",
|
|
" instance_str += f'wheels={Car.wheels}, {self.tire_description()})'\n",
|
|
" return instance_str"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "code",
|
|
"execution_count": 9,
|
|
"id": "fd34539f-abc4-4bb2-99f1-b49af1eb1570",
|
|
"metadata": {},
|
|
"outputs": [
|
|
{
|
|
"name": "stdout",
|
|
"output_type": "stream",
|
|
"text": [
|
|
"Car(make=Honda, model=Civic, year=2019, wheels=4, Car has 4 wheels, each with a tire pressure of 35)\n"
|
|
]
|
|
}
|
|
],
|
|
"source": [
|
|
"car1 = Car(\"Honda\", \"Civic\", 2019)\n",
|
|
"print(car1)"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "markdown",
|
|
"id": "45037ff6-af90-4934-9eea-ad9b2d241cc8",
|
|
"metadata": {},
|
|
"source": [
|
|
"### Alternate Constructors\n",
|
|
"\n",
|
|
"A common use of class methods is to define alternate ways to initialize an isntance. In Python there can only be one constructor (`__init__`), whereas some other languages allow multiple.\n",
|
|
"\n",
|
|
"Perhaps we have Car data coming from a file, meaning we'd have strings like:"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "code",
|
|
"execution_count": 40,
|
|
"id": "a1c858ea-8222-4776-a3b7-9f7443bd918f",
|
|
"metadata": {
|
|
"tags": []
|
|
},
|
|
"outputs": [],
|
|
"source": [
|
|
"car1str = \"Pontiac|Grand Am|1997|4892\"\n",
|
|
"car2str = \"Ford|Mustang|1970|800\"\n",
|
|
"car3str = \"Hyundai|Sonata|2007|0\"\n",
|
|
"\n",
|
|
"\n",
|
|
"def make_car_from_string(s: str) -> Car:\n",
|
|
" ..."
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "code",
|
|
"execution_count": 41,
|
|
"id": "5c10ac8b-deb5-4a23-9beb-d0d32306552d",
|
|
"metadata": {
|
|
"tags": []
|
|
},
|
|
"outputs": [],
|
|
"source": [
|
|
"from datetime import date\n",
|
|
"\n",
|
|
"class Car: \n",
|
|
" wheels = 4\n",
|
|
" psi = 35\n",
|
|
" \n",
|
|
" def __init__(self, make, model, year):\n",
|
|
" self.make = make \n",
|
|
" self.model = model \n",
|
|
" self.year = year\n",
|
|
" self.mileage = 0\n",
|
|
" \n",
|
|
" @classmethod\n",
|
|
" def from_string(cls, string):\n",
|
|
" make, model, year, mileage = string.split(\"|\")\n",
|
|
" # invoke Car's constructor\n",
|
|
" new_instance = cls(make, model, year)\n",
|
|
" new_instance.mileage = mileage\n",
|
|
" return new_instance\n",
|
|
" \n",
|
|
" def compute_age(self):\n",
|
|
" current_year = int(date.today().year)\n",
|
|
" return current_year - self.year \n",
|
|
" \n",
|
|
" @classmethod \n",
|
|
" def tire_description(cls):\n",
|
|
" return f'Car has {cls.wheels} wheels, each with a tire pressure of {Car.psi}' \n",
|
|
"\n",
|
|
" def __repr__(self): \n",
|
|
" instance_str = f'Car(make={self.make}, model={self.model}, year={self.year}, '\n",
|
|
" instance_str += f'wheels={Car.wheels})'\n",
|
|
" return instance_str"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "code",
|
|
"execution_count": 42,
|
|
"id": "39b783df-a6a6-4a6e-b33e-a20ec18f4671",
|
|
"metadata": {
|
|
"tags": []
|
|
},
|
|
"outputs": [],
|
|
"source": [
|
|
"car1 = Car.from_string(car1str)\n",
|
|
"car2 = Car.from_string(car2str)\n",
|
|
"car3 = Car.from_string(car3str)"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "code",
|
|
"execution_count": 43,
|
|
"id": "b5cde4b1-168f-48bd-ac95-b0ca87c66c5a",
|
|
"metadata": {
|
|
"tags": []
|
|
},
|
|
"outputs": [
|
|
{
|
|
"name": "stdout",
|
|
"output_type": "stream",
|
|
"text": [
|
|
"Car(make=Pontiac, model=Grand Am, year=1997, wheels=4)\n",
|
|
"Car(make=Ford, model=Mustang, year=1970, wheels=4)\n",
|
|
"Car(make=Hyundai, model=Sonata, year=2007, wheels=4)\n"
|
|
]
|
|
}
|
|
],
|
|
"source": [
|
|
"print(car1)\n",
|
|
"print(car2)\n",
|
|
"print(car3)"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "markdown",
|
|
"id": "c0001b40-39e0-4502-9d9c-563ed14b9978",
|
|
"metadata": {},
|
|
"source": [
|
|
"This is a common pattern, seen throughout Python:\n",
|
|
"\n",
|
|
" - ``int.from_bytes()``\n",
|
|
" - ``float.fromhex()`` \n",
|
|
" - ``datetime.date.fromtimestamp()``\n",
|
|
" - ``itertools.chain.from_iterable()``\n"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "code",
|
|
"execution_count": 45,
|
|
"id": "d673a408-4e12-49ae-b402-5179820e35fa",
|
|
"metadata": {},
|
|
"outputs": [
|
|
{
|
|
"ename": "TypeError",
|
|
"evalue": "map() must have at least two arguments.",
|
|
"output_type": "error",
|
|
"traceback": [
|
|
"\u001b[0;31m---------------------------------------------------------------------------\u001b[0m",
|
|
"\u001b[0;31mTypeError\u001b[0m Traceback (most recent call last)",
|
|
"Cell \u001b[0;32mIn[45], line 1\u001b[0m\n\u001b[0;32m----> 1\u001b[0m x \u001b[38;5;241m=\u001b[39m \u001b[38;5;28mlist\u001b[39m(\u001b[38;5;28;43mmap\u001b[39;49m\u001b[43m(\u001b[49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43m)\u001b[49m)\n\u001b[1;32m 2\u001b[0m y \u001b[38;5;241m=\u001b[39m \u001b[38;5;28mdict\u001b[39m(\u001b[38;5;241m.\u001b[39m\u001b[38;5;241m.\u001b[39m\u001b[38;5;241m.\u001b[39m)\n",
|
|
"\u001b[0;31mTypeError\u001b[0m: map() must have at least two arguments."
|
|
]
|
|
}
|
|
],
|
|
"source": [
|
|
"x = list(map(...))\n",
|
|
"y = dict(...)"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "code",
|
|
"execution_count": 13,
|
|
"id": "3a5ed2dd-732f-4fb2-b776-475c60f2f0ec",
|
|
"metadata": {},
|
|
"outputs": [
|
|
{
|
|
"data": {
|
|
"text/plain": [
|
|
"datetime.date(2024, 11, 11)"
|
|
]
|
|
},
|
|
"execution_count": 13,
|
|
"metadata": {},
|
|
"output_type": "execute_result"
|
|
}
|
|
],
|
|
"source": [
|
|
"import datetime\n",
|
|
"datetime.date(2024, 11, 11)"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "code",
|
|
"execution_count": 14,
|
|
"id": "dccd79cc-4f80-4609-bc11-d136beb1b9b9",
|
|
"metadata": {},
|
|
"outputs": [
|
|
{
|
|
"data": {
|
|
"text/plain": [
|
|
"datetime.date(2009, 2, 13)"
|
|
]
|
|
},
|
|
"execution_count": 14,
|
|
"metadata": {},
|
|
"output_type": "execute_result"
|
|
}
|
|
],
|
|
"source": [
|
|
"datetime.date.fromtimestamp(1234567890)"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "code",
|
|
"execution_count": 46,
|
|
"id": "6d9f0442-429c-4ed4-8dc2-3bd6f5064f09",
|
|
"metadata": {},
|
|
"outputs": [
|
|
{
|
|
"name": "stdout",
|
|
"output_type": "stream",
|
|
"text": [
|
|
"1\n",
|
|
"2\n",
|
|
"3\n",
|
|
"4\n",
|
|
"5\n",
|
|
"6\n"
|
|
]
|
|
}
|
|
],
|
|
"source": [
|
|
"import itertools\n",
|
|
"for x in itertools.chain.from_iterable([(1,2,3), (4,5,6)]):\n",
|
|
" print(x)\n",
|
|
"#for x in (1,2,3):\n",
|
|
"# print(x)\n",
|
|
"#for x in (4,5,6):\n",
|
|
"# print(x)"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "markdown",
|
|
"id": "c4d1ad9f-b280-4bcf-af74-bf8dc931dbab",
|
|
"metadata": {},
|
|
"source": [
|
|
"### staticmethod\n",
|
|
"\n",
|
|
"Sometimes it makes sense to just attach a method to a class for the purpose of namespacing."
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "code",
|
|
"execution_count": 17,
|
|
"id": "75dc6b84-f24c-4f95-a718-6c802c767e04",
|
|
"metadata": {},
|
|
"outputs": [
|
|
{
|
|
"data": {
|
|
"text/plain": [
|
|
"Car(make=Pontiac, model=Grand Am, year=1997, wheels=4)"
|
|
]
|
|
},
|
|
"execution_count": 17,
|
|
"metadata": {},
|
|
"output_type": "execute_result"
|
|
}
|
|
],
|
|
"source": [
|
|
"def which_is_newer(a, b):\n",
|
|
" if a.year > b.year:\n",
|
|
" return a\n",
|
|
" else:\n",
|
|
" return b\n",
|
|
"\n",
|
|
"which_is_newer(car1, car2)"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "code",
|
|
"execution_count": 18,
|
|
"id": "4c412729-d23c-470a-baa3-b9aab42beb84",
|
|
"metadata": {},
|
|
"outputs": [],
|
|
"source": [
|
|
"# it might make sense to attach this to the class, \n",
|
|
"# but neither a classmethod nor an instance method\n",
|
|
"\n",
|
|
"from datetime import date\n",
|
|
"class Car: \n",
|
|
" wheels = 4\n",
|
|
" psi = 35\n",
|
|
" \n",
|
|
" # does not take self or cls\n",
|
|
" @staticmethod\n",
|
|
" def which_is_newer(a, b):\n",
|
|
" if a.year > b.year:\n",
|
|
" return a\n",
|
|
" else:\n",
|
|
" return b\n",
|
|
" \n",
|
|
" @staticmethod\n",
|
|
" def something():\n",
|
|
" return []\n",
|
|
" \n",
|
|
"\n",
|
|
" \n",
|
|
" def __init__(self, make, model, year):\n",
|
|
" self.make = make \n",
|
|
" self.model = model \n",
|
|
" self.year = year\n",
|
|
" \n",
|
|
" @classmethod\n",
|
|
" def from_string(cls, string):\n",
|
|
" make, model, year = string.split(\"|\")\n",
|
|
" # invoke Car's constructor\n",
|
|
" return cls(make, model, year)\n",
|
|
"\n",
|
|
" def __repr__(self): \n",
|
|
" instance_str = f'Car(make={self.make}, model={self.model}, year={self.year}, '\n",
|
|
" instance_str += f'wheels={Car.wheels})'\n",
|
|
" return instance_str"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "code",
|
|
"execution_count": 19,
|
|
"id": "24ffc511-9540-4777-a71e-63a020705b3b",
|
|
"metadata": {},
|
|
"outputs": [
|
|
{
|
|
"data": {
|
|
"text/plain": [
|
|
"Car(make=Pontiac, model=Grand Am, year=1997, wheels=4)"
|
|
]
|
|
},
|
|
"execution_count": 19,
|
|
"metadata": {},
|
|
"output_type": "execute_result"
|
|
}
|
|
],
|
|
"source": [
|
|
"# now would be called this way\n",
|
|
"Car.which_is_newer(car1, car2)"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "markdown",
|
|
"id": "8100f484-0abd-495d-8580-c9ba1946e758",
|
|
"metadata": {},
|
|
"source": [
|
|
"### Encapsulation\n",
|
|
"\n",
|
|
">``[Encapsulation] allows the implementation of an object's interface to be changed without impacting the users of that object.\"\n",
|
|
"\n",
|
|
"The main idea of encapsulation is to hide implementation details from the users of an object. You only expose a public interface to the users.\n",
|
|
"\n",
|
|
"There are a few ways to encapsulation is handled in Python: \n",
|
|
"\n",
|
|
"- Private attributes using underscores\n",
|
|
"- Getter/Setters\n",
|
|
"- Properties\n",
|
|
"\n",
|
|
"### Private Attributes\n",
|
|
"\n",
|
|
"We saw last week, if we define class attributes with double underscores they are not accessible outside the class."
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "code",
|
|
"execution_count": 20,
|
|
"id": "5833020e-398c-4947-8698-0fa3183e63ab",
|
|
"metadata": {},
|
|
"outputs": [],
|
|
"source": [
|
|
"class Example:\n",
|
|
" def __init__(self, x, y, z):\n",
|
|
" self.x = x\n",
|
|
" self._y = y\n",
|
|
" self.__z = z\n",
|
|
" \n",
|
|
" def __repr__(self):\n",
|
|
" return f\"Example({self.x}, {self._y}, {self.__z})\"\n",
|
|
"\n",
|
|
"instance = Example(1, 2, 3)"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "code",
|
|
"execution_count": 21,
|
|
"id": "f76bc0e2-4454-4c66-9ff4-8602ee4e35d0",
|
|
"metadata": {},
|
|
"outputs": [
|
|
{
|
|
"data": {
|
|
"text/plain": [
|
|
"1"
|
|
]
|
|
},
|
|
"execution_count": 21,
|
|
"metadata": {},
|
|
"output_type": "execute_result"
|
|
}
|
|
],
|
|
"source": [
|
|
"# normal public attribute\n",
|
|
"instance.x"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "code",
|
|
"execution_count": 22,
|
|
"id": "857f934a-3b7c-4fcb-bdfa-f802e302c966",
|
|
"metadata": {},
|
|
"outputs": [
|
|
{
|
|
"data": {
|
|
"text/plain": [
|
|
"2"
|
|
]
|
|
},
|
|
"execution_count": 22,
|
|
"metadata": {},
|
|
"output_type": "execute_result"
|
|
}
|
|
],
|
|
"source": [
|
|
"# single underscore attributes are private by convention only\n",
|
|
"# (there is no enforcement)\n",
|
|
"instance._y"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "code",
|
|
"execution_count": 23,
|
|
"id": "69902de2-aa03-4132-8c9a-13a9e954877b",
|
|
"metadata": {},
|
|
"outputs": [
|
|
{
|
|
"ename": "AttributeError",
|
|
"evalue": "'Example' object has no attribute '__z'",
|
|
"output_type": "error",
|
|
"traceback": [
|
|
"\u001b[0;31m---------------------------------------------------------------------------\u001b[0m",
|
|
"\u001b[0;31mAttributeError\u001b[0m Traceback (most recent call last)",
|
|
"Cell \u001b[0;32mIn[23], line 2\u001b[0m\n\u001b[1;32m 1\u001b[0m \u001b[38;5;66;03m# double underscore methods are name-mangled\u001b[39;00m\n\u001b[0;32m----> 2\u001b[0m \u001b[43minstance\u001b[49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43m__z\u001b[49m\n",
|
|
"\u001b[0;31mAttributeError\u001b[0m: 'Example' object has no attribute '__z'"
|
|
]
|
|
}
|
|
],
|
|
"source": [
|
|
"# double underscore methods are name-mangled\n",
|
|
"instance.__z"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "code",
|
|
"execution_count": null,
|
|
"id": "43e08993-7858-4c91-b4b1-98d6bbe5b359",
|
|
"metadata": {},
|
|
"outputs": [],
|
|
"source": []
|
|
},
|
|
{
|
|
"cell_type": "markdown",
|
|
"id": "d7b06268-0bc1-4d70-9c54-eca2f728b590",
|
|
"metadata": {},
|
|
"source": [
|
|
"### Getters / Setters\n",
|
|
"\n",
|
|
"Another common pattern to hide data in OOP languages is to use getter and setter methods that control access."
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "code",
|
|
"execution_count": 2,
|
|
"id": "b5d16735-9280-4002-a4ba-bc267fd52ff2",
|
|
"metadata": {
|
|
"tags": []
|
|
},
|
|
"outputs": [],
|
|
"source": [
|
|
"class Person:\n",
|
|
" def __init__(self, name, age):\n",
|
|
" self.__name = name # Assume it has getter/setters \n",
|
|
" self.set_age(age)\n",
|
|
"\n",
|
|
" def _calculate_age_from_birthday():\n",
|
|
" pass\n",
|
|
"\n",
|
|
" def get_age(self):\n",
|
|
" return self._calculate_age_from_birthday()\n",
|
|
"\n",
|
|
" def set_age(self, age):\n",
|
|
" if age < 0:\n",
|
|
" raise ValueError(\"Person can't have a negative age!\")\n",
|
|
" self.__age = age\n",
|
|
" \n",
|
|
" def set_name(self, name):\n",
|
|
" if \" \" not in name:\n",
|
|
" raise ValueError(\"must be at least two words\")\n",
|
|
" self.__name = name"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "code",
|
|
"execution_count": 3,
|
|
"id": "1923f424-f9a4-4bb1-bd30-5847ecbc3064",
|
|
"metadata": {
|
|
"tags": []
|
|
},
|
|
"outputs": [],
|
|
"source": [
|
|
"p = Person(\"C. Montgomery Burns\", 100)"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "code",
|
|
"execution_count": 4,
|
|
"id": "74f206d8-3ecd-464d-a3ca-8a17f390f6ac",
|
|
"metadata": {
|
|
"tags": []
|
|
},
|
|
"outputs": [
|
|
{
|
|
"data": {
|
|
"text/plain": [
|
|
"100"
|
|
]
|
|
},
|
|
"execution_count": 4,
|
|
"metadata": {},
|
|
"output_type": "execute_result"
|
|
}
|
|
],
|
|
"source": [
|
|
"p.get_age()"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "code",
|
|
"execution_count": 6,
|
|
"id": "75b8d93c-c2f7-4712-9934-8ccf0ad22439",
|
|
"metadata": {
|
|
"tags": []
|
|
},
|
|
"outputs": [],
|
|
"source": [
|
|
"p.set_age(101)"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "code",
|
|
"execution_count": 7,
|
|
"id": "c89dd611-adec-405f-9ac8-0fb65a68e35d",
|
|
"metadata": {
|
|
"tags": []
|
|
},
|
|
"outputs": [
|
|
{
|
|
"data": {
|
|
"text/plain": [
|
|
"101"
|
|
]
|
|
},
|
|
"execution_count": 7,
|
|
"metadata": {},
|
|
"output_type": "execute_result"
|
|
}
|
|
],
|
|
"source": [
|
|
"p.get_age()"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "code",
|
|
"execution_count": 8,
|
|
"id": "af8b33ec-388a-4d9a-98c0-d5a824eede66",
|
|
"metadata": {
|
|
"tags": []
|
|
},
|
|
"outputs": [
|
|
{
|
|
"ename": "ValueError",
|
|
"evalue": "Person can't have a negative age!",
|
|
"output_type": "error",
|
|
"traceback": [
|
|
"\u001b[0;31m---------------------------------------------------------------------------\u001b[0m",
|
|
"\u001b[0;31mValueError\u001b[0m Traceback (most recent call last)",
|
|
"Cell \u001b[0;32mIn[8], line 1\u001b[0m\n\u001b[0;32m----> 1\u001b[0m \u001b[43mp\u001b[49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43mset_age\u001b[49m\u001b[43m(\u001b[49m\u001b[38;5;241;43m-\u001b[39;49m\u001b[38;5;241;43m1\u001b[39;49m\u001b[43m)\u001b[49m\n",
|
|
"Cell \u001b[0;32mIn[2], line 11\u001b[0m, in \u001b[0;36mPerson.set_age\u001b[0;34m(self, age)\u001b[0m\n\u001b[1;32m 9\u001b[0m \u001b[38;5;28;01mdef\u001b[39;00m \u001b[38;5;21mset_age\u001b[39m(\u001b[38;5;28mself\u001b[39m, age):\n\u001b[1;32m 10\u001b[0m \u001b[38;5;28;01mif\u001b[39;00m age \u001b[38;5;241m<\u001b[39m \u001b[38;5;241m0\u001b[39m:\n\u001b[0;32m---> 11\u001b[0m \u001b[38;5;28;01mraise\u001b[39;00m \u001b[38;5;167;01mValueError\u001b[39;00m(\u001b[38;5;124m\"\u001b[39m\u001b[38;5;124mPerson can\u001b[39m\u001b[38;5;124m'\u001b[39m\u001b[38;5;124mt have a negative age!\u001b[39m\u001b[38;5;124m\"\u001b[39m)\n\u001b[1;32m 12\u001b[0m \u001b[38;5;28mself\u001b[39m\u001b[38;5;241m.\u001b[39m__age \u001b[38;5;241m=\u001b[39m age\n",
|
|
"\u001b[0;31mValueError\u001b[0m: Person can't have a negative age!"
|
|
]
|
|
}
|
|
],
|
|
"source": [
|
|
"p.set_age(-1)"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "code",
|
|
"execution_count": 9,
|
|
"id": "9705d447-8568-476e-97ab-541b61f05002",
|
|
"metadata": {},
|
|
"outputs": [
|
|
{
|
|
"data": {
|
|
"text/plain": [
|
|
"101"
|
|
]
|
|
},
|
|
"execution_count": 9,
|
|
"metadata": {},
|
|
"output_type": "execute_result"
|
|
}
|
|
],
|
|
"source": [
|
|
"p.get_age()"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "markdown",
|
|
"id": "e339267b-e14d-4665-99e6-63a57ee9c575",
|
|
"metadata": {},
|
|
"source": [
|
|
"This can become very tedious, and as we've seen they don't actually protect access to variables. Therefore we typically **avoid getters and setters in Python.**"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "markdown",
|
|
"id": "a3b96d2d-5050-4973-a69b-469a0da63c40",
|
|
"metadata": {},
|
|
"source": [
|
|
"### Properties\n",
|
|
"\n",
|
|
"We want the advantages of encapsulation (being able to avoid improper use, hiding our internal representation, etc.) but without the need to start with a bunch of getter/setter functions from the get go.\n",
|
|
"\n",
|
|
"Python has a much nicer way to control access to attributes via **properties**.\n",
|
|
"\n",
|
|
"There is a built in function `property()` that creates and returns a property object.\n",
|
|
"\n",
|
|
"`property(fget=None, fset=None, fdel=None, doc=None)`\n",
|
|
"\n",
|
|
"- `fget` is a function to get value of the attribute\n",
|
|
"- `fset` is a function to set value of the attribute\n",
|
|
"- `fdel` is a function to delete the attribute\n",
|
|
"- `doc` is a docstring for the attribute"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "code",
|
|
"execution_count": 16,
|
|
"id": "b92490b9-867a-461b-b922-a54235ca9463",
|
|
"metadata": {
|
|
"tags": []
|
|
},
|
|
"outputs": [],
|
|
"source": [
|
|
"class Person:\n",
|
|
" \n",
|
|
" def __init__(self, name, age):\n",
|
|
" self.name = name # Assume it has getter/setters \n",
|
|
" self.age = age\n",
|
|
"\n",
|
|
" def _get_age(self):\n",
|
|
" print(\"inside get age\")\n",
|
|
" return self.__age\n",
|
|
"\n",
|
|
" def _set_age(self, age):\n",
|
|
" if age < 0:\n",
|
|
" raise ValueError(\"Person can't have a negative age!\")\n",
|
|
" self.__age = age\n",
|
|
" \n",
|
|
" def __repr__(self):\n",
|
|
" return f\"Person({self.__name!r}, {self.__age})\"\n",
|
|
" \n",
|
|
" age = property(_get_age, _set_age, doc=\"age of the person\")"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "code",
|
|
"execution_count": 17,
|
|
"id": "c32cd2dd-cb18-4858-b662-19790b74571e",
|
|
"metadata": {
|
|
"tags": []
|
|
},
|
|
"outputs": [
|
|
{
|
|
"name": "stdout",
|
|
"output_type": "stream",
|
|
"text": [
|
|
"inside get age\n"
|
|
]
|
|
},
|
|
{
|
|
"data": {
|
|
"text/plain": [
|
|
"1000"
|
|
]
|
|
},
|
|
"execution_count": 17,
|
|
"metadata": {},
|
|
"output_type": "execute_result"
|
|
}
|
|
],
|
|
"source": [
|
|
"p = Person(\"Wayne\", 30)\n",
|
|
"p.age"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "code",
|
|
"execution_count": null,
|
|
"id": "6c52d816-af8d-46bf-8e23-cc1417743868",
|
|
"metadata": {},
|
|
"outputs": [],
|
|
"source": []
|
|
},
|
|
{
|
|
"cell_type": "code",
|
|
"execution_count": 15,
|
|
"id": "30497dd2-ebf6-44f8-a4f9-d37b4384b06a",
|
|
"metadata": {
|
|
"tags": []
|
|
},
|
|
"outputs": [
|
|
{
|
|
"ename": "ValueError",
|
|
"evalue": "Person can't have a negative age!",
|
|
"output_type": "error",
|
|
"traceback": [
|
|
"\u001b[0;31m---------------------------------------------------------------------------\u001b[0m",
|
|
"\u001b[0;31mValueError\u001b[0m Traceback (most recent call last)",
|
|
"Cell \u001b[0;32mIn[15], line 1\u001b[0m\n\u001b[0;32m----> 1\u001b[0m \u001b[43mp\u001b[49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43mage\u001b[49m \u001b[38;5;241m=\u001b[39m \u001b[38;5;241m-\u001b[39m\u001b[38;5;241m1\u001b[39m\n",
|
|
"Cell \u001b[0;32mIn[10], line 12\u001b[0m, in \u001b[0;36mPerson.set_age\u001b[0;34m(self, age)\u001b[0m\n\u001b[1;32m 10\u001b[0m \u001b[38;5;28;01mdef\u001b[39;00m \u001b[38;5;21mset_age\u001b[39m(\u001b[38;5;28mself\u001b[39m, age):\n\u001b[1;32m 11\u001b[0m \u001b[38;5;28;01mif\u001b[39;00m age \u001b[38;5;241m<\u001b[39m \u001b[38;5;241m0\u001b[39m:\n\u001b[0;32m---> 12\u001b[0m \u001b[38;5;28;01mraise\u001b[39;00m \u001b[38;5;167;01mValueError\u001b[39;00m(\u001b[38;5;124m\"\u001b[39m\u001b[38;5;124mPerson can\u001b[39m\u001b[38;5;124m'\u001b[39m\u001b[38;5;124mt have a negative age!\u001b[39m\u001b[38;5;124m\"\u001b[39m)\n\u001b[1;32m 13\u001b[0m \u001b[38;5;28mself\u001b[39m\u001b[38;5;241m.\u001b[39m__age \u001b[38;5;241m=\u001b[39m age\n",
|
|
"\u001b[0;31mValueError\u001b[0m: Person can't have a negative age!"
|
|
]
|
|
}
|
|
],
|
|
"source": [
|
|
"p.age = -1"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "code",
|
|
"execution_count": 13,
|
|
"id": "06a9c879-8c84-4b57-b585-b162e6c489fa",
|
|
"metadata": {
|
|
"tags": []
|
|
},
|
|
"outputs": [
|
|
{
|
|
"name": "stdout",
|
|
"output_type": "stream",
|
|
"text": [
|
|
"inside get age\n",
|
|
"30\n"
|
|
]
|
|
}
|
|
],
|
|
"source": [
|
|
"print(p.age)"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "markdown",
|
|
"id": "725eb530-d85e-4c9d-979f-00ad1a12a0b7",
|
|
"metadata": {},
|
|
"source": [
|
|
"#### @property\n",
|
|
"\n",
|
|
"We can also use `property` as a decorator. \n",
|
|
"\n",
|
|
"- Place the `@property` directly above the function header of the getter function.\n",
|
|
"\n",
|
|
"- Place the code `@name_of_property.setter` above the function header of the setter function. You need to replace the name_of_property with the actual name of the property.\n",
|
|
"\n",
|
|
"- The function names for both the setter/getter need to match."
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "code",
|
|
"execution_count": 59,
|
|
"id": "abe093f5-b911-434b-8071-00c9d177e5bf",
|
|
"metadata": {},
|
|
"outputs": [],
|
|
"source": [
|
|
"class Person:\n",
|
|
" def __init__(self, name, age):\n",
|
|
" self.__name = name # Assume it has getter/setters \n",
|
|
" # invokes setter\n",
|
|
" self.age = age #self.set_age(age)\n",
|
|
" self.birth_date = ...\n",
|
|
"\n",
|
|
" @property\n",
|
|
" def age(self):\n",
|
|
" \"\"\" returns the age property \"\"\"\n",
|
|
" print('getter called')\n",
|
|
" return self.__age\n",
|
|
" #age = property(age)\n",
|
|
" \n",
|
|
" @age.setter\n",
|
|
" def age(self, age):\n",
|
|
" print('setter called')\n",
|
|
" if age < 0:\n",
|
|
" raise ValueError(\"Person can't have a negative age!\")\n",
|
|
" self.__age = age\n",
|
|
" \n",
|
|
" def __repr__(self):\n",
|
|
" return f\"Person({self.__name!r}, {self.__age})\""
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "code",
|
|
"execution_count": 61,
|
|
"id": "3bc6ff3e-9dc3-4b48-876b-3a7a98911da4",
|
|
"metadata": {},
|
|
"outputs": [
|
|
{
|
|
"name": "stdout",
|
|
"output_type": "stream",
|
|
"text": [
|
|
"setter called\n",
|
|
"getter called\n",
|
|
"28\n"
|
|
]
|
|
}
|
|
],
|
|
"source": [
|
|
"p2 = Person(\"Emma\", 28)\n",
|
|
"#p2.age = -1\n",
|
|
"print(p2.age)"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "markdown",
|
|
"id": "e492919b-2c90-4723-9a11-4cc50feb086f",
|
|
"metadata": {},
|
|
"source": [
|
|
"This allows us to start class attributes as public, and add properties as needed."
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "code",
|
|
"execution_count": 37,
|
|
"id": "4eb66674-f3c2-40fa-8d75-9aa08fcafa87",
|
|
"metadata": {},
|
|
"outputs": [],
|
|
"source": [
|
|
"class Point:\n",
|
|
" def __init__(self, x, y):\n",
|
|
" self.x = x \n",
|
|
" self.y = y"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "code",
|
|
"execution_count": 38,
|
|
"id": "60455fce-fd92-41a3-a770-7b6a9f348552",
|
|
"metadata": {},
|
|
"outputs": [],
|
|
"source": [
|
|
"p = Point(10, 10)"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "markdown",
|
|
"id": "06d8973f-0eb4-45f9-b234-7680b51775b3",
|
|
"metadata": {},
|
|
"source": [
|
|
"#### Read-only/Calculated Properties"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "code",
|
|
"execution_count": 26,
|
|
"id": "0c9fc2ed-9a6e-41d3-a1bb-a7c13641d617",
|
|
"metadata": {
|
|
"tags": []
|
|
},
|
|
"outputs": [],
|
|
"source": [
|
|
"class Rectangle: \n",
|
|
" \n",
|
|
" def __init__(self,width,height):\n",
|
|
" self.width = width \n",
|
|
" self.height = height \n",
|
|
" self.area = width*height\n",
|
|
" \n",
|
|
" # read-only calculated property\n",
|
|
" #@property \n",
|
|
" #def area(self):\n",
|
|
" # return self.width * self.height "
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "code",
|
|
"execution_count": 29,
|
|
"id": "2aa20ea3-1292-4224-9d99-b40eab748021",
|
|
"metadata": {
|
|
"tags": []
|
|
},
|
|
"outputs": [],
|
|
"source": [
|
|
"r = Rectangle(3, 9)"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "code",
|
|
"execution_count": 30,
|
|
"id": "a03f9038-7b97-4e0b-a46e-b8d558f12882",
|
|
"metadata": {
|
|
"tags": []
|
|
},
|
|
"outputs": [
|
|
{
|
|
"name": "stdout",
|
|
"output_type": "stream",
|
|
"text": [
|
|
"27\n"
|
|
]
|
|
}
|
|
],
|
|
"source": [
|
|
"print(r.area)"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "code",
|
|
"execution_count": 31,
|
|
"id": "7d9ce39e-bfc3-4389-9315-a9bb16dcdec0",
|
|
"metadata": {},
|
|
"outputs": [
|
|
{
|
|
"name": "stdout",
|
|
"output_type": "stream",
|
|
"text": [
|
|
"27\n"
|
|
]
|
|
}
|
|
],
|
|
"source": [
|
|
"# area is dynamically calculated each call\n",
|
|
"r.width = 6\n",
|
|
"print(r.area)"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "code",
|
|
"execution_count": 78,
|
|
"id": "489244f1-9568-4c73-93c8-86cb6ffecfe4",
|
|
"metadata": {},
|
|
"outputs": [
|
|
{
|
|
"ename": "AttributeError",
|
|
"evalue": "can't set attribute 'area'",
|
|
"output_type": "error",
|
|
"traceback": [
|
|
"\u001b[0;31m---------------------------------------------------------------------------\u001b[0m",
|
|
"\u001b[0;31mAttributeError\u001b[0m Traceback (most recent call last)",
|
|
"Cell \u001b[0;32mIn[78], line 2\u001b[0m\n\u001b[1;32m 1\u001b[0m \u001b[38;5;66;03m# but can't be set\u001b[39;00m\n\u001b[0;32m----> 2\u001b[0m \u001b[43mr\u001b[49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43marea\u001b[49m \u001b[38;5;241m=\u001b[39m \u001b[38;5;241m4\u001b[39m\n",
|
|
"\u001b[0;31mAttributeError\u001b[0m: can't set attribute 'area'"
|
|
]
|
|
}
|
|
],
|
|
"source": [
|
|
"# but can't be set\n",
|
|
"r.area = 4"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "code",
|
|
"execution_count": 25,
|
|
"id": "a5ffcdd9-5c89-4cdb-84cc-81b255c2e28f",
|
|
"metadata": {},
|
|
"outputs": [
|
|
{
|
|
"ename": "AttributeError",
|
|
"evalue": "can't delete attribute 'area'",
|
|
"output_type": "error",
|
|
"traceback": [
|
|
"\u001b[0;31m---------------------------------------------------------------------------\u001b[0m",
|
|
"\u001b[0;31mAttributeError\u001b[0m Traceback (most recent call last)",
|
|
"Cell \u001b[0;32mIn[25], line 1\u001b[0m\n\u001b[0;32m----> 1\u001b[0m \u001b[38;5;28;01mdel\u001b[39;00m r\u001b[38;5;241m.\u001b[39marea\n",
|
|
"\u001b[0;31mAttributeError\u001b[0m: can't delete attribute 'area'"
|
|
]
|
|
}
|
|
],
|
|
"source": [
|
|
"del r.area"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "code",
|
|
"execution_count": null,
|
|
"id": "952bb276-725e-43ea-835d-3ab4116696b6",
|
|
"metadata": {},
|
|
"outputs": [],
|
|
"source": []
|
|
}
|
|
],
|
|
"metadata": {
|
|
"kernelspec": {
|
|
"display_name": "Python 3 (ipykernel)",
|
|
"language": "python",
|
|
"name": "python3"
|
|
},
|
|
"language_info": {
|
|
"codemirror_mode": {
|
|
"name": "ipython",
|
|
"version": 3
|
|
},
|
|
"file_extension": ".py",
|
|
"mimetype": "text/x-python",
|
|
"name": "python",
|
|
"nbconvert_exporter": "python",
|
|
"pygments_lexer": "ipython3",
|
|
"version": "3.10.15"
|
|
}
|
|
},
|
|
"nbformat": 4,
|
|
"nbformat_minor": 5
|
|
}
|