1. Find Elements in a Contaminated Binary Tree

Description

Obvious dfs. Problem is how python code is written: gives me a revision on self.


selfの使い方

インスタンス変数として参照する

まずはじめに、コンストラクタで使用する例を見てみましょう。pythonにおけるコンストラクタは”init“と表記します。

1
2
3
4
5
6
7
8
class className():
def __init__(self, strA, strB):
self.strA = strA
self.strB = strB

test = className("Hello", "World!")
print(test.strA)
print(test.strB)

クラス変数として参照する
また、以下のようにクラス変数として別メソッドでも使う事ができます。

1
2
3
4
5
6
7
8
9
10
11
class className():
def __init__(self, strA, strB):
self.strA = strA
self.strB = strB

def output(self):
print(self.strA)
print(self.strB)

test = className("Hello", "World!")
test.output()

1
2
Hello
World!

クラス継承の際も使える

1
2
3
4
5
6
7
8
9
10
class classA():
def __init__(self):
self.strA = "Hello World!"

class classB(classA):
def output(self):
print(self.strA)

test = classB()
test.output()

Achtung⚠️

1
2
3
4
5
6
7
8
9
10
11
12
# -*- coding: utf-8 -*-

class cls():
strA = "Hello python"
def __init__(self):
print("1: " + self.strA)
self.strA = "Hello World!"
print("2: " + self.strA)
strA = "Hello python"
print("3: " + self.strA)

test = cls()
1
2
3
1: Hello python
2: Hello World!
3: Hello World!

可以看出, self总是与类相关,或者类创建出来的instance. 所以1261对应的python写法是:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
# Definition for a binary tree node.
# class TreeNode:
# def __init__(self, x):
# self.val = x
# self.left = None
# self.right = None

class FindElements:

def __init__(self, root: TreeNode):
self.seen = set()

def dfs(node: TreeNode, v: int) -> None:
if node:
node.val = v
self.seen.add(v)
dfs(node.left, 2 * v + 1)
dfs(node.right, 2 * v + 2)

dfs(root, 0)

def find(self, target: int) -> bool:
return target in self.seen


# Your FindElements object will be instantiated and called as such:
# obj = FindElements(root)
# param_1 = obj.find(target)

上面的写法的几点说明:

  1. __init__find方法第一个参数都是self, 是因为他们都是类方法, 所以第一参数是self.
  2. dfs并不是类方法,不会实例化对象代指对象本身赋值属性, 所以不需要self,调用dfs也不需要self;
  3. 但是外部方法变量seenself.seen相当于全局变量, 所以内部方法调用的时候还是要用self.seen.
  4. 同理, find调用同级函数,类方法__init__中的seen的时候,也需要self.

总结一下: 和普通数相比,在类中定义函数只有一点不同,就是第一参数永远是类的本身实例变量self,并且调用时,不用传递该参数。除此之外,类的方法(函数)和普通函数没啥区别


一个更一般出现的例子和类属性的私有化

例子:

1
2
3
4
5
6
class Student(obiect):
def __init__(self, name, score):
self.name = name
self.score = score
def print_score(self):
print "%s: %s" % (self.name, self.score)

1
2
3
>>>student = Student("Lei", 99)
>>>student.print_score
Lei: 99

如果让内部属性不被外部访问: 属性前面加2个下划线: instance变量如果以__两个下划线开头,那么外部就不能访问;

1
2
3
4
5
6
class Student(obiect):
def __init__(self, name, score):
self.__name = name
self.__score = score
def print_score(self):
print "%s: %s" % (self.name, self.score)

只是实例变量加了两个下划线,但是外部已经不能访问了

1
2
3
4
5
>>> student = Student('Lei', 99)
>>> student.__name
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
AttributeError: 'Student' object has no attribute '__name'

这样外部代码就不能修改对象内部的状态,增加了访问限制,代码robust

那如果外部代码要获取namescore呢? 给类增加get_name, get_score:

1
2
3
4
5
6
7
class Student(object):
...
def get_name(self):
return self.__name

def get_score(self):
return self.__score

要修改呢? 增加set:

1
2
3
4
5
class Student(object):
...

def set_score(self, score):
self.__score = score

变量名双下划线开头并结尾的, 类似__xxx__的是特殊变量,不是private,不要自定义这种.

有些时候,会看到以一个下划线开头的实例变量名,比如_name,这样的实例变量外部是可以访问的,但是,按照约定俗成的规定,当看到这样的变量时,意思就是,“虽然我可以被访问,但是,请把我视为私有变量,不要随意访问”。

(其实很无语, 这种单下划线想表明是private但是实际上没有用的,js中也有)


self deep dive

  1. self代表类的实例,不是类本身
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    class Test:
    def ppr(self):
    print(self)
    print(self.__class__)

    t = Test()
    t.ppr()

    执行结果:
    <__main__.Test object at 0x000000000284E080>
    <class '__main__.Test'>

从上面的例子看出, self代表类的实例,而self.__class__指向类.
self可以换成this.

  1. self代表类的实例 如果类的实例要用这个方法,self必须写,如果定义和调用都不传递类实例是可以的,这是类方法

不写self但是是用实例调用:

1
2
3
4
5
6
class Test:
def ppr():
print(self)

t = Test()
t.ppr()

调用t.ppr时, 实际上python解释成了Test.ppr(t), 也就是self替换成了类实例.

Result:

1
2
3
4
Traceback (most recent call last):
File "cl.py", line 6, in <module>
t.ppr()
TypeError: ppr() takes 0 positional arguments but 1 was given

因为调用t.ppr时python解释成了Test.ppr(t), 所以“多传递了一个参数”, 说明实例方法的地方,self在定义实例方法时不可以省略。

当然,如果定义和调用都不传递实例,是可以的,就是类方法;

1
2
3
4
5
6
7
8
class Test:
def ppr():
print(__class__)

Test.ppr()

运行结果:
<class '__main__.Test'>

Java version:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
/**
* Definition for a binary tree node.
* public class TreeNode {
* int val;
* TreeNode left;
* TreeNode right;
* TreeNode(int x) { val = x; }
* }
*/
class FindElements {

private Set<Integer> seen = new HashSet<>();

public FindElements(TreeNode root) {
dfs(root, 0);
}
private void dfs(TreeNode node, int val) {
if (node == null) return;
seen.add(val);
node.val = val;
dfs(node.left, 2 * val + 1);
dfs(node.right, 2 * val + 2);
}

public boolean find(int target) {
return seen.contains(target);
}
}

/**
* Your FindElements object will be instantiated and called as such:
* FindElements obj = new FindElements(root);
* boolean param_1 = obj.find(target);
*/