Section 6: Single Inheritance (73-80)

Extending

hasattr(self, ‘xxx’)

課外補充:

class attribute vs. instance attribute

Delegating to Parent

super().method()

will call method in the parent, but bound to the instance it is called from.

提醒:If you forget super() in super().sing() and use self.sing() you’ll end up with infinite recursion!

https://www.796t.com/post/N2w5d3E=.html

Delegation and Method Binding

I’m basically going to create attributes or properties for area and perimeter and I’m going to use lazy evaluation and caching.

UnitCircle 避免被設定新的半徑值

Slots

Only use slots in cases where you know that you will benefit both memory overhead and speed.

缺點:

  1. we cannot add attributes to our objects(instance) that are not defined in slots.
  2. cause difficulties in multiple inheritance.

__dict____slot__ 所使用的記憶體對照表

__init__(self, x, y) __slot__(x, y)
10,000 intances 1,729KB 635KB

Slots and Single Inheritance

Parent slots Children slots
1 :white_check_mark:
2 :white_check_mark: :ballot_box_with_check:
3 :white_check_mark:
4 Slots and Properties
5 slots & __dict__ 並存

1. Parent 有 slots, Children 沒有

這種情形下,除了 Parent 的 slots 中指定的變數(本例為 name)外,Children 保有自己的 __dict__

class Person:
    __slots__ = 'name',
    
    def __init__(self, name):
        self.name = name

class Student(Person):
    pass
p = Person('Eric')
try:
    print(p.__dict__)
except AttributeError as ex:
    print(ex)

輸出:Person object has no attribute __dict__

s = Student('Alex')
s.name, s.__dict__

輸出:(‘Alex’, {})

name 儲存在 Slot,但仍然保有 __dict__(但目前還沒有值)

s.age = 19
s.__dict__
s.name, s.age

輸出:{‘age’: 19} :arrow_right: 這是 __dict__ 的值

輸出:(‘Alex’, 19) :arrow_right: name(Alex) 儲存在 slot、age(19) 儲存在 __dict__

2. Parent 和 Children 都有 slots

這種情形下,Children 和 Parents 一樣,也沒有 __dict__。所以變數只有從 Parent Slots 繼承過來的(本例為),和自己 slots 的(本例為)。

兩個提醒:

  1. 不同的 slots:Children 的 slots,避免與 Parent 重覆。

  2. tuple():Children 的 slots 不一定要明確定義,即使只是空白的 tuple() 也可以。

class Person:
    __slots__ = 'name',
    
    def __init__(self, name):
        self.name = name

class Student(Person):
    __slots__ = 'school', 'student_number'
    
    def __init__(self, name, school, student_number):
        super().__init__(name)
        self.school = school
        self.student_number = student_number

s = Student('James', 'MI6 Prep', '007')

s.name, s.school, s.student_number

輸出:(‘James’, ‘MI6 Prep’, ‘007’)

:arrow_right: 全部儲存在 slots,name 繼承自 Parent,school和 student_number 則來自 Child 自己的 slots。

3. Parent 無 slot, Children 有 slots

和第一種情形類似,Children 既有 slots 中指定的變數(本例為 name),同時仍有 __dict__

問題:Children 的 __dict__,是否只能儲存 Parent 的變數?答案是可以儲存任何新增的變數,就像一般的 __dict__ 行為一樣。

class Person:
    def __init__(self, name):
        self.name = name
        
class Student(Person):
    __slots__ = 'age', 
    
    def __init__(self, name, age):
        super().__init__(name)
        self.age = age

s = Student('Python', 30)
s.name, s.age, s.__dict__

s.grade = 9
s.grade, s.name, s.age, s.__dict__

輸出:(‘Python’, 30, {‘name’: ‘Python’})

:arrow_right: name(Python) 儲存在 __dict__,age(30) 則在 slot。

輸出:(9, ‘Python’, 30, {‘name’: ‘Python’, ‘grade’: 9})

4. Relation between Slots and Properties - descriptor

Data descriptors are simply classes that implement certain methods, just like iterators

iterators: __iter__, __next__
descriptors : __get__, __set__

class Person:
    __slots__ = '_name', 'age'
    
    def __init__(self, name, age):
        self.name = name
        self.age = age
        
    @property
    def name(self):
        return self._name
    
    @name.setter
    def name(self, name):
        self._name = name

輸出:

5. Slots 和 instance dictionary(__dict__) 並存

想要同時擁有 slot 的效能,和 __dict__ 新增屬性的彈性,方法很簡單,把 __dict__ 加入 __slots__ 即可。

PS:不使用繼承的方法

class Person:
    __slots__ = 'name', '__dict__'
    
    def __init__(self, name, age):
        self.name = name
        self.age = age

p = Person('Alex', 19)
p.name, p.age, p.__dict__

輸出:(‘Alex’, 19, {‘age’: 19})

p.school = 'Berkeley'
p.__dict__

輸出:{‘age’: 19, ‘school’: ‘Berkeley’}

補充

老師的獨特幽默:巨蟒劇團 (Monty Python) 的一首伐木工經典歌曲

QA

來源

Q: 講座 74 中您建議返回 NotImplemented 一個“抽象”基類方法。
提高不是更合適NotImplementedError 嗎?請參閱 NotImplementedError

A: 這取決於上下文。通常 NotImplemented 返回用於實現二元運算符(例如等於、小於等於)的方法。這樣 Python 將嘗試執行反射操作。

另一方面,如果你正在創建某種你想定義“抽象”方法的基類,那麼你應該引發異常NotImplementedError

在這些練習中,我們正在實現二元運算符,因此返回 NotImplemented 似乎更合適。相等運算符可能有點不同——如果兩個對像根本不可比較(例如,它們有不同的模組),那麼我可能不關心正在嘗試的反射,在這種情況下,立即提高可能是好的,而NotImplementedError 不是讓 Python 嘗試執行反映相等的操作,只是想出相同的返回值 NotImplemented - 這樣我就節省了一個操作。

class Account:
    apr = 3.0
    
    def __init__(self, account_number, balance):
        self.account_number = account_number
        self.balance = balance
        self.account_type = 'Generic Account'
        
    def calc_interest(self):
        return f'Calc interest on {self.account_type} with APR = {type(self).apr}'
        # return f'Calc interest on {self.account_type} with APR = {self.__class__.apr}'
        

class Savings(Account):
    apr = 5.0
    
    def __init__(self, account_number, balance):
        self.account_number = account_number  # We'll revisit this later - this is clumsy
        self.balance = balance
        self.account_type = 'Savings Account'