Cảm ơn bạn đã đọc và ủng hộ blog KTMT ʘ‿ʘ Từ bây giờ chúng tôi sẽ là kipalog.com !

iOS
Comments

Tiếp theo phần trước, trong bài viết này sẽ giới thiệu 1 kỹ thuật khác trong Objective C: Swizzling method.

Swizzling

Thông thường, khi muốn thêm vào 1 class có sẵn 1 vài hàm mới, chúng ta có thể dùng Categories, đặc biệt là các class của thư viện (ko có source code) như NSArray, NSDictionary… Tuy nhiên, cách dùng Categories có 1 hạn chế là bạn không thể override các hàm có sẵn. Vậy đây chính là lý do chúng ta cần sử dụng đến Swizzling method.

Trong Objective C, khi bạn viết 1 đoạn code

1
[self presentViewController:mailController animated:YES completion:nil];

bạn không thực sự gọi đến hàm presentViewController:animated:completion: mà thay vào đó là gửi đi 1 message presentViewController:animated:completion:. Trong quá trình chạy, object sẽ tìm kiếm method tương ứng dựa vào id của message này. Chúng ta có thể dựa vào swizzling để thay đổi cách object tìm kiếm method tương ứng này:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
SEL firstMethodSelector = @selector(firstMethod);
SEL secondMethodSelector = @selector(secondMethod);
Method firstMethod = class_getInstanceMethod(self, firstMethodSelector);
Method secondMethod = class_getInstanceMethod(self, secondMethodSelector);

BOOL methodAdded = class_addMethod([self class],
                                   firstMethodSelector,
                                   method_getImplementation(secondMethod),
                                   method_getTypeEncoding(secondMethod));

if (methodAdded) {
class_replaceMethod([self class],
                      secondMethodSelector,
                      method_getImplementation(firstMethod),
                      method_getTypeEncoding(firstMethod));
} else {
  method_exchangeImplementations(firstMethod, secondMethod);
}

Đi từng bước cho đoạn code ở trên:

  1. Trước hết chúng ta tạo ra các selectors (SEL): firstMethodSelectorsecondMethodSelector

  2. Lấy ra các hàm tương ứng với selectors gán vào firstMethodsecondMethod Method

  3. Thêm vào class định nghĩa của method thứ 2 dưới cách gọi của method thứ nhất. Trường hợp này xảy ra khi method thứ nhất không thực sự tồn tại (trong 1 khả năng nào đó)

  4. Nếu điều này xảy ra, chúng ta cần 1 định nghĩa cho selector của method thứ 2, vì vậy thay thế nó bằng implementation của method thứ nhất (rỗng)

  5. Nếu không xảy ra, nghĩa là method thứ nhất có tồn tại, chúng ta thay đổi implementation của 2 method.

Ví dụ 1

Khi sử dụng Google Analystics, chúng ta muốn track page view cho tất cả các UIViewController trong project, tuy nhiên, nếu ở class nào cũng gọi hàm trackView:<class_name> thì tương đối nhiều, mà có thể còn bỏ sót. Vậy cách đơn giản nhất là override lại hàm viewDidLoad của UIViewController, trong đó chúng ta thực hiện trackView hoặc gọi 1 hàm khác bất kỳ, tuỳ theo mục đích của mình.

Chúng ta viết phần code trên trong Categories của NSObject, từ đó có thể gọi nó từ bất kỳ class nào:

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
#import "NSObject+Swizzle.h"
#import <objc/runtime.h>

@implementation NSObject (Swizzle)

+ (void) swizzleInstanceSelector:(SEL)originalSelector
                 withNewSelector:(SEL)newSelector
{
  Method originalMethod = class_getInstanceMethod(self, originalSelector);
  Method newMethod = class_getInstanceMethod(self, newSelector);

  BOOL methodAdded = class_addMethod([self class],
                                     originalSelector,
                                     method_getImplementation(newMethod),
                                     method_getTypeEncoding(newMethod));

  if (methodAdded) {
    class_replaceMethod([self class],
                        newSelector,
                        method_getImplementation(originalMethod),
                        method_getTypeEncoding(originalMethod));
  } else {
    method_exchangeImplementations(originalMethod, newMethod);
  }
}

@end

Bây giờ tạo tiếp Categories cho UIViewController:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
#import "UIViewController+ Swizzling.h"
#import "NSObject+Swizzle.h"

@implementation UIViewController (Swizzling)

+ (void)load {
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        [self swizzleInstanceSelector:@selector(viewDidLoad)
                      withNewSelector:@selector(myViewDidLoad)];
    });
}

- (void) myViewDidLoad {
    NSLog(@"This is my view did load");

    // Track Google Analystic here

    [self myViewDidLoad];

}

Khi Objective-C run-time load 1 category, nó sẽ gọi đến hàm load. Chúng ta sử dụng dispatch_once để chắc chắn rằng hàm swizzle chỉ được gọi 1 lần. Sau khi import category này, (tốt nhất là trong file prefix - pch) tất cả các hàm viewDidLoad của UIViewController sẽ được thay thế bằng hàm myViewDidLoad.

Ví dụ 2

1 ứng dụng khác của swizzling method là khi debug lỗi index out of range của NSArray. Nhiều khi gặp phải lỗi này nhưng chương trình không dừng lại ở đúng đoạn code bị lỗi (nhảy ra hàm main). 1 cách đơn giản để xử lý trường hợp này là override hàm objectAtIndex: của NSArray và bắt exception trong đó. Tuy nhiên, cách sử dụng swizzling method ở đây có hơi khác 1 chút.

Trước hết là tạo Category cho NSArray:

1
2
3
4
5
6
7
8
9
10
@implementation NSArray (OutOfRange)

-(void)safeObjectAtIndex:(NSUInteger)index {
    if (index >= self.count) {
        NSLog(@"%s self = %@, pointer = %p, index = %lu", __FUNCTION__, self, self, (unsigned long)index);
    }
    [self safeObjectAtIndex:index];
}

@end

Đặt 1 breakpoint vào trong điều kiện if (index >= self.count) để có thể biết được lỗi đến từ đâu. Sau đó, trong hàm main của main.m, thực hiện exchange method:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
#import <objc/runtime.h>
#import "NSArray+OutOfRange.h"

int main(int argc, char *argv[])
{
    Class arrayClass = NSClassFromString(@"__NSArrayM");
    Method originalMethod = class_getInstanceMethod(arrayClass, @selector(objectAtIndex:));
    Method categoryMethod = class_getInstanceMethod([NSArray class], @selector(safeObjectAtIndex:));
    method_exchangeImplementations(originalMethod, categoryMethod);

    NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init];
    int retVal = UIApplicationMain(argc, argv, nil, nil);
    [pool release];
    return retVal;
}

Lưu ý ở đây chúng ta gọi Class arrayClass = NSClassFromString(@"__NSArrayM"); là bởi vì hàm objectAtIndex: không đến từ NSArray class mà đến từ __NSArrayM (xem trên console debug). Chính vì thế chúng ta không thể sử dụng cách swizzle thông thường như trong ví dụ 1.

Để test đoạn code này, trong 1 đoạn chương trình bất kỳ, tạo ra 1 bug:

1
2
NSMutableArray *list = [NSMutableArray arrayWithObjects:@"1", @"2", nil];
NSLog(@"Test: %@", [list objectAtIndex:3]);

Bây giờ, chạy chương trình và tận hưởng thành quả :)

Giới thiệu về Data Analysis bằng Pig Latin

Nếu bạn đã từng làm việc với DB, chắc hẳn đã nghe đến Hadoop và Map-Reduce.

Map-Reduce, hay NoSQL style là một phương pháp tiếp cận ko thể thiếu cho các database lớn, tuy nhiên lượng knowhow cần phải có và phương pháp tư duy đặc thù là những rào cản lớn đối với những Data Analyser hay ngay cả những DB engineer thông thường.

Một Data Analyser muốn viết được job cho Map-Reduce, trước hết phải có kỹ năng của 1 Java Engineer, phải re-invent 1 số hàm common (JOIN, FILTER …)

Yahoo đã giới thiệu 1 hướng tiệp cận khác: Pig Latin, là 1 programming language build trên top của Hadoop, cú pháp tương đối giống SQL thuần tuý, tuy nhiên ở tầng dưới có thể “translate” program execution thành các job Map-Reduce và trả lại kết quả với tốc độ của Map-Reduce.

Pig Latin (kể từ đây sẽ gọi tắt là “Pig” :D ) với bộ engine đằng sau là Java, có thể extend bằng các thư viện viết = Java hay thậm chí Python. Pig có hiệu suất phát triển cao, nghĩa là thay vì bỏ ra 1 tiếng để viết job 100 lines Map-Reduce bằng Java, bạn có thể chỉ cần 10 phút với 10 lines Pig :D

Ở các phần tiếp theo của bài viết này, bạn sẽ được giới thiệu những bước học hỏi đầu tiên của Pig Developer.

Get Start

Rất may là chúng ta không phải ngồi tưởng tượng chay cách hoạt động của Pig. Cloudera có free VM image, bạn chỉ cần down bản tương ứng về chạy trên Virtual Box hoặc VMWare.

Pig có cấu trúc khá tương đồng với SQL. Trước hết để làm với 1 cục dữ liệu cần phân tích, cần LOAD cả cục lên rồi tiến hành “mổ xẻ”, sau đó STORE lại 1 file kết quả.

sample.sql
1
2
3
4
city = LOAD '/input/gotham/people.txt' AS (name:chararray, age:int, income:int);
citizens = ORDER city BY age;
max_age = LIMIT citizens 1;
STORE max_age INTO 'output/gotham/analysis1.txt'

people.txt là data đầu vào được tạo ra từ table trong DB.

Basic functions

Về cơ bản Pig có những function/commands sau :

  • LOAD, STORE: lấy dữ liệu trước khi xử lý và lưu sau khi xử lý. Ngoài ra DUMP có thể dùng để debug kiểu data.
  • GROUP, FILTER, ORDER BY, DISTINCT, LIMIT, UNION: những xử lý cơ bản giống hệt SQL.
  • FOREACH: loop function, để tạo nest operator (có thể hiểu đơn giản như cách tạo sub-query).
  • JOIN: giống JOIN của SQL, cũng có INNER, LEFT OUTER hay RIGHT OUTER… Những bước JOIN trong Pig thường là những bước quan trọng khi muốn tạo relation từ 2 cục data riêng rẽ trở lên.
  • Eval functions (MAX, AVG, COUNT, SUM….).
  • Math functions (SIN, COS, TAN, SQRT, …).
  • Tuple. Bag, Map functions. Phần này khá là khó và tác giả cũng không có nhiều kinh nghiệm sử dụng.
  • UDF (User Define Functions): là functions do developer tự viết bằng Java hoặc Python :D

Bạn có thể xem cụ thể ở Pig Latin Basics hoặc Pig Latin Built In Functions

Challenge 1: GROUP và FOREACH

Bài toán đơn giản đầu tiên, với data đầu vào là thông tin của các công dân thành phố gotham như ở trên, ta cần tìm người giàu nhất (income cao nhất) trong các nhóm độ tuổi 20~30, 30~40, 40~50, v.v..

sample.sql
1
2
3
4
5
6
7
8
9
10
11
12
city = LOAD '/input/gotham/people.txt' AS (name:chararray, age:int, income:int);
city_divide = FOREACH city GENERATE
  name,
  age/10 AS class,
  income;
city_classes = GROUP city_divide BY class;
citizens = FOREACH city_classes {
  ord = ORDER city_divide BY income;
  lim = LIMIT ord BY 1;
  GENERATE class*10 as class, lim.name AS name, lim.income AS income;

STORE citizens INTO 'output/gotham/analysis2.txt'

Đến đây chắc độc gỉả đã phần nào hình dung được data analyser dùng Pig Latin như thế nào :D

Challende 2: JOIN

Giả sử ngoài data về từng công dân của gotham, chúng ra có 1 data khác về các …“super heroes”, bao gồm “strength”, “ability”. Làm thế nào để biết các “super heroes” có thu nhập bao nhiêu trong cuộc sống thường ngày của họ ?

sample.sql
1
2
3
4
5
6
7
8
9
10
11
city = LOAD '/input/gotham/people.txt' AS (name:chararray, age:int, income:int);
heroes = LOAD '/input/gotham/heroes.txt' AS (name:chararray, strength:int, ability:chararray);

op = JOIN city BY name, heroes BY name;
opt = FOREACH op GENERATE
  $0 AS name,
  $1 AS age,
  $2 AS income,
  $4 AS strength,
  $5 AS ability;
STORE opt INTO 'output/gotham/analysis3.txt'

Ở đây bạn có thể để ý $0, $1, $2 lần lượt là name, age, income của biến city, $3, $4, $5 là name, strength, ability của biến heroes. Như vậy kết quả sau khi JOIN gồm tất cả các fields của 2 biến JOIN thành phần!

Pig Tuning

Qua 2 ví dụ trên đây, bạn có thể thế thấy Pig dễ phát triển như thế nào. Tuy nhiên khi engineer hoàn toàn không có kinh nghiệm về Map-Reduce viết Pig thì chắc chắn sẽ không thể biết cách optimize để các job Hadoop bên dưới đạt tốc độ nhanh nhất có thể.

Để giữ có Pig program có hiệu suất xử lý cao, engineer có thể áp dụng các trick dưới đây:

  • Dùng FILTER nhiều nhất và sớm nhất có thể. Nếu bạn JOIN a và b rồi lại FILTER, thì hãy tìm cách FILTER a và b trước rồi hãy JOIN.

  • Loại bỏ các cột (các fields) không cần thiết. Giả sử biến a có 11 fields và bạn chỉ cần 7 fields, hãy “FOREACH a GENERATE ($0…$6)” để lập tức loại bỏ 4 fields.

  • PARALLEL là 1 magic keyword. Dùng PARALLEL để chỉ định số lượng reduceers.

UDFs

Điều cuối cùng tác giả muốn chia sẻ, là khi bạn có những tasks xử lý nhỏ sử dụng nhiều lần với các fields, hãy cố gắng viết UDFs để xử lý. Pig được ship cùng với 1 package UDF viết sẵn Piggy Bank.

UDF có thể viết bằng Java hoặc Python. Java UDFs có tốc độ và khả năng ứng dụng trong Pig tốt hơn. Khi đã làm chủ được cấu trúc dữ liệu giữa Python/Java và Pig, bạn sẽ thấy UDFs là một feature mạnh mẽ và không thể sống thiếu :D

Tóm tắt:

Comments

Bài viết này trình bày về metaclass và một số cách dùng metaclass trong Python. Để hiểu bài viết này, bạn nên có kiến thức về magic method trong Python. Cụ thể là các hàm __init__, __new__, __call__

1. Metaclass là gì

Trong Python, tất cả mọi thứ đều là object, là instance của một Class nào đó. Để kiểm tra class của một object, chúng ta có thể sử dụng hàm type hoặc thuộc tính __class__

test.py
1
2
3
4
5
6
7
>>> int(1).__class__
<type 'int'>
>>> class A: pass
    ...
>>> a = A()
>>> type(a)
<class '__main__.A'>

Trong ví dụ trên, a là một instance của class A, class của a chính là A. Vậy còn kiểu của A là gì?

test.py
1
2
>>> type(A)
<type 'type'>

Kiểu của A là type. Bởi vì mặc đinh thì class A tạo bởi hàm type, với 3 tham số truyền vào

test.py
1
2
3
4
type(name, bases, dict)
    name - Tên ca class s được to ra
    bases - Tuple danh sách các parent class ca class s được to ra
    dict - Danh sách các thuc tính ca class s được to ra

Sử dụng hàm type, chúng ta có thể tạo ra một class mới

test.py
1
2
3
>>> Person = type('Person', (), {'country': 'vietnam'}
>>> Person.country
vietnam

Chúng ta có thể thay đổi quá trình tạo ra class A bằng cách set thuộc tính __metaclass__ của A. Thuộc tính __metaclass__ là một callable object (một function, hoặc một object) nhận 3 tham số truyền vào như hàm type nói trên.

Khi định nghĩa một class bằng từ khoá class, nếu __metaclass__ được set, metaclass sẽ được gọi sau khi các thuộc tính khác của class đã được set.

test.py
1
2
3
4
5
6
7
8
9
class PersonMeta(type):
    def __init__(self, name, bases, attrs):
        return super(PersonMeta, self).__init__(name, bases, attrs)

class Person(object):
    __metaclass__ = PersonMeta

    country = 'Vietnam'
    people = []

PersonMeta sẽ được gọi với các tham số (‘Person’, (object), {‘country’: ‘Vietnam’, ‘people’: []}). Bằng cách metaclass được gọi cuối cùng, chúng ta có thể sử dụng metaclass để can thiệp vào qúa trình tạo ra class, cụ thể là các thuộc tính ở mức class của class đó. Có thể coi Metaclass là Class của class. Và class là một instance của Metaclass. Những thuộc tính của ở mức class của một class (các class attribute, các @classmethod) chính là các thuộc tính ở mức instance của một Metaclass

2. Giới thiệu một số trường hợp sử dụng Metaclass

Metaclass là một trong những black magic và rất ít khi được sử dụng trong Python.

“Metaclasses are deeper magic than 99% of users should ever worry about. If you wonder whether you need them, you don’t (the people who actually need them know with certainty that they need them, and don’t need an explanation about why).” -Python Guru Tim Peters"

(Tạm dịch: Metaclass sâu sắc hơn 99% những gì mà người dùng nên lo lắng. Nếu bạn phân vân khi nào bạn cần chúng, bạn sẽ không bao giờ cần (những người thực sự cần Metaclass, sẽ biết chính xác trong trường hợp nào họ cần, và không cần phải giải thích lý do vì sao)

Tuy nhiên để giúp bạn đọc hiểu rõ metaclass, trong bài viết này, tôi trình bày 2 ví dụ về cách sử dụng metaclass

2.1. Sử dụng metaclass để can thiệp vào việc tạo instance của class

Giả sử chúng ta có B là metaclass của A. Việc tạo ra một instance của A chính là việc gọi hàm A(*args, **kwargs). Nhưng vì A là một instance của B, nên gọi A, chính là gọi hàm __call__ của một instance của B. Do đó, để can thiệp vào quá trình tạo instance của class, chúng ta có thể override hàm __call__ trong class B

Xét ví dụ chúng ta muốn tạo ra một Singleton class bằng Metaclass

singleton.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
class SingletonMeta(type):
    def __init__(self, *args, **kwargs):
        self._instance = None
        super(SingletonMeta, self).__init__(*args, **kwargs)

    def __call__(self, *args, **kwargs):
        if not self._instance:
            self._instance = super(SingletonMeta, self).__call__(*args, **kwargs)
        return self._instance

class Person(object):
    __metaclass__ = SingletonMeta

    def __init__(self):
        self.name = 'kiennt'
        self.age = 26

a = Person()
print a.name # kiennt
print a.age # 26
b = Person()
print b == a # True

Nếu bỏ thuộc tính __metaclass__ trong class Person, thì b sẽ không bằng a nữa

singleton.py
1
2
3
4
5
6
7
8
9
10
class Person(object):
    #__metaclass__ = SingletonMeta

    def __init__(self):
        self.name = 'kiennt'
        self.age = 26

a = Person()
b = Person()
print b == a # False

2.2. Sử dụng metaclass để can thiệp vào các thuộc tính của một class

Hãy xem xét một ví dụ về cài đặt ORM (Object Relational Mapping)

orm.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
class Field(object):
    def __init__(self, *args, **kwargs):
        pass


class CharField(Field):
    pass


class IntegerField(Field):
    pass


class Programmer(object):
    name = CharField(max_length=100)
    age = IntegerField(default=18)

Giờ nếu chúng ta muốn lưu một biến _fields để chứa tất cả các thuộc tính của class là một instance của Field, chúng ta có thể sử dụng metaclass để can thiệp

orm.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
class ModelMeta(type):
    def __new__(cls, name, bases, attrs):
        # get all Field attributes
        fields = []
        for k, v in attrs.items():
            if isinstance(v, Field):
                fields.append(v.__class__)

        ## update attrs
        attrs['_fields'] = fields
        return super(ModelMeta, cls).__new__(cls, name, bases, attrs)


class Programmer(object):
    __metaclass__ = ModelMeta

    name = CharField(max_length=100)
    age = IntegerField(default=18)


print Programmer._fields # [<class '__main__.IntegerField'>, <class '__main__.CharField'>]

Chú ý rằng __new__ là static class, nên chúng ta cần truyền cls vào trong lời gọi super(ModelMeta, cls).__new__(cls, name, bases, attrs)

Nếu không implement ở trong hàm __new__, chúng ta có thể implement ở trong hàm __init__ của metaclass như sau

orm.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
class ModelMeta(type):
    def __init__(self, name, bases, attrs):
        self._fields = []
        for k, v in attrs.items():
            if isinstance(v, Field):
                self._fields.append(v.__class__)


class Programmer(object):
    __metaclass__ = ModelMeta

    name = CharField(max_length=100)
    age = IntegerField(default=18)


print Programmer._fields # [<class '__main__.IntegerField'>, <class '__main__.CharField'>]

Tuy nhiên cách implement này thường it khi được sử dụng bằng cách implement thứ nhất trong hàm __new__, vì trong Python, hàm __new__ thường được sử dụng để allocate object, và __init__ được sử dụng để khởi tạo object

3. Kết luận

Bài viết trình bày về khái niệm metaclass và một số cách sử dụng metaclass trong python. Với metaclass, chúng ta có thể thay đổi các thuộc tính ở mức class của một class thông qua hàm __new____init__ của Metaclass, can thiệp vào quá trình tạo ra instance của class bằng cách thay đổi hàm __call__ của metaclass.

Metaclass là một trong những vấn đề khó và ít khi gặp trong python. Để hiểu và sử dụng metaclass một cách dễ dàng,cách ngăn nhất là code và đọc code python thật nhiều

4. Tham khảo

Python Cookbook

redisco

Copyright © 2015 kỹ thuật máy tính